/** * Wire contract for idempotent Runtime Postgres -> Convex Run Ledger delivery. * * Producers must use these builders and API boundaries must use these * validators. Keeping the format here prevents the scheduler and runtime route * from admitting different lifecycle-event keys. */ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i; function requireRunId(runId: string): void { if (!runId.trim()) { throw new Error('Run Ledger projection idempotency key requires a run id.'); } } function requireDeliveryKey(deliveryKey: string): void { if (!UUID_PATTERN.test(deliveryKey)) { throw new Error( 'Run Ledger projection delivery key must be a canonical UUID.', ); } } export function buildRunLedgerStartIdempotencyKey(input: { runId: string; deliveryKey: string; }): string { requireRunId(input.runId); requireDeliveryKey(input.deliveryKey); return `run-started:${input.runId}:${input.deliveryKey}`; } export function buildRunLedgerTerminalIdempotencyKey(input: { runId: string; deliveryKey: string; }): string { requireRunId(input.runId); requireDeliveryKey(input.deliveryKey); return `run-terminal:${input.runId}:${input.deliveryKey}`; } /** * Compatibility key for the one-row projector. The `run-terminal` prefix was * historically used for every non-start row, including lifecycle events, so * changing it during a retry could append the same fact twice across deploys. */ export function buildRunLedgerSingleEventIdempotencyKey(input: { runId: string; deliveryKey: string; }): string { return buildRunLedgerTerminalIdempotencyKey(input); } export function buildRunLedgerEventsIdempotencyKey(input: { runId: string; deliveryDigest: string; }): string { requireRunId(input.runId); if (!SHA256_HEX_PATTERN.test(input.deliveryDigest)) { throw new Error( 'Run Ledger projection delivery digest must be a SHA-256 hex value.', ); } return `run-events:${input.runId}:${input.deliveryDigest}`; } export function isValidRunLedgerStartIdempotencyKey(input: { runId: string; idempotencyKey: string; }): boolean { const prefix = `run-started:${input.runId}:`; return ( input.idempotencyKey.startsWith(prefix) && UUID_PATTERN.test(input.idempotencyKey.slice(prefix.length)) ); } export function isValidRunLedgerAppendIdempotencyKey(input: { runId: string; idempotencyKey: string; }): boolean { const terminalPrefix = `run-terminal:${input.runId}:`; if (input.idempotencyKey.startsWith(terminalPrefix)) { return UUID_PATTERN.test(input.idempotencyKey.slice(terminalPrefix.length)); } const eventsPrefix = `run-events:${input.runId}:`; return ( input.idempotencyKey.startsWith(eventsPrefix) && SHA256_HEX_PATTERN.test(input.idempotencyKey.slice(eventsPrefix.length)) ); }