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,654 @@
import type {
PostgresClient,
PostgresPool,
} from '@qinglong/runtime-core';
import {
ApprovalMutationConflictError,
ApprovalPolicyFenceConflictError,
ApprovalRequestNotFoundError,
ApprovalRequestStateConflictError,
ApprovalUnavailableError,
approvalRequestDigest,
approvedActionDispatchDigest,
consumeApprovalRequest,
decideApprovalRequest,
normalizeApprovalRequestRecord,
normalizeApprovedActionDispatchRecord,
type ApprovalRequestRecord,
type ApprovalRequestRepository,
type ApprovedActionDispatchRecord,
type ConsumeDurableApprovalRequestCommand,
type ConsumeDurableApprovalRequestResult,
type CreateApprovalRequestCommand,
type CreateApprovalRequestResult,
type DecideApprovalRequestResult,
type DecideDurableApprovalRequestCommand,
} from '@qinglong/runtime-core/approved-action';
import type {
SecurityPolicyFence,
SecuritySubject,
} from '@qinglong/runtime-core/security';
import {
normalizeSecurityAuditRecord,
type SecurityAuditRecord,
} from '@qinglong/runtime-core/security-audit';
import {
POSTGRES_DEFINITION_RETRYABLE_SQL_STATES,
POSTGRES_DEFINITION_TRANSACTION_ATTEMPTS,
configurePostgresDefinitionTransaction,
postgresRequiredBoolean,
postgresRequiredInteger,
postgresRequiredJsonObject,
postgresRequiredString,
postgresSqlState,
rollbackPostgresDefinitionTransaction,
} from '../repository/definitionRepositorySupport';
import {
findPostgresApprovedActionExecution,
insertPostgresApprovedActionExecutionBaseline,
} from './approvedActionExecutionRepository';
import { createApprovedActionExecution } from '@qinglong/runtime-core/approved-action-execution';
type Row = Record<string, unknown>;
type Queryable = Pick<PostgresPool, 'query'> | Pick<PostgresClient, 'query'>;
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
function unavailable(): ApprovalUnavailableError {
return new ApprovalUnavailableError();
}
function nullableString(value: unknown): string | null {
if (value === null) return null;
return postgresRequiredString(value, unavailable);
}
function nullableInteger(value: unknown): number | null {
if (value === null) return null;
return postgresRequiredInteger(value, unavailable);
}
function same(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
function parseRequest(row: Row): Readonly<ApprovalRequestRecord> {
try {
const request = normalizeApprovalRequestRecord(
postgresRequiredJsonObject(
row.requestJson,
unavailable,
) as unknown as ApprovalRequestRecord,
);
if (
approvalRequestDigest(request) !==
postgresRequiredString(row.requestDigest, unavailable)
) {
throw unavailable();
}
return request;
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw unavailable();
}
}
function parseDispatch(row: Row): Readonly<ApprovedActionDispatchRecord> {
try {
const dispatch = normalizeApprovedActionDispatchRecord(
postgresRequiredJsonObject(
row.dispatchJson,
unavailable,
) as unknown as ApprovedActionDispatchRecord,
);
if (
approvedActionDispatchDigest(dispatch) !==
postgresRequiredString(row.dispatchDigest, unavailable)
) {
throw unavailable();
}
return dispatch;
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw unavailable();
}
}
function parseAudit(row: Row): Readonly<SecurityAuditRecord> {
try {
const subjectType = nullableString(row.subjectType);
const subjectId = nullableString(row.subjectId);
const fenceProjectVersion = nullableInteger(row.fenceProjectVersion);
const reasons = row.reasonsJson;
if (!Array.isArray(reasons)) throw unavailable();
return normalizeSecurityAuditRecord({
eventId: postgresRequiredString(row.eventId, unavailable),
requestId: postgresRequiredString(row.requestId, unavailable),
operationId: postgresRequiredString(row.operationId, unavailable),
projectId: nullableString(row.projectId),
subject:
subjectType === null || subjectId === null
? null
: {
type: subjectType as SecuritySubject['type'],
id: subjectId,
},
authenticationId: nullableString(row.authenticationId),
outcome: postgresRequiredString(
row.outcome,
unavailable,
) as SecurityAuditRecord['outcome'],
reasons: reasons as readonly string[],
fence:
fenceProjectVersion === null
? null
: {
projectVersion: fenceProjectVersion,
bindingVersion: nullableInteger(row.fenceBindingVersion),
},
occurredAtMs: postgresRequiredInteger(row.occurredAtMs, unavailable),
});
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw unavailable();
}
}
function mappedError(error: unknown): Error {
if (
error instanceof ApprovalMutationConflictError ||
error instanceof ApprovalPolicyFenceConflictError ||
error instanceof ApprovalRequestNotFoundError ||
error instanceof ApprovalRequestStateConflictError ||
error instanceof ApprovalUnavailableError ||
(error instanceof Error &&
error.name.startsWith('Approval') &&
'code' in error)
) {
return error;
}
const state = postgresSqlState(error);
if (state === '23503' || state === '23505' || state === '23514') {
return new ApprovalMutationConflictError();
}
return new ApprovalUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
function auditMatches(
audit: Readonly<SecurityAuditRecord>,
expected: Readonly<{
operationId: string;
projectId: string;
subject: Readonly<SecuritySubject>;
authenticationId?: string;
outcome: SecurityAuditRecord['outcome'];
fence: Readonly<SecurityPolicyFence>;
}>,
): boolean {
return (
audit.operationId === expected.operationId &&
audit.projectId === expected.projectId &&
audit.subject?.type === expected.subject.type &&
audit.subject.id === expected.subject.id &&
(expected.authenticationId === undefined ||
audit.authenticationId === expected.authenticationId) &&
audit.outcome === expected.outcome &&
audit.fence?.projectVersion === expected.fence.projectVersion &&
audit.fence.bindingVersion === expected.fence.bindingVersion
);
}
async function requestById(
queryable: Queryable,
id: string,
): Promise<Readonly<ApprovalRequestRecord> | null> {
const result = await queryable.query<Row>(
`SELECT request_json AS "requestJson",
request_digest AS "requestDigest"
FROM "ql3"."approval_requests"
WHERE request_id = $1
LIMIT 2`,
[id],
);
if (result.rows.length === 0) return null;
if (result.rows.length !== 1) throw unavailable();
return parseRequest(result.rows[0]!);
}
async function dispatchById(
queryable: Queryable,
id: string,
): Promise<Readonly<ApprovedActionDispatchRecord> | null> {
const result = await queryable.query<Row>(
`SELECT dispatch_json AS "dispatchJson",
dispatch_digest AS "dispatchDigest"
FROM "ql3"."approved_action_dispatches"
WHERE dispatch_id = $1
LIMIT 2`,
[id],
);
if (result.rows.length === 0) return null;
if (result.rows.length !== 1) throw unavailable();
return parseDispatch(result.rows[0]!);
}
async function auditById(
queryable: Queryable,
id: string,
): Promise<Readonly<SecurityAuditRecord> | null> {
const result = await queryable.query<Row>(
`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 AS "outcome",
reasons AS "reasonsJson",
project_version AS "fenceProjectVersion",
binding_version AS "fenceBindingVersion",
occurred_at_ms AS "occurredAtMs"
FROM "ql3"."security_audit_events"
WHERE event_id = $1
LIMIT 2`,
[id],
);
if (result.rows.length === 0) return null;
if (result.rows.length !== 1) throw unavailable();
return parseAudit(result.rows[0]!);
}
async function assertFence(
client: PostgresClient,
projectId: string,
subject: Readonly<SecuritySubject>,
fence: Readonly<SecurityPolicyFence>,
): Promise<void> {
const result = await client.query<Row>(
`SELECT "ql3"."lock_approval_policy_fence"(
$1::varchar, $2::varchar, $3::varchar, $4::integer, $5::integer
) AS "matches"`,
[
projectId,
subject.type,
subject.id,
fence.projectVersion,
fence.bindingVersion,
],
);
if (
result.rows.length !== 1 ||
!postgresRequiredBoolean(result.rows[0]!.matches, unavailable)
) {
throw new ApprovalPolicyFenceConflictError();
}
}
async function insertAudit(
client: PostgresClient,
value: SecurityAuditRecord,
): Promise<void> {
const audit = normalizeSecurityAuditRecord(value);
const result = await client.query(
`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
)`,
[
audit.eventId,
audit.requestId,
audit.operationId,
audit.projectId,
audit.subject?.type ?? null,
audit.subject?.id ?? null,
audit.authenticationId,
audit.outcome,
JSON.stringify(audit.reasons),
audit.fence?.projectVersion ?? null,
audit.fence?.bindingVersion ?? null,
audit.occurredAtMs,
],
);
if (result.rowCount !== 1) throw unavailable();
}
async function insertRequest(
client: PostgresClient,
request: Readonly<ApprovalRequestRecord>,
): Promise<void> {
const result = await client.query(
`INSERT INTO "ql3"."approval_requests" (
request_id, project_id, version, state, action_type, action_ref,
action_digest, preview_digest, requested_by_type, requested_by_id,
decision_id, consumption_id, dispatch_id, expires_at_ms, request_json,
request_digest, updated_at_ms
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14,
$15::jsonb, $16, $17
)`,
[
request.id,
request.projectId,
request.version,
request.state,
request.action.actionType,
request.action.actionRef,
request.action.actionDigest,
request.action.previewDigest,
request.requestedBy.type,
request.requestedBy.id,
request.decisionId,
request.consumptionId,
request.dispatchId,
request.expiresAtMs,
JSON.stringify(request),
approvalRequestDigest(request),
request.consumedAtMs ?? request.decidedAtMs ?? request.requestedAtMs,
],
);
if (result.rowCount !== 1) throw unavailable();
}
async function updateRequest(
client: PostgresClient,
request: Readonly<ApprovalRequestRecord>,
expectedVersion: number,
): Promise<void> {
const result = await client.query(
`UPDATE "ql3"."approval_requests"
SET version = $1, state = $2, decision_id = $3, consumption_id = $4,
dispatch_id = $5, request_json = $6::jsonb, request_digest = $7,
updated_at_ms = $8
WHERE request_id = $9 AND version = $10`,
[
request.version,
request.state,
request.decisionId,
request.consumptionId,
request.dispatchId,
JSON.stringify(request),
approvalRequestDigest(request),
request.consumedAtMs ?? request.decidedAtMs ?? request.requestedAtMs,
request.id,
expectedVersion,
],
);
if (result.rowCount !== 1) throw new ApprovalRequestStateConflictError();
}
async function insertDispatch(
client: PostgresClient,
dispatch: Readonly<ApprovedActionDispatchRecord>,
): Promise<void> {
const result = await client.query(
`INSERT INTO "ql3"."approved_action_dispatches" (
dispatch_id, approval_request_id, project_id, action_type, action_ref,
action_digest, preview_digest, dispatch_json, dispatch_digest,
created_at_ms
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, $10
)`,
[
dispatch.id,
dispatch.approvalRequestId,
dispatch.projectId,
dispatch.action.actionType,
dispatch.action.actionRef,
dispatch.action.actionDigest,
dispatch.action.previewDigest,
JSON.stringify(dispatch),
approvedActionDispatchDigest(dispatch),
dispatch.createdAtMs,
],
);
if (result.rowCount !== 1) throw unavailable();
}
/** Administration-only PostgreSQL Approval/Approved Action authority. */
export class PostgresApprovalRequestRepository
implements ApprovalRequestRepository
{
constructor(private readonly pool: PostgresPool) {
if (
!pool ||
typeof pool.query !== 'function' ||
typeof pool.connect !== 'function'
) {
throw new TypeError('PostgreSQL Approval pool is invalid');
}
}
async #transaction<T>(
work: (client: PostgresClient) => Promise<T>,
): Promise<T> {
for (
let attempt = 0;
attempt < POSTGRES_DEFINITION_TRANSACTION_ATTEMPTS;
attempt += 1
) {
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch (error) {
throw mappedError(error);
}
let began = false;
try {
await configurePostgresDefinitionTransaction(client);
began = true;
const result = await work(client);
await client.query('COMMIT');
began = false;
return result;
} catch (error) {
if (began) await rollbackPostgresDefinitionTransaction(client);
const state = postgresSqlState(error);
if (
state &&
POSTGRES_DEFINITION_RETRYABLE_SQL_STATES.has(state) &&
attempt + 1 < POSTGRES_DEFINITION_TRANSACTION_ATTEMPTS
) {
continue;
}
throw mappedError(error);
} finally {
client.release();
}
}
throw unavailable();
}
async findById(
id: string,
): Promise<Readonly<ApprovalRequestRecord> | null> {
if (typeof id !== 'string' || !IDENTIFIER_PATTERN.test(id)) {
throw new TypeError('Approval request lookup identity is invalid');
}
try {
return await requestById(this.pool, id);
} catch (error) {
throw mappedError(error);
}
}
async findDispatchById(
id: string,
): Promise<Readonly<ApprovedActionDispatchRecord> | null> {
if (typeof id !== 'string' || !IDENTIFIER_PATTERN.test(id)) {
throw new TypeError('Approved action dispatch identity is invalid');
}
try {
return await dispatchById(this.pool, id);
} catch (error) {
throw mappedError(error);
}
}
create(
command: CreateApprovalRequestCommand,
): Promise<CreateApprovalRequestResult> {
const request = normalizeApprovalRequestRecord(command.request);
const audit = normalizeSecurityAuditRecord(command.audit);
if (
request.state !== 'pending' ||
request.version !== 1 ||
!auditMatches(audit, {
operationId: 'approval.request',
projectId: request.projectId,
subject: request.requestedBy,
outcome: 'approval_required',
fence: request.requestFence,
})
) {
throw new ApprovalMutationConflictError();
}
return this.#transaction(async (client) => {
const existing = await requestById(client, request.id);
if (existing) {
const storedAudit = await auditById(client, audit.eventId);
if (
!same(existing, request) ||
!storedAudit ||
!same(storedAudit, audit)
) {
throw new ApprovalMutationConflictError();
}
return Object.freeze({ status: 'existing' as const, request });
}
await assertFence(
client,
request.projectId,
request.requestedBy,
request.requestFence,
);
await insertRequest(client, request);
await insertAudit(client, audit);
return Object.freeze({ status: 'created' as const, request });
});
}
decide(
command: DecideDurableApprovalRequestCommand,
): Promise<DecideApprovalRequestResult> {
const audit = normalizeSecurityAuditRecord(command.audit);
return this.#transaction(async (client) => {
const current = await requestById(client, command.requestId);
if (!current) throw new ApprovalRequestNotFoundError();
const request = decideApprovalRequest(current, {
expectedVersion: command.expectedVersion,
decisionId: command.decisionId,
decision: command.decision,
reasonCode: command.reasonCode,
principal: command.principal,
decidedAtMs: command.decidedAtMs,
authorizationFence: command.authorizationFence,
});
if (
!auditMatches(audit, {
operationId: 'approval.decide',
projectId: request.projectId,
subject: command.principal.subject,
authenticationId: command.principal.authenticationId,
outcome: 'allowed',
fence: command.authorizationFence,
})
) {
throw new ApprovalMutationConflictError();
}
if (current.version === 2 && current.decisionId === command.decisionId) {
const storedAudit = await auditById(client, audit.eventId);
if (!storedAudit || !same(storedAudit, audit)) {
throw new ApprovalMutationConflictError();
}
return Object.freeze({ status: 'existing' as const, request });
}
await assertFence(
client,
request.projectId,
command.principal.subject,
command.authorizationFence,
);
await updateRequest(client, request, command.expectedVersion);
await insertAudit(client, audit);
return Object.freeze({ status: 'decided' as const, request });
});
}
consume(
command: ConsumeDurableApprovalRequestCommand,
): Promise<ConsumeDurableApprovalRequestResult> {
const audit = normalizeSecurityAuditRecord(command.audit);
return this.#transaction(async (client) => {
const current = await requestById(client, command.requestId);
if (!current) throw new ApprovalRequestNotFoundError();
const result = consumeApprovalRequest(current, {
expectedVersion: command.expectedVersion,
consumptionId: command.consumptionId,
dispatchId: command.dispatchId,
action: command.action,
requestedBy: command.requestedBy,
consumedBy: command.consumedBy,
consumedAtMs: command.consumedAtMs,
authorizationFence: command.authorizationFence,
});
if (
!auditMatches(audit, {
operationId: 'approval.consume',
projectId: result.request.projectId,
subject: command.consumedBy,
outcome: 'allowed',
fence: command.authorizationFence,
})
) {
throw new ApprovalMutationConflictError();
}
if (
current.version === 3 &&
current.consumptionId === command.consumptionId
) {
const storedAudit = await auditById(client, audit.eventId);
const dispatch = await dispatchById(client, result.dispatch.id);
const execution = await findPostgresApprovedActionExecution(
client,
result.dispatch.id,
);
if (
!storedAudit ||
!same(storedAudit, audit) ||
!dispatch ||
!same(dispatch, result.dispatch) ||
!execution ||
!same(execution, createApprovedActionExecution(result.dispatch))
) {
throw new ApprovalMutationConflictError();
}
return Object.freeze({
status: 'existing' as const,
request: result.request,
dispatch: result.dispatch,
});
}
await assertFence(
client,
result.request.projectId,
command.requestedBy,
command.authorizationFence,
);
await insertDispatch(client, result.dispatch);
await insertPostgresApprovedActionExecutionBaseline(
client,
result.dispatch,
);
await updateRequest(client, result.request, command.expectedVersion);
await insertAudit(client, audit);
return Object.freeze({
status: 'consumed' as const,
request: result.request,
dispatch: result.dispatch,
});
});
}
}
@@ -0,0 +1,229 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import {
ApprovalUnavailableError,
approvalRequestDigest,
normalizeApprovalRequestRecord,
type ApprovalRequestRecord,
} from '@qinglong/runtime-core/approved-action';
import {
approvalRequestUpdatedAtMs,
assertApprovalDiscoveryProjectId,
assertApprovalDiscoveryRequestId,
assertApprovalRequestPageSize,
normalizeApprovalRequestCursor,
type ApprovalRequestDetail,
type ApprovalRequestDetailSource,
type ApprovalRequestPage,
type ApprovalRequestSource,
} from '@qinglong/runtime-core/approval-discovery';
import {
normalizeToolInvocationPreviewArtifact,
type ToolInvocationPreviewArtifact,
} from '@qinglong/runtime-core/tool-invocation-artifact';
import {
postgresRequiredInteger,
postgresRequiredJsonObject,
postgresRequiredString,
} from '../repository/definitionRepositorySupport';
type Row = Record<string, unknown>;
function unavailable(): ApprovalUnavailableError {
return new ApprovalUnavailableError();
}
function request(row: Row): Readonly<ApprovalRequestRecord> {
try {
const value = normalizeApprovalRequestRecord(
postgresRequiredJsonObject(
row.requestJson,
unavailable,
) as unknown as ApprovalRequestRecord,
);
if (
approvalRequestDigest(value) !==
postgresRequiredString(row.requestDigest, unavailable) ||
approvalRequestUpdatedAtMs(value) !==
postgresRequiredInteger(row.updatedAtMs, unavailable)
) {
throw unavailable();
}
return value;
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw unavailable();
}
}
function previewArtifact(
row: Row,
): Readonly<ToolInvocationPreviewArtifact> | null {
if (row.previewArtifactId === null) return null;
try {
const value = normalizeToolInvocationPreviewArtifact(
postgresRequiredJsonObject(
row.previewArtifactJson,
unavailable,
) as unknown as ToolInvocationPreviewArtifact,
);
if (
value.artifactId !== postgresRequiredString(row.previewArtifactId, unavailable) ||
value.projectId !== postgresRequiredString(row.previewProjectId, unavailable) ||
value.actionRef !== postgresRequiredString(row.previewActionRef, unavailable) ||
value.actionDigest !== postgresRequiredString(row.previewActionDigest, unavailable) ||
value.previewDigest !== postgresRequiredString(row.storedPreviewDigest, unavailable) ||
value.redactionContractDigest !==
postgresRequiredString(row.redactionContractDigest, unavailable) ||
value.artifactDigest !== postgresRequiredString(row.previewArtifactDigest, unavailable) ||
value.byteLength !== postgresRequiredInteger(row.previewByteLength, unavailable) ||
value.sealedAtMs !== postgresRequiredInteger(row.previewSealedAtMs, unavailable)
) {
throw unavailable();
}
return value;
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw unavailable();
}
}
/** Read-only Approval history using the existing Project keyset index. */
export class PostgresApprovalRequestSource
implements ApprovalRequestSource, ApprovalRequestDetailSource
{
constructor(private readonly pool: Pick<PostgresPool, 'query'>) {
if (!pool || typeof pool.query !== 'function') {
throw new TypeError('PostgreSQL Approval discovery pool is invalid');
}
}
async listApprovalRequests(options: {
readonly projectId: string;
readonly limit: number;
readonly after?: { readonly updatedAtMs: number; readonly requestId: string };
}): Promise<Readonly<ApprovalRequestPage>> {
if (!options || typeof options !== 'object' || Array.isArray(options)) {
throw new TypeError('Approval discovery options are invalid');
}
const keys = Object.keys(options);
if (
!keys.includes('projectId') ||
!keys.includes('limit') ||
keys.some((key) => !['projectId', 'limit', 'after'].includes(key))
) {
throw new TypeError('Approval discovery options shape is invalid');
}
assertApprovalDiscoveryProjectId(options.projectId);
assertApprovalRequestPageSize(options.limit);
const after = options.after
? normalizeApprovalRequestCursor(options.after)
: undefined;
try {
const result = await this.pool.query<Row>(
`SELECT request_json AS "requestJson",
request_digest AS "requestDigest",
updated_at_ms AS "updatedAtMs"
FROM "ql3"."approval_requests"
WHERE project_id = $1
AND (
$2::bigint IS NULL OR updated_at_ms < $2 OR
(updated_at_ms = $2 AND request_id < $3)
)
ORDER BY updated_at_ms DESC, request_id DESC
LIMIT $4`,
[
options.projectId,
after?.updatedAtMs ?? null,
after?.requestId ?? '',
options.limit + 1,
],
);
const truncated = result.rows.length > options.limit;
const requests = Object.freeze(
result.rows.slice(0, options.limit).map(request),
);
const last = requests.at(-1);
return Object.freeze({
requests,
truncated,
...(truncated && last
? {
next: Object.freeze({
updatedAtMs: approvalRequestUpdatedAtMs(last),
requestId: last.id,
}),
}
: {}),
});
} catch {
throw unavailable();
}
}
async getApprovalRequestDetail(options: {
readonly projectId: string;
readonly requestId: string;
}): Promise<Readonly<ApprovalRequestDetail> | null> {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).length !== 2 ||
!Object.hasOwn(options, 'projectId') ||
!Object.hasOwn(options, 'requestId')
) {
throw new TypeError('Approval detail options are invalid');
}
assertApprovalDiscoveryProjectId(options.projectId);
assertApprovalDiscoveryRequestId(options.requestId);
try {
const result = await this.pool.query<Row>(
`SELECT a.request_json AS "requestJson",
a.request_digest AS "requestDigest",
a.updated_at_ms AS "updatedAtMs",
p.artifact_id AS "previewArtifactId",
p.project_id AS "previewProjectId",
p.action_ref AS "previewActionRef",
p.action_digest AS "previewActionDigest",
p.preview_digest AS "storedPreviewDigest",
p.redaction_contract_digest AS "redactionContractDigest",
p.artifact_digest AS "previewArtifactDigest",
p.byte_length AS "previewByteLength",
p.sealed_at_ms AS "previewSealedAtMs",
p.artifact_json AS "previewArtifactJson"
FROM "ql3"."approval_requests" a
LEFT JOIN "ql3"."tool_invocation_preview_artifacts" p
ON a.action_type = 'tool.invoke'
AND p.project_id = a.project_id
AND p.action_ref = a.action_ref
AND p.action_digest = a.action_digest
AND p.preview_digest = a.preview_digest
WHERE a.project_id = $1 AND a.request_id = $2
LIMIT 2`,
[options.projectId, options.requestId],
);
if (result.rows.length > 1) throw unavailable();
if (!result.rows[0]) return null;
const approval = request(result.rows[0]);
const preview = previewArtifact(result.rows[0]);
if (
preview &&
(approval.action.actionType !== 'tool.invoke' ||
approval.projectId !== preview.projectId ||
approval.action.actionRef !== preview.actionRef ||
approval.action.actionDigest !== preview.actionDigest ||
approval.action.previewDigest !== preview.previewDigest)
) {
throw unavailable();
}
return Object.freeze({
request: approval,
preview: preview?.preview ?? null,
});
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw unavailable();
}
}
}
@@ -0,0 +1,512 @@
import type {
PostgresClient,
PostgresPool,
} from '@qinglong/runtime-core';
import {
normalizeApprovedActionDispatchRecord,
type ApprovedActionDispatchRecord,
} from '@qinglong/runtime-core/approved-action';
import {
ApprovedActionExecutionFenceConflictError,
ApprovedActionExecutionStateConflictError,
ApprovedActionExecutionUnavailableError,
approvedActionExecutionEffectiveStatus,
claimApprovedActionExecution,
completeApprovedActionExecution,
createApprovedActionExecution,
normalizeApprovedActionExecutionCursor,
normalizeApprovedActionExecutionRecord,
normalizeApprovedActionExecutionSnapshot,
releaseApprovedActionExecutionBeforeStart,
renewApprovedActionExecution,
startApprovedActionExecution,
type ApprovedActionExecutionRecord,
type ApprovedActionExecutionRepository,
type ApprovedActionExecutionSnapshot,
type ClaimApprovedActionExecutionCommand,
type ClaimApprovedActionExecutionResult,
type CompleteApprovedActionExecutionCommand,
type ListDueApprovedActionExecutionsQuery,
type ListDueApprovedActionExecutionsResult,
type ReleaseApprovedActionExecutionBeforeStartCommand,
type RenewApprovedActionExecutionCommand,
type StartApprovedActionExecutionCommand,
} from '@qinglong/runtime-core/approved-action-execution';
import {
POSTGRES_DEFINITION_RETRYABLE_SQL_STATES,
POSTGRES_DEFINITION_TRANSACTION_ATTEMPTS,
configurePostgresDefinitionTransaction,
postgresRequiredJsonObject,
postgresRequiredString,
postgresSqlState,
rollbackPostgresDefinitionTransaction,
} from '../repository/definitionRepositorySupport';
type Row = Record<string, unknown>;
type Queryable = Pick<PostgresPool, 'query'> | Pick<PostgresClient, 'query'>;
function unavailable(): ApprovedActionExecutionUnavailableError {
return new ApprovedActionExecutionUnavailableError();
}
function parseExecution(row: Row): Readonly<ApprovedActionExecutionRecord> {
try {
const execution = normalizeApprovedActionExecutionRecord(
postgresRequiredJsonObject(
row.executionJson,
unavailable,
) as unknown as ApprovedActionExecutionRecord,
);
if (
execution.executionDigest !==
postgresRequiredString(row.executionDigest, unavailable)
) {
throw unavailable();
}
return execution;
} catch (error) {
if (error instanceof ApprovedActionExecutionUnavailableError) throw error;
throw unavailable();
}
}
function parseDispatch(row: Row): Readonly<ApprovedActionDispatchRecord> {
try {
return normalizeApprovedActionDispatchRecord(
postgresRequiredJsonObject(
row.dispatchJson,
unavailable,
) as unknown as ApprovedActionDispatchRecord,
);
} catch {
throw unavailable();
}
}
function mappedError(error: unknown): Error {
if (
error instanceof ApprovedActionExecutionFenceConflictError ||
error instanceof ApprovedActionExecutionStateConflictError ||
error instanceof ApprovedActionExecutionUnavailableError ||
(error instanceof Error &&
error.name.startsWith('ApprovedActionExecution'))
) {
return error;
}
const state = postgresSqlState(error);
if (state === '23503' || state === '23505' || state === '23514') {
return new ApprovedActionExecutionStateConflictError();
}
return new ApprovedActionExecutionUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
function executionValues(
execution: Readonly<ApprovedActionExecutionRecord>,
): readonly (string | number | null)[] {
return [
execution.dispatchId,
execution.dispatchDigest,
execution.projectId,
execution.status,
execution.version,
execution.attemptCount,
execution.maxAttempts,
execution.eligibleAtMs,
execution.nextAttemptAtMs,
execution.leaseOwner,
execution.leaseToken,
execution.leaseExpiresAtMs,
execution.startedAtMs,
execution.resultMutationId,
execution.resultCode,
execution.resultDigest,
execution.completedAtMs,
execution.createdAtMs,
execution.updatedAtMs,
JSON.stringify(execution),
execution.executionDigest,
];
}
export async function insertPostgresApprovedActionExecutionBaseline(
client: PostgresClient,
dispatchValue: ApprovedActionDispatchRecord,
): Promise<Readonly<ApprovedActionExecutionRecord>> {
const execution = createApprovedActionExecution(dispatchValue);
const result = await client.query(
`INSERT INTO "ql3"."approved_action_executions" (
dispatch_id, dispatch_digest, project_id, status, version,
attempt_count, max_attempts, eligible_at_ms, next_attempt_at_ms,
lease_owner, lease_token, lease_expires_at_ms, started_at_ms,
result_mutation_id, result_code, result_digest, completed_at_ms,
created_at_ms, updated_at_ms, execution_json, execution_digest
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14,
$15, $16, $17, $18, $19, $20::jsonb, $21
)`,
executionValues(execution),
);
if (result.rowCount !== 1) throw unavailable();
return execution;
}
export async function findPostgresApprovedActionExecution(
queryable: Queryable,
dispatchId: string,
): Promise<Readonly<ApprovedActionExecutionRecord> | null> {
const result = await queryable.query<Row>(
`SELECT execution_json AS "executionJson",
execution_digest AS "executionDigest"
FROM "ql3"."approved_action_executions"
WHERE dispatch_id = $1
LIMIT 2`,
[dispatchId],
);
if (result.rows.length === 0) return null;
if (result.rows.length !== 1) throw unavailable();
return parseExecution(result.rows[0]!);
}
async function loadSnapshot(
queryable: Queryable,
dispatchId: string,
lock = false,
): Promise<Readonly<ApprovedActionExecutionSnapshot> | null> {
const result = await queryable.query<Row>(
`SELECT execution.execution_json AS "executionJson",
execution.execution_digest AS "executionDigest",
dispatch.dispatch_json AS "dispatchJson"
FROM "ql3"."approved_action_executions" AS execution
JOIN "ql3"."approved_action_dispatches" AS dispatch
ON dispatch.dispatch_id = execution.dispatch_id
WHERE execution.dispatch_id = $1
LIMIT 2
${lock ? 'FOR UPDATE OF execution' : ''}`,
[dispatchId],
);
if (result.rows.length === 0) return null;
if (result.rows.length !== 1) throw unavailable();
return normalizeApprovedActionExecutionSnapshot({
dispatch: parseDispatch(result.rows[0]!),
execution: parseExecution(result.rows[0]!),
});
}
async function replaceExecution(
client: PostgresClient,
previous: Readonly<ApprovedActionExecutionRecord>,
next: Readonly<ApprovedActionExecutionRecord>,
): Promise<void> {
const values = executionValues(next);
const result = await client.query(
`UPDATE "ql3"."approved_action_executions"
SET dispatch_digest = $1, project_id = $2, status = $3,
version = $4, attempt_count = $5, max_attempts = $6,
eligible_at_ms = $7, next_attempt_at_ms = $8, lease_owner = $9,
lease_token = $10, lease_expires_at_ms = $11, started_at_ms = $12,
result_mutation_id = $13, result_code = $14, result_digest = $15,
completed_at_ms = $16, created_at_ms = $17, updated_at_ms = $18,
execution_json = $19::jsonb, execution_digest = $20
WHERE dispatch_id = $21 AND version = $22
AND execution_digest = $23`,
[
...values.slice(1),
previous.dispatchId,
previous.version,
previous.executionDigest,
],
);
if (result.rowCount !== 1) {
throw new ApprovedActionExecutionFenceConflictError();
}
}
function assertPageSize(value: number): void {
if (!Number.isSafeInteger(value) || value < 1 || value > 64) {
throw new ApprovedActionExecutionStateConflictError();
}
}
function actionTypes(values: readonly string[]): readonly string[] {
if (
!Array.isArray(values) ||
values.length > 64 ||
values.some(
(value) =>
typeof value !== 'string' ||
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value),
) ||
new Set(values).size !== values.length
) {
throw new ApprovedActionExecutionStateConflictError();
}
return values;
}
export class PostgresApprovedActionExecutionRepository
implements ApprovedActionExecutionRepository
{
constructor(private readonly pool: PostgresPool) {
if (
!pool ||
typeof pool.query !== 'function' ||
typeof pool.connect !== 'function'
) {
throw new TypeError('PostgreSQL Approved Action execution pool is invalid');
}
}
async #transaction<T>(
work: (client: PostgresClient) => Promise<T>,
): Promise<T> {
for (
let attempt = 0;
attempt < POSTGRES_DEFINITION_TRANSACTION_ATTEMPTS;
attempt += 1
) {
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch (error) {
throw mappedError(error);
}
let began = false;
try {
await configurePostgresDefinitionTransaction(client);
began = true;
const result = await work(client);
await client.query('COMMIT');
began = false;
return result;
} catch (error) {
if (began) await rollbackPostgresDefinitionTransaction(client);
const state = postgresSqlState(error);
if (
state &&
POSTGRES_DEFINITION_RETRYABLE_SQL_STATES.has(state) &&
attempt + 1 < POSTGRES_DEFINITION_TRANSACTION_ATTEMPTS
) {
continue;
}
throw mappedError(error);
} finally {
client.release();
}
}
throw unavailable();
}
async findExecutionByDispatchId(
dispatchId: string,
): Promise<Readonly<ApprovedActionExecutionSnapshot> | null> {
try {
return await loadSnapshot(this.pool, dispatchId);
} catch (error) {
throw mappedError(error);
}
}
async listDueExecutions(
query: ListDueApprovedActionExecutionsQuery,
): Promise<ListDueApprovedActionExecutionsResult> {
assertPageSize(query.limit);
const handledActionTypes = actionTypes(query.actionTypes);
if (!Number.isSafeInteger(query.nowMs) || query.nowMs < 0) {
throw new ApprovedActionExecutionStateConflictError();
}
if (handledActionTypes.length === 0) {
return Object.freeze({ executions: Object.freeze([]), truncated: false });
}
const cursor = query.cursor
? normalizeApprovedActionExecutionCursor(query.cursor)
: undefined;
try {
const result = await this.pool.query<Row>(
`SELECT execution.execution_json AS "executionJson",
execution.execution_digest AS "executionDigest",
dispatch.dispatch_json AS "dispatchJson"
FROM "ql3"."approved_action_executions" AS execution
JOIN "ql3"."approved_action_dispatches" AS dispatch
ON dispatch.dispatch_id = execution.dispatch_id
WHERE execution.status IN ('pending','leased','retry_wait')
AND execution.eligible_at_ms <= $1
AND dispatch.action_type = ANY($2::varchar[])
AND (
$3::varchar IS NULL OR execution.eligible_at_ms > $4 OR
(
execution.eligible_at_ms = $4
AND execution.dispatch_id > $3
)
)
ORDER BY execution.eligible_at_ms, execution.dispatch_id
LIMIT $5`,
[
query.nowMs,
handledActionTypes,
cursor?.dispatchId ?? null,
cursor?.eligibleAtMs ?? 0,
query.limit + 1,
],
);
const truncated = result.rows.length > query.limit;
const rows = truncated
? result.rows.slice(0, query.limit)
: result.rows;
const executions = rows.map((row) =>
normalizeApprovedActionExecutionSnapshot({
dispatch: parseDispatch(row),
execution: parseExecution(row),
}),
);
const last = executions.at(-1)?.execution;
return Object.freeze({
executions: Object.freeze(executions),
truncated,
...(truncated && last?.eligibleAtMs !== null
? {
nextCursor: Object.freeze({
eligibleAtMs: last!.eligibleAtMs!,
dispatchId: last!.dispatchId,
}),
}
: {}),
});
} catch (error) {
throw mappedError(error);
}
}
claimExecution(
command: ClaimApprovedActionExecutionCommand,
): Promise<ClaimApprovedActionExecutionResult> {
return this.#transaction(async (client) => {
const current = await loadSnapshot(client, command.dispatchId, true);
if (!current) return Object.freeze({ status: 'not_found' as const });
const effective = approvedActionExecutionEffectiveStatus(
current.execution,
command.nowMs,
);
if (
current.execution.status === 'leased' &&
current.execution.leaseExpiresAtMs !== null &&
current.execution.leaseExpiresAtMs <= command.nowMs &&
current.execution.attemptCount >= current.execution.maxAttempts
) {
return Object.freeze({
status: 'recovery_required' as const,
snapshot: current,
});
}
const due =
current.execution.eligibleAtMs !== null &&
current.execution.eligibleAtMs <= command.nowMs;
if (
effective !== 'pending' &&
effective !== 'retry_wait' &&
!(effective === 'leased' && due)
) {
return Object.freeze({ status: effective, snapshot: current });
}
if (!due) {
return Object.freeze({
status: 'not_due' as const,
snapshot: current,
});
}
const next = claimApprovedActionExecution(current.execution, {
owner: command.owner,
leaseToken: command.leaseToken,
nowMs: command.nowMs,
leaseDurationMs: command.leaseDurationMs,
});
await replaceExecution(client, current.execution, next);
return Object.freeze({
status: 'claimed' as const,
snapshot: Object.freeze({
dispatch: current.dispatch,
execution: next,
}),
});
});
}
startExecution(
command: StartApprovedActionExecutionCommand,
): Promise<Readonly<ApprovedActionExecutionSnapshot>> {
return this.#mutate(command.dispatchId, (current) =>
startApprovedActionExecution(current, command),
);
}
renewExecution(
command: RenewApprovedActionExecutionCommand,
): Promise<Readonly<ApprovedActionExecutionSnapshot>> {
return this.#mutate(command.dispatchId, (current) =>
renewApprovedActionExecution(current.execution, {
owner: command.owner,
leaseToken: command.leaseToken,
expectedVersion: command.expectedVersion,
nowMs: command.nowMs,
leaseDurationMs: command.leaseDurationMs,
}),
);
}
releaseExecutionBeforeStart(
command: ReleaseApprovedActionExecutionBeforeStartCommand,
): Promise<Readonly<ApprovedActionExecutionSnapshot>> {
return this.#mutate(command.dispatchId, (current) =>
releaseApprovedActionExecutionBeforeStart(current.execution, {
owner: command.owner,
leaseToken: command.leaseToken,
expectedVersion: command.expectedVersion,
resultMutationId: command.resultMutationId,
resultCode: command.resultCode,
atMs: command.atMs,
...(command.retryAtMs === undefined
? {}
: { retryAtMs: command.retryAtMs }),
}),
);
}
completeExecution(
command: CompleteApprovedActionExecutionCommand,
): Promise<Readonly<ApprovedActionExecutionSnapshot>> {
return this.#mutate(command.dispatchId, (current) =>
completeApprovedActionExecution(current.execution, {
owner: command.owner,
leaseToken: command.leaseToken,
expectedVersion: command.expectedVersion,
resultMutationId: command.resultMutationId,
outcome: command.outcome,
resultCode: command.resultCode,
...(command.resultDigest === undefined
? {}
: { resultDigest: command.resultDigest }),
completedAtMs: command.completedAtMs,
}),
);
}
#mutate(
dispatchId: string,
transition: (
current: Readonly<ApprovedActionExecutionSnapshot>,
) => Readonly<ApprovedActionExecutionRecord>,
): Promise<Readonly<ApprovedActionExecutionSnapshot>> {
return this.#transaction(async (client) => {
const current = await loadSnapshot(client, dispatchId, true);
if (!current) throw new ApprovedActionExecutionStateConflictError();
const next = transition(current);
await replaceExecution(client, current.execution, next);
return Object.freeze({
dispatch: current.dispatch,
execution: next,
});
});
}
}