feat(ql3): cut alpha.1 candidate milestone

This commit is contained in:
whyour
2026-08-26 01:41:20 +08:00
parent c2df0c7215
commit 07cdc76bae
94 changed files with 1469 additions and 168 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@qinglong/cluster-postgres",
"version": "3.0.0-alpha.0",
"version": "3.0.0-alpha.1",
"private": true,
"description": "QingLong 3.0 cluster-only PostgreSQL driver and schema package",
"license": "Apache-2.0",
@@ -33,6 +33,7 @@ export {
} from '../connection/certificateAuthority';
export * from '../security/identityAdministrationRepository';
export * from '../security/apiCredentialAdministrationRepository';
export * from '../security/apiCredentialPepperReferenceRepository';
export * from '../security/securityAuditQueryRepository';
export * from '../worker-credential/workerCredentialAdministrationRepository';
export * from '../automation/automationAdministrationRepository';
@@ -0,0 +1,118 @@
import {
ApiCredentialPepperReferenceUnavailableError,
normalizeApiCredentialPepperReferenceKeyId,
normalizeApiCredentialPepperReferenceLimit,
type ApiCredentialPepperReferenceInspection,
type ApiCredentialPepperReferenceRepository,
} from '@qinglong/runtime-core/api-credential-pepper-reference';
import type { PostgresPool } from '@qinglong/runtime-core';
import { assertApiCredentialId } from '@qinglong/runtime-core/api-credential';
interface ReferenceRow extends Record<string, unknown> {
observedAtMs: unknown;
credentialId: unknown;
}
const INSPECT_SQL = `
WITH clock AS (
SELECT floor(
extract(epoch FROM statement_timestamp()) * 1000
)::bigint AS observed_at_ms
), current_references AS (
SELECT credential.credential_id
FROM "ql3"."api_credentials" AS credential
CROSS JOIN clock
WHERE credential.pepper_key_id = $1
AND credential.state = 'active'
AND credential.expires_at_ms > clock.observed_at_ms
AND NOT EXISTS (
SELECT 1
FROM "ql3"."api_credentials" AS newer
WHERE newer.credential_id = credential.credential_id
AND newer.version > credential.version
)
ORDER BY credential.credential_id
LIMIT $2
)
SELECT
clock.observed_at_ms AS "observedAtMs",
reference.credential_id AS "credentialId"
FROM clock
LEFT JOIN current_references AS reference ON true
ORDER BY reference.credential_id
`.trim();
function safeInteger(value: unknown): number {
const parsed = typeof value === 'string' ? Number(value) : value;
if (!Number.isSafeInteger(parsed) || (parsed as number) < 0) {
throw new ApiCredentialPepperReferenceUnavailableError();
}
return parsed as number;
}
export class PostgresApiCredentialPepperReferenceRepository
implements ApiCredentialPepperReferenceRepository
{
constructor(private readonly pool: Pick<PostgresPool, 'query'>) {
if (!pool || typeof pool.query !== 'function') {
throw new TypeError(
'PostgreSQL API credential pepper reference pool is invalid',
);
}
}
async inspect(
requestedPepperKeyId: string,
requestedLimit?: number,
): Promise<Readonly<ApiCredentialPepperReferenceInspection>> {
const pepperKeyId = normalizeApiCredentialPepperReferenceKeyId(
requestedPepperKeyId,
);
const limit = normalizeApiCredentialPepperReferenceLimit(requestedLimit);
try {
const result = await this.pool.query<ReferenceRow>(INSPECT_SQL, [
pepperKeyId,
limit + 1,
]);
if (result.rows.length < 1) {
throw new ApiCredentialPepperReferenceUnavailableError();
}
const observedAtMs = safeInteger(result.rows[0]!.observedAtMs);
const emptyReferenceSet =
result.rows.length === 1 && result.rows[0]!.credentialId === null;
const allCredentialIds = emptyReferenceSet
? []
: result.rows.map((row) => {
if (typeof row.credentialId !== 'string') {
throw new ApiCredentialPepperReferenceUnavailableError();
}
try {
assertApiCredentialId(row.credentialId);
} catch {
throw new ApiCredentialPepperReferenceUnavailableError();
}
return row.credentialId;
});
const credentialIds = allCredentialIds.slice(0, limit);
if (
result.rows.some(
(row) => safeInteger(row.observedAtMs) !== observedAtMs,
) ||
new Set(credentialIds).size !== credentialIds.length
) {
throw new ApiCredentialPepperReferenceUnavailableError();
}
return Object.freeze({
pepperKeyId,
observedAtMs,
credentialIds: Object.freeze(credentialIds),
hasMore: allCredentialIds.length > limit,
});
} catch (error) {
if (error instanceof ApiCredentialPepperReferenceUnavailableError) {
throw error;
}
throw new ApiCredentialPepperReferenceUnavailableError();
}
}
}
@@ -0,0 +1,75 @@
const assert = require('node:assert/strict');
const test = require('node:test');
const {
PostgresApiCredentialPepperReferenceRepository,
} = require('../dist/security/apiCredentialPepperReferenceRepository.js');
test('returns bounded current pepper references using database time', async () => {
const calls = [];
const repository = new PostgresApiCredentialPepperReferenceRepository({
async query(sql, parameters) {
calls.push({ sql, parameters });
return {
rows: [
{ observedAtMs: '1000', credentialId: 'credential-a' },
{ observedAtMs: '1000', credentialId: 'credential-b' },
{ observedAtMs: '1000', credentialId: 'credential-c' },
],
};
},
});
assert.deepEqual(await repository.inspect('legacy-v1', 2), {
pepperKeyId: 'legacy-v1',
observedAtMs: 1000,
credentialIds: ['credential-a', 'credential-b'],
hasMore: true,
});
assert.deepEqual(calls[0].parameters, ['legacy-v1', 3]);
assert.match(calls[0].sql, /statement_timestamp\(\)/);
assert.match(calls[0].sql, /newer\.version > credential\.version/);
});
test('represents an empty reference set without losing database time', async () => {
const repository = new PostgresApiCredentialPepperReferenceRepository({
async query() {
return { rows: [{ observedAtMs: 1000, credentialId: null }] };
},
});
assert.deepEqual(await repository.inspect('retired-v1'), {
pepperKeyId: 'retired-v1',
observedAtMs: 1000,
credentialIds: [],
hasMore: false,
});
});
test('fails closed on invalid input and malformed database rows', async () => {
const repository = new PostgresApiCredentialPepperReferenceRepository({
async query() {
return { rows: [{ observedAtMs: 'invalid', credentialId: null }] };
},
});
await assert.rejects(() => repository.inspect('legacy-v1'), /unavailable/);
await assert.rejects(() => repository.inspect('legacy-v1', 65));
await assert.rejects(() => repository.inspect('bad key'));
for (const credentialId of [42, null, 'bad credential']) {
const malformedRepository =
new PostgresApiCredentialPepperReferenceRepository({
async query() {
return {
rows: [
{ observedAtMs: 1000, credentialId: 'credential-a' },
{ observedAtMs: 1000, credentialId },
],
};
},
});
await assert.rejects(
() => malformedRepository.inspect('legacy-v1'),
/unavailable/,
);
}
});