feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
@@ -0,0 +1,403 @@
// PostgreSQL administration authority for versioned API credential mutations.
import {
ApiCredentialAdministrationMutationConflictError,
ApiCredentialAdministrationSubjectNotFoundError,
ApiCredentialAdministrationUnavailableError,
ApiCredentialAdministrationVersionConflictError,
normalizeAppendApiCredentialCommand,
normalizeApiCredentialAdministrationMutationId,
type ApiCredentialAdministrationRepository,
type ApiCredentialMutationRecord,
type AppendApiCredentialCommand,
type AppendApiCredentialResult,
type ResolvedApiCredentialMutation,
} from '@qinglong/runtime-core/api-credential-administration';
import {
normalizeApiCredentialRecord,
type ApiCredentialRecord,
} from '@qinglong/runtime-core/api-credential';
import type { PostgresClient, PostgresPool } from '@qinglong/runtime-core';
import {
ADMINISTRATION_AUDIT_SELECT,
auditFromRow,
configureAdministrationTransaction,
insertAdministrationAudit,
requiredInteger,
requiredString,
retryableAdministrationError,
rollbackAdministrationTransaction,
sameAdministrationReplayAudit,
type AdministrationAuditRow,
} from '../repository/administrationSupport';
interface CredentialMutationRow extends AdministrationAuditRow {
mutationId: unknown;
operation: unknown;
credentialId: unknown;
credentialVersion: unknown;
expectedPreviousVersion: unknown;
state: unknown;
subjectType: unknown;
subjectId: unknown;
subjectStatus: unknown;
pepperKeyId: unknown;
secretDigest: unknown;
createdAtMs: unknown;
notBeforeAtMs: unknown;
expiresAtMs: unknown;
changedByType: unknown;
changedById: unknown;
}
interface CurrentCredentialRow extends Record<string, unknown> {
credentialId: unknown;
version: unknown;
state: unknown;
subjectType: unknown;
subjectId: unknown;
}
interface SubjectRow extends Record<string, unknown> {
status: unknown;
}
const MAX_TRANSACTION_ATTEMPTS = 3;
const MUTATION_SELECT = `
SELECT
mutation.mutation_id AS "mutationId",
mutation.operation,
mutation.credential_id AS "credentialId",
mutation.credential_version AS "credentialVersion",
mutation.expected_previous_version AS "expectedPreviousVersion",
mutation.state,
mutation.subject_type AS "subjectType",
mutation.subject_id AS "subjectId",
mutation.subject_status AS "subjectStatus",
mutation.changed_by_type AS "changedByType",
mutation.changed_by_id AS "changedById",
mutation.created_at_ms AS "createdAtMs",
credential.pepper_key_id AS "pepperKeyId",
credential.secret_digest AS "secretDigest",
credential.not_before_at_ms AS "notBeforeAtMs",
credential.expires_at_ms AS "expiresAtMs",
${ADMINISTRATION_AUDIT_SELECT}
FROM "ql3"."api_credential_mutations" AS mutation
JOIN "ql3"."api_credentials" AS credential
ON credential.credential_id = mutation.credential_id
AND credential.version = mutation.credential_version
JOIN "ql3"."security_audit_events" AS audit
ON audit.event_id = mutation.audit_event_id
WHERE mutation.mutation_id = $1
LIMIT 2
`.trim();
function storedMutation(row: CredentialMutationRow): {
credential: Readonly<ApiCredentialRecord>;
mutation: Readonly<ApiCredentialMutationRecord>;
audit: ReturnType<typeof auditFromRow>;
} {
const audit = auditFromRow(row);
const mutation: ApiCredentialMutationRecord = {
mutationId: requiredString(row, 'mutationId'),
operation: requiredString(
row,
'operation',
) as ApiCredentialMutationRecord['operation'],
credentialId: requiredString(row, 'credentialId'),
credentialVersion: requiredInteger(row, 'credentialVersion'),
expectedPreviousVersion: requiredInteger(row, 'expectedPreviousVersion'),
changedBy: {
type: requiredString(
row,
'changedByType',
) as ApiCredentialMutationRecord['changedBy']['type'],
id: requiredString(row, 'changedById'),
},
createdAtMs: requiredInteger(row, 'createdAtMs'),
};
const credential = normalizeApiCredentialRecord({
credentialId: mutation.credentialId,
version: mutation.credentialVersion,
pepperKeyId: requiredString(row, 'pepperKeyId'),
state: requiredString(row, 'state') as ApiCredentialRecord['state'],
subject: {
type: requiredString(
row,
'subjectType',
) as ApiCredentialRecord['subject']['type'],
id: requiredString(row, 'subjectId'),
},
subjectStatus: requiredString(
row,
'subjectStatus',
) as ApiCredentialRecord['subjectStatus'],
secretDigest: requiredString(row, 'secretDigest'),
createdAtMs: mutation.createdAtMs,
notBeforeAtMs: requiredInteger(row, 'notBeforeAtMs'),
expiresAtMs: requiredInteger(row, 'expiresAtMs'),
});
const normalized = normalizeAppendApiCredentialCommand({
expectedCurrentVersion: mutation.expectedPreviousVersion,
credential,
mutation,
audit,
});
return {
credential: normalized.credential,
mutation: normalized.mutation,
audit: normalized.audit,
};
}
function sameValue(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
function sameCredentialForReplay(
left: Readonly<ApiCredentialRecord>,
right: Readonly<ApiCredentialRecord>,
): boolean {
const {
secretDigest: _leftSecretDigest,
createdAtMs: _leftCreatedAtMs,
...leftSemantic
} = left;
const {
secretDigest: _rightSecretDigest,
createdAtMs: _rightCreatedAtMs,
...rightSemantic
} = right;
return sameValue(leftSemantic, rightSemantic);
}
function sameMutationForReplay(
left: Readonly<ApiCredentialMutationRecord>,
right: Readonly<ApiCredentialMutationRecord>,
): boolean {
const { createdAtMs: _leftCreatedAtMs, ...leftSemantic } = left;
const { createdAtMs: _rightCreatedAtMs, ...rightSemantic } = right;
return sameValue(leftSemantic, rightSemantic);
}
async function resolveStoredMutation(
queryable: Pick<PostgresClient, 'query'>,
mutationId: string,
): Promise<ResolvedApiCredentialMutation | null> {
const result = await queryable.query<CredentialMutationRow>(MUTATION_SELECT, [
mutationId,
]);
if (result.rows.length > 1) {
throw new ApiCredentialAdministrationUnavailableError();
}
if (!result.rows[0]) return null;
try {
return Object.freeze(storedMutation(result.rows[0]));
} catch {
throw new ApiCredentialAdministrationMutationConflictError();
}
}
async function replay(
client: PostgresClient,
command: Readonly<AppendApiCredentialCommand>,
): Promise<AppendApiCredentialResult | null> {
const stored = await resolveStoredMutation(
client,
command.mutation.mutationId,
);
if (!stored) return null;
if (
!sameCredentialForReplay(stored.credential, command.credential) ||
!sameMutationForReplay(stored.mutation, command.mutation) ||
!sameAdministrationReplayAudit(stored.audit, command.audit)
) {
throw new ApiCredentialAdministrationMutationConflictError();
}
return Object.freeze({
status: 'existing',
credential: stored.credential,
mutation: stored.mutation,
});
}
export class PostgresApiCredentialAdministrationRepository
implements ApiCredentialAdministrationRepository
{
constructor(private readonly pool: PostgresPool) {
if (
!pool ||
typeof pool.query !== 'function' ||
typeof pool.connect !== 'function'
) {
throw new TypeError(
'PostgreSQL API credential administration pool is invalid',
);
}
}
async resolveMutation(
requestedMutationId: string,
): Promise<ResolvedApiCredentialMutation | null> {
const mutationId =
normalizeApiCredentialAdministrationMutationId(requestedMutationId);
try {
return await resolveStoredMutation(this.pool, mutationId);
} catch (error) {
if (
error instanceof ApiCredentialAdministrationMutationConflictError ||
error instanceof ApiCredentialAdministrationUnavailableError
) {
throw error;
}
throw new ApiCredentialAdministrationUnavailableError();
}
}
async append(
input: AppendApiCredentialCommand,
): Promise<AppendApiCredentialResult> {
const command = normalizeAppendApiCredentialCommand(input);
for (let attempt = 0; attempt < MAX_TRANSACTION_ATTEMPTS; attempt += 1) {
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch {
throw new ApiCredentialAdministrationUnavailableError();
}
let began = false;
try {
await configureAdministrationTransaction(client);
began = true;
await client.query(
'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',
[`ql3-api-credential:${command.credential.credentialId}`],
);
const existing = await replay(client, command);
if (existing) {
await client.query('COMMIT');
began = false;
return existing;
}
const subjectResult = await client.query<SubjectRow>(
`SELECT status
FROM "ql3"."identity_subjects"
WHERE subject_type = $1 AND subject_id = $2
FOR SHARE`,
[command.credential.subject.type, command.credential.subject.id],
);
if (subjectResult.rows.length !== 1) {
throw new ApiCredentialAdministrationSubjectNotFoundError();
}
const subjectStatus = requiredString(subjectResult.rows[0]!, 'status');
if (
subjectStatus !== command.credential.subjectStatus ||
(command.mutation.operation !== 'revoke' &&
subjectStatus !== 'active')
) {
throw new ApiCredentialAdministrationSubjectNotFoundError();
}
const currentResult = await client.query<CurrentCredentialRow>(
`SELECT
credential_id AS "credentialId", version, state,
subject_type AS "subjectType", subject_id AS "subjectId"
FROM "ql3"."api_credentials"
WHERE credential_id = $1
ORDER BY version DESC
LIMIT 1`,
[command.credential.credentialId],
);
if (currentResult.rows.length > 1) {
throw new ApiCredentialAdministrationUnavailableError();
}
const current = currentResult.rows[0];
const currentVersion = current
? requiredInteger(current, 'version')
: 0;
if (currentVersion !== command.expectedCurrentVersion) {
throw new ApiCredentialAdministrationVersionConflictError();
}
if (
current &&
(requiredString(current, 'subjectType') !==
command.credential.subject.type ||
requiredString(current, 'subjectId') !==
command.credential.subject.id)
) {
throw new ApiCredentialAdministrationMutationConflictError();
}
await insertAdministrationAudit(client, command.audit);
await client.query(
`INSERT INTO "ql3"."api_credentials" (
credential_id, version, state, subject_type, subject_id,
pepper_key_id, secret_digest, created_at_ms, not_before_at_ms,
expires_at_ms
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
[
command.credential.credentialId,
command.credential.version,
command.credential.state,
command.credential.subject.type,
command.credential.subject.id,
command.credential.pepperKeyId,
command.credential.secretDigest,
command.credential.createdAtMs,
command.credential.notBeforeAtMs,
command.credential.expiresAtMs,
],
);
await client.query(
`INSERT INTO "ql3"."api_credential_mutations" (
mutation_id, operation, credential_id, credential_version,
expected_previous_version, state, subject_type, subject_id,
subject_status, changed_by_type, changed_by_id, audit_event_id,
created_at_ms
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $1, $12)`,
[
command.mutation.mutationId,
command.mutation.operation,
command.mutation.credentialId,
command.mutation.credentialVersion,
command.mutation.expectedPreviousVersion,
command.credential.state,
command.credential.subject.type,
command.credential.subject.id,
command.credential.subjectStatus,
command.mutation.changedBy.type,
command.mutation.changedBy.id,
command.mutation.createdAtMs,
],
);
await client.query('COMMIT');
began = false;
return Object.freeze({
status: 'inserted',
credential: command.credential,
mutation: command.mutation,
});
} catch (error) {
if (began) await rollbackAdministrationTransaction(client);
if (
error instanceof ApiCredentialAdministrationSubjectNotFoundError ||
error instanceof ApiCredentialAdministrationVersionConflictError ||
error instanceof ApiCredentialAdministrationMutationConflictError ||
error instanceof ApiCredentialAdministrationUnavailableError
) {
throw error;
}
if (
attempt < MAX_TRANSACTION_ATTEMPTS - 1 &&
retryableAdministrationError(error)
) {
continue;
}
throw new ApiCredentialAdministrationUnavailableError();
} finally {
client.release();
}
}
throw new ApiCredentialAdministrationUnavailableError();
}
}
@@ -0,0 +1,118 @@
// PostgreSQL runtime authority for resolving API credentials and identities.
import {
ApiCredentialUnavailableError,
assertApiCredentialId,
normalizeApiCredentialRecord,
type ApiCredentialRecord,
type ApiCredentialRepository,
} from '@qinglong/runtime-core/api-credential';
import type { PostgresPool } from '@qinglong/runtime-core';
interface ApiCredentialRow extends Record<string, unknown> {
credentialId: unknown;
version: unknown;
state: unknown;
subjectType: unknown;
subjectId: unknown;
subjectStatus: unknown;
pepperKeyId: unknown;
secretDigest: unknown;
createdAtMs: unknown;
notBeforeAtMs: unknown;
expiresAtMs: unknown;
}
const RESOLVE_SQL = `
SELECT
credential.credential_id AS "credentialId",
credential.version,
credential.state,
credential.subject_type AS "subjectType",
credential.subject_id AS "subjectId",
subject.status AS "subjectStatus",
credential.pepper_key_id AS "pepperKeyId",
credential.secret_digest AS "secretDigest",
credential.created_at_ms AS "createdAtMs",
credential.not_before_at_ms AS "notBeforeAtMs",
credential.expires_at_ms AS "expiresAtMs"
FROM "ql3"."api_credentials" AS credential
JOIN "ql3"."identity_subjects" AS subject
ON subject.subject_type = credential.subject_type
AND subject.subject_id = credential.subject_id
WHERE credential.credential_id = $1
ORDER BY credential.version DESC
LIMIT 1
`.trim();
function requiredString(row: ApiCredentialRow, name: string): string {
const value = row[name];
if (typeof value !== 'string' || value.length === 0) {
throw new ApiCredentialUnavailableError();
}
return value;
}
function requiredInteger(row: ApiCredentialRow, name: string): number {
const value = row[name];
if (typeof value === 'number' && Number.isSafeInteger(value)) return value;
if (typeof value === 'string' && /^(0|[1-9]\d*)$/.test(value)) {
const parsed = Number(value);
if (Number.isSafeInteger(parsed)) return parsed;
}
throw new ApiCredentialUnavailableError();
}
function recordFromRow(row: ApiCredentialRow): Readonly<ApiCredentialRecord> {
try {
return normalizeApiCredentialRecord({
credentialId: requiredString(row, 'credentialId'),
version: requiredInteger(row, 'version'),
pepperKeyId: requiredString(row, 'pepperKeyId'),
state: requiredString(row, 'state') as ApiCredentialRecord['state'],
subject: {
type: requiredString(
row,
'subjectType',
) as ApiCredentialRecord['subject']['type'],
id: requiredString(row, 'subjectId'),
},
subjectStatus: requiredString(
row,
'subjectStatus',
) as ApiCredentialRecord['subjectStatus'],
secretDigest: requiredString(row, 'secretDigest'),
createdAtMs: requiredInteger(row, 'createdAtMs'),
notBeforeAtMs: requiredInteger(row, 'notBeforeAtMs'),
expiresAtMs: requiredInteger(row, 'expiresAtMs'),
});
} catch {
throw new ApiCredentialUnavailableError();
}
}
export class PostgresApiCredentialRepository
implements ApiCredentialRepository
{
constructor(private readonly pool: PostgresPool) {
if (!pool || typeof pool.query !== 'function') {
throw new TypeError('PostgreSQL API credential pool is invalid');
}
}
async resolve(
credentialId: string,
): Promise<Readonly<ApiCredentialRecord> | null> {
assertApiCredentialId(credentialId);
try {
const result = await this.pool.query<ApiCredentialRow>(RESOLVE_SQL, [
credentialId,
]);
if (result.rows.length === 0) return null;
if (result.rows.length !== 1) throw new ApiCredentialUnavailableError();
return recordFromRow(result.rows[0]!);
} catch (error) {
if (error instanceof ApiCredentialUnavailableError) throw error;
throw new ApiCredentialUnavailableError();
}
}
}
@@ -0,0 +1,394 @@
// PostgreSQL administration authority for versioned identity mutations.
import {
IdentityAdministrationMutationConflictError,
IdentityAdministrationUnavailableError,
IdentityAdministrationVersionConflictError,
normalizeAppendIdentitySubjectCommand,
normalizeIdentityAdministrationMutationId,
normalizeIdentityAdministrationSubject,
normalizeIdentitySubjectRecord,
type AppendIdentitySubjectCommand,
type AppendIdentitySubjectResult,
type IdentityAdministrationRepository,
type IdentitySubjectMutationRecord,
type IdentitySubjectRecord,
type ResolvedIdentitySubjectMutation,
} from '@qinglong/runtime-core/identity-administration';
import type { PostgresClient, PostgresPool } from '@qinglong/runtime-core';
import {
ADMINISTRATION_AUDIT_SELECT,
auditFromRow,
configureAdministrationTransaction,
insertAdministrationAudit,
requiredInteger,
requiredString,
retryableAdministrationError,
rollbackAdministrationTransaction,
sameAdministrationReplayAudit,
type AdministrationAuditRow,
} from '../repository/administrationSupport';
interface IdentityMutationRow extends AdministrationAuditRow {
mutationId: unknown;
operation: unknown;
subjectType: unknown;
subjectId: unknown;
subjectVersion: unknown;
expectedPreviousVersion: unknown;
status: unknown;
changedByType: unknown;
changedById: unknown;
identityCreatedAtMs: unknown;
createdAtMs: unknown;
}
interface IdentityRow extends Record<string, unknown> {
subjectType: unknown;
subjectId: unknown;
status: unknown;
version: unknown;
createdAtMs: unknown;
updatedAtMs: unknown;
}
const MAX_TRANSACTION_ATTEMPTS = 3;
const MUTATION_SELECT = `
SELECT
mutation.mutation_id AS "mutationId",
mutation.operation,
mutation.subject_type AS "subjectType",
mutation.subject_id AS "subjectId",
mutation.subject_version AS "subjectVersion",
mutation.expected_previous_version AS "expectedPreviousVersion",
mutation.status,
mutation.changed_by_type AS "changedByType",
mutation.changed_by_id AS "changedById",
mutation.identity_created_at_ms AS "identityCreatedAtMs",
mutation.created_at_ms AS "createdAtMs",
${ADMINISTRATION_AUDIT_SELECT}
FROM "ql3"."identity_subject_mutations" AS mutation
JOIN "ql3"."security_audit_events" AS audit
ON audit.event_id = mutation.audit_event_id
WHERE mutation.mutation_id = $1
LIMIT 2
`.trim();
function identityFromRow(row: IdentityRow): Readonly<IdentitySubjectRecord> {
return normalizeIdentitySubjectRecord({
subject: {
type: requiredString(
row,
'subjectType',
) as IdentitySubjectRecord['subject']['type'],
id: requiredString(row, 'subjectId'),
},
status: requiredString(row, 'status') as IdentitySubjectRecord['status'],
version: requiredInteger(row, 'version'),
createdAtMs: requiredInteger(row, 'createdAtMs'),
updatedAtMs: requiredInteger(row, 'updatedAtMs'),
});
}
function storedMutation(row: IdentityMutationRow): {
identity: Readonly<IdentitySubjectRecord>;
mutation: Readonly<IdentitySubjectMutationRecord>;
audit: ReturnType<typeof auditFromRow>;
} {
const audit = auditFromRow(row);
const mutation: IdentitySubjectMutationRecord = {
mutationId: requiredString(row, 'mutationId'),
operation: requiredString(
row,
'operation',
) as IdentitySubjectMutationRecord['operation'],
subject: {
type: requiredString(
row,
'subjectType',
) as IdentitySubjectMutationRecord['subject']['type'],
id: requiredString(row, 'subjectId'),
},
subjectVersion: requiredInteger(row, 'subjectVersion'),
expectedPreviousVersion: requiredInteger(row, 'expectedPreviousVersion'),
status: requiredString(
row,
'status',
) as IdentitySubjectMutationRecord['status'],
changedBy: {
type: requiredString(
row,
'changedByType',
) as IdentitySubjectMutationRecord['changedBy']['type'],
id: requiredString(row, 'changedById'),
},
createdAtMs: requiredInteger(row, 'createdAtMs'),
};
const normalized = normalizeAppendIdentitySubjectCommand({
expectedCurrentVersion: mutation.expectedPreviousVersion,
mutation,
audit,
});
return {
mutation: normalized.mutation,
audit: normalized.audit,
identity: normalizeIdentitySubjectRecord({
subject: normalized.mutation.subject,
status: normalized.mutation.status,
version: normalized.mutation.subjectVersion,
createdAtMs: requiredInteger(row, 'identityCreatedAtMs'),
updatedAtMs: normalized.mutation.createdAtMs,
}),
};
}
function sameMutation(
left: Readonly<IdentitySubjectMutationRecord>,
right: Readonly<IdentitySubjectMutationRecord>,
): boolean {
const { createdAtMs: _leftCreatedAtMs, ...leftSemantic } = left;
const { createdAtMs: _rightCreatedAtMs, ...rightSemantic } = right;
return JSON.stringify(leftSemantic) === JSON.stringify(rightSemantic);
}
async function resolveStoredMutation(
queryable: Pick<PostgresClient, 'query'>,
mutationId: string,
): Promise<ResolvedIdentitySubjectMutation | null> {
const result = await queryable.query<IdentityMutationRow>(MUTATION_SELECT, [
mutationId,
]);
if (result.rows.length > 1)
throw new IdentityAdministrationUnavailableError();
if (!result.rows[0]) return null;
try {
return Object.freeze(storedMutation(result.rows[0]));
} catch {
throw new IdentityAdministrationMutationConflictError();
}
}
async function replay(
client: PostgresClient,
command: Readonly<AppendIdentitySubjectCommand>,
): Promise<AppendIdentitySubjectResult | null> {
const stored = await resolveStoredMutation(
client,
command.mutation.mutationId,
);
if (!stored) return null;
if (
!sameMutation(stored.mutation, command.mutation) ||
!sameAdministrationReplayAudit(stored.audit, command.audit)
) {
throw new IdentityAdministrationMutationConflictError();
}
return Object.freeze({
status: 'existing',
identity: stored.identity,
mutation: stored.mutation,
});
}
export class PostgresIdentityAdministrationRepository
implements IdentityAdministrationRepository
{
constructor(private readonly pool: PostgresPool) {
if (
!pool ||
typeof pool.query !== 'function' ||
typeof pool.connect !== 'function'
) {
throw new TypeError('PostgreSQL Identity administration pool is invalid');
}
}
async resolve(
requestedSubject: Parameters<
IdentityAdministrationRepository['resolve']
>[0],
): Promise<Readonly<IdentitySubjectRecord> | null> {
const subject = normalizeIdentityAdministrationSubject(requestedSubject);
try {
const result = await this.pool.query<IdentityRow>(
`SELECT
subject_type AS "subjectType", subject_id AS "subjectId", status,
version, created_at_ms AS "createdAtMs",
updated_at_ms AS "updatedAtMs"
FROM "ql3"."identity_subjects"
WHERE subject_type = $1 AND subject_id = $2
LIMIT 2`,
[subject.type, subject.id],
);
if (result.rows.length === 0) return null;
if (result.rows.length !== 1) {
throw new IdentityAdministrationUnavailableError();
}
return identityFromRow(result.rows[0]!);
} catch (error) {
if (error instanceof IdentityAdministrationUnavailableError) throw error;
throw new IdentityAdministrationUnavailableError();
}
}
async resolveMutation(
requestedMutationId: string,
): Promise<ResolvedIdentitySubjectMutation | null> {
const mutationId =
normalizeIdentityAdministrationMutationId(requestedMutationId);
try {
return await resolveStoredMutation(this.pool, mutationId);
} catch (error) {
if (
error instanceof IdentityAdministrationMutationConflictError ||
error instanceof IdentityAdministrationUnavailableError
) {
throw error;
}
throw new IdentityAdministrationUnavailableError();
}
}
async append(
input: AppendIdentitySubjectCommand,
): Promise<AppendIdentitySubjectResult> {
const command = normalizeAppendIdentitySubjectCommand(input);
for (let attempt = 0; attempt < MAX_TRANSACTION_ATTEMPTS; attempt += 1) {
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch {
throw new IdentityAdministrationUnavailableError();
}
let began = false;
try {
await configureAdministrationTransaction(client);
began = true;
await client.query(
'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',
[
`ql3-identity:${command.mutation.subject.type}:${command.mutation.subject.id}`,
],
);
const existing = await replay(client, command);
if (existing) {
await client.query('COMMIT');
began = false;
return existing;
}
const currentResult = await client.query<IdentityRow>(
`SELECT
subject_type AS "subjectType", subject_id AS "subjectId", status,
version, created_at_ms AS "createdAtMs",
updated_at_ms AS "updatedAtMs"
FROM "ql3"."identity_subjects"
WHERE subject_type = $1 AND subject_id = $2
FOR UPDATE`,
[command.mutation.subject.type, command.mutation.subject.id],
);
if (currentResult.rows.length > 1) {
throw new IdentityAdministrationUnavailableError();
}
const current = currentResult.rows[0]
? identityFromRow(currentResult.rows[0])
: null;
if ((current?.version ?? 0) !== command.expectedCurrentVersion) {
throw new IdentityAdministrationVersionConflictError();
}
await insertAdministrationAudit(client, command.audit);
let identity: Readonly<IdentitySubjectRecord>;
if (current === null) {
const inserted = await client.query<IdentityRow>(
`INSERT INTO "ql3"."identity_subjects" (
subject_type, subject_id, status, version, created_at_ms,
updated_at_ms
) VALUES ($1, $2, $3, $4, $5, $5)
RETURNING subject_type AS "subjectType", subject_id AS "subjectId",
status, version, created_at_ms AS "createdAtMs",
updated_at_ms AS "updatedAtMs"`,
[
command.mutation.subject.type,
command.mutation.subject.id,
command.mutation.status,
command.mutation.subjectVersion,
command.mutation.createdAtMs,
],
);
if (inserted.rows.length !== 1) {
throw new IdentityAdministrationUnavailableError();
}
identity = identityFromRow(inserted.rows[0]!);
} else {
const updated = await client.query<IdentityRow>(
`UPDATE "ql3"."identity_subjects"
SET status = $3, version = $4, updated_at_ms = $5
WHERE subject_type = $1 AND subject_id = $2 AND version = $6
RETURNING subject_type AS "subjectType", subject_id AS "subjectId",
status, version, created_at_ms AS "createdAtMs",
updated_at_ms AS "updatedAtMs"`,
[
command.mutation.subject.type,
command.mutation.subject.id,
command.mutation.status,
command.mutation.subjectVersion,
command.mutation.createdAtMs,
command.expectedCurrentVersion,
],
);
if (updated.rows.length !== 1) {
throw new IdentityAdministrationVersionConflictError();
}
identity = identityFromRow(updated.rows[0]!);
}
await client.query(
`INSERT INTO "ql3"."identity_subject_mutations" (
mutation_id, operation, subject_type, subject_id,
subject_version, expected_previous_version, status,
changed_by_type, changed_by_id, audit_event_id,
identity_created_at_ms, created_at_ms
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $1, $10, $11)`,
[
command.mutation.mutationId,
command.mutation.operation,
command.mutation.subject.type,
command.mutation.subject.id,
command.mutation.subjectVersion,
command.mutation.expectedPreviousVersion,
command.mutation.status,
command.mutation.changedBy.type,
command.mutation.changedBy.id,
identity.createdAtMs,
command.mutation.createdAtMs,
],
);
await client.query('COMMIT');
began = false;
return Object.freeze({
status: 'inserted',
identity,
mutation: command.mutation,
});
} catch (error) {
if (began) await rollbackAdministrationTransaction(client);
if (
error instanceof IdentityAdministrationVersionConflictError ||
error instanceof IdentityAdministrationMutationConflictError ||
error instanceof IdentityAdministrationUnavailableError
) {
throw error;
}
if (
attempt < MAX_TRANSACTION_ATTEMPTS - 1 &&
retryableAdministrationError(error)
) {
continue;
}
throw new IdentityAdministrationUnavailableError();
} finally {
client.release();
}
}
throw new IdentityAdministrationUnavailableError();
}
}
@@ -0,0 +1,402 @@
// PostgreSQL authority for project policy snapshots and role bindings.
import {
ProjectPolicyProjectNotFoundError,
ProjectPolicyUnavailableError,
ProjectRoleBindingMutationConflictError,
ProjectRoleBindingVersionConflictError,
assertProjectPolicyProjectId,
assertExpectedProjectRoleBindingVersion,
normalizeProjectPolicySnapshot,
normalizeProjectPolicySubject,
normalizeProjectRoleBinding,
type AppendProjectRoleBindingCommand,
type AppendProjectRoleBindingResult,
type ProjectPolicyRepository,
type ProjectPolicySnapshot,
type ProjectRoleBindingRecord,
} from '@qinglong/runtime-core/project-policy';
import type {
PostgresClient,
PostgresPool,
PostgresQueryResult,
} from '@qinglong/runtime-core';
type QueryRow = Record<string, unknown>;
interface SnapshotRow extends QueryRow {
projectId: unknown;
projectName: unknown;
projectSlug: unknown;
projectStatus: unknown;
projectVersion: unknown;
projectCreatedAtMs: unknown;
projectUpdatedAtMs: unknown;
bindingProjectId: unknown;
bindingSubjectType: unknown;
bindingSubjectId: unknown;
bindingVersion: unknown;
bindingState: unknown;
bindingRole: unknown;
bindingMutationId: unknown;
bindingChangedByType: unknown;
bindingChangedById: unknown;
bindingCreatedAtMs: unknown;
}
interface BindingRow extends QueryRow {
projectId: unknown;
subjectType: unknown;
subjectId: unknown;
version: unknown;
state: unknown;
role: unknown;
mutationId: unknown;
changedByType: unknown;
changedById: unknown;
createdAtMs: unknown;
}
const MAX_TRANSACTION_ATTEMPTS = 3;
const RETRYABLE_SQL_STATES = new Set(['40001', '40P01', '55P03']);
const SNAPSHOT_SQL = `
SELECT
project.id AS "projectId",
project.name AS "projectName",
project.slug AS "projectSlug",
project.status AS "projectStatus",
project.version AS "projectVersion",
project.created_at_ms AS "projectCreatedAtMs",
project.updated_at_ms AS "projectUpdatedAtMs",
binding.project_id AS "bindingProjectId",
binding.subject_type AS "bindingSubjectType",
binding.subject_id AS "bindingSubjectId",
binding.version AS "bindingVersion",
binding.state AS "bindingState",
binding.role AS "bindingRole",
binding.mutation_id AS "bindingMutationId",
binding.changed_by_type AS "bindingChangedByType",
binding.changed_by_id AS "bindingChangedById",
binding.created_at_ms AS "bindingCreatedAtMs"
FROM "ql3"."projects" AS project
LEFT JOIN LATERAL (
SELECT *
FROM "ql3"."project_role_bindings" AS candidate
WHERE candidate.project_id = project.id
AND candidate.subject_type = $2
AND candidate.subject_id = $3
ORDER BY candidate.version DESC
LIMIT 1
) AS binding ON TRUE
WHERE project.id = $1
LIMIT 2
`.trim();
const BINDING_SELECT = `
SELECT
project_id AS "projectId",
subject_type AS "subjectType",
subject_id AS "subjectId",
version,
state,
role,
mutation_id AS "mutationId",
changed_by_type AS "changedByType",
changed_by_id AS "changedById",
created_at_ms AS "createdAtMs"
FROM "ql3"."project_role_bindings"
`.trim();
function requiredString(row: QueryRow, name: string): string {
const value = row[name];
if (typeof value !== 'string' || value.length === 0) {
throw new ProjectPolicyUnavailableError();
}
return value;
}
function requiredInteger(row: QueryRow, name: string): number {
const value = row[name];
if (typeof value === 'number' && Number.isSafeInteger(value)) return value;
if (typeof value === 'string' && /^(0|[1-9]\d*)$/.test(value)) {
const parsed = Number(value);
if (Number.isSafeInteger(parsed)) return parsed;
}
throw new ProjectPolicyUnavailableError();
}
function optionalString(row: QueryRow, name: string): string | undefined {
const value = row[name];
if (value === null || value === undefined) return undefined;
return requiredString(row, name);
}
function bindingFromRow(row: BindingRow): Readonly<ProjectRoleBindingRecord> {
try {
const role = optionalString(row, 'role');
return normalizeProjectRoleBinding({
projectId: requiredString(row, 'projectId'),
subject: {
type: requiredString(
row,
'subjectType',
) as ProjectRoleBindingRecord['subject']['type'],
id: requiredString(row, 'subjectId'),
},
version: requiredInteger(row, 'version'),
state: requiredString(row, 'state') as ProjectRoleBindingRecord['state'],
...(role
? { role: role as NonNullable<ProjectRoleBindingRecord['role']> }
: {}),
mutationId: requiredString(row, 'mutationId'),
changedBy: {
type: requiredString(
row,
'changedByType',
) as ProjectRoleBindingRecord['changedBy']['type'],
id: requiredString(row, 'changedById'),
},
createdAtMs: requiredInteger(row, 'createdAtMs'),
});
} catch {
throw new ProjectPolicyUnavailableError();
}
}
function snapshotFromRow(row: SnapshotRow): Readonly<ProjectPolicySnapshot> {
const bindingValues = [
row.bindingProjectId,
row.bindingSubjectType,
row.bindingSubjectId,
row.bindingVersion,
row.bindingState,
row.bindingMutationId,
row.bindingChangedByType,
row.bindingChangedById,
row.bindingCreatedAtMs,
];
const noBinding = bindingValues.every((value) => value === null);
if (!noBinding && bindingValues.some((value) => value === null)) {
throw new ProjectPolicyUnavailableError();
}
try {
return normalizeProjectPolicySnapshot({
project: {
id: requiredString(row, 'projectId'),
name: requiredString(row, 'projectName'),
slug: requiredString(row, 'projectSlug'),
status: requiredString(
row,
'projectStatus',
) as ProjectPolicySnapshot['project']['status'],
version: requiredInteger(row, 'projectVersion'),
createdAtMs: requiredInteger(row, 'projectCreatedAtMs'),
updatedAtMs: requiredInteger(row, 'projectUpdatedAtMs'),
},
...(noBinding
? {}
: {
binding: bindingFromRow({
projectId: row.bindingProjectId,
subjectType: row.bindingSubjectType,
subjectId: row.bindingSubjectId,
version: row.bindingVersion,
state: row.bindingState,
role: row.bindingRole,
mutationId: row.bindingMutationId,
changedByType: row.bindingChangedByType,
changedById: row.bindingChangedById,
createdAtMs: row.bindingCreatedAtMs,
}),
}),
});
} catch {
throw new ProjectPolicyUnavailableError();
}
}
function sameBinding(
left: Readonly<ProjectRoleBindingRecord>,
right: Readonly<ProjectRoleBindingRecord>,
): boolean {
return (
left.projectId === right.projectId &&
left.subject.type === right.subject.type &&
left.subject.id === right.subject.id &&
left.version === right.version &&
left.state === right.state &&
left.role === right.role &&
left.mutationId === right.mutationId &&
left.changedBy.type === right.changedBy.type &&
left.changedBy.id === right.changedBy.id &&
left.createdAtMs === right.createdAtMs
);
}
function sqlState(error: unknown): string | undefined {
if (!error || typeof error !== 'object') return undefined;
const code = (error as { code?: unknown }).code;
return typeof code === 'string' ? code : undefined;
}
async function configureTransaction(client: PostgresClient): Promise<void> {
await client.query('BEGIN ISOLATION LEVEL SERIALIZABLE');
await client.query(`SELECT set_config('statement_timeout', $1, true)`, [
'5000ms',
]);
await client.query(`SELECT set_config('lock_timeout', $1, true)`, ['1000ms']);
await client.query(
`SELECT set_config('idle_in_transaction_session_timeout', $1, true)`,
['10000ms'],
);
}
async function rollback(client: PostgresClient): Promise<void> {
try {
await client.query('ROLLBACK');
} catch {
// Preserve the primary transaction failure; release discards broken clients.
}
}
/** PostgreSQL implementation of the shared append-only Project Policy port. */
export class PostgresProjectPolicyRepository
implements ProjectPolicyRepository
{
constructor(private readonly pool: PostgresPool) {
if (
!pool ||
typeof pool.query !== 'function' ||
typeof pool.connect !== 'function'
) {
throw new TypeError('PostgreSQL Project Policy pool is invalid');
}
}
async resolve(
projectId: string,
requestedSubject: Parameters<ProjectPolicyRepository['resolve']>[1],
): Promise<Readonly<ProjectPolicySnapshot> | null> {
assertProjectPolicyProjectId(projectId);
const subject = normalizeProjectPolicySubject(requestedSubject);
let result: PostgresQueryResult<SnapshotRow>;
try {
result = await this.pool.query<SnapshotRow>(SNAPSHOT_SQL, [
projectId,
subject.type,
subject.id,
]);
} catch {
throw new ProjectPolicyUnavailableError();
}
if (result.rows.length === 0) return null;
if (result.rows.length !== 1) throw new ProjectPolicyUnavailableError();
return snapshotFromRow(result.rows[0]!);
}
async append(
command: AppendProjectRoleBindingCommand,
): Promise<AppendProjectRoleBindingResult> {
if (!command || typeof command !== 'object' || Array.isArray(command)) {
throw new TypeError('Project role binding command is invalid');
}
assertExpectedProjectRoleBindingVersion(command.expectedCurrentVersion);
const binding = normalizeProjectRoleBinding(command.binding);
if (binding.version !== command.expectedCurrentVersion + 1) {
throw new ProjectRoleBindingVersionConflictError();
}
for (let attempt = 0; attempt < MAX_TRANSACTION_ATTEMPTS; attempt += 1) {
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch {
throw new ProjectPolicyUnavailableError();
}
let began = false;
try {
await configureTransaction(client);
began = true;
const project = await client.query(
'SELECT id FROM "ql3"."projects" WHERE id = $1 FOR UPDATE',
[binding.projectId],
);
if (project.rows.length === 0) {
throw new ProjectPolicyProjectNotFoundError();
}
if (project.rows.length !== 1)
throw new ProjectPolicyUnavailableError();
const replay = await client.query<BindingRow>(
`${BINDING_SELECT} WHERE project_id = $1 AND mutation_id = $2 LIMIT 2`,
[binding.projectId, binding.mutationId],
);
if (replay.rows.length > 1) throw new ProjectPolicyUnavailableError();
if (replay.rows[0]) {
const previous = bindingFromRow(replay.rows[0]);
if (!sameBinding(previous, binding)) {
throw new ProjectRoleBindingMutationConflictError();
}
await client.query('COMMIT');
began = false;
return Object.freeze({ status: 'existing', binding: previous });
}
const current = await client.query<{ version: unknown }>(
`SELECT version FROM "ql3"."project_role_bindings"
WHERE project_id = $1 AND subject_type = $2 AND subject_id = $3
ORDER BY version DESC LIMIT 1`,
[binding.projectId, binding.subject.type, binding.subject.id],
);
if (current.rows.length > 1) throw new ProjectPolicyUnavailableError();
const currentVersion = current.rows[0]
? requiredInteger(current.rows[0], 'version')
: 0;
if (currentVersion !== command.expectedCurrentVersion) {
throw new ProjectRoleBindingVersionConflictError();
}
await client.query(
`INSERT INTO "ql3"."project_role_bindings" (
project_id, subject_type, subject_id, version, state, role,
mutation_id, changed_by_type, changed_by_id, created_at_ms
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
[
binding.projectId,
binding.subject.type,
binding.subject.id,
binding.version,
binding.state,
binding.role ?? null,
binding.mutationId,
binding.changedBy.type,
binding.changedBy.id,
binding.createdAtMs,
],
);
await client.query('COMMIT');
began = false;
return Object.freeze({ status: 'inserted', binding });
} catch (error) {
if (began) await rollback(client);
if (
error instanceof ProjectPolicyProjectNotFoundError ||
error instanceof ProjectRoleBindingVersionConflictError ||
error instanceof ProjectRoleBindingMutationConflictError ||
error instanceof ProjectPolicyUnavailableError
) {
throw error;
}
if (
attempt < MAX_TRANSACTION_ATTEMPTS - 1 &&
(RETRYABLE_SQL_STATES.has(sqlState(error) ?? '') ||
sqlState(error) === '23505')
) {
continue;
}
throw new ProjectPolicyUnavailableError();
} finally {
client.release();
}
}
throw new ProjectPolicyUnavailableError();
}
}
@@ -0,0 +1,161 @@
// PostgreSQL administration authority for querying security audit events.
import {
SecurityAuditQueryUnavailableError,
normalizeSecurityAuditQuery,
type SecurityAuditQuery,
type SecurityAuditQueryPage,
type SecurityAuditQueryRepository,
} from '@qinglong/runtime-core/security-audit-query';
import {
normalizeSecurityAuditRecord,
type SecurityAuditRecord,
} from '@qinglong/runtime-core/security-audit';
import type { PostgresPool } from '@qinglong/runtime-core';
import {
requiredInteger,
requiredString,
type AdministrationRow,
} from '../repository/administrationSupport';
interface AuditRow extends AdministrationRow {
eventId: unknown;
requestId: unknown;
operationId: unknown;
projectId: unknown;
subjectType: unknown;
subjectId: unknown;
authenticationId: unknown;
outcome: unknown;
reasons: unknown;
projectVersion: unknown;
bindingVersion: unknown;
occurredAtMs: unknown;
}
function optionalString(row: AuditRow, name: string): string | null {
const value = row[name];
if (value === null) return null;
return requiredString(row, name);
}
function optionalInteger(row: AuditRow, name: string): number | null {
const value = row[name];
if (value === null) return null;
return requiredInteger(row, name);
}
function recordFromRow(row: AuditRow): Readonly<SecurityAuditRecord> {
const subjectType = optionalString(row, 'subjectType');
const subjectId = optionalString(row, 'subjectId');
if ((subjectType === null) !== (subjectId === null)) {
throw new SecurityAuditQueryUnavailableError();
}
const projectVersion = optionalInteger(row, 'projectVersion');
const bindingVersion = optionalInteger(row, 'bindingVersion');
if (!Array.isArray(row.reasons)) {
throw new SecurityAuditQueryUnavailableError();
}
try {
return normalizeSecurityAuditRecord({
eventId: requiredString(row, 'eventId'),
requestId: requiredString(row, 'requestId'),
operationId: requiredString(row, 'operationId'),
projectId: optionalString(row, 'projectId'),
subject:
subjectType === null
? null
: {
type: subjectType as NonNullable<
SecurityAuditRecord['subject']
>['type'],
id: subjectId!,
},
authenticationId: optionalString(row, 'authenticationId'),
outcome: requiredString(row, 'outcome') as SecurityAuditRecord['outcome'],
reasons: row.reasons as string[],
fence:
projectVersion === null ? null : { projectVersion, bindingVersion },
occurredAtMs: requiredInteger(row, 'occurredAtMs'),
});
} catch {
throw new SecurityAuditQueryUnavailableError();
}
}
export class PostgresSecurityAuditQueryRepository
implements SecurityAuditQueryRepository
{
constructor(private readonly pool: PostgresPool) {
if (!pool || typeof pool.query !== 'function') {
throw new TypeError('PostgreSQL security audit query pool is invalid');
}
}
async list(input: SecurityAuditQuery): Promise<SecurityAuditQueryPage> {
const query = normalizeSecurityAuditQuery(input);
const conditions: string[] = [];
const values: unknown[] = [];
const parameter = (value: unknown): string => {
values.push(value);
return `$${values.length}`;
};
if (query.before) {
const occurred = parameter(query.before.occurredAtMs);
const event = parameter(query.before.eventId);
conditions.push(
`(occurred_at_ms, event_id) < (${occurred}, ${event}::uuid)`,
);
}
if (query.filter.projectId !== undefined) {
conditions.push(`project_id = ${parameter(query.filter.projectId)}`);
}
if (query.filter.subject !== undefined) {
conditions.push(
`subject_type = ${parameter(
query.filter.subject.type,
)} AND subject_id = ${parameter(query.filter.subject.id)}`,
);
}
if (query.filter.outcome !== undefined) {
conditions.push(`outcome = ${parameter(query.filter.outcome)}`);
}
const limit = parameter(query.limit);
let result;
try {
result = await this.pool.query<AuditRow>(
`SELECT
event_id AS "eventId", request_id AS "requestId",
operation_id AS "operationId", project_id AS "projectId",
subject_type AS "subjectType", subject_id AS "subjectId",
authentication_id AS "authenticationId", outcome, reasons,
project_version AS "projectVersion",
binding_version AS "bindingVersion",
occurred_at_ms AS "occurredAtMs"
FROM "ql3"."security_audit_events"
${conditions.length === 0 ? '' : `WHERE ${conditions.join(' AND ')}`}
ORDER BY occurred_at_ms DESC, event_id DESC
LIMIT ${limit}`,
values,
);
} catch {
throw new SecurityAuditQueryUnavailableError();
}
let records: readonly Readonly<SecurityAuditRecord>[];
try {
records = Object.freeze(result.rows.map(recordFromRow));
} catch {
throw new SecurityAuditQueryUnavailableError();
}
const last = records.at(-1);
return Object.freeze({
records,
nextCursor:
records.length === query.limit && last
? Object.freeze({
occurredAtMs: last.occurredAtMs,
eventId: last.eventId,
})
: null,
});
}
}
@@ -0,0 +1,61 @@
// PostgreSQL runtime sink for immutable security audit events.
import {
SecurityAuditUnavailableError,
normalizeSecurityAuditRecord,
type SecurityAuditRecord,
type SecurityAuditSink,
} from '@qinglong/runtime-core/security-audit';
import type { PostgresPool } from '@qinglong/runtime-core';
const INSERT_SQL = `
INSERT INTO "ql3"."security_audit_events" (
event_id,
request_id,
operation_id,
project_id,
subject_type,
subject_id,
authentication_id,
outcome,
reasons,
project_version,
binding_version,
occurred_at_ms
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11, $12)
`.trim();
export class PostgresSecurityAuditRepository implements SecurityAuditSink {
constructor(private readonly pool: PostgresPool) {
if (!pool || typeof pool.query !== 'function') {
throw new TypeError('PostgreSQL security audit pool is invalid');
}
}
async record(value: SecurityAuditRecord): Promise<void> {
let record: Readonly<SecurityAuditRecord>;
try {
record = normalizeSecurityAuditRecord(value);
} catch {
throw new SecurityAuditUnavailableError();
}
try {
await this.pool.query(INSERT_SQL, [
record.eventId,
record.requestId,
record.operationId,
record.projectId,
record.subject?.type ?? null,
record.subject?.id ?? null,
record.authenticationId,
record.outcome,
JSON.stringify(record.reasons),
record.fence?.projectVersion ?? null,
record.fence?.bindingVersion ?? null,
record.occurredAtMs,
]);
} catch {
throw new SecurityAuditUnavailableError();
}
}
}