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,484 @@
import type { ApiCredentialRepository } from '@qinglong/runtime-core/api-credential';
import type { LocalOwnerPepperRepository } from '@qinglong/runtime-core/local-owner-pepper';
import type { PluginPackageQuarantineRepository } from '@qinglong/runtime-core/plugin-package-quarantine';
import type { PluginPackageAutomationPublicationRepository } from '@qinglong/runtime-core/plugin-package-automation-publication';
import type { LocalSecretEnvelopeRepository } from '@qinglong/runtime-core/local-secret';
import type { ProjectPolicyRepository } from '@qinglong/runtime-core/project-policy';
import type { SecurityAuditSink } from '@qinglong/runtime-core/security-audit';
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
import type {
SecurityPolicyFence,
SecuritySubject,
} from '@qinglong/runtime-core/security';
import { LocalSqliteApiCredentialRepository } from '../security/apiCredentialRepository';
import {
assertLocalSqliteOptions,
assertLocalSqlitePathBoundary,
LocalSqliteConfigurationError,
openLocalSqliteClient,
type LocalSqliteDatabaseOptions,
type LocalSqliteProfile,
} from '../storage/config';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
import { LocalSqliteOwnerPepperRepository } from '../local-owner/ownerPepperRepository';
import {
EDGE_PLUGIN_PACKAGE_QUARANTINE_ACTIVE_SOURCE_LIMIT,
LocalSqlitePluginPackageQuarantineRepository,
STANDALONE_PLUGIN_PACKAGE_QUARANTINE_ACTIVE_SOURCE_LIMIT,
} from '../plugin-package/pluginPackageQuarantineRepository';
import { LocalSqlitePluginPackageAutomationPublicationRepository } from '../plugin-package/pluginPackageAutomationPublicationRepository';
import {
insertLocalSecurityAudit,
localSecurityAuditFromRow,
sameSecurityAuditSemantic,
} from '../security/securityPersistence';
import { LocalSqliteSecurityAuthorityStore } from '../security/securityAuthorityStore';
import {
auditLocalSqliteReadiness,
type LocalSqliteReadinessEvidence,
} from '../readiness/readiness';
/**
* One short-lived local authority for authenticated package management.
* It deliberately stays behind an explicit subpath and never migrates schema.
*/
export interface LocalSqlitePluginPackageManagementDatabase {
readonly profile: LocalSqliteProfile;
readonly readiness: LocalSqliteReadinessEvidence;
readonly authority: LocalSqliteOperationAuthority;
readonly apiCredentials: ApiCredentialRepository;
readonly ownerPepper: Pick<LocalOwnerPepperRepository, 'resolveKey'>;
readonly pluginPackageQuarantine: PluginPackageQuarantineRepository;
readonly securityAudit: SecurityAuditSink;
confirmUserCredentialFence(
fence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
): void;
confirmDefaultProjectOwnerFence(
fence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
): void;
close(): Promise<void>;
}
export interface LocalSqliteOptionalFeatureRuntimeDatabase {
readonly profile: LocalSqliteProfile;
readonly readiness: LocalSqliteReadinessEvidence;
readonly authority: LocalSqliteOperationAuthority;
readonly apiCredentials: ApiCredentialRepository;
readonly ownerPepper: Pick<LocalOwnerPepperRepository, 'resolveKey'>;
readonly projectPolicy: ProjectPolicyRepository;
readonly automationPublications: Pick<
PluginPackageAutomationPublicationRepository,
'findCurrent' | 'findByDigest'
>;
readonly localSecrets: LocalSecretEnvelopeRepository;
readonly securityAudit: SecurityAuditSink;
close(): Promise<void>;
}
export interface LocalSqliteAuthenticatedUserCredentialFence {
readonly credentialId: string;
readonly credentialVersion: number;
readonly pepperKeyId: string;
readonly materialDigest: string;
readonly subjectType: 'user';
readonly subjectId: string;
readonly secretDigest: string;
readonly notBeforeAtMs: number;
readonly expiresAtMs: number;
}
export class LocalSqliteAuthenticatedManagementFenceError extends Error {
readonly code = 'LOCAL_SQLITE_AUTHENTICATED_MANAGEMENT_FENCE_REJECTED';
constructor() {
super('The authenticated local management credential fence was rejected');
this.name = 'LocalSqliteAuthenticatedManagementFenceError';
}
}
export class LocalSqliteAuthenticatedManagementOwnerError extends Error {
readonly code = 'LOCAL_SQLITE_AUTHENTICATED_MANAGEMENT_OWNER_REJECTED';
constructor() {
super('The authenticated local management User is not a current Owner');
this.name = 'LocalSqliteAuthenticatedManagementOwnerError';
}
}
function exactFence(
value: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
): void {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.keys(value).sort().join('\0') !==
[
'credentialId',
'credentialVersion',
'expiresAtMs',
'materialDigest',
'notBeforeAtMs',
'pepperKeyId',
'secretDigest',
'subjectId',
'subjectType',
]
.sort()
.join('\0') ||
typeof value.credentialId !== 'string' ||
value.credentialId.length < 1 ||
value.credentialId.length > 128 ||
!Number.isSafeInteger(value.credentialVersion) ||
value.credentialVersion < 1 ||
typeof value.pepperKeyId !== 'string' ||
value.pepperKeyId.length < 1 ||
value.pepperKeyId.length > 128 ||
!/^[0-9a-f]{64}$/.test(value.materialDigest) ||
value.subjectType !== 'user' ||
typeof value.subjectId !== 'string' ||
value.subjectId.length < 1 ||
value.subjectId.length > 255 ||
!/^[0-9a-f]{64}$/.test(value.secretDigest) ||
!Number.isSafeInteger(value.notBeforeAtMs) ||
value.notBeforeAtMs < 0 ||
!Number.isSafeInteger(value.expiresAtMs) ||
value.expiresAtMs <= value.notBeforeAtMs
) {
throw new LocalSqliteAuthenticatedManagementFenceError();
}
}
function confirmUserCredentialFence(
authority: LocalSqliteOperationAuthority,
fence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
): void {
exactFence(fence);
try {
const row = authority.client
.prepare(
`SELECT
credential."state" AS "credentialState",
credential."subject_type" AS "subjectType",
credential."subject_id" AS "subjectId",
credential."secret_digest" AS "secretDigest",
credential."not_before_at_ms" AS "notBeforeAtMs",
credential."expires_at_ms" AS "expiresAtMs",
identity."status" AS "subjectStatus",
binding."pepper_key_id" AS "pepperKeyId",
pepper."state" AS "pepperState",
pepper."material_digest" AS "materialDigest",
CAST(unixepoch('subsec') * 1000 AS INTEGER) AS "nowMs"
FROM "QingLong3ApiCredentials" AS credential
JOIN "QingLong3IdentitySubjects" AS identity
ON identity."subject_type" = credential."subject_type"
AND identity."subject_id" = credential."subject_id"
JOIN "QingLong3ApiCredentialPepperBindings" AS binding
ON binding."credential_id" = credential."credential_id"
AND binding."credential_version" = credential."version"
JOIN "QingLong3LocalOwnerPepperKeys" AS pepper
ON pepper."pepper_key_id" = binding."pepper_key_id"
WHERE credential."credential_id" = ?
AND credential."version" = ?`,
)
.get(fence.credentialId, fence.credentialVersion) as
| Record<string, unknown>
| undefined;
if (
!row ||
row.credentialState !== 'active' ||
row.subjectStatus !== 'active' ||
row.subjectType !== fence.subjectType ||
row.subjectId !== fence.subjectId ||
row.secretDigest !== fence.secretDigest ||
row.pepperKeyId !== fence.pepperKeyId ||
(row.pepperState !== 'active' && row.pepperState !== 'retired') ||
row.materialDigest !== fence.materialDigest ||
row.notBeforeAtMs !== fence.notBeforeAtMs ||
row.expiresAtMs !== fence.expiresAtMs ||
!Number.isSafeInteger(row.nowMs) ||
(row.nowMs as number) < fence.notBeforeAtMs ||
(row.nowMs as number) >= fence.expiresAtMs
) {
throw new LocalSqliteAuthenticatedManagementFenceError();
}
} catch (error) {
if (error instanceof LocalSqliteAuthenticatedManagementFenceError) {
throw error;
}
throw new LocalSqliteAuthenticatedManagementFenceError();
}
}
export function confirmLocalSqliteAuthenticatedUserCredentialFence(
authority: LocalSqliteOperationAuthority,
fence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
): void {
confirmUserCredentialFence(authority, fence);
}
export function confirmLocalSqliteProjectPolicyFence(
authority: LocalSqliteOperationAuthority,
projectId: string,
actor: Readonly<SecuritySubject>,
fence: Readonly<SecurityPolicyFence>,
): void {
try {
if (
!Number.isSafeInteger(fence.projectVersion) ||
fence.projectVersion < 1 ||
!Number.isSafeInteger(fence.bindingVersion) ||
(fence.bindingVersion as number) < 1
) {
throw new LocalSqliteAuthenticatedManagementFenceError();
}
const row = authority.client
.prepare(
`SELECT project."status" AS "projectStatus",
project."version" AS "projectVersion",
binding."state" AS "bindingState",
binding."version" AS "bindingVersion"
FROM "QingLong3Projects" AS project
JOIN "QingLong3ProjectRoleBindings" AS binding
ON binding."project_id" = project."id"
AND binding."subject_type" = ?
AND binding."subject_id" = ?
WHERE project."id" = ?
AND binding."version" = (
SELECT max(latest."version")
FROM "QingLong3ProjectRoleBindings" AS latest
WHERE latest."project_id" = binding."project_id"
AND latest."subject_type" = binding."subject_type"
AND latest."subject_id" = binding."subject_id"
)`,
)
.get(actor.type, actor.id, projectId) as
| Record<string, unknown>
| undefined;
if (
!row ||
row.projectStatus !== 'active' ||
row.bindingState !== 'active' ||
row.projectVersion !== fence.projectVersion ||
row.bindingVersion !== fence.bindingVersion
) {
throw new LocalSqliteAuthenticatedManagementFenceError();
}
} catch (error) {
if (error instanceof LocalSqliteAuthenticatedManagementFenceError) {
throw error;
}
throw new LocalSqliteAuthenticatedManagementFenceError();
}
}
const AUTHENTICATED_AUDIT_SELECT = `
"event_id" AS "eventId",
"request_id" AS "requestId",
"operation_id" AS "operationId",
"project_id" AS "auditProjectId",
"subject_type" AS "subjectType",
"subject_id" AS "subjectId",
"authentication_id" AS "authenticationId",
"outcome" AS "outcome",
"reasons_json" AS "reasonsJson",
"fence_project_version" AS "fenceProjectVersion",
"fence_binding_version" AS "fenceBindingVersion",
"occurred_at_ms" AS "occurredAtMs"`;
export function commitLocalSqliteSecurityAuditInTransaction(
authority: LocalSqliteOperationAuthority,
audit: Readonly<SecurityAuditRecord>,
replay: boolean,
): void {
try {
if (!authority.client.isTransaction) {
throw new LocalSqliteAuthenticatedManagementFenceError();
}
const row = authority.client
.prepare(
`SELECT ${AUTHENTICATED_AUDIT_SELECT}
FROM "QingLong3SecurityAuditEvents" WHERE "event_id" = ?`,
)
.get(audit.eventId) as Record<string, unknown> | undefined;
if (replay) {
const stored = row ? localSecurityAuditFromRow(row) : null;
const storedWithoutTime = stored
? Object.freeze({ ...stored, occurredAtMs: audit.occurredAtMs })
: null;
if (
!storedWithoutTime ||
!sameSecurityAuditSemantic(storedWithoutTime, audit)
) {
throw new LocalSqliteAuthenticatedManagementFenceError();
}
return;
}
if (row) throw new LocalSqliteAuthenticatedManagementFenceError();
insertLocalSecurityAudit(authority.client, audit);
} catch (error) {
if (error instanceof LocalSqliteAuthenticatedManagementFenceError) {
throw error;
}
throw new LocalSqliteAuthenticatedManagementFenceError();
}
}
function confirmDefaultProjectOwnerFence(
authority: LocalSqliteOperationAuthority,
fence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
): void {
exactFence(fence);
try {
const row = authority.client
.prepare(
`SELECT
project."status" AS "projectStatus",
binding."state" AS "bindingState",
binding."role" AS "role"
FROM "QingLong3Projects" AS project
JOIN "QingLong3ProjectRoleBindings" AS binding
ON binding."project_id" = project."id"
AND binding."subject_type" = ?
AND binding."subject_id" = ?
WHERE project."id" = 'default'
AND binding."version" = (
SELECT max(latest."version")
FROM "QingLong3ProjectRoleBindings" AS latest
WHERE latest."project_id" = binding."project_id"
AND latest."subject_type" = binding."subject_type"
AND latest."subject_id" = binding."subject_id"
)`,
)
.get(fence.subjectType, fence.subjectId) as
| Record<string, unknown>
| undefined;
if (
!row ||
row.projectStatus !== 'active' ||
row.bindingState !== 'active' ||
row.role !== 'owner'
) {
throw new LocalSqliteAuthenticatedManagementOwnerError();
}
} catch (error) {
if (error instanceof LocalSqliteAuthenticatedManagementOwnerError) {
throw error;
}
throw new LocalSqliteAuthenticatedManagementOwnerError();
}
}
export async function openLocalSqlitePluginPackageManagementDatabase(
options: LocalSqliteDatabaseOptions,
): Promise<LocalSqlitePluginPackageManagementDatabase> {
assertLocalSqliteOptions(options);
assertLocalSqlitePathBoundary(options.databasePath, false);
const client = openLocalSqliteClient(options, false);
try {
const readiness = await auditLocalSqliteReadiness(client);
const authority = new LocalSqliteOperationAuthority(client);
const apiCredentials = new LocalSqliteApiCredentialRepository(authority);
const ownerPepper = new LocalSqliteOwnerPepperRepository(authority);
const pluginPackageQuarantine =
new LocalSqlitePluginPackageQuarantineRepository(authority, {
activeSourceLimit:
options.profile === 'edge'
? EDGE_PLUGIN_PACKAGE_QUARANTINE_ACTIVE_SOURCE_LIMIT
: STANDALONE_PLUGIN_PACKAGE_QUARANTINE_ACTIVE_SOURCE_LIMIT,
});
const securityAudit = new LocalSqliteSecurityAuthorityStore(authority);
let closePromise: Promise<void> | undefined;
return Object.freeze({
profile: options.profile,
readiness,
authority,
apiCredentials,
ownerPepper,
pluginPackageQuarantine,
securityAudit,
confirmUserCredentialFence(
fence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
) {
confirmUserCredentialFence(authority, fence);
},
confirmDefaultProjectOwnerFence(
fence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
) {
confirmDefaultProjectOwnerFence(authority, fence);
},
close() {
if (closePromise) return closePromise;
closePromise = authority.close();
return closePromise;
},
});
} catch (error) {
if (client.isOpen) client.close();
throw error;
}
}
/**
* Opens one explicit optional-feature runtime authority without loading any
* feature implementation. Callers must inspect their durable feature head
* before importing the optional package and must close this authority on every
* inactive, failed, or stopped path.
*/
export async function openLocalSqliteOptionalFeatureRuntimeDatabase(
options: LocalSqliteDatabaseOptions,
): Promise<LocalSqliteOptionalFeatureRuntimeDatabase> {
assertLocalSqliteOptions(options);
assertLocalSqlitePathBoundary(options.databasePath, false);
const client = openLocalSqliteClient(options, false);
try {
const readiness = await auditLocalSqliteReadiness(client);
const authority = new LocalSqliteOperationAuthority(client);
const securityAuthority = new LocalSqliteSecurityAuthorityStore(authority);
const projectPolicy: ProjectPolicyRepository = Object.freeze({
resolve: (
...[projectId, subject]: Parameters<ProjectPolicyRepository['resolve']>
) => securityAuthority.resolve(projectId, subject),
append: (...[command]: Parameters<ProjectPolicyRepository['append']>) =>
securityAuthority.append(command),
});
let closePromise: Promise<void> | undefined;
return Object.freeze({
profile: options.profile,
readiness,
authority,
apiCredentials: new LocalSqliteApiCredentialRepository(authority),
ownerPepper: new LocalSqliteOwnerPepperRepository(authority),
projectPolicy,
automationPublications:
new LocalSqlitePluginPackageAutomationPublicationRepository(authority),
localSecrets: securityAuthority,
securityAudit: securityAuthority,
close() {
if (closePromise) return closePromise;
closePromise = authority.close();
return closePromise;
},
});
} catch (error) {
if (client.isOpen) client.close();
throw error;
}
}
/**
* Neutral names for shared short-lived authenticated management ceremonies.
* The historical Plugin Package names remain compatible aliases.
*/
export type LocalSqliteAuthenticatedManagementDatabase =
LocalSqlitePluginPackageManagementDatabase;
export const openLocalSqliteAuthenticatedManagementDatabase =
openLocalSqlitePluginPackageManagementDatabase;
export {
LocalSqliteConfigurationError,
type LocalSqliteDatabaseOptions,
type LocalSqliteProfile,
};
export type { LocalSqliteReadinessEvidence } from '../readiness/readiness';
@@ -0,0 +1,126 @@
import type { ApiCredentialRepository } from '@qinglong/runtime-core/api-credential';
import type { LocalOwnerPepperRepository } from '@qinglong/runtime-core/local-owner-pepper';
import type { LocalSecretAdministrationRepository } from '@qinglong/runtime-core/local-secret-administration';
import type { ProjectPolicyRepository } from '@qinglong/runtime-core/project-policy';
import type { SecurityAuditSink } from '@qinglong/runtime-core/security-audit';
import { LocalSqliteApiCredentialRepository } from '../security/apiCredentialRepository';
import {
assertLocalSqliteOptions,
assertLocalSqlitePathBoundary,
LocalSqliteConfigurationError,
openLocalSqliteClient,
type LocalSqliteDatabaseOptions,
type LocalSqliteProfile,
} from '../storage/config';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
import { LocalSqliteOwnerPepperRepository } from '../local-owner/ownerPepperRepository';
import {
confirmLocalSqliteAuthenticatedUserCredentialFence,
LocalSqliteAuthenticatedManagementFenceError,
type LocalSqliteAuthenticatedUserCredentialFence,
} from './packageManagement';
import {
auditLocalSqliteReadiness,
type LocalSqliteReadinessEvidence,
} from '../readiness/readiness';
import { LocalSqliteSecurityAuthorityStore } from '../security/securityAuthorityStore';
/**
* One short-lived authority for authenticated Secret creation and rotation.
* It deliberately excludes task execution, scheduling, plugin management and
* schema migration capabilities.
*/
export interface LocalSqliteSecretAdministrationDatabase {
readonly profile: LocalSqliteProfile;
readonly readiness: LocalSqliteReadinessEvidence;
readonly apiCredentials: ApiCredentialRepository;
readonly ownerPepper: Pick<LocalOwnerPepperRepository, 'resolveKey'>;
readonly projectPolicy: ProjectPolicyRepository;
readonly localSecretAdministration: LocalSecretAdministrationRepository;
readonly securityAudit: SecurityAuditSink;
activateUserCredentialFence(
fence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
): void;
close(): Promise<void>;
}
function sameFence(
left: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
right: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
): boolean {
return (
left.credentialId === right.credentialId &&
left.credentialVersion === right.credentialVersion &&
left.pepperKeyId === right.pepperKeyId &&
left.materialDigest === right.materialDigest &&
left.subjectType === right.subjectType &&
left.subjectId === right.subjectId &&
left.secretDigest === right.secretDigest &&
left.notBeforeAtMs === right.notBeforeAtMs &&
left.expiresAtMs === right.expiresAtMs
);
}
export async function openLocalSqliteSecretAdministrationDatabase(
options: LocalSqliteDatabaseOptions,
): Promise<LocalSqliteSecretAdministrationDatabase> {
assertLocalSqliteOptions(options);
assertLocalSqlitePathBoundary(options.databasePath, false);
const client = openLocalSqliteClient(options, false);
try {
const readiness = await auditLocalSqliteReadiness(client);
const authority = new LocalSqliteOperationAuthority(client);
let activeFence:
| Readonly<LocalSqliteAuthenticatedUserCredentialFence>
| undefined;
const securityAuthority = new LocalSqliteSecurityAuthorityStore(authority, {
beforeAuthorizedLocalSecretMutation() {
if (!activeFence) {
throw new LocalSqliteAuthenticatedManagementFenceError();
}
confirmLocalSqliteAuthenticatedUserCredentialFence(
authority,
activeFence,
);
},
});
const apiCredentials = new LocalSqliteApiCredentialRepository(authority);
const ownerPepper = new LocalSqliteOwnerPepperRepository(authority);
const projectPolicy: ProjectPolicyRepository = Object.freeze({
resolve: (
...[projectId, subject]: Parameters<ProjectPolicyRepository['resolve']>
) => securityAuthority.resolve(projectId, subject),
append: (...[command]: Parameters<ProjectPolicyRepository['append']>) =>
securityAuthority.append(command),
});
let closePromise: Promise<void> | undefined;
return Object.freeze({
profile: options.profile,
readiness,
apiCredentials,
ownerPepper,
projectPolicy,
localSecretAdministration: securityAuthority,
securityAudit: securityAuthority,
activateUserCredentialFence(
fence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
) {
confirmLocalSqliteAuthenticatedUserCredentialFence(authority, fence);
if (activeFence && !sameFence(activeFence, fence)) {
throw new LocalSqliteAuthenticatedManagementFenceError();
}
activeFence = Object.freeze({ ...fence });
},
close() {
if (closePromise) return closePromise;
closePromise = authority.close();
return closePromise;
},
});
} catch (error) {
if (client.isOpen) client.close();
if (error instanceof LocalSqliteConfigurationError) throw error;
throw error;
}
}
@@ -0,0 +1,738 @@
import { createHash } from 'node:crypto';
import type { DatabaseSync } from 'node:sqlite';
import {
createTaskDefinitionRecord,
normalizeAppendTaskDefinitionRevisionCommand,
type TaskDefinitionSpec,
} from '@qinglong/runtime-core/task-definition';
import {
BUILT_IN_COMMAND_TASK_SPEC_SCHEMA,
createBuiltInTaskSpecSemanticRegistry,
} from '@qinglong/runtime-core/task-spec-semantic';
import {
BUILT_IN_CRON_TRIGGER_SPEC_SCHEMA,
createBuiltInTriggerSpecSemanticRegistry,
createTriggerRecord,
normalizeAppendTriggerRevisionCommand,
type TriggerSpec,
} from '@qinglong/runtime-core/trigger';
import {
normalizeProjectPolicySubject,
type ProjectPolicyRepository,
} from '@qinglong/runtime-core/project-policy';
import type {
SecurityPolicyFence,
SecuritySubject,
} from '@qinglong/runtime-core/security';
import {
normalizeSecurityAuditRecord,
type SecurityAuditRecord,
type SecurityAuditSink,
} from '@qinglong/runtime-core/security-audit';
import {
assertLocalSqliteOptions,
assertLocalSqlitePathBoundary,
openLocalSqliteClient,
type LocalSqliteDatabaseOptions,
type LocalSqliteProfile,
} from '../storage/config';
import { LocalSqliteDispatchDefinitionStore } from '../task-definition/dispatchDefinitionStore';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
import {
auditLocalSqliteReadiness,
type LocalSqliteReadinessEvidence,
} from '../readiness/readiness';
import { LocalSqliteSecurityAuthorityStore } from '../security/securityAuthorityStore';
export const MAX_LOCAL_LEGACY_ADOPTION_TASKS = 100_000;
export const MAX_LOCAL_LEGACY_ADOPTION_TRIGGERS = 500_000;
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
const UUID_V4_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
const UUID_V7_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
export interface LocalLegacyAdoptionCandidate {
readonly rowOrdinal: number;
readonly sourceDigest: string;
readonly task: Readonly<{
taskId: string;
name: string;
kind: 'command';
spec: TaskDefinitionSpec;
labels: Readonly<Record<string, string>>;
enabled: boolean;
}>;
readonly triggers: readonly Readonly<{
triggerId: string;
spec: TriggerSpec;
enabled: boolean;
}>[];
}
export interface PublishLocalLegacyAdoptionCommand {
readonly mutationId: string;
readonly decisionId: string;
readonly projectId: string;
readonly profile: LocalSqliteProfile;
readonly planDigest: string;
readonly inventoryDigest: string;
readonly decisionDigest: string;
readonly receiptDigest: string;
readonly authorizationFileDigest: string;
readonly rowCount: number;
readonly skippedCount: number;
readonly subject: SecuritySubject;
readonly fence: SecurityPolicyFence;
readonly audit: SecurityAuditRecord;
readonly candidates: Iterable<LocalLegacyAdoptionCandidate>;
readonly confirmExternalAuthority: () => void | Promise<void>;
readonly createdAtMs: number;
}
export interface LocalLegacyAdoptionRecord {
readonly mutationId: string;
readonly decisionId: string;
readonly projectId: string;
readonly profile: LocalSqliteProfile;
readonly planDigest: string;
readonly inventoryDigest: string;
readonly decisionDigest: string;
readonly receiptDigest: string;
readonly authorizationFileDigest: string;
readonly publicationDigest: string;
readonly rowCount: number;
readonly adoptedTaskCount: number;
readonly adoptedTriggerCount: number;
readonly skippedCount: number;
readonly auditEventId: string;
readonly createdAtMs: number;
}
export interface PublishLocalLegacyAdoptionResult {
readonly status: 'inserted' | 'existing';
readonly adoption: LocalLegacyAdoptionRecord;
}
export class LocalLegacyAdoptionConflictError extends Error {
readonly code = 'LOCAL_LEGACY_ADOPTION_CONFLICT';
constructor() {
super('Local legacy adoption conflicts with durable state');
this.name = 'LocalLegacyAdoptionConflictError';
}
}
export class LocalLegacyAdoptionAuthorizationFenceConflictError extends Error {
readonly code = 'LOCAL_LEGACY_ADOPTION_AUTHORIZATION_FENCE_CONFLICT';
constructor() {
super('Local legacy adoption authorization fence changed');
this.name = 'LocalLegacyAdoptionAuthorizationFenceConflictError';
}
}
export class LocalLegacyAdoptionUnavailableError extends Error {
readonly code = 'LOCAL_LEGACY_ADOPTION_UNAVAILABLE';
constructor(readonly cause?: unknown) {
super('Local legacy adoption storage is unavailable');
this.name = 'LocalLegacyAdoptionUnavailableError';
}
}
type Row = Record<string, unknown>;
function text(row: Row, key: string): string {
const value = row[key];
if (typeof value !== 'string')
throw new LocalLegacyAdoptionUnavailableError();
return value;
}
function integer(row: Row, key: string): number {
const value = row[key];
if (!Number.isSafeInteger(value)) {
throw new LocalLegacyAdoptionUnavailableError();
}
return value as number;
}
function adoptionRecord(row: Row): LocalLegacyAdoptionRecord {
const profile = text(row, 'profile');
if (profile !== 'edge' && profile !== 'standalone') {
throw new LocalLegacyAdoptionUnavailableError();
}
return Object.freeze({
mutationId: text(row, 'mutationId'),
decisionId: text(row, 'decisionId'),
projectId: text(row, 'projectId'),
profile,
planDigest: text(row, 'planDigest'),
inventoryDigest: text(row, 'inventoryDigest'),
decisionDigest: text(row, 'decisionDigest'),
receiptDigest: text(row, 'receiptDigest'),
authorizationFileDigest: text(row, 'authorizationFileDigest'),
publicationDigest: text(row, 'publicationDigest'),
rowCount: integer(row, 'rowCount'),
adoptedTaskCount: integer(row, 'adoptedTaskCount'),
adoptedTriggerCount: integer(row, 'adoptedTriggerCount'),
skippedCount: integer(row, 'skippedCount'),
auditEventId: text(row, 'auditEventId'),
createdAtMs: integer(row, 'createdAtMs'),
});
}
function deterministicMutationId(
batchMutationId: string,
identity: string,
): string {
const bytes = createHash('sha256')
.update('qinglong3.legacy-adoption-mutation.v1\0')
.update(batchMutationId)
.update('\0')
.update(identity)
.digest()
.subarray(0, 16);
bytes[6] = ((bytes[6] as number) & 0x0f) | 0x40;
bytes[8] = ((bytes[8] as number) & 0x3f) | 0x80;
const hex = bytes.toString('hex');
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(
12,
16,
)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
function exactReplay(
existing: LocalLegacyAdoptionRecord,
command: PublishLocalLegacyAdoptionCommand,
): boolean {
return (
existing.mutationId === command.mutationId &&
existing.decisionId === command.decisionId &&
existing.projectId === command.projectId &&
existing.profile === command.profile &&
existing.planDigest === command.planDigest &&
existing.inventoryDigest === command.inventoryDigest &&
existing.decisionDigest === command.decisionDigest &&
existing.receiptDigest === command.receiptDigest &&
existing.authorizationFileDigest === command.authorizationFileDigest &&
existing.rowCount === command.rowCount &&
existing.skippedCount === command.skippedCount &&
existing.auditEventId === command.audit.eventId &&
existing.createdAtMs === command.createdAtMs
);
}
function assertCommand(command: PublishLocalLegacyAdoptionCommand): void {
if (
!command ||
typeof command !== 'object' ||
!UUID_V4_PATTERN.test(command.mutationId) ||
!UUID_V7_PATTERN.test(command.decisionId) ||
(command.profile !== 'edge' && command.profile !== 'standalone') ||
![
command.planDigest,
command.inventoryDigest,
command.decisionDigest,
command.receiptDigest,
command.authorizationFileDigest,
].every((digest) => DIGEST_PATTERN.test(digest)) ||
!Number.isSafeInteger(command.rowCount) ||
command.rowCount < 0 ||
command.rowCount > MAX_LOCAL_LEGACY_ADOPTION_TASKS ||
!Number.isSafeInteger(command.skippedCount) ||
command.skippedCount < 0 ||
command.skippedCount > command.rowCount ||
!Number.isSafeInteger(command.createdAtMs) ||
command.createdAtMs < 0 ||
!command.candidates ||
typeof command.candidates[Symbol.iterator] !== 'function' ||
typeof command.confirmExternalAuthority !== 'function'
) {
throw new LocalLegacyAdoptionConflictError();
}
}
function insertAudit(client: DatabaseSync, audit: SecurityAuditRecord): void {
client
.prepare(
`INSERT INTO "QingLong3SecurityAuditEvents" (
"event_id", "request_id", "operation_id", "project_id",
"subject_type", "subject_id", "authentication_id", "outcome",
"reasons_json", "fence_project_version", "fence_binding_version",
"occurred_at_ms"
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
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,
);
}
const LEDGER_SELECT = `
"mutation_id" AS "mutationId", "decision_id" AS "decisionId",
"project_id" AS "projectId", "profile" AS "profile",
"plan_digest" AS "planDigest", "inventory_digest" AS "inventoryDigest",
"decision_digest" AS "decisionDigest", "receipt_digest" AS "receiptDigest",
"authorization_file_digest" AS "authorizationFileDigest",
"publication_digest" AS "publicationDigest", "row_count" AS "rowCount",
"adopted_task_count" AS "adoptedTaskCount",
"adopted_trigger_count" AS "adoptedTriggerCount",
"skipped_count" AS "skippedCount", "audit_event_id" AS "auditEventId",
"created_at_ms" AS "createdAtMs"`;
export class LocalSqliteLegacyAdoptionPublisher {
constructor(private readonly authority: LocalSqliteOperationAuthority) {}
publish(
input: PublishLocalLegacyAdoptionCommand,
): Promise<PublishLocalLegacyAdoptionResult> {
assertCommand(input);
const subject = normalizeProjectPolicySubject(input.subject);
const audit = normalizeSecurityAuditRecord(input.audit);
const fence = input.fence;
if (
!fence ||
!Number.isSafeInteger(fence.projectVersion) ||
fence.projectVersion < 1 ||
!Number.isSafeInteger(fence.bindingVersion) ||
(fence.bindingVersion as number) < 1 ||
audit.eventId !== input.mutationId ||
audit.operationId !== 'task.adopt' ||
audit.projectId !== input.projectId ||
audit.subject?.type !== subject.type ||
audit.subject?.id !== subject.id ||
audit.outcome !== 'allowed' ||
audit.fence?.projectVersion !== fence.projectVersion ||
audit.fence.bindingVersion !== fence.bindingVersion ||
audit.occurredAtMs !== input.createdAtMs
) {
return Promise.reject(new LocalLegacyAdoptionConflictError());
}
return import(
'@qinglong/runtime-core/task-definition-execution-compiler'
).then(({ compileLocalCommandTaskDefinition }) =>
this.authority.enqueue(
async () => {
const client = this.authority.client;
let began = false;
try {
client.exec('BEGIN IMMEDIATE');
began = true;
const replayRows = client
.prepare(
`SELECT ${LEDGER_SELECT}
FROM "QingLong3LegacyAdoptions"
WHERE "mutation_id" = ? OR "decision_id" = ? LIMIT 2`,
)
.all(input.mutationId, input.decisionId) as Row[];
if (replayRows.length > 0) {
if (replayRows.length !== 1) {
throw new LocalLegacyAdoptionConflictError();
}
const existing = adoptionRecord(replayRows[0] as Row);
if (!exactReplay(existing, input)) {
throw new LocalLegacyAdoptionConflictError();
}
await input.confirmExternalAuthority();
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'existing' as const,
adoption: existing,
});
}
const project = client
.prepare(
`SELECT "version", "status" FROM "QingLong3Projects"
WHERE "id" = ? LIMIT 1`,
)
.get(input.projectId) as Row | undefined;
if (
!project ||
integer(project, 'version') !== fence.projectVersion ||
text(project, 'status') !== 'active'
) {
throw new LocalLegacyAdoptionAuthorizationFenceConflictError();
}
const binding = client
.prepare(
`SELECT "version", "state", "role"
FROM "QingLong3ProjectRoleBindings"
WHERE "project_id" = ? AND "subject_type" = ?
AND "subject_id" = ?
ORDER BY "version" DESC LIMIT 1`,
)
.get(input.projectId, subject.type, subject.id) as
| Row
| undefined;
if (
!binding ||
integer(binding, 'version') !== fence.bindingVersion ||
text(binding, 'state') !== 'active' ||
!['owner', 'admin'].includes(text(binding, 'role'))
) {
throw new LocalLegacyAdoptionAuthorizationFenceConflictError();
}
const taskRegistry = createBuiltInTaskSpecSemanticRegistry();
const triggerRegistry = createBuiltInTriggerSpecSemanticRegistry();
const dispatch = new LocalSqliteDispatchDefinitionStore(client);
const publication = createHash('sha256')
.update('qinglong3.legacy-adoption-publication.v1\0')
.update(input.mutationId);
let adoptedTaskCount = 0;
let adoptedTriggerCount = 0;
let previousRowOrdinal = 0;
for (const candidate of input.candidates) {
adoptedTaskCount += 1;
if (
adoptedTaskCount > MAX_LOCAL_LEGACY_ADOPTION_TASKS ||
!candidate ||
typeof candidate !== 'object' ||
!Number.isSafeInteger(candidate.rowOrdinal) ||
candidate.rowOrdinal <= previousRowOrdinal ||
candidate.rowOrdinal > input.rowCount ||
!DIGEST_PATTERN.test(candidate.sourceDigest) ||
candidate.task.kind !== 'command'
) {
throw new LocalLegacyAdoptionConflictError();
}
previousRowOrdinal = candidate.rowOrdinal;
const taskCommand = normalizeAppendTaskDefinitionRevisionCommand({
projectId: input.projectId,
taskId: candidate.task.taskId,
expectedRevision: null,
mutationId: deterministicMutationId(
input.mutationId,
`task:${candidate.rowOrdinal}`,
),
name: candidate.task.name,
kind: candidate.task.kind,
spec: candidate.task.spec,
labels: candidate.task.labels,
enabled: candidate.task.enabled,
occurredAtMs: input.createdAtMs,
});
const task = createTaskDefinitionRecord(
{
...taskCommand,
spec: taskRegistry.normalize({
projectId: input.projectId,
taskId: taskCommand.taskId,
kind: taskCommand.kind,
spec: taskCommand.spec,
}),
},
input.createdAtMs,
);
if (
task.spec.schema !== BUILT_IN_COMMAND_TASK_SPEC_SCHEMA ||
client
.prepare(
`SELECT 1 FROM "QingLong3TaskDefinitions"
WHERE "project_id" = ? AND "task_id" = ?`,
)
.get(task.projectId, task.taskId)
) {
throw new LocalLegacyAdoptionConflictError();
}
client
.prepare(
`INSERT INTO "QingLong3TaskDefinitions" (
"project_id", "task_id", "current_revision",
"created_at_ms", "updated_at_ms"
) VALUES (?, ?, 1, ?, ?)`,
)
.run(
task.projectId,
task.taskId,
task.createdAtMs,
task.updatedAtMs,
);
client
.prepare(
`INSERT INTO "QingLong3TaskDefinitionRevisions" (
"project_id", "task_id", "revision", "mutation_id",
"name", "description", "kind", "spec_json", "labels_json",
"enabled", "content_digest", "created_at_ms"
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
task.projectId,
task.taskId,
task.revision,
task.mutationId,
task.name,
task.description ?? null,
task.kind,
JSON.stringify(task.spec),
JSON.stringify(task.labels),
task.enabled ? 1 : 0,
task.contentDigest,
task.updatedAtMs,
);
if (task.enabled) {
dispatch.appendPlan(
compileLocalCommandTaskDefinition(task, taskRegistry),
);
}
publication
.update('\0task\0')
.update(String(candidate.rowOrdinal))
.update('\0')
.update(candidate.sourceDigest)
.update('\0')
.update(task.contentDigest);
for (const [
triggerIndex,
value,
] of candidate.triggers.entries()) {
adoptedTriggerCount += 1;
if (
adoptedTriggerCount > MAX_LOCAL_LEGACY_ADOPTION_TRIGGERS ||
(value.enabled && !task.enabled)
) {
throw new LocalLegacyAdoptionConflictError();
}
const triggerCommand = normalizeAppendTriggerRevisionCommand({
projectId: input.projectId,
triggerId: value.triggerId,
expectedRevision: null,
mutationId: deterministicMutationId(
input.mutationId,
`trigger:${candidate.rowOrdinal}:${triggerIndex + 1}`,
),
taskId: task.taskId,
taskRevision: task.revision,
taskContentDigest: task.contentDigest,
spec: value.spec,
enabled: value.enabled,
occurredAtMs: input.createdAtMs,
});
const trigger = createTriggerRecord(
{
...triggerCommand,
spec: triggerRegistry.normalize({
projectId: input.projectId,
triggerId: triggerCommand.triggerId,
taskId: task.taskId,
taskRevision: task.revision,
spec: triggerCommand.spec,
}),
},
input.createdAtMs,
);
if (
client
.prepare(
`SELECT 1 FROM "QingLong3Triggers"
WHERE "project_id" = ? AND "trigger_id" = ?`,
)
.get(trigger.projectId, trigger.triggerId)
) {
throw new LocalLegacyAdoptionConflictError();
}
client
.prepare(
`INSERT INTO "QingLong3Triggers" (
"project_id", "trigger_id", "task_id", "current_revision",
"created_at_ms", "updated_at_ms"
) VALUES (?, ?, ?, 1, ?, ?)`,
)
.run(
trigger.projectId,
trigger.triggerId,
trigger.taskId,
trigger.createdAtMs,
trigger.updatedAtMs,
);
client
.prepare(
`INSERT INTO "QingLong3TriggerRevisions" (
"project_id", "trigger_id", "revision", "mutation_id",
"task_id", "task_revision", "task_content_digest",
"spec_json", "enabled", "content_digest", "created_at_ms"
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
trigger.projectId,
trigger.triggerId,
trigger.revision,
trigger.mutationId,
trigger.taskId,
trigger.taskRevision,
trigger.taskContentDigest,
JSON.stringify(trigger.spec),
trigger.enabled ? 1 : 0,
trigger.contentDigest,
trigger.updatedAtMs,
);
const scheduleConfig = trigger.spec.config as Readonly<{
expression?: unknown;
timezone?: unknown;
}>;
if (
trigger.spec.schema !== BUILT_IN_CRON_TRIGGER_SPEC_SCHEMA ||
typeof scheduleConfig.expression !== 'string' ||
typeof scheduleConfig.timezone !== 'string'
) {
throw new LocalLegacyAdoptionConflictError();
}
client
.prepare(
`INSERT INTO "QingLong3LocalTriggerSchedules" (
"project_id", "trigger_id", "trigger_revision",
"next_fire_at_ms", "last_scheduled_at_ms",
"state_version", "updated_at_ms"
) VALUES (?, ?, ?, NULL, NULL, 0, ?)`,
)
.run(
trigger.projectId,
trigger.triggerId,
trigger.revision,
trigger.updatedAtMs,
);
publication.update('\0trigger\0').update(trigger.contentDigest);
}
}
if (adoptedTaskCount + input.skippedCount !== input.rowCount) {
throw new LocalLegacyAdoptionConflictError();
}
const publicationDigest = publication.digest('hex');
insertAudit(client, audit);
client
.prepare(
`INSERT INTO "QingLong3LegacyAdoptions" (
"mutation_id", "decision_id", "project_id", "profile",
"plan_digest", "inventory_digest", "decision_digest",
"receipt_digest", "authorization_file_digest",
"publication_digest", "row_count", "adopted_task_count",
"adopted_trigger_count", "skipped_count", "audit_event_id",
"created_at_ms"
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
input.mutationId,
input.decisionId,
input.projectId,
input.profile,
input.planDigest,
input.inventoryDigest,
input.decisionDigest,
input.receiptDigest,
input.authorizationFileDigest,
publicationDigest,
input.rowCount,
adoptedTaskCount,
adoptedTriggerCount,
input.skippedCount,
audit.eventId,
input.createdAtMs,
);
const stored = adoptionRecord(
client
.prepare(
`SELECT ${LEDGER_SELECT} FROM "QingLong3LegacyAdoptions"
WHERE "mutation_id" = ?`,
)
.get(input.mutationId) as Row,
);
await input.confirmExternalAuthority();
client.exec('COMMIT');
began = false;
return Object.freeze({
status: 'inserted' as const,
adoption: stored,
});
} catch (error) {
if (began && client.isTransaction) {
try {
client.exec('ROLLBACK');
} catch {
// Preserve the original failure.
}
}
if (
error instanceof LocalLegacyAdoptionConflictError ||
error instanceof
LocalLegacyAdoptionAuthorizationFenceConflictError ||
error instanceof LocalLegacyAdoptionUnavailableError
) {
throw error;
}
if (
error &&
typeof error === 'object' &&
'code' in error &&
typeof error.code === 'string' &&
error.code.startsWith('SQLITE_CONSTRAINT')
) {
throw new LocalLegacyAdoptionConflictError();
}
throw new LocalLegacyAdoptionUnavailableError(error);
}
},
() => new LocalLegacyAdoptionUnavailableError(),
),
);
}
}
export interface LocalSqliteAdoptionDatabase {
readonly profile: LocalSqliteProfile;
readonly readiness: LocalSqliteReadinessEvidence;
readonly projectPolicy: ProjectPolicyRepository;
readonly securityAudit: SecurityAuditSink;
readonly publisher: LocalSqliteLegacyAdoptionPublisher;
close(): Promise<void>;
}
/** Short-lived reviewed adoption authority; long-lived Profile hosts must not import it. */
export async function openLocalSqliteAdoptionDatabase(
options: LocalSqliteDatabaseOptions,
): Promise<LocalSqliteAdoptionDatabase> {
assertLocalSqliteOptions(options);
assertLocalSqlitePathBoundary(options.databasePath, false);
const client = openLocalSqliteClient(options, false);
try {
const readiness = await auditLocalSqliteReadiness(client);
const authority = new LocalSqliteOperationAuthority(client);
const securityAuthority = new LocalSqliteSecurityAuthorityStore(authority);
const projectPolicy: ProjectPolicyRepository = Object.freeze({
resolve: (
...[projectId, subject]: Parameters<ProjectPolicyRepository['resolve']>
) => securityAuthority.resolve(projectId, subject),
append: (...[command]: Parameters<ProjectPolicyRepository['append']>) =>
securityAuthority.append(command),
});
let closePromise: Promise<void> | undefined;
return Object.freeze({
profile: options.profile,
readiness,
projectPolicy,
securityAudit: securityAuthority,
publisher: new LocalSqliteLegacyAdoptionPublisher(authority),
close() {
if (closePromise) return closePromise;
closePromise = authority.close();
return closePromise;
},
});
} catch (error) {
client.close();
throw error;
}
}
@@ -0,0 +1,119 @@
import type { ApiCredentialRepository } from '@qinglong/runtime-core/api-credential';
import type { ApprovalRequestRepository } from '@qinglong/runtime-core/approved-action';
import type { ApprovalRequestDetailSource } from '@qinglong/runtime-core/approval-discovery';
import type { LocalOwnerPepperRepository } from '@qinglong/runtime-core/local-owner-pepper';
import type { ProjectPolicyRepository } from '@qinglong/runtime-core/project-policy';
import type { SecurityAuditSink } from '@qinglong/runtime-core/security-audit';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
import { LocalSqliteOwnerPepperRepository } from '../local-owner/ownerPepperRepository';
import {
confirmLocalSqliteAuthenticatedUserCredentialFence,
LocalSqliteAuthenticatedManagementFenceError,
type LocalSqliteAuthenticatedUserCredentialFence,
} from '../administration/packageManagement';
import {
auditLocalSqliteReadiness,
type LocalSqliteReadinessEvidence,
} from '../readiness/readiness';
import { LocalSqliteApiCredentialRepository } from '../security/apiCredentialRepository';
import { LocalSqliteProjectPolicyRepository } from '../security/projectPolicyRepository';
import { LocalSqliteSecurityAuthorityStore } from '../security/securityAuthorityStore';
import {
assertLocalSqliteOptions,
assertLocalSqlitePathBoundary,
openLocalSqliteClient,
type LocalSqliteDatabaseOptions,
type LocalSqliteProfile,
} from '../storage/config';
import { LocalSqliteApprovalRequestRepository } from './approvalRequestRepository';
import { LocalSqliteApprovalRequestSource } from './approvalRequestSource';
export interface LocalSqliteApprovalDecisionDatabase {
readonly profile: LocalSqliteProfile;
readonly readiness: LocalSqliteReadinessEvidence;
readonly apiCredentials: ApiCredentialRepository;
readonly ownerPepper: Pick<LocalOwnerPepperRepository, 'resolveKey'>;
readonly projectPolicy: ProjectPolicyRepository;
readonly approvals: Pick<ApprovalRequestRepository, 'findById' | 'decide'>;
readonly approvalDetails: ApprovalRequestDetailSource;
readonly securityAudit: SecurityAuditSink;
activateUserCredentialFence(
fence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
): void;
confirmUserCredentialFence(): void;
close(): Promise<void>;
}
function sameCredentialFence(
left: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
right: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
): boolean {
return (
left.credentialId === right.credentialId &&
left.credentialVersion === right.credentialVersion &&
left.pepperKeyId === right.pepperKeyId &&
left.materialDigest === right.materialDigest &&
left.subjectType === right.subjectType &&
left.subjectId === right.subjectId &&
left.secretDigest === right.secretDigest &&
left.notBeforeAtMs === right.notBeforeAtMs &&
left.expiresAtMs === right.expiresAtMs
);
}
/** Short-lived Owner authority; it never migrates schema or starts workers. */
export async function openLocalSqliteApprovalDecisionDatabase(
options: LocalSqliteDatabaseOptions,
): Promise<LocalSqliteApprovalDecisionDatabase> {
assertLocalSqliteOptions(options);
assertLocalSqlitePathBoundary(options.databasePath, false);
const client = openLocalSqliteClient(options, false);
try {
const readiness = await auditLocalSqliteReadiness(client);
const authority = new LocalSqliteOperationAuthority(client);
let activeFence:
| Readonly<LocalSqliteAuthenticatedUserCredentialFence>
| undefined;
const confirmActiveFence = () => {
if (!activeFence) {
throw new LocalSqliteAuthenticatedManagementFenceError();
}
confirmLocalSqliteAuthenticatedUserCredentialFence(authority, activeFence);
};
const securityAuthority = new LocalSqliteSecurityAuthorityStore(authority);
const approvals = new LocalSqliteApprovalRequestRepository(
authority,
confirmActiveFence,
);
let closePromise: Promise<void> | undefined;
return Object.freeze({
profile: options.profile,
readiness,
apiCredentials: new LocalSqliteApiCredentialRepository(authority),
ownerPepper: new LocalSqliteOwnerPepperRepository(authority),
projectPolicy: new LocalSqliteProjectPolicyRepository(authority),
approvals,
approvalDetails: new LocalSqliteApprovalRequestSource(authority),
securityAudit: securityAuthority,
activateUserCredentialFence(
fence: Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
) {
confirmLocalSqliteAuthenticatedUserCredentialFence(authority, fence);
if (activeFence && !sameCredentialFence(activeFence, fence)) {
throw new LocalSqliteAuthenticatedManagementFenceError();
}
activeFence = Object.freeze({ ...fence });
},
confirmUserCredentialFence: confirmActiveFence,
close() {
if (closePromise) return closePromise;
closePromise = authority.close();
return closePromise;
},
});
} catch (error) {
if (client.isOpen) client.close();
throw error;
}
}
@@ -0,0 +1,622 @@
import type { DatabaseSync } from 'node:sqlite';
import {
ApprovalMutationConflictError,
ApprovalPolicyFenceConflictError,
ApprovalRequestNotFoundError,
ApprovalRequestStateConflictError,
ApprovalUnavailableError,
approvalRequestDigest,
approvedActionDispatchDigest,
consumeApprovalRequest,
decideApprovalRequest,
normalizeApprovalRequestRecord,
normalizeApprovedActionDispatchRecord,
normalizeApprovedActionFence,
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 {
normalizeSecurityAuditRecord,
type SecurityAuditRecord,
} from '@qinglong/runtime-core/security-audit';
import type {
SecurityPolicyFence,
SecuritySubject,
} from '@qinglong/runtime-core/security';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
import {
findLocalApprovedActionExecution,
insertLocalApprovedActionExecutionBaseline,
} from './approvedActionExecutionRepository';
import { createApprovedActionExecution } from '@qinglong/runtime-core/approved-action-execution';
type Row = Record<string, unknown>;
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
function text(row: Row, key: string): string {
const value = row[key];
if (typeof value !== 'string') throw new ApprovalUnavailableError();
return value;
}
function nullableText(row: Row, key: string): string | null {
const value = row[key];
if (value !== null && typeof value !== 'string') {
throw new ApprovalUnavailableError();
}
return value;
}
function integer(row: Row, key: string): number {
const value = row[key];
if (!Number.isSafeInteger(value)) throw new ApprovalUnavailableError();
return value as number;
}
function nullableInteger(row: Row, key: string): number | null {
const value = row[key];
if (value !== null && !Number.isSafeInteger(value)) {
throw new ApprovalUnavailableError();
}
return value as number | null;
}
function same(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
function parseRequest(row: Row): Readonly<ApprovalRequestRecord> {
try {
const request = normalizeApprovalRequestRecord(
JSON.parse(text(row, 'requestJson')) as ApprovalRequestRecord,
);
if (approvalRequestDigest(request) !== text(row, 'requestDigest')) {
throw new ApprovalUnavailableError();
}
return request;
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw new ApprovalUnavailableError();
}
}
function parseDispatch(row: Row): Readonly<ApprovedActionDispatchRecord> {
try {
const dispatch = normalizeApprovedActionDispatchRecord(
JSON.parse(text(row, 'dispatchJson')) as ApprovedActionDispatchRecord,
);
if (
approvedActionDispatchDigest(dispatch) !== text(row, 'dispatchDigest')
) {
throw new ApprovalUnavailableError();
}
return dispatch;
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw new ApprovalUnavailableError();
}
}
function parseAudit(row: Row): Readonly<SecurityAuditRecord> {
try {
const subjectType = nullableText(row, 'subjectType');
const subjectId = nullableText(row, 'subjectId');
const fenceProjectVersion = nullableInteger(row, 'fenceProjectVersion');
return normalizeSecurityAuditRecord({
eventId: text(row, 'eventId'),
requestId: text(row, 'requestId'),
operationId: text(row, 'operationId'),
projectId: nullableText(row, 'projectId'),
subject:
subjectType === null || subjectId === null
? null
: {
type: subjectType as SecuritySubject['type'],
id: subjectId,
},
authenticationId: nullableText(row, 'authenticationId'),
outcome: text(row, 'outcome') as SecurityAuditRecord['outcome'],
reasons: JSON.parse(text(row, 'reasonsJson')) as readonly string[],
fence:
fenceProjectVersion === null
? null
: {
projectVersion: fenceProjectVersion,
bindingVersion: nullableInteger(row, 'fenceBindingVersion'),
},
occurredAtMs: integer(row, 'occurredAtMs'),
});
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw new ApprovalUnavailableError();
}
}
function storageError(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;
}
if (
error &&
typeof error === 'object' &&
'code' in error &&
typeof error.code === 'string' &&
error.code.startsWith('SQLITE_CONSTRAINT')
) {
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
);
}
export class LocalSqliteApprovalRequestRepository
implements ApprovalRequestRepository
{
readonly #authority: LocalSqliteOperationAuthority;
readonly #client: DatabaseSync;
readonly #confirmMutation: () => void;
constructor(
authority: LocalSqliteOperationAuthority | DatabaseSync,
confirmMutation: () => void = () => undefined,
) {
if (typeof confirmMutation !== 'function') {
throw new TypeError('Local Approval mutation guard is invalid');
}
this.#authority =
authority instanceof LocalSqliteOperationAuthority
? authority
: new LocalSqliteOperationAuthority(authority);
this.#client = this.#authority.client;
this.#confirmMutation = confirmMutation;
}
#enqueue<T>(work: () => T | Promise<T>): Promise<T> {
return this.#authority.enqueue(
async () => {
try {
return await work();
} catch (error) {
throw storageError(error);
}
},
() => new ApprovalUnavailableError(),
);
}
#request(id: string): Readonly<ApprovalRequestRecord> | null {
const row = this.#client
.prepare(
`SELECT "request_json" AS "requestJson",
"request_digest" AS "requestDigest"
FROM "QingLong3ApprovalRequests"
WHERE "request_id" = ?`,
)
.get(id) as Row | undefined;
return row ? parseRequest(row) : null;
}
#dispatch(id: string): Readonly<ApprovedActionDispatchRecord> | null {
const row = this.#client
.prepare(
`SELECT "dispatch_json" AS "dispatchJson",
"dispatch_digest" AS "dispatchDigest"
FROM "QingLong3ApprovedActionDispatches"
WHERE "dispatch_id" = ?`,
)
.get(id) as Row | undefined;
return row ? parseDispatch(row) : null;
}
#audit(id: string): Readonly<SecurityAuditRecord> | null {
const row = this.#client
.prepare(
`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_json" AS "reasonsJson",
"fence_project_version" AS "fenceProjectVersion",
"fence_binding_version" AS "fenceBindingVersion",
"occurred_at_ms" AS "occurredAtMs"
FROM "QingLong3SecurityAuditEvents"
WHERE "event_id" = ?`,
)
.get(id) as Row | undefined;
return row ? parseAudit(row) : null;
}
#assertFence(
projectId: string,
subject: Readonly<SecuritySubject>,
expectedValue: Readonly<SecurityPolicyFence>,
): void {
const expected = normalizeApprovedActionFence(expectedValue);
const row = this.#client
.prepare(
`SELECT project."status" AS "status",
project."version" AS "projectVersion",
(
SELECT max(binding."version")
FROM "QingLong3ProjectRoleBindings" AS binding
WHERE binding."project_id" = project."id"
AND binding."subject_type" = ?
AND binding."subject_id" = ?
) AS "bindingVersion"
FROM "QingLong3Projects" AS project
WHERE project."id" = ?`,
)
.get(subject.type, subject.id, projectId) as Row | undefined;
if (
!row ||
row.status !== 'active' ||
integer(row, 'projectVersion') !== expected.projectVersion ||
nullableInteger(row, 'bindingVersion') !== expected.bindingVersion
) {
throw new ApprovalPolicyFenceConflictError();
}
}
#insertAudit(value: SecurityAuditRecord): void {
const audit = normalizeSecurityAuditRecord(value);
this.#client
.prepare(
`INSERT INTO "QingLong3SecurityAuditEvents" (
"event_id", "request_id", "operation_id", "project_id",
"subject_type", "subject_id", "authentication_id", "outcome",
"reasons_json", "fence_project_version",
"fence_binding_version", "occurred_at_ms"
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
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,
);
}
#insertRequest(request: Readonly<ApprovalRequestRecord>): void {
this.#client
.prepare(
`INSERT INTO "QingLong3ApprovalRequests" (
"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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
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,
);
}
#updateRequest(
request: Readonly<ApprovalRequestRecord>,
expectedVersion: number,
): void {
const update = this.#client
.prepare(
`UPDATE "QingLong3ApprovalRequests"
SET "version" = ?, "state" = ?, "decision_id" = ?,
"consumption_id" = ?, "dispatch_id" = ?, "request_json" = ?,
"request_digest" = ?, "updated_at_ms" = ?
WHERE "request_id" = ? AND "version" = ?`,
)
.run(
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 (update.changes !== 1) {
throw new ApprovalRequestStateConflictError();
}
}
#insertDispatch(dispatch: Readonly<ApprovedActionDispatchRecord>): void {
this.#client
.prepare(
`INSERT INTO "QingLong3ApprovedActionDispatches" (
"dispatch_id", "approval_request_id", "project_id", "action_type",
"action_ref", "action_digest", "preview_digest", "dispatch_json",
"dispatch_digest", "created_at_ms"
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
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,
);
}
findById(id: string): Promise<Readonly<ApprovalRequestRecord> | null> {
if (typeof id !== 'string' || !IDENTIFIER_PATTERN.test(id)) {
throw new TypeError('Approval request lookup identity is invalid');
}
return this.#enqueue(() => this.#request(id));
}
findDispatchById(
id: string,
): Promise<Readonly<ApprovedActionDispatchRecord> | null> {
if (typeof id !== 'string' || !IDENTIFIER_PATTERN.test(id)) {
throw new TypeError('Approved action dispatch identity is invalid');
}
return this.#enqueue(() => this.#dispatch(id));
}
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.#enqueue(() => {
this.#client.exec('BEGIN IMMEDIATE');
try {
this.#confirmMutation();
const existing = this.#request(request.id);
if (existing) {
const storedAudit = this.#audit(audit.eventId);
if (
!same(existing, request) ||
!storedAudit ||
!same(storedAudit, audit)
) {
throw new ApprovalMutationConflictError();
}
this.#client.exec('COMMIT');
return Object.freeze({ status: 'existing' as const, request });
}
this.#assertFence(
request.projectId,
request.requestedBy,
request.requestFence,
);
this.#insertRequest(request);
this.#insertAudit(audit);
this.#client.exec('COMMIT');
return Object.freeze({ status: 'created' as const, request });
} catch (error) {
if (this.#client.isTransaction) this.#client.exec('ROLLBACK');
throw error;
}
});
}
decide(
command: DecideDurableApprovalRequestCommand,
): Promise<DecideApprovalRequestResult> {
const audit = normalizeSecurityAuditRecord(command.audit);
return this.#enqueue(() => {
this.#client.exec('BEGIN IMMEDIATE');
try {
this.#confirmMutation();
const current = this.#request(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 = this.#audit(audit.eventId);
if (!storedAudit || !same(storedAudit, audit)) {
throw new ApprovalMutationConflictError();
}
this.#client.exec('COMMIT');
return Object.freeze({
status: 'existing' as const,
request,
});
}
this.#assertFence(
request.projectId,
command.principal.subject,
command.authorizationFence,
);
this.#updateRequest(request, command.expectedVersion);
this.#insertAudit(audit);
this.#client.exec('COMMIT');
return Object.freeze({ status: 'decided' as const, request });
} catch (error) {
if (this.#client.isTransaction) this.#client.exec('ROLLBACK');
throw error;
}
});
}
consume(
command: ConsumeDurableApprovalRequestCommand,
): Promise<ConsumeDurableApprovalRequestResult> {
const audit = normalizeSecurityAuditRecord(command.audit);
return this.#enqueue(() => {
this.#client.exec('BEGIN IMMEDIATE');
try {
this.#confirmMutation();
const current = this.#request(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 = this.#audit(audit.eventId);
const dispatch = this.#dispatch(result.dispatch.id);
const execution = findLocalApprovedActionExecution(
this.#client,
result.dispatch.id,
);
if (
!storedAudit ||
!same(storedAudit, audit) ||
!dispatch ||
!same(dispatch, result.dispatch) ||
!execution ||
!same(execution, createApprovedActionExecution(result.dispatch))
) {
throw new ApprovalMutationConflictError();
}
this.#client.exec('COMMIT');
return Object.freeze({
status: 'existing' as const,
request: result.request,
dispatch: result.dispatch,
});
}
this.#assertFence(
result.request.projectId,
command.requestedBy,
command.authorizationFence,
);
this.#insertDispatch(result.dispatch);
insertLocalApprovedActionExecutionBaseline(
this.#client,
result.dispatch,
);
this.#updateRequest(result.request, command.expectedVersion);
this.#insertAudit(audit);
this.#client.exec('COMMIT');
return Object.freeze({
status: 'consumed' as const,
request: result.request,
dispatch: result.dispatch,
});
} catch (error) {
if (this.#client.isTransaction) this.#client.exec('ROLLBACK');
throw error;
}
});
}
}
@@ -0,0 +1,242 @@
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 { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
type Row = Record<string, unknown>;
function text(row: Row, key: string): string {
const value = row[key];
if (typeof value !== 'string') throw new ApprovalUnavailableError();
return value;
}
function integer(row: Row, key: string): number {
const value = row[key];
if (!Number.isSafeInteger(value) || Number(value) < 0) {
throw new ApprovalUnavailableError();
}
return Number(value);
}
function previewArtifact(
row: Row,
): Readonly<ToolInvocationPreviewArtifact> | null {
if (row.previewArtifactId === null) return null;
try {
const value = normalizeToolInvocationPreviewArtifact(
JSON.parse(text(row, 'previewArtifactJson')) as ToolInvocationPreviewArtifact,
);
if (
value.artifactId !== text(row, 'previewArtifactId') ||
value.projectId !== text(row, 'previewProjectId') ||
value.actionRef !== text(row, 'previewActionRef') ||
value.actionDigest !== text(row, 'previewActionDigest') ||
value.previewDigest !== text(row, 'storedPreviewDigest') ||
value.redactionContractDigest !== text(row, 'redactionContractDigest') ||
value.artifactDigest !== text(row, 'previewArtifactDigest') ||
value.byteLength !== integer(row, 'previewByteLength') ||
value.sealedAtMs !== integer(row, 'previewSealedAtMs')
) {
throw new ApprovalUnavailableError();
}
return value;
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw new ApprovalUnavailableError();
}
}
function request(row: Row): Readonly<ApprovalRequestRecord> {
try {
const value = normalizeApprovalRequestRecord(
JSON.parse(text(row, 'requestJson')) as ApprovalRequestRecord,
);
if (
approvalRequestDigest(value) !== text(row, 'requestDigest') ||
approvalRequestUpdatedAtMs(value) !== integer(row, 'updatedAtMs')
) {
throw new ApprovalUnavailableError();
}
return value;
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw new ApprovalUnavailableError();
}
}
/** Read-only, Project-scoped Approval history over the shared SQLite queue. */
export class LocalSqliteApprovalRequestSource
implements ApprovalRequestSource, ApprovalRequestDetailSource
{
constructor(private readonly authority: LocalSqliteOperationAuthority) {
if (!(authority instanceof LocalSqliteOperationAuthority)) {
throw new TypeError('Local Approval discovery authority is invalid');
}
}
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;
return this.authority.enqueue(
async () => {
try {
const rows = this.authority.client
.prepare(
`SELECT "request_json" AS "requestJson",
"request_digest" AS "requestDigest",
"updated_at_ms" AS "updatedAtMs"
FROM "QingLong3ApprovalRequests"
WHERE "project_id" = ?
AND (
? IS NULL OR "updated_at_ms" < ? OR
("updated_at_ms" = ? AND "request_id" < ?)
)
ORDER BY "updated_at_ms" DESC, "request_id" DESC
LIMIT ?`,
)
.all(
options.projectId,
after?.updatedAtMs ?? null,
after?.updatedAtMs ?? null,
after?.updatedAtMs ?? null,
after?.requestId ?? '',
options.limit + 1,
) as Row[] | undefined;
if (!Array.isArray(rows)) throw new ApprovalUnavailableError();
const truncated = rows.length > options.limit;
const requests = Object.freeze(
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 new ApprovalUnavailableError();
}
},
() => new ApprovalUnavailableError(),
);
}
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);
return this.authority.enqueue(
async () => {
try {
const rows = this.authority.client
.prepare(
`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 "QingLong3ApprovalRequests" a
LEFT JOIN "ToolInvocationPreviewArtifacts" 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" = ? AND a."request_id" = ?
LIMIT 2`,
)
.all(options.projectId, options.requestId) as Row[] | undefined;
if (!Array.isArray(rows) || rows.length > 1) {
throw new ApprovalUnavailableError();
}
if (!rows[0]) return null;
const approval = request(rows[0]);
const preview = previewArtifact(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 new ApprovalUnavailableError();
}
return Object.freeze({
request: approval,
preview: preview?.preview ?? null,
});
} catch (error) {
if (error instanceof ApprovalUnavailableError) throw error;
throw new ApprovalUnavailableError();
}
},
() => new ApprovalUnavailableError(),
);
}
}
@@ -0,0 +1,476 @@
import type { DatabaseSync } from 'node:sqlite';
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 {
normalizeApprovedActionDispatchRecord,
type ApprovedActionDispatchRecord,
} from '@qinglong/runtime-core/approved-action';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
type Row = Record<string, unknown>;
function text(row: Row, key: string): string {
const value = row[key];
if (typeof value !== 'string') {
throw new ApprovedActionExecutionUnavailableError();
}
return value;
}
function parseExecution(row: Row): Readonly<ApprovedActionExecutionRecord> {
try {
const execution = normalizeApprovedActionExecutionRecord(
JSON.parse(text(row, 'executionJson')) as ApprovedActionExecutionRecord,
);
if (execution.executionDigest !== text(row, 'executionDigest')) {
throw new ApprovedActionExecutionUnavailableError();
}
return execution;
} catch (error) {
if (error instanceof ApprovedActionExecutionUnavailableError) throw error;
throw new ApprovedActionExecutionUnavailableError();
}
}
function parseDispatch(row: Row): Readonly<ApprovedActionDispatchRecord> {
try {
return normalizeApprovedActionDispatchRecord(
JSON.parse(text(row, 'dispatchJson')) as ApprovedActionDispatchRecord,
);
} catch {
throw new ApprovedActionExecutionUnavailableError();
}
}
function storageError(error: unknown): Error {
if (
error instanceof ApprovedActionExecutionFenceConflictError ||
error instanceof ApprovedActionExecutionStateConflictError ||
error instanceof ApprovedActionExecutionUnavailableError ||
(error instanceof Error &&
error.name.startsWith('ApprovedActionExecution'))
) {
return error;
}
return new ApprovedActionExecutionUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
const EXECUTION_COLUMNS = `
"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"
`;
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 function insertLocalApprovedActionExecutionBaseline(
client: DatabaseSync,
dispatchValue: ApprovedActionDispatchRecord,
): Readonly<ApprovedActionExecutionRecord> {
const execution = createApprovedActionExecution(dispatchValue);
client
.prepare(
`INSERT INTO "QingLong3ApprovedActionExecutions" (${EXECUTION_COLUMNS})
VALUES (${new Array(21).fill('?').join(', ')})`,
)
.run(...executionValues(execution));
return execution;
}
export function findLocalApprovedActionExecution(
client: DatabaseSync,
dispatchId: string,
): Readonly<ApprovedActionExecutionRecord> | null {
const row = client
.prepare(
`SELECT "execution_json" AS "executionJson",
"execution_digest" AS "executionDigest"
FROM "QingLong3ApprovedActionExecutions"
WHERE "dispatch_id" = ?`,
)
.get(dispatchId) as Row | undefined;
return row ? parseExecution(row) : null;
}
function snapshot(
client: DatabaseSync,
dispatchId: string,
): Readonly<ApprovedActionExecutionSnapshot> | null {
const row = client
.prepare(
`SELECT execution."execution_json" AS "executionJson",
execution."execution_digest" AS "executionDigest",
dispatch."dispatch_json" AS "dispatchJson"
FROM "QingLong3ApprovedActionExecutions" AS execution
JOIN "QingLong3ApprovedActionDispatches" AS dispatch
ON dispatch."dispatch_id" = execution."dispatch_id"
WHERE execution."dispatch_id" = ?`,
)
.get(dispatchId) as Row | undefined;
if (!row) return null;
return normalizeApprovedActionExecutionSnapshot({
dispatch: parseDispatch(row),
execution: parseExecution(row),
});
}
function replaceExecution(
client: DatabaseSync,
previous: Readonly<ApprovedActionExecutionRecord>,
next: Readonly<ApprovedActionExecutionRecord>,
): void {
const result = client
.prepare(
`UPDATE "QingLong3ApprovedActionExecutions"
SET "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" = ?
WHERE "dispatch_id" = ? AND "version" = ?
AND "execution_digest" = ?`,
)
.run(
...executionValues(next).slice(1),
previous.dispatchId,
previous.version,
previous.executionDigest,
);
if (result.changes !== 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 LocalSqliteApprovedActionExecutionRepository
implements ApprovedActionExecutionRepository
{
readonly #authority: LocalSqliteOperationAuthority;
readonly #client: DatabaseSync;
constructor(authority: LocalSqliteOperationAuthority | DatabaseSync) {
this.#authority =
authority instanceof LocalSqliteOperationAuthority
? authority
: new LocalSqliteOperationAuthority(authority);
this.#client = this.#authority.client;
}
#enqueue<T>(work: () => T | Promise<T>): Promise<T> {
return this.#authority.enqueue(
async () => {
try {
return await work();
} catch (error) {
throw storageError(error);
}
},
() => new ApprovedActionExecutionUnavailableError(),
);
}
#transaction<T>(work: () => T): T {
this.#client.exec('BEGIN IMMEDIATE');
try {
const result = work();
this.#client.exec('COMMIT');
return result;
} catch (error) {
if (this.#client.isTransaction) this.#client.exec('ROLLBACK');
throw error;
}
}
findExecutionByDispatchId(
dispatchId: string,
): Promise<Readonly<ApprovedActionExecutionSnapshot> | null> {
return this.#enqueue(() => snapshot(this.#client, dispatchId));
}
listDueExecutions(
query: ListDueApprovedActionExecutionsQuery,
): Promise<ListDueApprovedActionExecutionsResult> {
return this.#enqueue(() => {
assertPageSize(query.limit);
const handledActionTypes = actionTypes(query.actionTypes);
const cursor = query.cursor
? normalizeApprovedActionExecutionCursor(query.cursor)
: undefined;
if (!Number.isSafeInteger(query.nowMs) || query.nowMs < 0) {
throw new ApprovedActionExecutionStateConflictError();
}
if (handledActionTypes.length === 0) {
return Object.freeze({
executions: Object.freeze([]),
truncated: false,
});
}
const actionTypePlaceholders = handledActionTypes.map(() => '?').join(',');
const rows = this.#client
.prepare(
`SELECT execution."execution_json" AS "executionJson",
execution."execution_digest" AS "executionDigest",
dispatch."dispatch_json" AS "dispatchJson"
FROM "QingLong3ApprovedActionExecutions" AS execution
JOIN "QingLong3ApprovedActionDispatches" AS dispatch
ON dispatch."dispatch_id" = execution."dispatch_id"
WHERE execution."status" IN ('pending','leased','retry_wait')
AND execution."eligible_at_ms" <= ?
AND dispatch."action_type" IN (${actionTypePlaceholders})
AND (
? IS NULL OR execution."eligible_at_ms" > ? OR
(execution."eligible_at_ms" = ? AND execution."dispatch_id" > ?)
)
ORDER BY execution."eligible_at_ms", execution."dispatch_id"
LIMIT ?`,
)
.all(
query.nowMs,
...handledActionTypes,
cursor?.dispatchId ?? null,
cursor?.eligibleAtMs ?? 0,
cursor?.eligibleAtMs ?? 0,
cursor?.dispatchId ?? '',
query.limit + 1,
) as Row[];
const truncated = rows.length > query.limit;
const pageRows = truncated ? rows.slice(0, query.limit) : rows;
const executions = pageRows.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,
}),
}
: {}),
});
});
}
claimExecution(
command: ClaimApprovedActionExecutionCommand,
): Promise<ClaimApprovedActionExecutionResult> {
return this.#enqueue(() =>
this.#transaction(() => {
const current = snapshot(this.#client, command.dispatchId);
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,
});
replaceExecution(this.#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.#enqueue(() =>
this.#transaction(() => {
const current = snapshot(this.#client, dispatchId);
if (!current) {
throw new ApprovedActionExecutionStateConflictError();
}
const next = transition(current);
replaceExecution(this.#client, current.execution, next);
return Object.freeze({
dispatch: current.dispatch,
execution: next,
});
}),
);
}
}
@@ -0,0 +1,35 @@
import type { DatabaseSync } from 'node:sqlite';
type Row = Record<string, unknown>;
function projectId(row: Row): string {
const value = row.projectId;
if (typeof value !== 'string') {
throw new TypeError('Local instance authority Project is invalid');
}
return value;
}
export function resolveLocalInstanceAuthorityProjectId(
client: DatabaseSync,
): string | null {
const claimed = client
.prepare(
`SELECT "project_id" AS "projectId"
FROM "QingLong3LocalOwnerBootstrapChallenges"
WHERE "consumed_at_ms" IS NOT NULL
ORDER BY "consumed_at_ms" ASC, "project_id" ASC, "version" ASC
LIMIT 1`,
)
.get() as Row | undefined;
if (claimed) return projectId(claimed);
const fallback = client
.prepare(
`SELECT "id" AS "projectId"
FROM "QingLong3Projects"
WHERE "id" = 'default'
LIMIT 1`,
)
.get() as Row | undefined;
return fallback ? projectId(fallback) : null;
}
@@ -0,0 +1,45 @@
import type { DatabaseSync } from 'node:sqlite';
export const MAX_LOCAL_SQLITE_PENDING_OPERATIONS = 256;
/**
* Owns the one synchronous SQLite connection, its bounded async admission
* queue, and its close fence. Narrow repositories share this authority rather
* than growing one public god repository or opening sibling connections.
*/
export class LocalSqliteOperationAuthority {
private tail: Promise<void> = Promise.resolve();
private pending = 0;
private accepting = true;
private closePromise?: Promise<void>;
constructor(readonly client: DatabaseSync) {}
enqueue<T>(
work: () => Promise<T>,
rejection: (reason: 'closed' | 'busy') => Error,
): Promise<T> {
if (!this.accepting) return Promise.reject(rejection('closed'));
if (this.pending >= MAX_LOCAL_SQLITE_PENDING_OPERATIONS) {
return Promise.reject(rejection('busy'));
}
this.pending += 1;
const result = this.tail.then(work, work);
this.tail = result.then(
() => undefined,
() => undefined,
);
return result.finally(() => {
this.pending -= 1;
});
}
close(): Promise<void> {
if (this.closePromise) return this.closePromise;
this.accepting = false;
this.closePromise = this.tail.then(() => {
if (this.client.isOpen) this.client.close();
});
return this.closePromise;
}
}
+30
View File
@@ -0,0 +1,30 @@
export {
LocalSqliteConfigurationError,
auditLocalSqlitePath,
migrateLocalSqlitePath,
openLocalSqliteRuntimeDatabase,
type LocalSqliteDatabaseOptions,
type LocalSqliteMigrationResult,
type LocalSqliteProfile,
type LocalSqliteRuntimeDatabase,
type LocalRunStartupRecoveryCandidate,
type LocalRunStartupRecoveryPage,
type LocalRunStartupRecoverySource,
type LocalRunStartupRecoveryStatus,
MAX_LOCAL_RUN_STARTUP_RECOVERY_CANDIDATES,
} from './storage/database';
export {
LOCAL_SQLITE_CONTRACT_NAME,
LOCAL_SQLITE_CONTRACT_VERSION,
LocalSqliteReadinessError,
type LocalSqliteReadinessEvidence,
} from './readiness/readiness';
export { LocalSqliteRunRepository } from './run/runRepository';
export { LocalSqliteToolInvocationArtifactRepository } from './tool-execution/toolInvocationArtifactRepository';
export { LocalSqliteApiCredentialRepository } from './security/apiCredentialRepository';
export { LocalSqliteScheduleRepository } from './scheduling/scheduleRepository';
export { LocalSqliteOwnerCredentialRecoveryRepository } from './local-owner/ownerCredentialRecoveryRepository';
export { LocalSqliteOwnerDeliveryAcknowledgementGcRepository } from './local-owner/ownerDeliveryAcknowledgementGcRepository';
export { LocalSqliteOwnerPepperMaterialGcRepository } from './local-owner/ownerPepperMaterialGcRepository';
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,671 @@
import type { DatabaseSync } from 'node:sqlite';
import {
LocalOwnerCredentialRecoveryCredentialUnavailableError,
LocalOwnerCredentialRecoveryInProgressError,
LocalOwnerCredentialRecoveryMutationConflictError,
LocalOwnerCredentialRecoveryNotAcknowledgedError,
LocalOwnerCredentialRecoveryRepositoryUnavailableError,
normalizeAcknowledgeLocalOwnerCredentialRecoveryCommand,
normalizeCompleteLocalOwnerCredentialRecoveryCommand,
normalizeIssueLocalOwnerCredentialRecoveryCommand,
type AcknowledgeLocalOwnerCredentialRecoveryCommand,
type CompleteLocalOwnerCredentialRecoveryCommand,
type IssueLocalOwnerCredentialRecoveryCommand,
type LocalOwnerCredentialRecoveryRecord,
type LocalOwnerCredentialRecoveryRepository,
type LocalOwnerCredentialRecoveryResult,
} from '@qinglong/runtime-core/local-owner-credential-recovery';
import {
normalizeApiCredentialRecord,
type ApiCredentialRecord,
} from '@qinglong/runtime-core/api-credential';
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
type QueryRow = Record<string, unknown>;
const RECOVERY_SELECT = `
SELECT recovery.*,
replacement."state" AS "replacement_state",
replacement."subject_type" AS "replacement_subject_type",
replacement."subject_id" AS "replacement_subject_id",
replacement."secret_digest" AS "replacement_secret_digest",
replacement."created_at_ms" AS "replacement_created_at_ms",
replacement."not_before_at_ms" AS "replacement_not_before_at_ms",
replacement."expires_at_ms" AS "replacement_expires_at_ms",
identity."status" AS "replacement_subject_status",
pepper."pepper_key_id" AS "replacement_pepper_key_id"
FROM "QingLong3LocalOwnerCredentialRecoveries" AS recovery
JOIN "QingLong3ApiCredentials" AS replacement
ON replacement."credential_id" = recovery."replacement_credential_id"
AND replacement."version" = recovery."replacement_credential_version"
JOIN "QingLong3IdentitySubjects" AS identity
ON identity."subject_type" = replacement."subject_type"
AND identity."subject_id" = replacement."subject_id"
JOIN "QingLong3ApiCredentialPepperBindings" AS pepper
ON pepper."credential_id" = replacement."credential_id"
AND pepper."credential_version" = replacement."version"`;
const CREDENTIAL_SELECT = `
SELECT credential."credential_id" AS "credential_credential_id",
credential."version" AS "credential_credential_version",
credential."state" AS "credential_state",
credential."subject_type" AS "credential_subject_type",
credential."subject_id" AS "credential_subject_id",
credential."secret_digest" AS "credential_secret_digest",
credential."created_at_ms" AS "credential_created_at_ms",
credential."not_before_at_ms" AS "credential_not_before_at_ms",
credential."expires_at_ms" AS "credential_expires_at_ms",
identity."status" AS "credential_subject_status",
pepper."pepper_key_id" AS "credential_pepper_key_id"
FROM "QingLong3ApiCredentials" AS credential
JOIN "QingLong3IdentitySubjects" AS identity
ON identity."subject_type" = credential."subject_type"
AND identity."subject_id" = credential."subject_id"
JOIN "QingLong3ApiCredentialPepperBindings" AS pepper
ON pepper."credential_id" = credential."credential_id"
AND pepper."credential_version" = credential."version"`;
function string(row: QueryRow, name: string): string {
const value = row[name];
if (typeof value !== 'string') {
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
return value;
}
function optionalString(row: QueryRow, name: string): string | undefined {
const value = row[name];
if (value === null || value === undefined) return undefined;
return string(row, name);
}
function integer(row: QueryRow, name: string): number {
const value = row[name];
if (typeof value !== 'number' || !Number.isSafeInteger(value)) {
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
return value;
}
function optionalInteger(row: QueryRow, name: string): number | undefined {
const value = row[name];
if (value === null || value === undefined) return undefined;
return integer(row, name);
}
function credentialFromRow(
row: QueryRow,
prefix: 'credential' | 'replacement',
): Readonly<ApiCredentialRecord> {
try {
return normalizeApiCredentialRecord({
credentialId: string(row, `${prefix}_credential_id`),
version: integer(row, `${prefix}_credential_version`),
pepperKeyId: string(row, `${prefix}_pepper_key_id`),
state: string(row, `${prefix}_state`) as ApiCredentialRecord['state'],
subject: {
type: string(
row,
`${prefix}_subject_type`,
) as ApiCredentialRecord['subject']['type'],
id: string(row, `${prefix}_subject_id`),
},
subjectStatus: string(
row,
`${prefix}_subject_status`,
) as ApiCredentialRecord['subjectStatus'],
secretDigest: string(row, `${prefix}_secret_digest`),
createdAtMs: integer(row, `${prefix}_created_at_ms`),
notBeforeAtMs: integer(row, `${prefix}_not_before_at_ms`),
expiresAtMs: integer(row, `${prefix}_expires_at_ms`),
});
} catch (error) {
if (
error instanceof LocalOwnerCredentialRecoveryRepositoryUnavailableError
) {
throw error;
}
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
}
function recoveryFromRow(
row: QueryRow,
): Readonly<LocalOwnerCredentialRecoveryRecord> {
const state = string(row, 'state');
if (state !== 'issued' && state !== 'acknowledged' && state !== 'completed') {
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
const deliveryDigest = optionalString(row, 'delivery_digest');
const acknowledgedAtMs = optionalInteger(row, 'acknowledged_at_ms');
const completeMutationId = optionalString(row, 'complete_mutation_id');
const completeRequestId = optionalString(row, 'complete_request_id');
const revokedCredentialVersion = optionalInteger(
row,
'revoked_credential_version',
);
const completedAtMs = optionalInteger(row, 'completed_at_ms');
if (
(state === 'issued' &&
(deliveryDigest !== undefined ||
acknowledgedAtMs !== undefined ||
completeMutationId !== undefined ||
completeRequestId !== undefined ||
revokedCredentialVersion !== undefined ||
completedAtMs !== undefined)) ||
(state === 'acknowledged' &&
(deliveryDigest === undefined ||
acknowledgedAtMs === undefined ||
completeMutationId !== undefined ||
completeRequestId !== undefined ||
revokedCredentialVersion !== undefined ||
completedAtMs !== undefined)) ||
(state === 'completed' &&
(deliveryDigest === undefined ||
acknowledgedAtMs === undefined ||
completeMutationId === undefined ||
completeRequestId === undefined ||
revokedCredentialVersion === undefined ||
completedAtMs === undefined))
) {
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
return Object.freeze({
issueMutationId: string(row, 'issue_mutation_id'),
issueRequestId: string(row, 'issue_request_id'),
subjectId: string(row, 'subject_id'),
previousCredentialId: string(row, 'previous_credential_id'),
previousCredentialVersion: integer(row, 'previous_credential_version'),
replacementCredential: credentialFromRow(row, 'replacement'),
state,
issuedAtMs: integer(row, 'issued_at_ms'),
...(deliveryDigest === undefined ? {} : { deliveryDigest }),
...(acknowledgedAtMs === undefined ? {} : { acknowledgedAtMs }),
...(completeMutationId === undefined ? {} : { completeMutationId }),
...(completeRequestId === undefined ? {} : { completeRequestId }),
...(revokedCredentialVersion === undefined
? {}
: { revokedCredentialVersion }),
...(completedAtMs === undefined ? {} : { completedAtMs }),
});
}
function sameCredential(
left: Readonly<ApiCredentialRecord>,
right: Readonly<ApiCredentialRecord>,
): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
function insertAudit(
client: DatabaseSync,
audit: Readonly<SecurityAuditRecord>,
) {
client
.prepare(
`INSERT INTO "QingLong3SecurityAuditEvents" (
event_id, request_id, operation_id, project_id, subject_type,
subject_id, authentication_id, outcome, reasons_json,
fence_project_version, fence_binding_version, occurred_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
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,
);
}
function isDomainError(error: unknown): boolean {
return (
error instanceof LocalOwnerCredentialRecoveryMutationConflictError ||
error instanceof LocalOwnerCredentialRecoveryInProgressError ||
error instanceof LocalOwnerCredentialRecoveryNotAcknowledgedError ||
error instanceof LocalOwnerCredentialRecoveryCredentialUnavailableError ||
error instanceof LocalOwnerCredentialRecoveryRepositoryUnavailableError
);
}
export class LocalSqliteOwnerCredentialRecoveryRepository
implements LocalOwnerCredentialRecoveryRepository
{
private readonly authority: LocalSqliteOperationAuthority;
constructor(authority: LocalSqliteOperationAuthority | DatabaseSync) {
this.authority =
authority instanceof LocalSqliteOperationAuthority
? authority
: new LocalSqliteOperationAuthority(authority);
}
private resolveDirect(
issueMutationId: string,
): Readonly<LocalOwnerCredentialRecoveryRecord> | null {
const rows = this.authority.client
.prepare(
`${RECOVERY_SELECT} WHERE recovery."issue_mutation_id" = ? LIMIT 2`,
)
.all(issueMutationId) as QueryRow[];
if (rows.length > 1) {
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
return rows[0] ? recoveryFromRow(rows[0]) : null;
}
private currentCredentialDirect(
credentialId: string,
): Readonly<ApiCredentialRecord> | null {
const row = this.authority.client
.prepare(
`${CREDENTIAL_SELECT}
WHERE credential."credential_id" = ?
ORDER BY credential."version" DESC LIMIT 1`,
)
.get(credentialId) as QueryRow | undefined;
return row ? credentialFromRow(row, 'credential') : null;
}
resolve(
issueMutationId: string,
): Promise<Readonly<LocalOwnerCredentialRecoveryRecord> | null> {
return this.authority.enqueue(
async () => {
try {
return this.resolveDirect(issueMutationId);
} catch (error) {
if (
error instanceof
LocalOwnerCredentialRecoveryRepositoryUnavailableError
) {
throw error;
}
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
},
() => new LocalOwnerCredentialRecoveryRepositoryUnavailableError(),
);
}
issue(
input: IssueLocalOwnerCredentialRecoveryCommand,
): Promise<LocalOwnerCredentialRecoveryResult> {
const command = normalizeIssueLocalOwnerCredentialRecoveryCommand(input);
return this.authority.enqueue(
async () => {
const client = this.authority.client;
client.exec('BEGIN IMMEDIATE');
try {
const replay = this.resolveDirect(command.mutationId);
if (replay) {
if (
replay.issueRequestId !== command.requestId ||
replay.previousCredentialId !== command.previousCredentialId ||
replay.previousCredentialVersion !==
command.expectedPreviousVersion ||
!sameCredential(
replay.replacementCredential,
command.replacementCredential,
)
) {
throw new LocalOwnerCredentialRecoveryMutationConflictError();
}
client.exec('COMMIT');
return Object.freeze({
status: 'existing' as const,
recovery: replay,
});
}
if (
client
.prepare(
`SELECT 1 FROM "QingLong3LocalOwnerCredentialRecoveries"
WHERE "subject_id" = ? AND "state" <> 'completed' LIMIT 1`,
)
.get(command.replacementCredential.subject.id)
) {
throw new LocalOwnerCredentialRecoveryInProgressError();
}
const previous = this.currentCredentialDirect(
command.previousCredentialId,
);
if (
!previous ||
previous.version !== command.expectedPreviousVersion ||
previous.state !== 'active' ||
previous.subject.type !== 'user' ||
previous.subject.id !== command.replacementCredential.subject.id ||
previous.subjectStatus !== 'active' ||
previous.notBeforeAtMs >
command.replacementCredential.createdAtMs ||
previous.expiresAtMs <= command.replacementCredential.createdAtMs
) {
throw new LocalOwnerCredentialRecoveryCredentialUnavailableError();
}
const activePepper = client
.prepare(
`SELECT key."pepper_key_id"
FROM "QingLong3LocalOwnerPepperActivations" AS activation
JOIN "QingLong3LocalOwnerPepperKeys" AS key
ON key."pepper_key_id" = activation."active_pepper_key_id"
WHERE key."state" = 'active'
ORDER BY activation."generation" DESC LIMIT 1`,
)
.get() as QueryRow | undefined;
if (
!activePepper ||
string(activePepper, 'pepper_key_id') !==
command.replacementCredential.pepperKeyId
) {
throw new LocalOwnerCredentialRecoveryCredentialUnavailableError();
}
if (
client
.prepare(
`SELECT 1 FROM "QingLong3ApiCredentials"
WHERE "credential_id" = ? LIMIT 1`,
)
.get(command.replacementCredential.credentialId) ||
client
.prepare(
`SELECT 1 FROM "QingLong3SecurityAuditEvents"
WHERE "event_id" = ? LIMIT 1`,
)
.get(command.audit.eventId)
) {
throw new LocalOwnerCredentialRecoveryMutationConflictError();
}
const replacement = command.replacementCredential;
client
.prepare(
`INSERT INTO "QingLong3ApiCredentials" (
credential_id, version, state, subject_type, subject_id,
secret_digest, created_at_ms, not_before_at_ms, expires_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
replacement.credentialId,
replacement.version,
replacement.state,
replacement.subject.type,
replacement.subject.id,
replacement.secretDigest,
replacement.createdAtMs,
replacement.notBeforeAtMs,
replacement.expiresAtMs,
);
client
.prepare(
`INSERT INTO "QingLong3ApiCredentialPepperBindings" (
credential_id, credential_version, pepper_key_id
) VALUES (?, ?, ?)`,
)
.run(
replacement.credentialId,
replacement.version,
replacement.pepperKeyId,
);
insertAudit(client, command.audit);
client
.prepare(
`INSERT INTO "QingLong3LocalOwnerCredentialRecoveries" (
issue_mutation_id, issue_request_id, subject_type, subject_id,
previous_credential_id, previous_credential_version,
replacement_credential_id, replacement_credential_version,
state, issued_at_ms, issue_audit_event_id
) VALUES (?, ?, 'user', ?, ?, ?, ?, ?, 'issued', ?, ?)`,
)
.run(
command.mutationId,
command.requestId,
replacement.subject.id,
command.previousCredentialId,
command.expectedPreviousVersion,
replacement.credentialId,
replacement.version,
replacement.createdAtMs,
command.audit.eventId,
);
const recovery = this.resolveDirect(command.mutationId);
if (!recovery) {
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
client.exec('COMMIT');
return Object.freeze({ status: 'inserted' as const, recovery });
} catch (error) {
if (client.isTransaction) client.exec('ROLLBACK');
if (isDomainError(error)) throw error;
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
},
() => new LocalOwnerCredentialRecoveryRepositoryUnavailableError(),
);
}
acknowledge(
input: AcknowledgeLocalOwnerCredentialRecoveryCommand,
): Promise<LocalOwnerCredentialRecoveryResult> {
const command =
normalizeAcknowledgeLocalOwnerCredentialRecoveryCommand(input);
return this.authority.enqueue(
async () => {
const client = this.authority.client;
client.exec('BEGIN IMMEDIATE');
try {
const recovery = this.resolveDirect(command.issueMutationId);
if (
!recovery ||
recovery.issueRequestId !== command.requestId ||
recovery.replacementCredential.credentialId !==
command.credentialId ||
recovery.replacementCredential.secretDigest !==
command.factDigest ||
command.acknowledgedAtMs < recovery.issuedAtMs ||
command.acknowledgedAtMs >=
recovery.replacementCredential.expiresAtMs
) {
throw new LocalOwnerCredentialRecoveryMutationConflictError();
}
if (recovery.state !== 'issued') {
if (
recovery.deliveryDigest !== command.deliveryDigest ||
recovery.acknowledgedAtMs !== command.acknowledgedAtMs
) {
throw new LocalOwnerCredentialRecoveryMutationConflictError();
}
client.exec('COMMIT');
return Object.freeze({
status: 'existing' as const,
recovery,
});
}
const changed = client
.prepare(
`UPDATE "QingLong3LocalOwnerCredentialRecoveries"
SET state = 'acknowledged', delivery_digest = ?,
acknowledged_at_ms = ?
WHERE issue_mutation_id = ? AND state = 'issued'`,
)
.run(
command.deliveryDigest,
command.acknowledgedAtMs,
command.issueMutationId,
);
if (changed.changes !== 1) {
throw new LocalOwnerCredentialRecoveryMutationConflictError();
}
const acknowledged = this.resolveDirect(command.issueMutationId);
if (!acknowledged || acknowledged.state !== 'acknowledged') {
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
client.exec('COMMIT');
return Object.freeze({
status: 'inserted' as const,
recovery: acknowledged,
});
} catch (error) {
if (client.isTransaction) client.exec('ROLLBACK');
if (isDomainError(error)) throw error;
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
},
() => new LocalOwnerCredentialRecoveryRepositoryUnavailableError(),
);
}
complete(
input: CompleteLocalOwnerCredentialRecoveryCommand,
): Promise<LocalOwnerCredentialRecoveryResult> {
const command = normalizeCompleteLocalOwnerCredentialRecoveryCommand(input);
return this.authority.enqueue(
async () => {
const client = this.authority.client;
client.exec('BEGIN IMMEDIATE');
try {
const replayRow = client
.prepare(
`SELECT "issue_mutation_id"
FROM "QingLong3LocalOwnerCredentialRecoveries"
WHERE "complete_mutation_id" = ? LIMIT 1`,
)
.get(command.mutationId) as QueryRow | undefined;
if (replayRow) {
const replay = this.resolveDirect(
string(replayRow, 'issue_mutation_id'),
);
const replayedRevocation = replay
? this.currentCredentialDirect(replay.previousCredentialId)
: null;
if (
!replay ||
!replayedRevocation ||
replay.issueMutationId !== command.issueMutationId ||
replay.completeRequestId !== command.requestId ||
replay.previousCredentialVersion !==
command.expectedPreviousVersion ||
replay.revokedCredentialVersion !==
command.revokedCredential.version ||
replay.completedAtMs !== command.revokedCredential.createdAtMs ||
!sameCredential(replayedRevocation, command.revokedCredential)
) {
throw new LocalOwnerCredentialRecoveryMutationConflictError();
}
client.exec('COMMIT');
return Object.freeze({
status: 'existing' as const,
recovery: replay,
});
}
const recovery = this.resolveDirect(command.issueMutationId);
if (!recovery) {
throw new LocalOwnerCredentialRecoveryCredentialUnavailableError();
}
if (recovery.state === 'issued') {
throw new LocalOwnerCredentialRecoveryNotAcknowledgedError();
}
if (recovery.state === 'completed') {
throw new LocalOwnerCredentialRecoveryMutationConflictError();
}
const previous = this.currentCredentialDirect(
recovery.previousCredentialId,
);
const revoked = command.revokedCredential;
if (
!previous ||
previous.version !== command.expectedPreviousVersion ||
previous.version !== recovery.previousCredentialVersion ||
previous.state !== 'active' ||
previous.subject.id !== recovery.subjectId ||
revoked.credentialId !== previous.credentialId ||
revoked.subject.type !== previous.subject.type ||
revoked.subject.id !== previous.subject.id ||
revoked.subjectStatus !== previous.subjectStatus ||
revoked.pepperKeyId !== previous.pepperKeyId ||
revoked.createdAtMs < (recovery.acknowledgedAtMs ?? 0) ||
revoked.createdAtMs >= recovery.replacementCredential.expiresAtMs
) {
throw new LocalOwnerCredentialRecoveryCredentialUnavailableError();
}
if (
client
.prepare(
`SELECT 1 FROM "QingLong3SecurityAuditEvents"
WHERE "event_id" = ? LIMIT 1`,
)
.get(command.audit.eventId)
) {
throw new LocalOwnerCredentialRecoveryMutationConflictError();
}
client
.prepare(
`INSERT INTO "QingLong3ApiCredentials" (
credential_id, version, state, subject_type, subject_id,
secret_digest, created_at_ms, not_before_at_ms, expires_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
revoked.credentialId,
revoked.version,
revoked.state,
revoked.subject.type,
revoked.subject.id,
revoked.secretDigest,
revoked.createdAtMs,
revoked.notBeforeAtMs,
revoked.expiresAtMs,
);
client
.prepare(
`INSERT INTO "QingLong3ApiCredentialPepperBindings" (
credential_id, credential_version, pepper_key_id
) VALUES (?, ?, ?)`,
)
.run(revoked.credentialId, revoked.version, revoked.pepperKeyId);
insertAudit(client, command.audit);
const changed = client
.prepare(
`UPDATE "QingLong3LocalOwnerCredentialRecoveries"
SET state = 'completed', complete_mutation_id = ?,
complete_request_id = ?, revoked_credential_version = ?,
completed_at_ms = ?, complete_audit_event_id = ?
WHERE issue_mutation_id = ? AND state = 'acknowledged'`,
)
.run(
command.mutationId,
command.requestId,
revoked.version,
revoked.createdAtMs,
command.audit.eventId,
command.issueMutationId,
);
if (changed.changes !== 1) {
throw new LocalOwnerCredentialRecoveryMutationConflictError();
}
const completed = this.resolveDirect(command.issueMutationId);
if (!completed || completed.state !== 'completed') {
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
client.exec('COMMIT');
return Object.freeze({
status: 'inserted' as const,
recovery: completed,
});
} catch (error) {
if (client.isTransaction) client.exec('ROLLBACK');
if (isDomainError(error)) throw error;
throw new LocalOwnerCredentialRecoveryRepositoryUnavailableError();
}
},
() => new LocalOwnerCredentialRecoveryRepositoryUnavailableError(),
);
}
}
@@ -0,0 +1,508 @@
import type { DatabaseSync } from 'node:sqlite';
import {
localOwnerSecretDeliveryAcknowledgementSemanticDigest,
normalizeLocalOwnerSecretDeliveryAcknowledgementRecord,
type LocalOwnerSecretDeliveryAcknowledgementRecord,
} from '@qinglong/runtime-core/local-owner-bootstrap';
import {
LocalOwnerDeliveryAcknowledgementGcMutationConflictError,
LocalOwnerDeliveryAcknowledgementGcReferenceConflictError,
LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError,
LocalOwnerDeliveryAcknowledgementGcRetentionPendingError,
localOwnerDeliveryAcknowledgementGcRetentionPolicyDigest,
normalizeCompactLocalOwnerDeliveryAcknowledgementCommand,
type CompactLocalOwnerDeliveryAcknowledgementCommand,
type LocalOwnerDeliveryAcknowledgementGcRecord,
type LocalOwnerDeliveryAcknowledgementGcRepository,
type LocalOwnerDeliveryAcknowledgementGcResult,
} from '@qinglong/runtime-core/local-owner-delivery-acknowledgement-gc';
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
type QueryRow = Record<string, unknown>;
const GC_SELECT = `
SELECT * FROM "QingLong3LocalOwnerDeliveryAcknowledgementGc"`;
function text(row: QueryRow, name: string): string {
const value = row[name];
if (typeof value !== 'string') {
throw new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError();
}
return value;
}
function optionalText(row: QueryRow, name: string): string | undefined {
const value = row[name];
if (value === null || value === undefined) return undefined;
return text(row, name);
}
function integer(row: QueryRow, name: string): number {
const value = row[name];
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
throw new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError();
}
return value;
}
function safeRetentionTimestamp(base: number, duration: number): number {
const result = base + duration;
if (!Number.isSafeInteger(result) || result < 0) {
throw new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError();
}
return result;
}
function acknowledgementFromRow(
row: QueryRow,
): Readonly<LocalOwnerSecretDeliveryAcknowledgementRecord> {
try {
const common = {
mutationId: text(row, 'mutation_id'),
requestId: text(row, 'request_id'),
factDigest: text(row, 'fact_digest'),
deliveryDigest: text(row, 'delivery_digest'),
ttlMs: integer(row, 'ttl_ms'),
acknowledgedAtMs: integer(row, 'acknowledged_at_ms'),
};
const kind = text(row, 'kind');
return normalizeLocalOwnerSecretDeliveryAcknowledgementRecord(
kind === 'credential'
? {
...common,
kind,
subjectId: text(row, 'subject_id'),
credentialId: text(row, 'credential_id'),
}
: {
...common,
kind: kind as 'challenge',
projectId: text(row, 'project_id'),
challengeId: text(row, 'challenge_id'),
},
);
} catch (error) {
if (
error instanceof
LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError
) {
throw error;
}
throw new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError();
}
}
function gcRecord(
row: QueryRow,
): Readonly<LocalOwnerDeliveryAcknowledgementGcRecord> {
const kind = text(row, 'acknowledgement_kind');
const provisioningMutationId = optionalText(row, 'provisioning_mutation_id');
const challengeMutationId = optionalText(row, 'challenge_mutation_id');
const acknowledgementMutationId = text(row, 'acknowledgement_mutation_id');
if (
(kind !== 'credential' && kind !== 'challenge') ||
(kind === 'credential' &&
(provisioningMutationId !== acknowledgementMutationId ||
challengeMutationId !== undefined)) ||
(kind === 'challenge' &&
(challengeMutationId !== acknowledgementMutationId ||
provisioningMutationId !== undefined))
) {
throw new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError();
}
const retentionPolicy = Object.freeze({
version: 1 as const,
replayRetentionMs: integer(row, 'replay_retention_ms'),
auditRetentionMs: integer(row, 'audit_retention_ms'),
});
const retentionPolicyDigest = text(row, 'retention_policy_digest');
if (
integer(row, 'retention_policy_version') !== 1 ||
localOwnerDeliveryAcknowledgementGcRetentionPolicyDigest(
retentionPolicy,
) !== retentionPolicyDigest
) {
throw new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError();
}
return Object.freeze({
mutationId: text(row, 'gc_mutation_id'),
requestId: text(row, 'gc_request_id'),
acknowledgementMutationId,
acknowledgementKind: kind,
deliveryDigest: text(row, 'delivery_digest'),
acknowledgedAtMs: integer(row, 'acknowledged_at_ms'),
acknowledgementSemanticDigest: text(row, 'acknowledgement_semantic_digest'),
bridgeClearEvidenceDigest: text(row, 'bridge_clear_evidence_digest'),
retentionPolicy,
retentionPolicyDigest,
retentionEligibleAtMs: integer(row, 'retention_eligible_at_ms'),
compactedAtMs: integer(row, 'compacted_at_ms'),
});
}
function sameCompact(
record: Readonly<LocalOwnerDeliveryAcknowledgementGcRecord>,
command: Readonly<CompactLocalOwnerDeliveryAcknowledgementCommand>,
): boolean {
return (
record.mutationId === command.mutationId &&
record.requestId === command.requestId &&
record.acknowledgementMutationId === command.acknowledgementMutationId &&
record.acknowledgementKind === command.expectedKind &&
record.deliveryDigest === command.expectedDeliveryDigest &&
record.bridgeClearEvidenceDigest ===
command.bridgeClearEvidence.evidenceDigest &&
record.retentionPolicyDigest ===
localOwnerDeliveryAcknowledgementGcRetentionPolicyDigest(
command.retentionPolicy,
) &&
record.compactedAtMs === command.compactedAtMs
);
}
function insertAudit(
client: DatabaseSync,
audit: Readonly<SecurityAuditRecord>,
): void {
client
.prepare(
`INSERT INTO "QingLong3SecurityAuditEvents" (
event_id, request_id, operation_id, project_id, subject_type,
subject_id, authentication_id, outcome, reasons_json,
fence_project_version, fence_binding_version, occurred_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
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,
);
}
function sourceRetentionEligibleAt(
client: DatabaseSync,
acknowledgement: Readonly<LocalOwnerSecretDeliveryAcknowledgementRecord>,
command: Readonly<CompactLocalOwnerDeliveryAcknowledgementCommand>,
): number {
const replayEligibleAt = safeRetentionTimestamp(
acknowledgement.acknowledgedAtMs,
command.retentionPolicy.replayRetentionMs,
);
if (acknowledgement.kind === 'credential') {
const source = client
.prepare(
`SELECT provisioning."request_id", provisioning."subject_id",
provisioning."credential_id", credential."version",
credential."secret_digest", credential."not_before_at_ms",
credential."expires_at_ms", audit."occurred_at_ms"
FROM "QingLong3LocalIdentityProvisionings" AS provisioning
JOIN "QingLong3ApiCredentials" AS credential
ON credential."credential_id" = provisioning."credential_id"
AND credential."version" = provisioning."credential_version"
JOIN "QingLong3SecurityAuditEvents" AS audit
ON audit."event_id" = provisioning."audit_event_id"
WHERE provisioning."mutation_id" = ?`,
)
.get(acknowledgement.mutationId) as QueryRow | undefined;
if (
!source ||
text(source, 'request_id') !== acknowledgement.requestId ||
text(source, 'subject_id') !== acknowledgement.subjectId ||
text(source, 'credential_id') !== acknowledgement.credentialId ||
text(source, 'secret_digest') !== acknowledgement.factDigest ||
integer(source, 'expires_at_ms') - integer(source, 'not_before_at_ms') !==
acknowledgement.ttlMs
) {
throw new LocalOwnerDeliveryAcknowledgementGcMutationConflictError();
}
const current = client
.prepare(
`SELECT "state", "expires_at_ms"
FROM "QingLong3ApiCredentials"
WHERE "credential_id" = ?
ORDER BY "version" DESC LIMIT 1`,
)
.get(acknowledgement.credentialId) as QueryRow | undefined;
if (
!current ||
(text(current, 'state') === 'active' &&
integer(current, 'expires_at_ms') > command.compactedAtMs)
) {
throw new LocalOwnerDeliveryAcknowledgementGcReferenceConflictError();
}
if (
client
.prepare(
`SELECT 1
FROM "QingLong3LocalOwnerCredentialRecoveries"
WHERE "state" <> 'completed'
AND (("previous_credential_id" = ? AND "previous_credential_version" = ?)
OR ("replacement_credential_id" = ? AND "replacement_credential_version" = ?))
LIMIT 1`,
)
.get(
acknowledgement.credentialId,
integer(source, 'version'),
acknowledgement.credentialId,
integer(source, 'version'),
)
) {
throw new LocalOwnerDeliveryAcknowledgementGcReferenceConflictError();
}
return Math.max(
replayEligibleAt,
integer(source, 'expires_at_ms'),
safeRetentionTimestamp(
integer(source, 'occurred_at_ms'),
command.retentionPolicy.auditRetentionMs,
),
);
}
const source = client
.prepare(
`SELECT challenge."issue_request_id", challenge."project_id",
challenge."challenge_id", challenge."token_digest",
challenge."issued_at_ms", challenge."expires_at_ms",
challenge."consumed_at_ms",
issue_audit."occurred_at_ms" AS "issue_audit_at_ms",
claim_audit."occurred_at_ms" AS "claim_audit_at_ms"
FROM "QingLong3LocalOwnerBootstrapChallenges" AS challenge
JOIN "QingLong3SecurityAuditEvents" AS issue_audit
ON issue_audit."event_id" = challenge."issue_audit_event_id"
LEFT JOIN "QingLong3SecurityAuditEvents" AS claim_audit
ON claim_audit."event_id" = challenge."claim_audit_event_id"
WHERE challenge."issue_mutation_id" = ?`,
)
.get(acknowledgement.mutationId) as QueryRow | undefined;
if (
!source ||
text(source, 'issue_request_id') !== acknowledgement.requestId ||
text(source, 'project_id') !== acknowledgement.projectId ||
text(source, 'challenge_id') !== acknowledgement.challengeId ||
text(source, 'token_digest') !== acknowledgement.factDigest ||
integer(source, 'expires_at_ms') - integer(source, 'issued_at_ms') !==
acknowledgement.ttlMs
) {
throw new LocalOwnerDeliveryAcknowledgementGcMutationConflictError();
}
if (
source.consumed_at_ms === null &&
integer(source, 'expires_at_ms') > command.compactedAtMs
) {
throw new LocalOwnerDeliveryAcknowledgementGcReferenceConflictError();
}
const claimAuditAtMs =
source.claim_audit_at_ms === null
? undefined
: integer(source, 'claim_audit_at_ms');
return Math.max(
replayEligibleAt,
integer(source, 'expires_at_ms'),
safeRetentionTimestamp(
integer(source, 'issue_audit_at_ms'),
command.retentionPolicy.auditRetentionMs,
),
...(claimAuditAtMs === undefined
? []
: [
safeRetentionTimestamp(
claimAuditAtMs,
command.retentionPolicy.auditRetentionMs,
),
]),
);
}
function isDomainError(error: unknown): boolean {
return (
error instanceof LocalOwnerDeliveryAcknowledgementGcMutationConflictError ||
error instanceof
LocalOwnerDeliveryAcknowledgementGcReferenceConflictError ||
error instanceof LocalOwnerDeliveryAcknowledgementGcRetentionPendingError ||
error instanceof
LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError
);
}
export class LocalSqliteOwnerDeliveryAcknowledgementGcRepository
implements LocalOwnerDeliveryAcknowledgementGcRepository
{
private readonly authority: LocalSqliteOperationAuthority;
constructor(authority: LocalSqliteOperationAuthority | DatabaseSync) {
this.authority =
authority instanceof LocalSqliteOperationAuthority
? authority
: new LocalSqliteOperationAuthority(authority);
}
resolveByAcknowledgement(
acknowledgementMutationId: string,
): Promise<Readonly<LocalOwnerDeliveryAcknowledgementGcRecord> | null> {
if (typeof acknowledgementMutationId !== 'string') {
throw new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError();
}
return this.authority.enqueue(
async () => {
try {
const row = this.authority.client
.prepare(`${GC_SELECT} WHERE "acknowledgement_mutation_id" = ?`)
.get(acknowledgementMutationId) as QueryRow | undefined;
return row ? gcRecord(row) : null;
} catch (error) {
if (isDomainError(error)) throw error;
throw new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError();
}
},
() => new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError(),
);
}
compact(
candidate: CompactLocalOwnerDeliveryAcknowledgementCommand,
): Promise<Readonly<LocalOwnerDeliveryAcknowledgementGcResult>> {
const command =
normalizeCompactLocalOwnerDeliveryAcknowledgementCommand(candidate);
return this.authority.enqueue(
async () => {
const client = this.authority.client;
try {
client.exec('BEGIN IMMEDIATE');
const replay = client
.prepare(`${GC_SELECT} WHERE "gc_mutation_id" = ?`)
.get(command.mutationId) as QueryRow | undefined;
if (replay) {
const record = gcRecord(replay);
if (!sameCompact(record, command)) {
throw new LocalOwnerDeliveryAcknowledgementGcMutationConflictError();
}
client.exec('COMMIT');
return Object.freeze({ status: 'existing' as const, record });
}
if (
client
.prepare(`${GC_SELECT} WHERE "acknowledgement_mutation_id" = ?`)
.get(command.acknowledgementMutationId)
) {
throw new LocalOwnerDeliveryAcknowledgementGcMutationConflictError();
}
const row = client
.prepare(
`SELECT * FROM "QingLong3LocalOwnerDeliveryAcknowledgements"
WHERE "mutation_id" = ?`,
)
.get(command.acknowledgementMutationId) as QueryRow | undefined;
if (!row) {
throw new LocalOwnerDeliveryAcknowledgementGcMutationConflictError();
}
const acknowledgement = acknowledgementFromRow(row);
if (
acknowledgement.kind !== command.expectedKind ||
acknowledgement.deliveryDigest !== command.expectedDeliveryDigest
) {
throw new LocalOwnerDeliveryAcknowledgementGcMutationConflictError();
}
const eligibleAtMs = sourceRetentionEligibleAt(
client,
acknowledgement,
command,
);
if (command.compactedAtMs < eligibleAtMs) {
throw new LocalOwnerDeliveryAcknowledgementGcRetentionPendingError(
eligibleAtMs,
);
}
if (
client
.prepare(
`SELECT 1 FROM "QingLong3SecurityAuditEvents"
WHERE "event_id" = ?`,
)
.get(command.audit.eventId)
) {
throw new LocalOwnerDeliveryAcknowledgementGcMutationConflictError();
}
const semanticDigest =
localOwnerSecretDeliveryAcknowledgementSemanticDigest(
acknowledgement,
);
const policyDigest =
localOwnerDeliveryAcknowledgementGcRetentionPolicyDigest(
command.retentionPolicy,
);
insertAudit(client, command.audit);
client
.prepare(
`INSERT INTO "QingLong3LocalOwnerDeliveryAcknowledgementGc" (
gc_mutation_id, gc_request_id,
acknowledgement_mutation_id, acknowledgement_kind,
delivery_digest, acknowledged_at_ms,
acknowledgement_semantic_digest,
bridge_clear_evidence_digest, retention_policy_version,
replay_retention_ms, audit_retention_ms,
retention_policy_digest, retention_eligible_at_ms,
compacted_at_ms, audit_event_id,
provisioning_mutation_id, challenge_mutation_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
command.mutationId,
command.requestId,
acknowledgement.mutationId,
acknowledgement.kind,
acknowledgement.deliveryDigest,
acknowledgement.acknowledgedAtMs,
semanticDigest,
command.bridgeClearEvidence.evidenceDigest,
command.retentionPolicy.replayRetentionMs,
command.retentionPolicy.auditRetentionMs,
policyDigest,
eligibleAtMs,
command.compactedAtMs,
command.audit.eventId,
acknowledgement.kind === 'credential'
? acknowledgement.mutationId
: null,
acknowledgement.kind === 'challenge'
? acknowledgement.mutationId
: null,
);
const deleted = client
.prepare(
`DELETE FROM "QingLong3LocalOwnerDeliveryAcknowledgements"
WHERE "mutation_id" = ?`,
)
.run(acknowledgement.mutationId);
if (deleted.changes !== 1) {
throw new LocalOwnerDeliveryAcknowledgementGcMutationConflictError();
}
const inserted = client
.prepare(`${GC_SELECT} WHERE "gc_mutation_id" = ?`)
.get(command.mutationId) as QueryRow | undefined;
if (!inserted) {
throw new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError();
}
const record = gcRecord(inserted);
client.exec('COMMIT');
return Object.freeze({ status: 'inserted' as const, record });
} catch (error) {
if (client.isTransaction) client.exec('ROLLBACK');
if (isDomainError(error)) throw error;
throw new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError();
}
},
() => new LocalOwnerDeliveryAcknowledgementGcRepositoryUnavailableError(),
);
}
}
@@ -0,0 +1,607 @@
import type { DatabaseSync } from 'node:sqlite';
import {
LocalOwnerPepperMaterialGcInProgressError,
LocalOwnerPepperMaterialGcMutationConflictError,
LocalOwnerPepperMaterialGcReferenceConflictError,
LocalOwnerPepperMaterialGcRepositoryUnavailableError,
LocalOwnerPepperMaterialGcRetentionPendingError,
localOwnerPepperMaterialGcRetentionPolicyDigest,
normalizeCompleteLocalOwnerPepperMaterialGcCommand,
normalizePrepareLocalOwnerPepperMaterialGcCommand,
type CompleteLocalOwnerPepperMaterialGcCommand,
type LocalOwnerPepperMaterialGcRecord,
type LocalOwnerPepperMaterialGcRepository,
type LocalOwnerPepperMaterialGcResult,
type PrepareLocalOwnerPepperMaterialGcCommand,
} from '@qinglong/runtime-core/local-owner-pepper-material-gc';
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
type QueryRow = Record<string, unknown>;
const GC_SELECT = `
SELECT * FROM "QingLong3LocalOwnerPepperMaterialGc"`;
function text(row: QueryRow, name: string): string {
const value = row[name];
if (typeof value !== 'string') {
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
return value;
}
function optionalText(row: QueryRow, name: string): string | undefined {
const value = row[name];
if (value === null || value === undefined) return undefined;
return text(row, name);
}
function integer(row: QueryRow, name: string): number {
const value = row[name];
if (typeof value !== 'number' || !Number.isSafeInteger(value)) {
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
return value;
}
function optionalInteger(row: QueryRow, name: string): number | undefined {
const value = row[name];
if (value === null || value === undefined) return undefined;
return integer(row, name);
}
function gcRecord(row: QueryRow): Readonly<LocalOwnerPepperMaterialGcRecord> {
const state = text(row, 'state');
const completeMutationId = optionalText(row, 'complete_mutation_id');
const completeRequestId = optionalText(row, 'complete_request_id');
const destructionProofDigest = optionalText(row, 'destruction_proof_digest');
const completedAtMs = optionalInteger(row, 'completed_at_ms');
if (
(state !== 'prepared' && state !== 'completed') ||
(state === 'prepared' &&
(completeMutationId !== undefined ||
completeRequestId !== undefined ||
destructionProofDigest !== undefined ||
completedAtMs !== undefined)) ||
(state === 'completed' &&
(completeMutationId === undefined ||
completeRequestId === undefined ||
destructionProofDigest === undefined ||
completedAtMs === undefined))
) {
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
const retentionPolicy = Object.freeze({
version: 1 as const,
acknowledgementRetentionMs: integer(row, 'acknowledgement_retention_ms'),
auditRetentionMs: integer(row, 'audit_retention_ms'),
backupRetentionMs: integer(row, 'backup_retention_ms'),
});
if (
integer(row, 'retention_policy_version') !== 1 ||
localOwnerPepperMaterialGcRetentionPolicyDigest(retentionPolicy) !==
text(row, 'retention_policy_digest')
) {
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
return Object.freeze({
prepareMutationId: text(row, 'prepare_mutation_id'),
prepareRequestId: text(row, 'prepare_request_id'),
pepperKeyId: text(row, 'pepper_key_id'),
materialDigest: text(row, 'material_digest'),
backupMaterialDigest: text(row, 'backup_material_digest'),
activePepperKeyId: text(row, 'active_pepper_key_id'),
activeGeneration: integer(row, 'active_generation'),
activeMaterialDigest: text(row, 'active_material_digest'),
retentionPolicy,
retentionPolicyDigest: text(row, 'retention_policy_digest'),
referencesInspectedAtMs: integer(row, 'references_inspected_at_ms'),
retentionEligibleAtMs: integer(row, 'retention_eligible_at_ms'),
preparedAtMs: integer(row, 'prepared_at_ms'),
state,
...(completeMutationId === undefined ? {} : { completeMutationId }),
...(completeRequestId === undefined ? {} : { completeRequestId }),
...(destructionProofDigest === undefined ? {} : { destructionProofDigest }),
...(completedAtMs === undefined ? {} : { completedAtMs }),
});
}
function insertAudit(
client: DatabaseSync,
audit: Readonly<SecurityAuditRecord>,
): void {
client
.prepare(
`INSERT INTO "QingLong3SecurityAuditEvents" (
event_id, request_id, operation_id, project_id, subject_type,
subject_id, authentication_id, outcome, reasons_json,
fence_project_version, fence_binding_version, occurred_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
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,
);
}
function safeRetentionTimestamp(base: number, duration: number): number {
const result = base + duration;
if (!Number.isSafeInteger(result) || result < 0) {
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
return result;
}
function nullableMaximum(row: QueryRow | undefined): number | undefined {
const value = row?.latest_at_ms;
if (value === null || value === undefined) return undefined;
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
return value;
}
function retentionEligibleAt(
client: DatabaseSync,
pepperKeyId: string,
retiredAtMs: number,
command: Readonly<PrepareLocalOwnerPepperMaterialGcCommand>,
): number {
const acknowledgement = nullableMaximum(
client
.prepare(
`SELECT MAX("acknowledged_at_ms") AS "latest_at_ms"
FROM (
SELECT acknowledgement."acknowledged_at_ms"
FROM "QingLong3LocalOwnerDeliveryAcknowledgements" AS acknowledgement
JOIN "QingLong3ApiCredentialPepperBindings" AS binding
ON binding."credential_id" = acknowledgement."credential_id"
WHERE acknowledgement."kind" = 'credential'
AND binding."pepper_key_id" = ?
UNION ALL
SELECT compacted."acknowledged_at_ms"
FROM "QingLong3LocalOwnerDeliveryAcknowledgementGc" AS compacted
JOIN "QingLong3LocalIdentityProvisionings" AS provisioning
ON provisioning."mutation_id" = compacted."provisioning_mutation_id"
JOIN "QingLong3ApiCredentialPepperBindings" AS binding
ON binding."credential_id" = provisioning."credential_id"
AND binding."credential_version" = provisioning."credential_version"
WHERE compacted."acknowledgement_kind" = 'credential'
AND binding."pepper_key_id" = ?
UNION ALL
SELECT recovery."acknowledged_at_ms"
FROM "QingLong3LocalOwnerCredentialRecoveries" AS recovery
WHERE recovery."acknowledged_at_ms" IS NOT NULL
AND (
EXISTS (
SELECT 1 FROM "QingLong3ApiCredentialPepperBindings" AS previous
WHERE previous."credential_id" = recovery."previous_credential_id"
AND previous."credential_version" = recovery."previous_credential_version"
AND previous."pepper_key_id" = ?
)
OR EXISTS (
SELECT 1 FROM "QingLong3ApiCredentialPepperBindings" AS replacement
WHERE replacement."credential_id" = recovery."replacement_credential_id"
AND replacement."credential_version" = recovery."replacement_credential_version"
AND replacement."pepper_key_id" = ?
)
)
)`,
)
.get(pepperKeyId, pepperKeyId, pepperKeyId, pepperKeyId) as
| QueryRow
| undefined,
);
const securityAudit = nullableMaximum(
client
.prepare(
`SELECT MAX(audit."occurred_at_ms") AS "latest_at_ms"
FROM "QingLong3SecurityAuditEvents" AS audit
WHERE audit."event_id" IN (
SELECT provisioning."audit_event_id"
FROM "QingLong3LocalIdentityProvisionings" AS provisioning
JOIN "QingLong3ApiCredentialPepperBindings" AS binding
ON binding."credential_id" = provisioning."credential_id"
AND binding."credential_version" = provisioning."credential_version"
WHERE binding."pepper_key_id" = ?
UNION
SELECT recovery."issue_audit_event_id"
FROM "QingLong3LocalOwnerCredentialRecoveries" AS recovery
WHERE EXISTS (
SELECT 1 FROM "QingLong3ApiCredentialPepperBindings" AS binding
WHERE binding."pepper_key_id" = ?
AND (
(binding."credential_id" = recovery."previous_credential_id"
AND binding."credential_version" = recovery."previous_credential_version")
OR (binding."credential_id" = recovery."replacement_credential_id"
AND binding."credential_version" = recovery."replacement_credential_version")
)
)
UNION
SELECT recovery."complete_audit_event_id"
FROM "QingLong3LocalOwnerCredentialRecoveries" AS recovery
WHERE recovery."complete_audit_event_id" IS NOT NULL
AND EXISTS (
SELECT 1 FROM "QingLong3ApiCredentialPepperBindings" AS binding
WHERE binding."pepper_key_id" = ?
AND (
(binding."credential_id" = recovery."previous_credential_id"
AND binding."credential_version" = recovery."previous_credential_version")
OR (binding."credential_id" = recovery."replacement_credential_id"
AND binding."credential_version" = recovery."replacement_credential_version")
)
)
)`,
)
.get(pepperKeyId, pepperKeyId, pepperKeyId) as QueryRow | undefined,
);
return Math.max(
safeRetentionTimestamp(
retiredAtMs,
command.retentionPolicy.backupRetentionMs,
),
...(acknowledgement === undefined
? []
: [
safeRetentionTimestamp(
acknowledgement,
command.retentionPolicy.acknowledgementRetentionMs,
),
]),
...(securityAudit === undefined
? []
: [
safeRetentionTimestamp(
securityAudit,
command.retentionPolicy.auditRetentionMs,
),
]),
);
}
function hasRuntimeReferences(
client: DatabaseSync,
pepperKeyId: string,
inspectedAtMs: number,
): boolean {
const current = client
.prepare(
`SELECT 1
FROM "QingLong3ApiCredentialPepperBindings" AS binding
JOIN "QingLong3ApiCredentials" AS credential
ON credential."credential_id" = binding."credential_id"
AND credential."version" = binding."credential_version"
WHERE binding."pepper_key_id" = ?
AND credential."state" = 'active'
AND credential."expires_at_ms" > ?
AND NOT EXISTS (
SELECT 1 FROM "QingLong3ApiCredentials" AS later
WHERE later."credential_id" = credential."credential_id"
AND later."version" > credential."version"
)
LIMIT 1`,
)
.get(pepperKeyId, inspectedAtMs);
if (current) return true;
return !!client
.prepare(
`SELECT 1
FROM "QingLong3LocalOwnerCredentialRecoveries" AS recovery
WHERE recovery."state" <> 'completed'
AND (
EXISTS (
SELECT 1 FROM "QingLong3ApiCredentialPepperBindings" AS previous
WHERE previous."credential_id" = recovery."previous_credential_id"
AND previous."credential_version" = recovery."previous_credential_version"
AND previous."pepper_key_id" = ?
)
OR EXISTS (
SELECT 1 FROM "QingLong3ApiCredentialPepperBindings" AS replacement
WHERE replacement."credential_id" = recovery."replacement_credential_id"
AND replacement."credential_version" = recovery."replacement_credential_version"
AND replacement."pepper_key_id" = ?
)
)
LIMIT 1`,
)
.get(pepperKeyId, pepperKeyId);
}
function samePrepare(
record: Readonly<LocalOwnerPepperMaterialGcRecord>,
command: Readonly<PrepareLocalOwnerPepperMaterialGcCommand>,
): boolean {
return (
record.prepareMutationId === command.mutationId &&
record.prepareRequestId === command.requestId &&
record.pepperKeyId === command.pepperKeyId &&
record.materialDigest === command.expectedMaterialDigest &&
record.backupMaterialDigest === command.expectedBackupMaterialDigest &&
record.activePepperKeyId === command.expectedActivePepperKeyId &&
record.activeGeneration === command.expectedActiveGeneration &&
record.activeMaterialDigest === command.expectedActiveMaterialDigest &&
record.retentionPolicyDigest ===
localOwnerPepperMaterialGcRetentionPolicyDigest(
command.retentionPolicy,
) &&
record.preparedAtMs === command.preparedAtMs
);
}
function sameComplete(
record: Readonly<LocalOwnerPepperMaterialGcRecord>,
command: Readonly<CompleteLocalOwnerPepperMaterialGcCommand>,
): boolean {
return (
record.completeMutationId === command.mutationId &&
record.completeRequestId === command.requestId &&
record.destructionProofDigest === command.destructionProofDigest &&
record.completedAtMs === command.completedAtMs
);
}
function isDomainError(error: unknown): boolean {
return (
error instanceof LocalOwnerPepperMaterialGcMutationConflictError ||
error instanceof LocalOwnerPepperMaterialGcInProgressError ||
error instanceof LocalOwnerPepperMaterialGcReferenceConflictError ||
error instanceof LocalOwnerPepperMaterialGcRetentionPendingError ||
error instanceof LocalOwnerPepperMaterialGcRepositoryUnavailableError
);
}
export class LocalSqliteOwnerPepperMaterialGcRepository
implements LocalOwnerPepperMaterialGcRepository
{
private readonly authority: LocalSqliteOperationAuthority;
constructor(authority: LocalSqliteOperationAuthority | DatabaseSync) {
this.authority =
authority instanceof LocalSqliteOperationAuthority
? authority
: new LocalSqliteOperationAuthority(authority);
}
resolve(
prepareMutationId: string,
): Promise<Readonly<LocalOwnerPepperMaterialGcRecord> | null> {
if (typeof prepareMutationId !== 'string') {
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
return this.authority.enqueue(
async () => {
try {
const row = this.authority.client
.prepare(`${GC_SELECT} WHERE "prepare_mutation_id" = ?`)
.get(prepareMutationId) as QueryRow | undefined;
return row ? gcRecord(row) : null;
} catch (error) {
if (isDomainError(error)) throw error;
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
},
() => new LocalOwnerPepperMaterialGcRepositoryUnavailableError(),
);
}
prepare(
candidate: PrepareLocalOwnerPepperMaterialGcCommand,
): Promise<LocalOwnerPepperMaterialGcResult> {
const command =
normalizePrepareLocalOwnerPepperMaterialGcCommand(candidate);
return this.authority.enqueue(
async () => {
const client = this.authority.client;
try {
client.exec('BEGIN IMMEDIATE');
const replay = client
.prepare(`${GC_SELECT} WHERE "prepare_mutation_id" = ?`)
.get(command.mutationId) as QueryRow | undefined;
if (replay) {
const record = gcRecord(replay);
if (!samePrepare(record, command)) {
throw new LocalOwnerPepperMaterialGcMutationConflictError();
}
client.exec('COMMIT');
return Object.freeze({ status: 'existing' as const, record });
}
if (
client
.prepare(`${GC_SELECT} WHERE "state" = 'prepared' LIMIT 1`)
.get()
) {
throw new LocalOwnerPepperMaterialGcInProgressError();
}
const key = client
.prepare(
`SELECT "material_digest", "backup_digest", "retired_at_ms"
FROM "QingLong3LocalOwnerPepperKeys"
WHERE "pepper_key_id" = ? AND "state" = 'retired'`,
)
.get(command.pepperKeyId) as QueryRow | undefined;
if (
!key ||
text(key, 'material_digest') !== command.expectedMaterialDigest ||
text(key, 'backup_digest') !== command.expectedBackupMaterialDigest
) {
throw new LocalOwnerPepperMaterialGcMutationConflictError();
}
const active = client
.prepare(
`SELECT activation."generation", key."pepper_key_id",
key."material_digest"
FROM "QingLong3LocalOwnerPepperActivations" AS activation
JOIN "QingLong3LocalOwnerPepperKeys" AS key
ON key."pepper_key_id" = activation."active_pepper_key_id"
WHERE key."state" = 'active'
ORDER BY activation."generation" DESC
LIMIT 1`,
)
.get() as QueryRow | undefined;
if (
!active ||
integer(active, 'generation') !==
command.expectedActiveGeneration ||
text(active, 'pepper_key_id') !==
command.expectedActivePepperKeyId ||
text(active, 'material_digest') !==
command.expectedActiveMaterialDigest
) {
throw new LocalOwnerPepperMaterialGcMutationConflictError();
}
if (
hasRuntimeReferences(
client,
command.pepperKeyId,
command.preparedAtMs,
)
) {
throw new LocalOwnerPepperMaterialGcReferenceConflictError();
}
const eligibleAtMs = retentionEligibleAt(
client,
command.pepperKeyId,
integer(key, 'retired_at_ms'),
command,
);
if (command.preparedAtMs < eligibleAtMs) {
throw new LocalOwnerPepperMaterialGcRetentionPendingError(
eligibleAtMs,
);
}
const policyDigest = localOwnerPepperMaterialGcRetentionPolicyDigest(
command.retentionPolicy,
);
insertAudit(client, command.audit);
client
.prepare(
`INSERT INTO "QingLong3LocalOwnerPepperMaterialGc" (
prepare_mutation_id, prepare_request_id, pepper_key_id,
material_digest, backup_material_digest,
active_pepper_key_id, active_generation,
active_material_digest, retention_policy_version,
acknowledgement_retention_ms, audit_retention_ms,
backup_retention_ms, retention_policy_digest,
references_inspected_at_ms, retention_eligible_at_ms,
prepared_at_ms, prepare_audit_event_id, state
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, 'prepared')`,
)
.run(
command.mutationId,
command.requestId,
command.pepperKeyId,
command.expectedMaterialDigest,
command.expectedBackupMaterialDigest,
command.expectedActivePepperKeyId,
command.expectedActiveGeneration,
command.expectedActiveMaterialDigest,
command.retentionPolicy.acknowledgementRetentionMs,
command.retentionPolicy.auditRetentionMs,
command.retentionPolicy.backupRetentionMs,
policyDigest,
command.preparedAtMs,
eligibleAtMs,
command.preparedAtMs,
command.audit.eventId,
);
const inserted = client
.prepare(`${GC_SELECT} WHERE "prepare_mutation_id" = ?`)
.get(command.mutationId) as QueryRow | undefined;
if (!inserted) {
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
const record = gcRecord(inserted);
client.exec('COMMIT');
return Object.freeze({ status: 'inserted' as const, record });
} catch (error) {
if (client.isTransaction) client.exec('ROLLBACK');
if (isDomainError(error)) throw error;
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
},
() => new LocalOwnerPepperMaterialGcRepositoryUnavailableError(),
);
}
complete(
candidate: CompleteLocalOwnerPepperMaterialGcCommand,
): Promise<LocalOwnerPepperMaterialGcResult> {
const command =
normalizeCompleteLocalOwnerPepperMaterialGcCommand(candidate);
return this.authority.enqueue(
async () => {
const client = this.authority.client;
try {
client.exec('BEGIN IMMEDIATE');
const row = client
.prepare(`${GC_SELECT} WHERE "prepare_mutation_id" = ?`)
.get(command.prepareMutationId) as QueryRow | undefined;
if (!row) {
throw new LocalOwnerPepperMaterialGcMutationConflictError();
}
const existing = gcRecord(row);
if (existing.state === 'completed') {
if (!sameComplete(existing, command)) {
throw new LocalOwnerPepperMaterialGcMutationConflictError();
}
client.exec('COMMIT');
return Object.freeze({
status: 'existing' as const,
record: existing,
});
}
if (command.completedAtMs < existing.preparedAtMs) {
throw new LocalOwnerPepperMaterialGcMutationConflictError();
}
insertAudit(client, command.audit);
const result = client
.prepare(
`UPDATE "QingLong3LocalOwnerPepperMaterialGc"
SET "state" = 'completed',
"complete_mutation_id" = ?,
"complete_request_id" = ?,
"destruction_proof_digest" = ?,
"completed_at_ms" = ?,
"complete_audit_event_id" = ?
WHERE "prepare_mutation_id" = ? AND "state" = 'prepared'`,
)
.run(
command.mutationId,
command.requestId,
command.destructionProofDigest,
command.completedAtMs,
command.audit.eventId,
command.prepareMutationId,
);
if (result.changes !== 1) {
throw new LocalOwnerPepperMaterialGcMutationConflictError();
}
const completed = client
.prepare(`${GC_SELECT} WHERE "prepare_mutation_id" = ?`)
.get(command.prepareMutationId) as QueryRow | undefined;
if (!completed) {
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
const record = gcRecord(completed);
client.exec('COMMIT');
return Object.freeze({ status: 'inserted' as const, record });
} catch (error) {
if (client.isTransaction) client.exec('ROLLBACK');
if (isDomainError(error)) throw error;
throw new LocalOwnerPepperMaterialGcRepositoryUnavailableError();
}
},
() => new LocalOwnerPepperMaterialGcRepositoryUnavailableError(),
);
}
}
@@ -0,0 +1,566 @@
import {
LocalOwnerPepperCatalogFullError,
LocalOwnerPepperGenerationConflictError,
LocalOwnerPepperKeyNotActivatableError,
LocalOwnerPepperMutationConflictError,
LocalOwnerPepperRepositoryUnavailableError,
MAX_LOCAL_OWNER_PEPPER_KEYS,
normalizeActivateLocalOwnerPepperKeyCommand,
normalizeRegisterLocalOwnerPepperKeyCommand,
type ActivateLocalOwnerPepperKeyCommand,
type ActivateLocalOwnerPepperKeyResult,
type LocalOwnerPepperActivationRecord,
type LocalOwnerPepperKeyRecord,
type LocalOwnerPepperKeyState,
type LocalOwnerPepperReferenceRepository,
type LocalOwnerPepperReferenceSummary,
type RegisterLocalOwnerPepperKeyCommand,
type RegisterLocalOwnerPepperKeyResult,
} from '@qinglong/runtime-core/local-owner-pepper';
import { assertApiCredentialPepperKeyId } from '@qinglong/runtime-core/api-credential';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
interface KeyRow {
pepper_key_id: unknown;
material_digest: unknown;
backup_digest: unknown;
state: unknown;
version: unknown;
register_mutation_id: unknown;
activate_mutation_id: unknown;
retire_mutation_id: unknown;
registered_at_ms: unknown;
activated_at_ms: unknown;
retired_at_ms: unknown;
}
interface ActivationRow {
generation: unknown;
mutation_id: unknown;
expected_generation: unknown;
previous_pepper_key_id: unknown;
active_pepper_key_id: unknown;
material_digest: unknown;
backup_digest: unknown;
activated_at_ms: unknown;
}
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
const KEY_STATES = new Set<LocalOwnerPepperKeyState>([
'recovery_required',
'staged',
'active',
'retired',
]);
function requiredString(value: unknown): string {
if (typeof value !== 'string') {
throw new LocalOwnerPepperRepositoryUnavailableError();
}
return value;
}
function optionalString(value: unknown): string | undefined {
if (value === null) return undefined;
return requiredString(value);
}
function integer(value: unknown, minimum: number): number {
if (
typeof value !== 'number' ||
!Number.isSafeInteger(value) ||
value < minimum ||
value > 2_147_483_647
) {
throw new LocalOwnerPepperRepositoryUnavailableError();
}
return value;
}
function optionalTimestamp(value: unknown): number | undefined {
if (value === null) return undefined;
return timestamp(value);
}
function timestamp(value: unknown): number {
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
throw new LocalOwnerPepperRepositoryUnavailableError();
}
return value;
}
function digest(value: unknown): string {
const result = requiredString(value);
if (!DIGEST_PATTERN.test(result)) {
throw new LocalOwnerPepperRepositoryUnavailableError();
}
return result;
}
function keyRecord(row: KeyRow): Readonly<LocalOwnerPepperKeyRecord> {
const pepperKeyId = requiredString(row.pepper_key_id);
const state = requiredString(row.state) as LocalOwnerPepperKeyState;
try {
assertApiCredentialPepperKeyId(pepperKeyId);
} catch {
throw new LocalOwnerPepperRepositoryUnavailableError();
}
if (!KEY_STATES.has(state)) {
throw new LocalOwnerPepperRepositoryUnavailableError();
}
const materialDigest = optionalString(row.material_digest);
const backupDigest = optionalString(row.backup_digest);
if (
(materialDigest !== undefined && !DIGEST_PATTERN.test(materialDigest)) ||
(backupDigest !== undefined && !DIGEST_PATTERN.test(backupDigest)) ||
(materialDigest === undefined) !== (backupDigest === undefined)
) {
throw new LocalOwnerPepperRepositoryUnavailableError();
}
return Object.freeze({
pepperKeyId,
...(materialDigest === undefined ? {} : { materialDigest }),
...(backupDigest === undefined ? {} : { backupDigest }),
state,
version: integer(row.version, 1),
...(optionalString(row.register_mutation_id) === undefined
? {}
: { registerMutationId: requiredString(row.register_mutation_id) }),
...(optionalString(row.activate_mutation_id) === undefined
? {}
: { activateMutationId: requiredString(row.activate_mutation_id) }),
...(optionalString(row.retire_mutation_id) === undefined
? {}
: { retireMutationId: requiredString(row.retire_mutation_id) }),
registeredAtMs: timestamp(row.registered_at_ms),
...(optionalTimestamp(row.activated_at_ms) === undefined
? {}
: { activatedAtMs: timestamp(row.activated_at_ms) }),
...(optionalTimestamp(row.retired_at_ms) === undefined
? {}
: { retiredAtMs: timestamp(row.retired_at_ms) }),
});
}
function activationRecord(
row: ActivationRow,
): Readonly<LocalOwnerPepperActivationRecord> {
const activePepperKeyId = requiredString(row.active_pepper_key_id);
const previousPepperKeyId = optionalString(row.previous_pepper_key_id);
try {
assertApiCredentialPepperKeyId(activePepperKeyId);
if (previousPepperKeyId !== undefined) {
assertApiCredentialPepperKeyId(previousPepperKeyId);
}
} catch {
throw new LocalOwnerPepperRepositoryUnavailableError();
}
return Object.freeze({
generation: integer(row.generation, 1),
mutationId: requiredString(row.mutation_id),
expectedGeneration: integer(row.expected_generation, 0),
...(previousPepperKeyId === undefined ? {} : { previousPepperKeyId }),
activePepperKeyId,
materialDigest: digest(row.material_digest),
backupDigest: digest(row.backup_digest),
activatedAtMs: timestamp(row.activated_at_ms),
});
}
function sameRegistration(
key: LocalOwnerPepperKeyRecord,
command: RegisterLocalOwnerPepperKeyCommand,
): boolean {
return (
key.pepperKeyId === command.pepperKeyId &&
key.materialDigest === command.materialDigest &&
key.backupDigest === command.backupDigest &&
key.registerMutationId === command.mutationId &&
key.registeredAtMs === command.registeredAtMs
);
}
function sameActivation(
activation: LocalOwnerPepperActivationRecord,
command: ActivateLocalOwnerPepperKeyCommand,
): boolean {
return (
activation.mutationId === command.mutationId &&
activation.activePepperKeyId === command.pepperKeyId &&
activation.expectedGeneration === command.expectedGeneration &&
activation.previousPepperKeyId === command.expectedActivePepperKeyId &&
activation.activatedAtMs === command.activatedAtMs
);
}
const KEY_SELECT = `
SELECT pepper_key_id, material_digest, backup_digest, state, version,
register_mutation_id, activate_mutation_id, retire_mutation_id,
registered_at_ms, activated_at_ms, retired_at_ms
FROM "QingLong3LocalOwnerPepperKeys"`;
const ACTIVATION_SELECT = `
SELECT generation, mutation_id, expected_generation,
previous_pepper_key_id, active_pepper_key_id,
material_digest, backup_digest, activated_at_ms
FROM "QingLong3LocalOwnerPepperActivations"`;
export class LocalSqliteOwnerPepperRepository
implements LocalOwnerPepperReferenceRepository
{
private readonly authority: LocalSqliteOperationAuthority;
constructor(authority: LocalSqliteOperationAuthority) {
this.authority = authority;
}
resolveKey(
pepperKeyId: string,
): Promise<Readonly<LocalOwnerPepperKeyRecord> | null> {
try {
assertApiCredentialPepperKeyId(pepperKeyId);
} catch {
throw new LocalOwnerPepperRepositoryUnavailableError();
}
return this.authority.enqueue(
async () => {
try {
const row = this.authority.client
.prepare(`${KEY_SELECT} WHERE pepper_key_id = ?`)
.get(pepperKeyId) as KeyRow | undefined;
return row ? keyRecord(row) : null;
} catch (error) {
if (error instanceof LocalOwnerPepperRepositoryUnavailableError) {
throw error;
}
throw new LocalOwnerPepperRepositoryUnavailableError();
}
},
() => new LocalOwnerPepperRepositoryUnavailableError(),
);
}
resolveActive(): Promise<Readonly<LocalOwnerPepperActivationRecord> | null> {
return this.authority.enqueue(
async () => {
try {
const row = this.authority.client
.prepare(`${ACTIVATION_SELECT} ORDER BY generation DESC LIMIT 1`)
.get() as ActivationRow | undefined;
return row ? activationRecord(row) : null;
} catch (error) {
if (error instanceof LocalOwnerPepperRepositoryUnavailableError) {
throw error;
}
throw new LocalOwnerPepperRepositoryUnavailableError();
}
},
() => new LocalOwnerPepperRepositoryUnavailableError(),
);
}
inspectReferences(
pepperKeyId: string,
inspectedAtMs: number,
): Promise<Readonly<LocalOwnerPepperReferenceSummary>> {
try {
assertApiCredentialPepperKeyId(pepperKeyId);
} catch {
throw new LocalOwnerPepperRepositoryUnavailableError();
}
if (!Number.isSafeInteger(inspectedAtMs) || inspectedAtMs < 0) {
throw new LocalOwnerPepperRepositoryUnavailableError();
}
return this.authority.enqueue(
async () => {
try {
const keyRow = this.authority.client
.prepare(`${KEY_SELECT} WHERE pepper_key_id = ?`)
.get(pepperKeyId) as KeyRow | undefined;
if (!keyRow) throw new LocalOwnerPepperRepositoryUnavailableError();
const key = keyRecord(keyRow);
const historical = this.authority.client
.prepare(
`SELECT COUNT(*) AS count
FROM "QingLong3ApiCredentialPepperBindings"
WHERE "pepper_key_id" = ?`,
)
.get(pepperKeyId) as { count?: unknown } | undefined;
const current = this.authority.client
.prepare(
`SELECT COUNT(*) AS count
FROM "QingLong3ApiCredentialPepperBindings" AS binding
JOIN "QingLong3ApiCredentials" AS credential
ON credential."credential_id" = binding."credential_id"
AND credential."version" = binding."credential_version"
WHERE binding."pepper_key_id" = ?
AND credential."state" = 'active'
AND credential."expires_at_ms" > ?
AND NOT EXISTS (
SELECT 1 FROM "QingLong3ApiCredentials" AS later
WHERE later."credential_id" = credential."credential_id"
AND later."version" > credential."version"
)`,
)
.get(pepperKeyId, inspectedAtMs) as { count?: unknown } | undefined;
const inFlight = this.authority.client
.prepare(
`SELECT COUNT(*) AS count
FROM "QingLong3LocalOwnerCredentialRecoveries" AS recovery
WHERE recovery."state" <> 'completed'
AND (
EXISTS (
SELECT 1
FROM "QingLong3ApiCredentialPepperBindings" AS previous
WHERE previous."credential_id" = recovery."previous_credential_id"
AND previous."credential_version" = recovery."previous_credential_version"
AND previous."pepper_key_id" = ?
)
OR EXISTS (
SELECT 1
FROM "QingLong3ApiCredentialPepperBindings" AS replacement
WHERE replacement."credential_id" = recovery."replacement_credential_id"
AND replacement."credential_version" = recovery."replacement_credential_version"
AND replacement."pepper_key_id" = ?
)
)`,
)
.get(pepperKeyId, pepperKeyId) as { count?: unknown } | undefined;
const currentCredentialReferences = integer(current?.count, 0);
const inFlightRecoveryReferences = integer(inFlight?.count, 0);
return Object.freeze({
pepperKeyId,
inspectedAtMs,
currentCredentialReferences,
inFlightRecoveryReferences,
historicalCredentialReferences: integer(historical?.count, 0),
runtimeReferencesClear:
key.state === 'retired' &&
currentCredentialReferences === 0 &&
inFlightRecoveryReferences === 0,
});
} catch (error) {
if (error instanceof LocalOwnerPepperRepositoryUnavailableError) {
throw error;
}
throw new LocalOwnerPepperRepositoryUnavailableError();
}
},
() => new LocalOwnerPepperRepositoryUnavailableError(),
);
}
register(
input: RegisterLocalOwnerPepperKeyCommand,
): Promise<RegisterLocalOwnerPepperKeyResult> {
const command = normalizeRegisterLocalOwnerPepperKeyCommand(input);
return this.authority.enqueue(
async () => {
const client = this.authority.client;
client.exec('BEGIN IMMEDIATE');
try {
const replay = client
.prepare(`${KEY_SELECT} WHERE register_mutation_id = ?`)
.get(command.mutationId) as KeyRow | undefined;
if (replay) {
const key = keyRecord(replay);
if (!sameRegistration(key, command)) {
throw new LocalOwnerPepperMutationConflictError();
}
client.exec('COMMIT');
return Object.freeze({ status: 'existing' as const, key });
}
const existingRow = client
.prepare(`${KEY_SELECT} WHERE pepper_key_id = ?`)
.get(command.pepperKeyId) as KeyRow | undefined;
if (existingRow) {
const existing = keyRecord(existingRow);
if (existing.state !== 'recovery_required') {
throw new LocalOwnerPepperMutationConflictError();
}
const changed = client
.prepare(
`UPDATE "QingLong3LocalOwnerPepperKeys"
SET material_digest = ?, backup_digest = ?, state = 'staged',
version = version + 1, register_mutation_id = ?,
registered_at_ms = ?
WHERE pepper_key_id = ? AND version = ?
AND state = 'recovery_required'`,
)
.run(
command.materialDigest,
command.backupDigest,
command.mutationId,
command.registeredAtMs,
command.pepperKeyId,
existing.version,
);
if (changed.changes !== 1) {
throw new LocalOwnerPepperMutationConflictError();
}
} else {
const count = client
.prepare(
`SELECT COUNT(*) AS count FROM "QingLong3LocalOwnerPepperKeys"`,
)
.get() as { count?: unknown } | undefined;
if (integer(count?.count, 0) >= MAX_LOCAL_OWNER_PEPPER_KEYS) {
throw new LocalOwnerPepperCatalogFullError();
}
client
.prepare(
`INSERT INTO "QingLong3LocalOwnerPepperKeys" (
pepper_key_id, material_digest, backup_digest, state,
version, register_mutation_id, registered_at_ms
) VALUES (?, ?, ?, 'staged', 1, ?, ?)`,
)
.run(
command.pepperKeyId,
command.materialDigest,
command.backupDigest,
command.mutationId,
command.registeredAtMs,
);
}
const key = keyRecord(
client
.prepare(`${KEY_SELECT} WHERE pepper_key_id = ?`)
.get(command.pepperKeyId) as unknown as KeyRow,
);
client.exec('COMMIT');
return Object.freeze({ status: 'inserted' as const, key });
} catch (error) {
if (client.isTransaction) client.exec('ROLLBACK');
if (
error instanceof LocalOwnerPepperMutationConflictError ||
error instanceof LocalOwnerPepperCatalogFullError ||
error instanceof LocalOwnerPepperRepositoryUnavailableError
) {
throw error;
}
throw new LocalOwnerPepperRepositoryUnavailableError();
}
},
() => new LocalOwnerPepperRepositoryUnavailableError(),
);
}
activate(
input: ActivateLocalOwnerPepperKeyCommand,
): Promise<ActivateLocalOwnerPepperKeyResult> {
const command = normalizeActivateLocalOwnerPepperKeyCommand(input);
return this.authority.enqueue(
async () => {
const client = this.authority.client;
client.exec('BEGIN IMMEDIATE');
try {
const replay = client
.prepare(`${ACTIVATION_SELECT} WHERE mutation_id = ?`)
.get(command.mutationId) as ActivationRow | undefined;
if (replay) {
const activation = activationRecord(replay);
if (!sameActivation(activation, command)) {
throw new LocalOwnerPepperMutationConflictError();
}
client.exec('COMMIT');
return Object.freeze({ status: 'existing' as const, activation });
}
const currentRow = client
.prepare(`${ACTIVATION_SELECT} ORDER BY generation DESC LIMIT 1`)
.get() as ActivationRow | undefined;
const current = currentRow ? activationRecord(currentRow) : null;
const generation = current?.generation ?? 0;
if (
generation !== command.expectedGeneration ||
current?.activePepperKeyId !== command.expectedActivePepperKeyId
) {
throw new LocalOwnerPepperGenerationConflictError();
}
const targetRow = client
.prepare(`${KEY_SELECT} WHERE pepper_key_id = ?`)
.get(command.pepperKeyId) as KeyRow | undefined;
const target = targetRow ? keyRecord(targetRow) : null;
if (
!target ||
target.state !== 'staged' ||
!target.materialDigest ||
!target.backupDigest ||
command.activatedAtMs < target.registeredAtMs
) {
throw new LocalOwnerPepperKeyNotActivatableError();
}
if (current) {
const retired = client
.prepare(
`UPDATE "QingLong3LocalOwnerPepperKeys"
SET state = 'retired', version = version + 1,
retire_mutation_id = ?, retired_at_ms = ?
WHERE pepper_key_id = ? AND state = 'active'`,
)
.run(
command.mutationId,
command.activatedAtMs,
current.activePepperKeyId,
);
if (retired.changes !== 1) {
throw new LocalOwnerPepperGenerationConflictError();
}
}
const activated = client
.prepare(
`UPDATE "QingLong3LocalOwnerPepperKeys"
SET state = 'active', version = version + 1,
activate_mutation_id = ?, activated_at_ms = ?
WHERE pepper_key_id = ? AND version = ? AND state = 'staged'`,
)
.run(
command.mutationId,
command.activatedAtMs,
command.pepperKeyId,
target.version,
);
if (activated.changes !== 1) {
throw new LocalOwnerPepperGenerationConflictError();
}
client
.prepare(
`INSERT INTO "QingLong3LocalOwnerPepperActivations" (
generation, mutation_id, expected_generation,
previous_pepper_key_id, active_pepper_key_id,
material_digest, backup_digest, activated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
generation + 1,
command.mutationId,
command.expectedGeneration,
current?.activePepperKeyId ?? null,
command.pepperKeyId,
target.materialDigest,
target.backupDigest,
command.activatedAtMs,
);
const activation = activationRecord(
client
.prepare(`${ACTIVATION_SELECT} WHERE mutation_id = ?`)
.get(command.mutationId) as unknown as ActivationRow,
);
client.exec('COMMIT');
return Object.freeze({ status: 'inserted' as const, activation });
} catch (error) {
if (client.isTransaction) client.exec('ROLLBACK');
if (
error instanceof LocalOwnerPepperMutationConflictError ||
error instanceof LocalOwnerPepperGenerationConflictError ||
error instanceof LocalOwnerPepperKeyNotActivatableError ||
error instanceof LocalOwnerPepperRepositoryUnavailableError
) {
throw error;
}
throw new LocalOwnerPepperRepositoryUnavailableError();
}
},
() => new LocalOwnerPepperRepositoryUnavailableError(),
);
}
}
@@ -0,0 +1,63 @@
import type { LocalOwnerBootstrapRepository } from '@qinglong/runtime-core/local-owner-bootstrap';
import type { LocalOwnerDeliveryAcknowledgementGcRepository } from '@qinglong/runtime-core/local-owner-delivery-acknowledgement-gc';
import {
assertLocalSqliteOptions,
assertLocalSqlitePathBoundary,
LocalSqliteConfigurationError,
openLocalSqliteClient,
type LocalSqliteDatabaseOptions,
type LocalSqliteProfile,
} from '../storage/config';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
import { LocalSqliteOwnerBootstrapRepository } from '../local-owner/ownerBootstrapRepository';
import { LocalSqliteOwnerDeliveryAcknowledgementGcRepository } from '../local-owner/ownerDeliveryAcknowledgementGcRepository';
import {
auditLocalSqliteReadiness,
type LocalSqliteReadinessEvidence,
} from '../readiness/readiness';
/** Short-lived authority for one reviewed acknowledgement compaction. */
export interface LocalSqliteAcknowledgementGcDatabase {
readonly profile: LocalSqliteProfile;
readonly readiness: LocalSqliteReadinessEvidence;
readonly ownerBootstrap: LocalOwnerBootstrapRepository;
readonly acknowledgementGc: LocalOwnerDeliveryAcknowledgementGcRepository;
close(): Promise<void>;
}
export async function openLocalSqliteAcknowledgementGcDatabase(
options: LocalSqliteDatabaseOptions,
): Promise<LocalSqliteAcknowledgementGcDatabase> {
assertLocalSqliteOptions(options);
assertLocalSqlitePathBoundary(options.databasePath, false);
const client = openLocalSqliteClient(options, false);
try {
const readiness = await auditLocalSqliteReadiness(client);
const authority = new LocalSqliteOperationAuthority(client);
const ownerBootstrap = new LocalSqliteOwnerBootstrapRepository(authority);
const acknowledgementGc =
new LocalSqliteOwnerDeliveryAcknowledgementGcRepository(authority);
let closePromise: Promise<void> | undefined;
return Object.freeze({
profile: options.profile,
readiness,
ownerBootstrap,
acknowledgementGc,
close() {
if (closePromise) return closePromise;
closePromise = authority.close();
return closePromise;
},
});
} catch (error) {
if (client.isOpen) client.close();
throw error;
}
}
export {
LocalSqliteConfigurationError,
type LocalSqliteDatabaseOptions,
type LocalSqliteProfile,
};
export type { LocalSqliteReadinessEvidence } from '../readiness/readiness';
@@ -0,0 +1,64 @@
import type { LocalOwnerPepperReferenceRepository } from '@qinglong/runtime-core/local-owner-pepper';
import type { LocalOwnerPepperMaterialGcRepository } from '@qinglong/runtime-core/local-owner-pepper-material-gc';
import {
assertLocalSqliteOptions,
assertLocalSqlitePathBoundary,
LocalSqliteConfigurationError,
openLocalSqliteClient,
type LocalSqliteDatabaseOptions,
type LocalSqliteProfile,
} from '../storage/config';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
import { LocalSqliteOwnerPepperMaterialGcRepository } from '../local-owner/ownerPepperMaterialGcRepository';
import { LocalSqliteOwnerPepperRepository } from '../local-owner/ownerPepperRepository';
import {
auditLocalSqliteReadiness,
type LocalSqliteReadinessEvidence,
} from '../readiness/readiness';
/** Short-lived authority for one reviewed pepper material GC operation. */
export interface LocalSqlitePepperGcDatabase {
readonly profile: LocalSqliteProfile;
readonly readiness: LocalSqliteReadinessEvidence;
readonly ownerPepper: LocalOwnerPepperReferenceRepository;
readonly materialGc: LocalOwnerPepperMaterialGcRepository;
close(): Promise<void>;
}
export async function openLocalSqlitePepperGcDatabase(
options: LocalSqliteDatabaseOptions,
): Promise<LocalSqlitePepperGcDatabase> {
assertLocalSqliteOptions(options);
assertLocalSqlitePathBoundary(options.databasePath, false);
const client = openLocalSqliteClient(options, false);
try {
const readiness = await auditLocalSqliteReadiness(client);
const authority = new LocalSqliteOperationAuthority(client);
const ownerPepper = new LocalSqliteOwnerPepperRepository(authority);
const materialGc = new LocalSqliteOwnerPepperMaterialGcRepository(
authority,
);
let closePromise: Promise<void> | undefined;
return Object.freeze({
profile: options.profile,
readiness,
ownerPepper,
materialGc,
close() {
if (closePromise) return closePromise;
closePromise = authority.close();
return closePromise;
},
});
} catch (error) {
if (client.isOpen) client.close();
throw error;
}
}
export {
LocalSqliteConfigurationError,
type LocalSqliteDatabaseOptions,
type LocalSqliteProfile,
};
export type { LocalSqliteReadinessEvidence } from '../readiness/readiness';
@@ -0,0 +1,256 @@
import {
runMigrationStream,
type MigrationStreamDefinition,
} from '@qinglong/runtime-core/migration-stream';
import type { DatabaseSync } from 'node:sqlite';
import fs from 'node:fs';
import {
assertLocalSqliteOptions,
assertLocalSqlitePathBoundary,
openLocalSqliteClient,
type LocalSqliteDatabaseOptions,
} from '../storage/config';
import { local0001RunCoreMigration } from '../migrations/0001-run-core';
import { local0002CapabilityMigration } from '../migrations/0002-capability';
import { local0003CompletionReceiptJournalMigration } from '../migrations/0003-completion-receipt-journal';
import { local0004CapabilityV2Migration } from '../migrations/0004-capability-v2';
import { local0005LocalDispatchPlanMigration } from '../migrations/0005-local-dispatch-plan';
import { local0006CapabilityV3Migration } from '../migrations/0006-capability-v3';
import { local0007LocalSecretEnvelopesMigration } from '../migrations/0007-local-secret-envelopes';
import { local0008CapabilityV4Migration } from '../migrations/0008-capability-v4';
import { local0009LocalProjectPolicyAuditMigration } from '../migrations/0009-local-project-policy-audit';
import { local0010CapabilityV5Migration } from '../migrations/0010-capability-v5';
import { local0011LocalIdentityCredentialMigration } from '../migrations/0011-local-identity-credential';
import { local0012CapabilityV6Migration } from '../migrations/0012-capability-v6';
import { local0013LocalOwnerBootstrapMigration } from '../migrations/0013-local-owner-bootstrap';
import { local0014CapabilityV7Migration } from '../migrations/0014-capability-v7';
import { local0015LocalOwnerDeliveryAcknowledgementsMigration } from '../migrations/0015-local-owner-delivery-acknowledgements';
import { local0016CapabilityV8Migration } from '../migrations/0016-capability-v8';
import { local0017ApiCredentialPepperBindingsMigration } from '../migrations/0017-api-credential-pepper-bindings';
import { local0018CapabilityV9Migration } from '../migrations/0018-capability-v9';
import { local0019LocalOwnerPepperCatalogMigration } from '../migrations/0019-local-owner-pepper-catalog';
import { local0020CapabilityV10Migration } from '../migrations/0020-capability-v10';
import { local0021LocalOwnerCredentialRecoveryMigration } from '../migrations/0021-local-owner-credential-recovery';
import { local0022CapabilityV11Migration } from '../migrations/0022-capability-v11';
import { local0023LocalOwnerPepperMaterialGcMigration } from '../migrations/0023-local-owner-pepper-material-gc';
import { local0024CapabilityV12Migration } from '../migrations/0024-capability-v12';
import { local0025LocalOwnerDeliveryAcknowledgementGcMigration } from '../migrations/0025-local-owner-delivery-acknowledgement-gc';
import { local0026CapabilityV13Migration } from '../migrations/0026-capability-v13';
import { local0027TaskDefinitionsMigration } from '../migrations/0027-task-definitions';
import { local0028CapabilityV14Migration } from '../migrations/0028-capability-v14';
import { local0029LocalExecutionRevisionDigestMigration } from '../migrations/0029-local-execution-revision-digest';
import { local0030CapabilityV15Migration } from '../migrations/0030-capability-v15';
import { local0031TriggerDefinitionsMigration } from '../migrations/0031-trigger-definitions';
import { local0032CapabilityV16Migration } from '../migrations/0032-capability-v16';
import { local0033LegacyAdoptionLedgerMigration } from '../migrations/0033-legacy-adoption-ledger';
import { local0034CapabilityV17Migration } from '../migrations/0034-capability-v17';
import { local0035LocalSchedulerMigration } from '../migrations/0035-local-scheduler';
import { local0036CapabilityV18Migration } from '../migrations/0036-capability-v18';
import { local0037PluginPackageInstallsMigration } from '../migrations/0037-plugin-package-installs';
import { local0038CapabilityV19Migration } from '../migrations/0038-capability-v19';
import { local0039ApprovedActionsMigration } from '../migrations/0039-approved-actions';
import { local0040CapabilityV20Migration } from '../migrations/0040-capability-v20';
import { local0041PluginPackageAdmissionReceiptsMigration } from '../migrations/0041-plugin-package-admission-receipts';
import { local0042CapabilityV21Migration } from '../migrations/0042-capability-v21';
import { local0043ApprovedActionExecutionsAndPackageProposalsMigration } from '../migrations/0043-approved-action-executions-and-package-proposals';
import { local0044CapabilityV22Migration } from '../migrations/0044-capability-v22';
import { local0045PluginPackageMaterializedRevisionsMigration } from '../migrations/0045-plugin-package-materialized-revisions';
import { local0046CapabilityV23Migration } from '../migrations/0046-capability-v23';
import { local0047PluginPackageTaskReconciliationsMigration } from '../migrations/0047-plugin-package-task-reconciliations';
import { local0048CapabilityV24Migration } from '../migrations/0048-capability-v24';
import { local0049ProjectToolDefinitionSnapshotsMigration } from '../migrations/0049-project-tool-definition-snapshots';
import { local0050CapabilityV25Migration } from '../migrations/0050-capability-v25';
import { local0051StepRunsMigration } from '../migrations/0051-step-runs';
import { local0052CapabilityV26Migration } from '../migrations/0052-capability-v26';
import { local0053ToolExecutionEvidenceMigration } from '../migrations/0053-tool-execution-evidence';
import { local0054CapabilityV27Migration } from '../migrations/0054-capability-v27';
import { local0055ToolExecutionStartBarriersMigration } from '../migrations/0055-tool-execution-start-barriers';
import { local0056CapabilityV28Migration } from '../migrations/0056-capability-v28';
import { local0057ToolInvocationArtifactsMigration } from '../migrations/0057-tool-invocation-artifacts';
import { local0058CapabilityV29Migration } from '../migrations/0058-capability-v29';
import { local0059ToolExecutionArtifactBindingsMigration } from '../migrations/0059-tool-execution-artifact-bindings';
import { local0060CapabilityV30Migration } from '../migrations/0060-capability-v30';
import { local0061ToolExecutionCompletionsMigration } from '../migrations/0061-tool-execution-completions';
import { local0062CapabilityV31Migration } from '../migrations/0062-capability-v31';
import { local0063ToolExecutionFailureCompletionsMigration } from '../migrations/0063-tool-execution-failure-completions';
import { local0064CapabilityV32Migration } from '../migrations/0064-capability-v32';
import { local0065ToolResultKeyCatalogMigration } from '../migrations/0065-tool-result-key-catalog';
import { local0066CapabilityV33Migration } from '../migrations/0066-capability-v33';
import { local0067ToolResultRekeyOverlaysMigration } from '../migrations/0067-tool-result-rekey-overlays';
import { local0068CapabilityV34Migration } from '../migrations/0068-capability-v34';
import { local0069PluginPackageQuarantineMigration } from '../migrations/0069-plugin-package-quarantine';
import { local0070CapabilityV35Migration } from '../migrations/0070-capability-v35';
import { local0071LocalIdentityCredentialAdministrationMigration } from '../migrations/0071-local-identity-credential-administration';
import { local0072CapabilityV36Migration } from '../migrations/0072-capability-v36';
import { local0073LocalProjectAdministrationMigration } from '../migrations/0073-local-project-administration';
import { local0074CapabilityV37Migration } from '../migrations/0074-capability-v37';
import { local0075SecurityAuditCompactionsMigration } from '../migrations/0075-security-audit-compactions';
import { local0076CapabilityV38Migration } from '../migrations/0076-capability-v38';
import { local0077PluginPackageLifecycleMigration } from '../migrations/0077-plugin-package-lifecycle';
import { local0078CapabilityV39Migration } from '../migrations/0078-capability-v39';
import { local0079PluginPackageAutomationPublicationsMigration } from '../migrations/0079-plugin-package-automation-publications';
import { local0080CapabilityV40Migration } from '../migrations/0080-capability-v40';
import { local0081PluginPackageWorkflowAdmissionsMigration } from '../migrations/0081-plugin-package-workflow-admissions';
import { local0082CapabilityV41Migration } from '../migrations/0082-capability-v41';
import { local0083PluginPackageWorkflowTaskAttemptAdmissionsMigration } from '../migrations/0083-plugin-package-workflow-task-attempt-admissions';
import { local0084CapabilityV42Migration } from '../migrations/0084-capability-v42';
import { local0085PluginPackageWorkflowRunListIndexMigration } from '../migrations/0085-plugin-package-workflow-run-list-index';
import { local0086CapabilityV43Migration } from '../migrations/0086-capability-v43';
import type { LocalSqliteMigrationContext } from '../migrations/sqlMigration';
import {
LOCAL_SQLITE_MIGRATION_STREAM_ID,
LocalSqliteMigrationStreamStore,
} from './migrationStreamStore';
import { localSqliteMigrationManifest } from './migrationManifest';
import {
auditLocalSqliteReadiness,
type LocalSqliteReadinessEvidence,
} from '../readiness/readiness';
export interface LocalSqliteMigrationResult {
readonly readiness: LocalSqliteReadinessEvidence;
}
export const localSqliteMigrationDefinition: MigrationStreamDefinition<LocalSqliteMigrationContext> =
Object.freeze({
id: LOCAL_SQLITE_MIGRATION_STREAM_ID,
dialect: 'sqlite',
migrationIdScheme: 'sqlite-numbered',
checksumScheme: 'sha256',
migrations: Object.freeze([
local0001RunCoreMigration,
local0002CapabilityMigration,
local0003CompletionReceiptJournalMigration,
local0004CapabilityV2Migration,
local0005LocalDispatchPlanMigration,
local0006CapabilityV3Migration,
local0007LocalSecretEnvelopesMigration,
local0008CapabilityV4Migration,
local0009LocalProjectPolicyAuditMigration,
local0010CapabilityV5Migration,
local0011LocalIdentityCredentialMigration,
local0012CapabilityV6Migration,
local0013LocalOwnerBootstrapMigration,
local0014CapabilityV7Migration,
local0015LocalOwnerDeliveryAcknowledgementsMigration,
local0016CapabilityV8Migration,
local0017ApiCredentialPepperBindingsMigration,
local0018CapabilityV9Migration,
local0019LocalOwnerPepperCatalogMigration,
local0020CapabilityV10Migration,
local0021LocalOwnerCredentialRecoveryMigration,
local0022CapabilityV11Migration,
local0023LocalOwnerPepperMaterialGcMigration,
local0024CapabilityV12Migration,
local0025LocalOwnerDeliveryAcknowledgementGcMigration,
local0026CapabilityV13Migration,
local0027TaskDefinitionsMigration,
local0028CapabilityV14Migration,
local0029LocalExecutionRevisionDigestMigration,
local0030CapabilityV15Migration,
local0031TriggerDefinitionsMigration,
local0032CapabilityV16Migration,
local0033LegacyAdoptionLedgerMigration,
local0034CapabilityV17Migration,
local0035LocalSchedulerMigration,
local0036CapabilityV18Migration,
local0037PluginPackageInstallsMigration,
local0038CapabilityV19Migration,
local0039ApprovedActionsMigration,
local0040CapabilityV20Migration,
local0041PluginPackageAdmissionReceiptsMigration,
local0042CapabilityV21Migration,
local0043ApprovedActionExecutionsAndPackageProposalsMigration,
local0044CapabilityV22Migration,
local0045PluginPackageMaterializedRevisionsMigration,
local0046CapabilityV23Migration,
local0047PluginPackageTaskReconciliationsMigration,
local0048CapabilityV24Migration,
local0049ProjectToolDefinitionSnapshotsMigration,
local0050CapabilityV25Migration,
local0051StepRunsMigration,
local0052CapabilityV26Migration,
local0053ToolExecutionEvidenceMigration,
local0054CapabilityV27Migration,
local0055ToolExecutionStartBarriersMigration,
local0056CapabilityV28Migration,
local0057ToolInvocationArtifactsMigration,
local0058CapabilityV29Migration,
local0059ToolExecutionArtifactBindingsMigration,
local0060CapabilityV30Migration,
local0061ToolExecutionCompletionsMigration,
local0062CapabilityV31Migration,
local0063ToolExecutionFailureCompletionsMigration,
local0064CapabilityV32Migration,
local0065ToolResultKeyCatalogMigration,
local0066CapabilityV33Migration,
local0067ToolResultRekeyOverlaysMigration,
local0068CapabilityV34Migration,
local0069PluginPackageQuarantineMigration,
local0070CapabilityV35Migration,
local0071LocalIdentityCredentialAdministrationMigration,
local0072CapabilityV36Migration,
local0073LocalProjectAdministrationMigration,
local0074CapabilityV37Migration,
local0075SecurityAuditCompactionsMigration,
local0076CapabilityV38Migration,
local0077PluginPackageLifecycleMigration,
local0078CapabilityV39Migration,
local0079PluginPackageAutomationPublicationsMigration,
local0080CapabilityV40Migration,
local0081PluginPackageWorkflowAdmissionsMigration,
local0082CapabilityV41Migration,
local0083PluginPackageWorkflowTaskAttemptAdmissionsMigration,
local0084CapabilityV42Migration,
local0085PluginPackageWorkflowRunListIndexMigration,
local0086CapabilityV43Migration,
]),
});
function assertReviewedManifestMatchesDefinition(): void {
const generated = localSqliteMigrationDefinition.migrations.map(
({ id, checksum }) => ({ id, checksum }),
);
if (
localSqliteMigrationManifest.id !== localSqliteMigrationDefinition.id ||
localSqliteMigrationManifest.dialect !==
localSqliteMigrationDefinition.dialect ||
JSON.stringify(localSqliteMigrationManifest.migrations) !==
JSON.stringify(generated)
) {
throw new Error(
'Local SQLite executable migrations do not match the reviewed runtime manifest',
);
}
}
assertReviewedManifestMatchesDefinition();
export async function migrateLocalSqliteDatabase(
client: DatabaseSync,
): Promise<void> {
await runMigrationStream({
stream: localSqliteMigrationDefinition,
store: new LocalSqliteMigrationStreamStore(client),
});
}
export { localSqliteMigrationManifest };
/** Short-lived migration authority; long-lived Profile hosts must not import it. */
export async function migrateLocalSqlitePath(
options: LocalSqliteDatabaseOptions,
): Promise<LocalSqliteMigrationResult> {
assertLocalSqliteOptions(options);
assertLocalSqlitePathBoundary(options.databasePath, true);
const client = openLocalSqliteClient(options, false);
try {
await migrateLocalSqliteDatabase(client);
fs.chmodSync(options.databasePath, 0o600);
return Object.freeze({
readiness: await auditLocalSqliteReadiness(client),
});
} finally {
client.close();
}
}
@@ -0,0 +1,446 @@
import type { MigrationStreamManifest } from '@qinglong/runtime-core/migration-stream';
import { LOCAL_SQLITE_MIGRATION_STREAM_ID } from './migrationStreamStore';
/**
* Runtime-safe reviewed migration history. Checksums are frozen audit facts; executable
* SQL lives only behind the /migration entrypoint.
*/
export const localSqliteMigrationManifest: MigrationStreamManifest =
Object.freeze({
id: LOCAL_SQLITE_MIGRATION_STREAM_ID,
dialect: 'sqlite',
migrationIdScheme: 'sqlite-numbered',
checksumScheme: 'sha256',
migrations: Object.freeze([
Object.freeze({
id: '0001-run-core',
checksum:
'39568123409d2c7f0bc719418640552b71e9880bd7f04edf827c6b281111c1ed',
}),
Object.freeze({
id: '0002-capability',
checksum:
'67742152a864e4e3b01dce74b547ff47f1630fe38d955b5f98a9e9c0d1a2f85f',
}),
Object.freeze({
id: '0003-completion-receipt-journal',
checksum:
'f097d92e50be11812c8d501e173319e4142e7ea6b53b91e170f0f5bd46d0b454',
}),
Object.freeze({
id: '0004-capability-v2',
checksum:
'74f26d198e73dbf1ec46ff1a0d5874c7a2ba4bd0bcfc49270b19111c0005fe10',
}),
Object.freeze({
id: '0005-local-dispatch-plan',
checksum:
'20dea02fd01f6577624078d2ceae7f93939faa60fdb1a6bc145dab65f38388e9',
}),
Object.freeze({
id: '0006-capability-v3',
checksum:
'ba2d32b53be6f9a5684178e33bdd3af6b48fb03532832eb359a273bd5be419e5',
}),
Object.freeze({
id: '0007-local-secret-envelopes',
checksum:
'bc24730051cd8306ccb7e5ecbdb7911dd2352f8bcb4cd448a0a4eb3004b68224',
}),
Object.freeze({
id: '0008-capability-v4',
checksum:
'eee83101204917ba2651dbbf962c5f8b6783223deecf298345f39b842afbc5e1',
}),
Object.freeze({
id: '0009-local-project-policy-audit',
checksum:
'2dd3d535aa2cce27d4032ac8d0a9e0afe0e0ac05e0ed8f91328765a8ad9b3175',
}),
Object.freeze({
id: '0010-capability-v5',
checksum:
'43e4d311fa5705588a5b66f3c32f942d220b7d86a23003f7b73ddad4bf6bb12d',
}),
Object.freeze({
id: '0011-local-identity-credential',
checksum:
'9ad007caaf28a2e5310a84c0d08f8314ee8db17f309cfd323b86f763b0f349c9',
}),
Object.freeze({
id: '0012-capability-v6',
checksum:
'c0a3321d546c98f651180802e94e8d819246b5e51449b749d8f0e82710464d0c',
}),
Object.freeze({
id: '0013-local-owner-bootstrap',
checksum:
'83304887fc0a265b71e47b61d74554f63bccf0e0208d85c3e79e4cfd5dcbbe48',
}),
Object.freeze({
id: '0014-capability-v7',
checksum:
'46df0f0fc48a8510adab4b34e0697eb21572267fce46c5764f0b7e35e412d50e',
}),
Object.freeze({
id: '0015-local-owner-delivery-acknowledgements',
checksum:
'e4c5f4dd7f9a5f717054fea9f3eb3b4f0aa3c01ce66559bb55b47ab9928a5dfa',
}),
Object.freeze({
id: '0016-capability-v8',
checksum:
'd5abd73e4422555e13d989e646523d766c07c4d2261026e53097d7e756d7265a',
}),
Object.freeze({
id: '0017-api-credential-pepper-bindings',
checksum:
'32144992e70ea8ea1f45a5e88bfa7b94da6f7943349c0ec974e23e7ad14488e6',
}),
Object.freeze({
id: '0018-capability-v9',
checksum:
'da15b963e45c49c5d145d7c5c9c06ab41921ff054a5573bb5ebb698da6524562',
}),
Object.freeze({
id: '0019-local-owner-pepper-catalog',
checksum:
'b2c7afe88d02eb8e3bc0cceaa590edb252adf578f9d5a1292d5969a50847a89d',
}),
Object.freeze({
id: '0020-capability-v10',
checksum:
'c5caf45785d4efe2885ffe857a59109acdc36ec1b79ca2079b0b6274f68d541b',
}),
Object.freeze({
id: '0021-local-owner-credential-recovery',
checksum:
'7d52b983194d2078e33fef181e1cba115266ac7fb253415a71e222df873af507',
}),
Object.freeze({
id: '0022-capability-v11',
checksum:
'bcb367bc095d97a47f512c002dd12e74314b36fbbbd71862d3441b810f94d006',
}),
Object.freeze({
id: '0023-local-owner-pepper-material-gc',
checksum:
'995bf7b84d62381e1620347d3306349fcaa4c71c2072f23be504eeb93ca3e7d4',
}),
Object.freeze({
id: '0024-capability-v12',
checksum:
'40f4819c3877cdc1626fc780a5ac916f6bb6f8d3d1a1d437f55dc2fd8afe9b0b',
}),
Object.freeze({
id: '0025-local-owner-delivery-acknowledgement-gc',
checksum:
'3982b92013917606c561b6fe7a4fdaae72e4da3eae91befa1a19a44382e39507',
}),
Object.freeze({
id: '0026-capability-v13',
checksum:
'2a606be91f4f216abddbfb6b6918971a7e5cf086df7c620de60fdb40d0849340',
}),
Object.freeze({
id: '0027-task-definitions',
checksum:
'3ae15a3f964dbcbeacb311862ad52dd6489715fe87cfe1a8e8ec9bec59c2a81f',
}),
Object.freeze({
id: '0028-capability-v14',
checksum:
'408fc34ad2aa5439b05ccf7438ee98bcc952cd2c72bcbf81931a0f6598e6039d',
}),
Object.freeze({
id: '0029-local-execution-revision-digest',
checksum:
'273ebd3f10ee0f17b2037b446d67943d2c5d1e819ca61651abdb52de7245ccbf',
}),
Object.freeze({
id: '0030-capability-v15',
checksum:
'c51cbab2a4f4747b14a7ddec4629356a923b840414efb52388a7ebe7e463a369',
}),
Object.freeze({
id: '0031-trigger-definitions',
checksum:
'c3d37ccf8f62ed006f8236157c1917696d7d3ea41db1db14b662b76e2f1203da',
}),
Object.freeze({
id: '0032-capability-v16',
checksum:
'28c927e5787323b4df2b39a755de8f7c629bea46f7bd1bac2502c6de93a4aba7',
}),
Object.freeze({
id: '0033-legacy-adoption-ledger',
checksum:
'73cb0155c1321f15fbef3a22649d97278245fa1d3865391b4d053104f0102f80',
}),
Object.freeze({
id: '0034-capability-v17',
checksum:
'a973fd2471d42844a5eb8f40b9a6ae4ed2aa1507ab8cae1b9de9a783d593f0f2',
}),
Object.freeze({
id: '0035-local-scheduler',
checksum:
'eb312b03ea61f4d3fa4f8751d93ede7bf6b3c2c1893b785e8d83d24eda8f8c5e',
}),
Object.freeze({
id: '0036-capability-v18',
checksum:
'0ea27d9d5be58c03a092f5717b64d6eae8a45fb732461ca684744a220ce1c1d1',
}),
Object.freeze({
id: '0037-plugin-package-installs',
checksum:
'6d40e13043b6dddd7ef16c1562b17fa4faf3fbc832e152976ac6e5334ba26175',
}),
Object.freeze({
id: '0038-capability-v19',
checksum:
'6709d442e985ffc91ea4622c3bc322c838c022745f498f5ea94b8503d286e0c4',
}),
Object.freeze({
id: '0039-approved-actions',
checksum:
'2f0a258aaf99eb83e28af76b95529dd0c76b8f6ea28baa3d3cdbfd353ad6eef9',
}),
Object.freeze({
id: '0040-capability-v20',
checksum:
'c06ce8f021a4e1874e14deb4b824a45273a1e96f5625a487f2e6d940ce364ee2',
}),
Object.freeze({
id: '0041-plugin-package-admission-receipts',
checksum:
'fac42799fad2b80c34d49f922ccd42e5b9f213435767ae7efb865370f6799432',
}),
Object.freeze({
id: '0042-capability-v21',
checksum:
'b4789c615f92299b8dac879c75d377ddddf22e547f069ee8ccc9a16647b21410',
}),
Object.freeze({
id: '0043-approved-action-executions-and-package-proposals',
checksum:
'e0d2d5718e2e58e5cc841bff67d3b533840a727ea0d860850c67c6d4e4edbba9',
}),
Object.freeze({
id: '0044-capability-v22',
checksum:
'9ae0d6d3dde8f4e09abefab67cedd4cfdb9f5ad9725f136e69e1c9480d1bb99b',
}),
Object.freeze({
id: '0045-plugin-package-materialized-revisions',
checksum:
'935b2c1806bdb2c45fafc9813089070088c8f660f2f4b479a3866d790b161f8b',
}),
Object.freeze({
id: '0046-capability-v23',
checksum:
'5d874653fe3971a52c71d3706b89f38aefc03fe00c28fb6ba23f2249413cf8c0',
}),
Object.freeze({
id: '0047-plugin-package-task-reconciliations',
checksum:
'cc4caf2409c513d982d910ff853cbebf281e7999c8e1b2e6d00d50f0e6ebc2f2',
}),
Object.freeze({
id: '0048-capability-v24',
checksum:
'd2257f4b08f01703507c49d7b2bf197930a88eb70d6e07ab13b3627657df0031',
}),
Object.freeze({
id: '0049-project-tool-definition-snapshots',
checksum:
'7b7d02243fb3ab3fce5444dd73dba96a4e16598f082a4c0e786404b860266831',
}),
Object.freeze({
id: '0050-capability-v25',
checksum:
'9d128ecd3bac1a45e7bea1cde1e6f5fee364761de673cb2116b4abc2e8b492af',
}),
Object.freeze({
id: '0051-step-runs',
checksum:
'3a659aff64ad2927a886b0e3f4309139a379f0a91f90ae147fd959d7db461a2a',
}),
Object.freeze({
id: '0052-capability-v26',
checksum:
'613ba0cddf1ed12ab00f4e3bf87e11cd14bac497c6502015496c58d909dfc5fc',
}),
Object.freeze({
id: '0053-tool-execution-evidence',
checksum:
'315cbdba8af417d38435d44317a1ada33a0b7b38a8a4f06ab08ba434a63e6a12',
}),
Object.freeze({
id: '0054-capability-v27',
checksum:
'8b6e33971669562b42ac7bd31689cbeff1247a71eea0b5533ab53cd8b2ec3551',
}),
Object.freeze({
id: '0055-tool-execution-start-barriers',
checksum:
'2209e9dbb301263ea79f7a3a08b61dc81586586c54eabed6c3911a10b81bc628',
}),
Object.freeze({
id: '0056-capability-v28',
checksum:
'58fe4e491d8c9e013fe94297e6a5d35591a524b19af468e9a7c1ae94750296a2',
}),
Object.freeze({
id: '0057-tool-invocation-artifacts',
checksum:
'430aeddb502e14ad750f56987ce7cfa8de8425e410ae859fae4b84b5120633a6',
}),
Object.freeze({
id: '0058-capability-v29',
checksum:
'e5cbd9874d5cc54aa4b99afcf26b3d514c329e00b32f09980d409c8d91fc164e',
}),
Object.freeze({
id: '0059-tool-execution-artifact-bindings',
checksum:
'0298421262b7b4b8accf4f9eba7619735399a6de8a4a11db51561f1fe9204c9d',
}),
Object.freeze({
id: '0060-capability-v30',
checksum:
'e05c92b3f01b2e38b71d18b63ba7d1932b971861d10c5d17b348ee1446b0efd9',
}),
Object.freeze({
id: '0061-tool-execution-completions',
checksum:
'b17111ce2f486a357931fe05d23fd7eccce3140793f1224ab59c20e922c615eb',
}),
Object.freeze({
id: '0062-capability-v31',
checksum:
'4e1aff95a4b573ca8147fbbdded001fb3a7dbbf657469eb08491a8e398cab367',
}),
Object.freeze({
id: '0063-tool-execution-failure-completions',
checksum:
'a6894b10361bbcf98f36e59cd79d8546f51ea45b1aa3798f20bfb54b00ea6712',
}),
Object.freeze({
id: '0064-capability-v32',
checksum:
'076978dabd6042cc04dc8ccb339a9eb788808bb12bcadd255b870f69fe5acc11',
}),
Object.freeze({
id: '0065-tool-result-key-catalog',
checksum:
'f7d241395920688e311dfc195bf051e3211e0ba7b39d5e97e0142df9cbb425d2',
}),
Object.freeze({
id: '0066-capability-v33',
checksum:
'ab5e64b4c4ae1fa23f8bb25cb34a83898d934f87744c4e0e98bdbbba678db669',
}),
Object.freeze({
id: '0067-tool-result-rekey-overlays',
checksum:
'b03d99ab33e53916890a1d2220038f7d3012162579a526e7c04aa54e775e7d89',
}),
Object.freeze({
id: '0068-capability-v34',
checksum:
'17d8a86261f4378f57efd3f134cc83d412a76a49a462380cd9c6976c326b635f',
}),
Object.freeze({
id: '0069-plugin-package-quarantine',
checksum:
'9e6055352f11c3d156551d3d4f5fb043e19193ed24a6517db2a0567e79cf3cf0',
}),
Object.freeze({
id: '0070-capability-v35',
checksum:
'bbca98db050d676174e23906cdeb065bdfa375d54673ed8ee898577c44694c82',
}),
Object.freeze({
id: '0071-local-identity-credential-administration',
checksum:
'94524df88e410a8c7f2b28832766f5dccc3d81e7075112819d51567c88896f77',
}),
Object.freeze({
id: '0072-capability-v36',
checksum:
'339b0d4ab576813aa37ee4b3347db68f415c01016c8fd8e0b293ae9e89ac1ba1',
}),
Object.freeze({
id: '0073-local-project-administration',
checksum:
'd8881e79284d4e63622ae7addc4d272c301290b83ca455ca642eb706c293093f',
}),
Object.freeze({
id: '0074-capability-v37',
checksum:
'3af70476c4a56b12dd208a3bee6cbe7da99788a7965e9691a36608afa88fa9ef',
}),
Object.freeze({
id: '0075-security-audit-compactions',
checksum:
'ccb424944e1392ed9a503d1d365fcaf6d0a4258b411eae0dc21da2f6fdf88b7e',
}),
Object.freeze({
id: '0076-capability-v38',
checksum:
'c73d5de0a6c846c606e2c345800f9fce570fced43cbe946e558f5ccc58a235b3',
}),
Object.freeze({
id: '0077-plugin-package-lifecycle',
checksum:
'9b308b31a53897e726f3e23a7ac3158754885d1d3aa487be5a7ec6a631fe1f71',
}),
Object.freeze({
id: '0078-capability-v39',
checksum:
'b9e26a65125bf43a40aee0a47eac65cf232411b084af2672ae06ef834dc17252',
}),
Object.freeze({
id: '0079-plugin-package-automation-publications',
checksum:
'6e4ede9a5b6c1480bba99bab3d257e5138cd8b3a7841d8fd5d6252dd264e78fd',
}),
Object.freeze({
id: '0080-capability-v40',
checksum:
'3cfd4dd22b3c0f399be3b3e6b1f85f65065c53a6b97361faa8c9ce5e0ec35702',
}),
Object.freeze({
id: '0081-plugin-package-workflow-admissions',
checksum:
'129e98a34ff88669218f31847b735a26afed9401a01d8384d71d04613adc51a3',
}),
Object.freeze({
id: '0082-capability-v41',
checksum:
'2ca9170340ef13d608e2f417df0a8a7df5252bf7a0dba81b1edc149a311e503c',
}),
Object.freeze({
id: '0083-plugin-package-workflow-task-attempt-admissions',
checksum:
'de30414a9667309960e99c6b232904e1fee769d6d319340ec8f40a36daeb213b',
}),
Object.freeze({
id: '0084-capability-v42',
checksum:
'0755449ac02f97e2b4074bd768e84d30fa62c9274d6f87370cff1b628fb0825e',
}),
Object.freeze({
id: '0085-plugin-package-workflow-run-list-index',
checksum:
'7f595bb1d0daf38a0f50594859ee475e5a377656b9ca086583ad1089911e1247',
}),
Object.freeze({
id: '0086-capability-v43',
checksum:
'd7affd7b3d1f3719dabc7abc7d5e8a2880fc4dc455b5103585befe9b51f705f9',
}),
]),
});
@@ -0,0 +1,125 @@
import type {
MigrationStreamRecord,
MigrationStreamStore,
MigrationStreamTransaction,
} from '@qinglong/runtime-core/migration-stream';
import type { DatabaseSync } from 'node:sqlite';
import type { LocalSqliteMigrationContext } from '../migrations/sqlMigration';
export const LOCAL_SQLITE_MIGRATION_STREAM_ID = 'ql3-local-sqlite';
interface HistoryRow {
migration_id: unknown;
stream_id: unknown;
dialect: unknown;
checksum: unknown;
applied_at_ms: unknown;
}
function record(row: HistoryRow): MigrationStreamRecord {
if (
typeof row.migration_id !== 'string' ||
typeof row.stream_id !== 'string' ||
row.dialect !== 'sqlite' ||
typeof row.checksum !== 'string' ||
typeof row.applied_at_ms !== 'number' ||
!Number.isSafeInteger(row.applied_at_ms) ||
row.applied_at_ms < 0
) {
throw new TypeError('Local SQLite migration history row is invalid');
}
return {
migrationId: row.migration_id,
streamId: row.stream_id,
dialect: row.dialect,
checksum: row.checksum,
appliedAtMs: row.applied_at_ms,
};
}
export class LocalSqliteMigrationStreamStore
implements MigrationStreamStore<LocalSqliteMigrationContext>
{
constructor(private readonly client: DatabaseSync) {}
async ensureHistory(): Promise<void> {
this.client.exec(`
CREATE TABLE IF NOT EXISTS "QingLong3SchemaMigrations" (
migration_id TEXT PRIMARY KEY,
stream_id TEXT NOT NULL,
dialect TEXT NOT NULL
CONSTRAINT ql3_local_migrations_dialect_check CHECK (dialect = 'sqlite'),
checksum TEXT NOT NULL
CONSTRAINT ql3_local_migrations_checksum_check
CHECK (length(checksum) = 64 AND checksum NOT GLOB '*[^0-9a-f]*'),
applied_at_ms INTEGER NOT NULL
CONSTRAINT ql3_local_migrations_applied_at_check CHECK (applied_at_ms >= 0)
)
`);
}
async listAll(): Promise<readonly MigrationStreamRecord[]> {
return (
this.client
.prepare(
`SELECT migration_id, stream_id, dialect, checksum, applied_at_ms
FROM "QingLong3SchemaMigrations"
ORDER BY migration_id`,
)
.all() as unknown as HistoryRow[]
).map(record);
}
async findById(
migrationId: string,
): Promise<MigrationStreamRecord | null> {
const row = this.client
.prepare(
`SELECT migration_id, stream_id, dialect, checksum, applied_at_ms
FROM "QingLong3SchemaMigrations"
WHERE migration_id = ?`,
)
.get(migrationId) as HistoryRow | undefined;
return row ? record(row) : null;
}
async transaction<T>(
work: (
transaction: MigrationStreamTransaction<LocalSqliteMigrationContext>,
) => Promise<T>,
): Promise<T> {
this.client.exec('BEGIN IMMEDIATE');
try {
const result = await work({
context: { client: this.client },
findById: (migrationId) => this.findById(migrationId),
insert: async (value) => {
if (
value.streamId !== LOCAL_SQLITE_MIGRATION_STREAM_ID ||
value.dialect !== 'sqlite'
) {
throw new TypeError('Local SQLite migration identity is invalid');
}
this.client
.prepare(
`INSERT INTO "QingLong3SchemaMigrations"
(migration_id, stream_id, dialect, checksum, applied_at_ms)
VALUES (?, ?, ?, ?, ?)`,
)
.run(
value.migrationId,
value.streamId,
value.dialect,
value.checksum,
value.appliedAtMs,
);
},
});
this.client.exec('COMMIT');
return result;
} catch (error) {
if (this.client.isTransaction) this.client.exec('ROLLBACK');
throw error;
}
}
}
@@ -0,0 +1,184 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0001RunCoreMigration = defineLocalSqliteMigration({
id: '0001-run-core',
statements: [
`
CREATE TABLE "QingLong3SchemaCapabilities" (
contract_name TEXT PRIMARY KEY,
contract_version INTEGER NOT NULL
CONSTRAINT ql3_local_capabilities_version_check CHECK (contract_version >= 1),
migration_id TEXT NOT NULL,
capabilities TEXT NOT NULL
CONSTRAINT ql3_local_capabilities_json_check
CHECK (json_valid(capabilities) AND json_type(capabilities) = 'object'),
updated_at_ms INTEGER NOT NULL
CONSTRAINT ql3_local_capabilities_updated_at_check CHECK (updated_at_ms >= 0),
CONSTRAINT ql3_local_capabilities_migration_fk
FOREIGN KEY (migration_id)
REFERENCES "QingLong3SchemaMigrations" (migration_id)
)
`,
`
CREATE TABLE "Runs" (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
task_id TEXT NOT NULL,
task_revision TEXT NOT NULL,
task_name TEXT,
task_snapshot_ref TEXT,
legacy_cron_id INTEGER,
parent_run_id TEXT,
retry_of_run_id TEXT,
trigger_id TEXT,
trigger_type TEXT NOT NULL,
execution_origin TEXT NOT NULL
CONSTRAINT ql3_local_runs_execution_origin_check
CHECK (execution_origin IN ('manual','scheduled_system','scheduled_node','once','boot','grpc','subscription','system','script','legacy_import')),
execution_owner TEXT NOT NULL
CONSTRAINT ql3_local_runs_execution_owner_check
CHECK (execution_owner IN ('legacy','runtime')),
triggered_by TEXT,
request_id TEXT,
scheduled_for_ms INTEGER,
status TEXT NOT NULL
CONSTRAINT ql3_local_runs_status_check
CHECK (status IN ('created','queued','dispatching','running','waiting_approval','retry_wait','lost','succeeded','failed','cancelled','timed_out')),
version INTEGER NOT NULL DEFAULT 0
CONSTRAINT ql3_local_runs_version_check CHECK (version >= 0),
event_sequence INTEGER NOT NULL DEFAULT 0
CONSTRAINT ql3_local_runs_event_sequence_check CHECK (event_sequence >= 0),
priority INTEGER NOT NULL DEFAULT 0,
idempotency_key TEXT,
input_ref TEXT,
output_ref TEXT,
created_at_ms INTEGER NOT NULL,
queued_at_ms INTEGER,
started_at_ms INTEGER,
finished_at_ms INTEGER,
cancel_requested_at_ms INTEGER,
cancel_reason TEXT
CONSTRAINT ql3_local_runs_cancel_reason_check
CHECK (cancel_reason IS NULL OR cancel_reason IN ('user','policy','shutdown','reconcile','timeout')),
error_code TEXT,
error_summary TEXT,
CONSTRAINT ql3_local_runs_time_check CHECK (
created_at_ms >= 0 AND
(scheduled_for_ms IS NULL OR scheduled_for_ms >= 0) AND
(queued_at_ms IS NULL OR queued_at_ms >= 0) AND
(started_at_ms IS NULL OR started_at_ms >= 0) AND
(finished_at_ms IS NULL OR finished_at_ms >= 0) AND
(cancel_requested_at_ms IS NULL OR cancel_requested_at_ms >= 0)
),
CONSTRAINT ql3_local_runs_parent_fk FOREIGN KEY (parent_run_id) REFERENCES "Runs" (id),
CONSTRAINT ql3_local_runs_retry_fk FOREIGN KEY (retry_of_run_id) REFERENCES "Runs" (id)
)
`,
`CREATE UNIQUE INDEX ql3_local_runs_project_idempotency_uidx ON "Runs" (project_id, idempotency_key) WHERE idempotency_key IS NOT NULL`,
`CREATE INDEX ql3_local_runs_project_created_idx ON "Runs" (project_id, created_at_ms, id)`,
`CREATE INDEX ql3_local_runs_task_created_idx ON "Runs" (task_id, created_at_ms, id)`,
`CREATE INDEX ql3_local_runs_cancel_requested_idx ON "Runs" (status, cancel_requested_at_ms, id)`,
`CREATE INDEX ql3_local_runs_lost_retry_idx ON "Runs" (execution_owner, status, id)`,
`
CREATE TABLE "RunAttempts" (
id TEXT PRIMARY KEY,
run_id TEXT NOT NULL,
step_run_id TEXT,
attempt INTEGER NOT NULL
CONSTRAINT ql3_local_attempts_attempt_check CHECK (attempt >= 1),
status TEXT NOT NULL
CONSTRAINT ql3_local_attempts_status_check
CHECK (status IN ('claimed','starting','running','succeeded','failed','cancelled','timed_out','lost')),
executor_type TEXT NOT NULL,
worker_id TEXT,
worker_session_id TEXT,
worker_generation INTEGER,
executor_handle TEXT,
pid INTEGER,
log_artifact_id TEXT,
lease_token TEXT,
lease_token_digest TEXT,
lease_generation INTEGER,
lease_version INTEGER,
lease_expires_at_ms INTEGER,
offer_id TEXT,
deadline_at_ms INTEGER,
callback_token_hash TEXT,
callback_sequence INTEGER NOT NULL DEFAULT 0
CONSTRAINT ql3_local_attempts_callback_sequence_check CHECK (callback_sequence >= 0),
created_at_ms INTEGER NOT NULL,
started_at_ms INTEGER,
finished_at_ms INTEGER,
exit_code INTEGER,
error_code TEXT,
error_summary TEXT,
CONSTRAINT ql3_local_attempts_time_check CHECK (
created_at_ms >= 0 AND
(started_at_ms IS NULL OR started_at_ms >= 0) AND
(finished_at_ms IS NULL OR finished_at_ms >= 0) AND
(lease_expires_at_ms IS NULL OR lease_expires_at_ms >= 0) AND
(deadline_at_ms IS NULL OR deadline_at_ms >= 0)
),
CONSTRAINT ql3_local_attempts_run_fk FOREIGN KEY (run_id) REFERENCES "Runs" (id) ON DELETE CASCADE
)
`,
`CREATE UNIQUE INDEX ql3_local_attempts_run_attempt_uidx ON "RunAttempts" (run_id, attempt)`,
`CREATE INDEX ql3_local_attempts_run_status_idx ON "RunAttempts" (run_id, status, id)`,
`CREATE INDEX ql3_local_attempts_lease_idx ON "RunAttempts" (lease_expires_at_ms, id)`,
`CREATE INDEX ql3_local_attempts_deadline_idx ON "RunAttempts" (status, deadline_at_ms, id)`,
`
CREATE TABLE "RunEvents" (
id TEXT PRIMARY KEY,
run_id TEXT NOT NULL,
sequence INTEGER NOT NULL
CONSTRAINT ql3_local_events_sequence_check CHECK (sequence >= 1),
type TEXT NOT NULL,
dedupe_key TEXT,
actor_type TEXT NOT NULL
CONSTRAINT ql3_local_events_actor_type_check
CHECK (actor_type IN ('user','api_app','trigger','agent','mcp_client','worker','executor','system','legacy_shell','scheduler','reconciler','compatibility')),
actor_id TEXT,
attempt_id TEXT,
step_run_id TEXT,
payload TEXT NOT NULL
CONSTRAINT ql3_local_events_payload_check
CHECK (json_valid(payload) AND json_type(payload) = 'object'),
created_at_ms INTEGER NOT NULL
CONSTRAINT ql3_local_events_created_at_check CHECK (created_at_ms >= 0),
CONSTRAINT ql3_local_events_run_fk FOREIGN KEY (run_id) REFERENCES "Runs" (id) ON DELETE CASCADE,
CONSTRAINT ql3_local_events_attempt_fk FOREIGN KEY (attempt_id) REFERENCES "RunAttempts" (id) ON DELETE SET NULL
)
`,
`CREATE UNIQUE INDEX ql3_local_events_run_sequence_uidx ON "RunEvents" (run_id, sequence)`,
`CREATE UNIQUE INDEX ql3_local_events_run_dedupe_uidx ON "RunEvents" (run_id, dedupe_key) WHERE dedupe_key IS NOT NULL`,
`CREATE INDEX ql3_local_events_run_created_idx ON "RunEvents" (run_id, created_at_ms, id)`,
`
CREATE TABLE "RunRetryPolicies" (
run_id TEXT PRIMARY KEY,
max_attempts INTEGER NOT NULL
CONSTRAINT ql3_local_retry_max_attempts_check CHECK (max_attempts BETWEEN 1 AND 16),
retry_on_lost INTEGER NOT NULL
CONSTRAINT ql3_local_retry_on_lost_check CHECK (retry_on_lost IN (0, 1)),
safety TEXT NOT NULL
CONSTRAINT ql3_local_retry_safety_check CHECK (safety IN ('unknown','idempotent','deduplicated')),
backoff_base_ms INTEGER NOT NULL,
backoff_max_ms INTEGER NOT NULL,
next_attempt_at_ms INTEGER,
version INTEGER NOT NULL DEFAULT 0
CONSTRAINT ql3_local_retry_version_check CHECK (version >= 0),
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
CONSTRAINT ql3_local_retry_backoff_check CHECK (
backoff_base_ms BETWEEN 0 AND 86400000 AND
backoff_max_ms BETWEEN backoff_base_ms AND 86400000
),
CONSTRAINT ql3_local_retry_time_check CHECK (
created_at_ms >= 0 AND updated_at_ms >= created_at_ms AND
(next_attempt_at_ms IS NULL OR next_attempt_at_ms >= 0)
),
CONSTRAINT ql3_local_retry_run_fk FOREIGN KEY (run_id) REFERENCES "Runs" (id) ON DELETE CASCADE
)
`,
`CREATE INDEX ql3_local_retry_due_idx ON "RunRetryPolicies" (next_attempt_at_ms, run_id) WHERE next_attempt_at_ms IS NOT NULL`,
],
});
@@ -0,0 +1,15 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0002CapabilityMigration = defineLocalSqliteMigration({
id: '0002-capability',
statements: [
`
INSERT INTO "QingLong3SchemaCapabilities" (
contract_name, contract_version, migration_id, capabilities, updated_at_ms
) VALUES (
'local-control-core', 1, '0001-run-core', '{"run_core":1,"run_retry_policy":1}',
CAST(unixepoch('subsec') * 1000 AS INTEGER)
)
`,
],
});
@@ -0,0 +1,35 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0003CompletionReceiptJournalMigration =
defineLocalSqliteMigration({
id: '0003-completion-receipt-journal',
statements: [
`
CREATE TABLE "LocalCompletionReceiptJournal" (
attempt_id TEXT PRIMARY KEY,
run_id TEXT NOT NULL,
state TEXT NOT NULL
CONSTRAINT ql3_local_receipt_journal_state_check
CHECK (state IN ('pending','quarantined')),
quarantine_ref TEXT,
purge_after_ms INTEGER,
registered_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
CONSTRAINT ql3_local_receipt_journal_time_check CHECK (
registered_at_ms >= 0 AND updated_at_ms >= registered_at_ms AND
(purge_after_ms IS NULL OR purge_after_ms >= updated_at_ms)
),
CONSTRAINT ql3_local_receipt_journal_shape_check CHECK (
(state = 'pending' AND quarantine_ref IS NULL AND purge_after_ms IS NULL) OR
(state = 'quarantined' AND quarantine_ref IS NOT NULL AND purge_after_ms IS NOT NULL)
),
CONSTRAINT ql3_local_receipt_journal_attempt_fk
FOREIGN KEY (attempt_id) REFERENCES "RunAttempts" (id) ON DELETE CASCADE,
CONSTRAINT ql3_local_receipt_journal_run_fk
FOREIGN KEY (run_id) REFERENCES "Runs" (id) ON DELETE CASCADE
)
`,
`CREATE INDEX ql3_local_receipt_journal_scan_idx ON "LocalCompletionReceiptJournal" (state, updated_at_ms, attempt_id)`,
`CREATE INDEX ql3_local_receipt_journal_purge_idx ON "LocalCompletionReceiptJournal" (purge_after_ms, attempt_id) WHERE state = 'quarantined'`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0004CapabilityV2Migration = defineLocalSqliteMigration({
id: '0004-capability-v2',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 2,
migration_id = '0003-completion-receipt-journal',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 1
AND migration_id = '0001-run-core'
AND capabilities = '{"run_core":1,"run_retry_policy":1}'
`,
],
});
@@ -0,0 +1,75 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0005LocalDispatchPlanMigration = defineLocalSqliteMigration({
id: '0005-local-dispatch-plan',
statements: [
`
CREATE TABLE "QingLong3LocalExecutionContextRecipes" (
"context_ref" TEXT PRIMARY KEY NOT NULL,
"environment_json" TEXT NOT NULL,
"content_digest" TEXT NOT NULL,
"created_at_ms" INTEGER NOT NULL,
CONSTRAINT ql3_local_context_ref_check CHECK (
length("context_ref") = 80
AND "context_ref" GLOB 'localctx:sha256:[0-9a-f]*'
AND length(replace("context_ref", 'localctx:sha256:', '')) = 64
),
CONSTRAINT ql3_local_context_environment_check CHECK (
json_valid("environment_json")
AND json_type("environment_json") = 'array'
AND length("environment_json") <= 262144
),
CONSTRAINT ql3_local_context_digest_check CHECK (
length("content_digest") = 64
AND "content_digest" NOT GLOB '*[^0-9a-f]*'
AND "context_ref" = 'localctx:sha256:' || "content_digest"
),
CONSTRAINT ql3_local_context_created_check CHECK ("created_at_ms" >= 0)
)
`,
`
CREATE TABLE "QingLong3LocalTaskExecutionRevisions" (
"project_id" TEXT NOT NULL,
"task_id" TEXT NOT NULL,
"task_revision" TEXT NOT NULL,
"executor_type" TEXT NOT NULL,
"command_json" TEXT NOT NULL,
"working_directory" TEXT,
"timeout_ms" INTEGER,
"context_ref" TEXT NOT NULL,
"created_at_ms" INTEGER NOT NULL,
PRIMARY KEY ("project_id", "task_id", "task_revision"),
CONSTRAINT ql3_local_revision_executor_check CHECK (
"executor_type" = 'local_process'
),
CONSTRAINT ql3_local_revision_command_check CHECK (
json_valid("command_json")
AND json_type("command_json") = 'object'
AND length("command_json") BETWEEN 1 AND 131072
),
CONSTRAINT ql3_local_revision_working_directory_check CHECK (
"working_directory" IS NULL
OR (length("working_directory") BETWEEN 1 AND 4096
AND substr("working_directory", 1, 1) = '/')
),
CONSTRAINT ql3_local_revision_timeout_check CHECK (
"timeout_ms" IS NULL OR "timeout_ms" BETWEEN 1 AND 31536000000
),
CONSTRAINT ql3_local_revision_created_check CHECK ("created_at_ms" >= 0),
FOREIGN KEY ("context_ref")
REFERENCES "QingLong3LocalExecutionContextRecipes" ("context_ref")
ON DELETE RESTRICT
)
`,
`
CREATE INDEX "ql3_local_runs_dispatch_idx"
ON "Runs" (
"execution_owner", "status", "priority" DESC,
"queued_at_ms", "id"
)
WHERE "execution_owner" = 'runtime'
AND "status" = 'queued'
AND "cancel_requested_at_ms" IS NULL
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0006CapabilityV3Migration = defineLocalSqliteMigration({
id: '0006-capability-v3',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 3,
migration_id = '0005-local-dispatch-plan',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 2
AND migration_id = '0003-completion-receipt-journal'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1}'
`,
],
});
@@ -0,0 +1,68 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0007LocalSecretEnvelopesMigration =
defineLocalSqliteMigration({
id: '0007-local-secret-envelopes',
statements: [
`
CREATE TABLE "QingLong3LocalSecretEnvelopes" (
"project_id" TEXT NOT NULL,
"secret_name" TEXT NOT NULL,
"version" INTEGER NOT NULL,
"mutation_id" TEXT NOT NULL,
"key_id" TEXT NOT NULL,
"algorithm" TEXT NOT NULL,
"nonce" BLOB NOT NULL,
"ciphertext" BLOB NOT NULL,
"auth_tag" BLOB NOT NULL,
"created_at_ms" INTEGER NOT NULL,
PRIMARY KEY ("project_id", "secret_name", "version"),
CONSTRAINT ql3_local_secret_project_check CHECK (
length("project_id") BETWEEN 1 AND 128
AND "project_id" NOT GLOB '*[' || char(0) || '-' || char(31) || char(127) || ']*'
),
CONSTRAINT ql3_local_secret_name_check CHECK (
length("secret_name") BETWEEN 1 AND 128
AND "secret_name" NOT GLOB '*[' || char(0) || '-' || char(31) || char(127) || ']*'
),
CONSTRAINT ql3_local_secret_version_check CHECK (
"version" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_local_secret_mutation_check CHECK (
length("mutation_id") BETWEEN 1 AND 64
),
CONSTRAINT ql3_local_secret_key_check CHECK (
length("key_id") BETWEEN 1 AND 128
AND "key_id" NOT GLOB '*[^A-Za-z0-9._-]*'
),
CONSTRAINT ql3_local_secret_algorithm_check CHECK (
"algorithm" = 'aes-256-gcm'
),
CONSTRAINT ql3_local_secret_crypto_shape_check CHECK (
length("nonce") = 12
AND length("ciphertext") <= 16384
AND length("auth_tag") = 16
),
CONSTRAINT ql3_local_secret_created_check CHECK ("created_at_ms" >= 0)
)
`,
`
CREATE UNIQUE INDEX "ql3_local_secret_mutation_uidx"
ON "QingLong3LocalSecretEnvelopes" (
"project_id", "secret_name", "mutation_id"
)
`,
`
CREATE INDEX "ql3_local_secret_current_idx"
ON "QingLong3LocalSecretEnvelopes" (
"project_id", "secret_name", "version" DESC
)
`,
`
CREATE INDEX "ql3_local_secret_key_usage_idx"
ON "QingLong3LocalSecretEnvelopes" (
"key_id", "project_id", "secret_name", "version"
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0008CapabilityV4Migration = defineLocalSqliteMigration({
id: '0008-capability-v4',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 4,
migration_id = '0007-local-secret-envelopes',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 3
AND migration_id = '0005-local-dispatch-plan'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1}'
`,
],
});
@@ -0,0 +1,166 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0009LocalProjectPolicyAuditMigration =
defineLocalSqliteMigration({
id: '0009-local-project-policy-audit',
statements: [
`
CREATE TABLE "QingLong3Projects" (
"id" TEXT PRIMARY KEY NOT NULL,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"status" TEXT NOT NULL,
"version" INTEGER NOT NULL,
"created_at_ms" INTEGER NOT NULL,
"updated_at_ms" INTEGER NOT NULL,
CONSTRAINT ql3_local_projects_id_check CHECK (
length("id") BETWEEN 1 AND 128
AND "id" NOT GLOB '*[' || char(0) || '-' || char(31) || char(127) || ']*'
),
CONSTRAINT ql3_local_projects_name_check CHECK (
length("name") BETWEEN 1 AND 255
),
CONSTRAINT ql3_local_projects_slug_check CHECK (
length("slug") BETWEEN 1 AND 128
AND "slug" = lower("slug")
AND "slug" NOT GLOB '*[^a-z0-9-]*'
AND substr("slug", 1, 1) NOT GLOB '[^a-z0-9]'
AND substr("slug", -1, 1) NOT GLOB '[^a-z0-9]'
),
CONSTRAINT ql3_local_projects_status_check CHECK (
"status" IN ('active', 'archived')
),
CONSTRAINT ql3_local_projects_version_check CHECK (
"version" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_local_projects_time_check CHECK (
"created_at_ms" >= 0 AND "updated_at_ms" >= "created_at_ms"
)
)
`,
`
CREATE UNIQUE INDEX "ql3_local_projects_slug_uidx"
ON "QingLong3Projects" ("slug")
`,
`
INSERT INTO "QingLong3Projects" (
"id", "name", "slug", "status", "version", "created_at_ms", "updated_at_ms"
) VALUES ('default', 'Default', 'default', 'active', 1, 0, 0)
`,
`
CREATE TABLE "QingLong3ProjectRoleBindings" (
"project_id" TEXT NOT NULL,
"subject_type" TEXT NOT NULL,
"subject_id" TEXT NOT NULL,
"version" INTEGER NOT NULL,
"state" TEXT NOT NULL,
"role" TEXT,
"mutation_id" TEXT NOT NULL,
"changed_by_type" TEXT NOT NULL,
"changed_by_id" TEXT NOT NULL,
"created_at_ms" INTEGER NOT NULL,
PRIMARY KEY ("project_id", "subject_type", "subject_id", "version"),
FOREIGN KEY ("project_id") REFERENCES "QingLong3Projects" ("id") ON DELETE RESTRICT,
CONSTRAINT ql3_local_bindings_subject_type_check CHECK (
"subject_type" IN ('user','api_app','mcp_client','agent','system','worker')
),
CONSTRAINT ql3_local_bindings_subject_id_check CHECK (
length("subject_id") BETWEEN 1 AND 255
),
CONSTRAINT ql3_local_bindings_version_check CHECK (
"version" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_local_bindings_state_role_check CHECK (
("state" = 'active' AND "role" IN ('owner','admin','operator','viewer'))
OR ("state" = 'revoked' AND "role" IS NULL)
),
CONSTRAINT ql3_local_bindings_mutation_check CHECK (
length("mutation_id") BETWEEN 1 AND 64
),
CONSTRAINT ql3_local_bindings_changed_by_type_check CHECK (
"changed_by_type" IN ('user','api_app','mcp_client','agent','system','worker')
),
CONSTRAINT ql3_local_bindings_changed_by_id_check CHECK (
length("changed_by_id") BETWEEN 1 AND 255
),
CONSTRAINT ql3_local_bindings_created_check CHECK ("created_at_ms" >= 0)
)
`,
`
CREATE UNIQUE INDEX "ql3_local_bindings_mutation_uidx"
ON "QingLong3ProjectRoleBindings" (
"project_id", "subject_type", "subject_id", "mutation_id"
)
`,
`
CREATE INDEX "ql3_local_bindings_current_idx"
ON "QingLong3ProjectRoleBindings" (
"project_id", "subject_type", "subject_id", "version" DESC
)
`,
`
CREATE INDEX "ql3_local_bindings_project_idx"
ON "QingLong3ProjectRoleBindings" ("project_id", "version" DESC)
`,
`
CREATE TABLE "QingLong3SecurityAuditEvents" (
"event_id" TEXT PRIMARY KEY NOT NULL,
"request_id" TEXT NOT NULL,
"operation_id" TEXT NOT NULL,
"project_id" TEXT,
"subject_type" TEXT,
"subject_id" TEXT,
"authentication_id" TEXT,
"outcome" TEXT NOT NULL,
"reasons_json" TEXT NOT NULL,
"fence_project_version" INTEGER,
"fence_binding_version" INTEGER,
"occurred_at_ms" INTEGER NOT NULL,
FOREIGN KEY ("project_id") REFERENCES "QingLong3Projects" ("id") ON DELETE RESTRICT,
CONSTRAINT ql3_local_audit_event_check CHECK (length("event_id") = 36),
CONSTRAINT ql3_local_audit_request_check CHECK (length("request_id") BETWEEN 1 AND 128),
CONSTRAINT ql3_local_audit_operation_check CHECK (length("operation_id") BETWEEN 1 AND 128),
CONSTRAINT ql3_local_audit_subject_check CHECK (
("subject_type" IS NULL AND "subject_id" IS NULL AND "authentication_id" IS NULL)
OR (
"subject_type" IN ('user','api_app','mcp_client','agent','system','worker')
AND length("subject_id") BETWEEN 1 AND 255
AND length("authentication_id") BETWEEN 1 AND 128
)
),
CONSTRAINT ql3_local_audit_outcome_check CHECK (
"outcome" IN (
'authentication_rejected','authentication_unavailable',
'authorization_unavailable','denied','approval_required','allowed'
)
),
CONSTRAINT ql3_local_audit_reasons_check CHECK (
json_valid("reasons_json")
AND json_type("reasons_json") = 'array'
AND json_array_length("reasons_json") BETWEEN 1 AND 8
AND length("reasons_json") <= 2048
),
CONSTRAINT ql3_local_audit_fence_check CHECK (
("fence_project_version" IS NULL AND "fence_binding_version" IS NULL)
OR (
"fence_project_version" BETWEEN 1 AND 2147483647
AND ("fence_binding_version" IS NULL OR "fence_binding_version" BETWEEN 1 AND 2147483647)
)
),
CONSTRAINT ql3_local_audit_time_check CHECK ("occurred_at_ms" >= 0)
)
`,
`
CREATE INDEX "ql3_local_audit_project_time_idx"
ON "QingLong3SecurityAuditEvents" (
"project_id", "occurred_at_ms" DESC, "event_id" DESC
)
`,
`
CREATE INDEX "ql3_local_audit_subject_time_idx"
ON "QingLong3SecurityAuditEvents" (
"subject_type", "subject_id", "occurred_at_ms" DESC, "event_id" DESC
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0010CapabilityV5Migration = defineLocalSqliteMigration({
id: '0010-capability-v5',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 5,
migration_id = '0009-local-project-policy-audit',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 4
AND migration_id = '0007-local-secret-envelopes'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1}'
`,
],
});
@@ -0,0 +1,92 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0011LocalIdentityCredentialMigration =
defineLocalSqliteMigration({
id: '0011-local-identity-credential',
statements: [
`
CREATE TABLE "QingLong3IdentitySubjects" (
"subject_type" TEXT NOT NULL,
"subject_id" TEXT NOT NULL,
"status" TEXT NOT NULL,
"version" INTEGER NOT NULL,
"created_at_ms" INTEGER NOT NULL,
"updated_at_ms" INTEGER NOT NULL,
PRIMARY KEY ("subject_type", "subject_id"),
CONSTRAINT ql3_local_identity_type_check CHECK (
"subject_type" IN ('user','api_app','mcp_client','agent','system','worker')
),
CONSTRAINT ql3_local_identity_id_check CHECK (
length("subject_id") BETWEEN 1 AND 255
),
CONSTRAINT ql3_local_identity_status_check CHECK (
"status" IN ('active','disabled')
),
CONSTRAINT ql3_local_identity_version_check CHECK (
"version" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_local_identity_time_check CHECK (
"created_at_ms" >= 0 AND "updated_at_ms" >= "created_at_ms"
)
)
`,
`
CREATE INDEX "ql3_local_identity_status_idx"
ON "QingLong3IdentitySubjects" (
"status", "subject_type", "subject_id"
)
`,
`
CREATE TABLE "QingLong3ApiCredentials" (
"credential_id" TEXT NOT NULL,
"version" INTEGER NOT NULL,
"state" TEXT NOT NULL,
"subject_type" TEXT NOT NULL,
"subject_id" TEXT NOT NULL,
"secret_digest" TEXT NOT NULL,
"created_at_ms" INTEGER NOT NULL,
"not_before_at_ms" INTEGER NOT NULL,
"expires_at_ms" INTEGER NOT NULL,
PRIMARY KEY ("credential_id", "version"),
FOREIGN KEY ("subject_type", "subject_id")
REFERENCES "QingLong3IdentitySubjects" ("subject_type", "subject_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_local_credentials_id_check CHECK (
length("credential_id") BETWEEN 1 AND 64
AND "credential_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
),
CONSTRAINT ql3_local_credentials_version_check CHECK (
"version" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_local_credentials_state_check CHECK (
"state" IN ('active','revoked')
),
CONSTRAINT ql3_local_credentials_subject_type_check CHECK (
"subject_type" IN ('user','api_app','mcp_client','agent')
),
CONSTRAINT ql3_local_credentials_subject_id_check CHECK (
length("subject_id") BETWEEN 1 AND 255
),
CONSTRAINT ql3_local_credentials_digest_check CHECK (
length("secret_digest") = 64
AND "secret_digest" NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_local_credentials_lifetime_check CHECK (
"created_at_ms" >= 0
AND "not_before_at_ms" >= "created_at_ms"
AND "expires_at_ms" > "not_before_at_ms"
)
)
`,
`
CREATE INDEX "ql3_local_credentials_current_idx"
ON "QingLong3ApiCredentials" ("credential_id", "version" DESC)
`,
`
CREATE INDEX "ql3_local_credentials_subject_idx"
ON "QingLong3ApiCredentials" (
"subject_type", "subject_id", "credential_id", "version" DESC
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0012CapabilityV6Migration = defineLocalSqliteMigration({
id: '0012-capability-v6',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 6,
migration_id = '0011-local-identity-credential',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 5
AND migration_id = '0009-local-project-policy-audit'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1}'
`,
],
});
@@ -0,0 +1,193 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0013LocalOwnerBootstrapMigration =
defineLocalSqliteMigration({
id: '0013-local-owner-bootstrap',
statements: [
`
CREATE TABLE "QingLong3LocalIdentityProvisionings" (
"slot" INTEGER PRIMARY KEY NOT NULL,
"mutation_id" TEXT NOT NULL,
"request_id" TEXT NOT NULL,
"subject_type" TEXT NOT NULL,
"subject_id" TEXT NOT NULL,
"credential_id" TEXT NOT NULL,
"credential_version" INTEGER NOT NULL,
"issuer_authentication_id" TEXT NOT NULL,
"issuer_authenticated_at_ms" INTEGER NOT NULL,
"issuer_expires_at_ms" INTEGER NOT NULL,
"audit_event_id" TEXT NOT NULL,
"created_at_ms" INTEGER NOT NULL,
FOREIGN KEY ("subject_type", "subject_id")
REFERENCES "QingLong3IdentitySubjects" ("subject_type", "subject_id")
ON DELETE RESTRICT,
FOREIGN KEY ("credential_id", "credential_version")
REFERENCES "QingLong3ApiCredentials" ("credential_id", "version")
ON DELETE RESTRICT,
FOREIGN KEY ("audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_local_provisioning_singleton_check CHECK ("slot" = 1),
CONSTRAINT ql3_local_provisioning_mutation_check CHECK (
length("mutation_id") = 36
),
CONSTRAINT ql3_local_provisioning_request_check CHECK (
length("request_id") BETWEEN 1 AND 128
),
CONSTRAINT ql3_local_provisioning_subject_check CHECK (
"subject_type" = 'user' AND length("subject_id") BETWEEN 1 AND 255
),
CONSTRAINT ql3_local_provisioning_credential_check CHECK (
length("credential_id") BETWEEN 1 AND 64 AND "credential_version" = 1
),
CONSTRAINT ql3_local_provisioning_issuer_check CHECK (
length("issuer_authentication_id") BETWEEN 1 AND 128
AND "issuer_authenticated_at_ms" <= "created_at_ms"
AND "issuer_expires_at_ms" > "created_at_ms"
),
CONSTRAINT ql3_local_provisioning_audit_check CHECK (
"audit_event_id" = "mutation_id"
),
CONSTRAINT ql3_local_provisioning_time_check CHECK ("created_at_ms" >= 0)
)
`,
`
CREATE UNIQUE INDEX "ql3_local_provisioning_mutation_uidx"
ON "QingLong3LocalIdentityProvisionings" ("mutation_id")
`,
`
CREATE UNIQUE INDEX "ql3_local_provisioning_subject_uidx"
ON "QingLong3LocalIdentityProvisionings" ("subject_type", "subject_id")
`,
`
CREATE UNIQUE INDEX "ql3_local_provisioning_credential_uidx"
ON "QingLong3LocalIdentityProvisionings" (
"credential_id", "credential_version"
)
`,
`
CREATE TABLE "QingLong3LocalOwnerBootstrapChallenges" (
"project_id" TEXT NOT NULL,
"version" INTEGER NOT NULL,
"issue_mutation_id" TEXT NOT NULL,
"issue_request_id" TEXT NOT NULL,
"challenge_id" TEXT NOT NULL,
"token_digest" TEXT NOT NULL,
"issuer_authentication_id" TEXT NOT NULL,
"issuer_authenticated_at_ms" INTEGER NOT NULL,
"issuer_expires_at_ms" INTEGER NOT NULL,
"issued_at_ms" INTEGER NOT NULL,
"expires_at_ms" INTEGER NOT NULL,
"issue_audit_event_id" TEXT NOT NULL,
"consumed_at_ms" INTEGER,
"claim_mutation_id" TEXT,
"claim_request_id" TEXT,
"claimed_subject_type" TEXT,
"claimed_subject_id" TEXT,
"credential_id" TEXT,
"credential_version" INTEGER,
"claim_authentication_id" TEXT,
"claim_authenticated_at_ms" INTEGER,
"claim_expires_at_ms" INTEGER,
"claim_assurance" TEXT,
"claim_audit_event_id" TEXT,
PRIMARY KEY ("project_id", "version"),
FOREIGN KEY ("project_id")
REFERENCES "QingLong3Projects" ("id") ON DELETE RESTRICT,
FOREIGN KEY ("issue_audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id") ON DELETE RESTRICT,
FOREIGN KEY ("claimed_subject_type", "claimed_subject_id")
REFERENCES "QingLong3IdentitySubjects" ("subject_type", "subject_id")
ON DELETE RESTRICT,
FOREIGN KEY ("credential_id", "credential_version")
REFERENCES "QingLong3ApiCredentials" ("credential_id", "version")
ON DELETE RESTRICT,
FOREIGN KEY ("claim_audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id") ON DELETE RESTRICT,
CONSTRAINT ql3_local_owner_challenge_version_check CHECK (
"version" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_local_owner_challenge_issue_identity_check CHECK (
length("issue_mutation_id") = 36
AND length("issue_request_id") BETWEEN 1 AND 128
AND "issue_audit_event_id" = "issue_mutation_id"
),
CONSTRAINT ql3_local_owner_challenge_id_check CHECK (
length("challenge_id") = 22
AND "challenge_id" NOT GLOB '*[^A-Za-z0-9_-]*'
),
CONSTRAINT ql3_local_owner_challenge_digest_check CHECK (
length("token_digest") = 64
AND "token_digest" NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_local_owner_challenge_issuer_check CHECK (
length("issuer_authentication_id") BETWEEN 1 AND 128
AND "issuer_authenticated_at_ms" <= "issued_at_ms"
AND "issuer_expires_at_ms" > "issued_at_ms"
),
CONSTRAINT ql3_local_owner_challenge_lifetime_check CHECK (
"issued_at_ms" >= 0
AND "expires_at_ms" > "issued_at_ms"
AND "expires_at_ms" - "issued_at_ms" BETWEEN 60000 AND 1800000
),
CONSTRAINT ql3_local_owner_challenge_claim_shape_check CHECK (
(
"consumed_at_ms" IS NULL
AND "claim_mutation_id" IS NULL
AND "claim_request_id" IS NULL
AND "claimed_subject_type" IS NULL
AND "claimed_subject_id" IS NULL
AND "credential_id" IS NULL
AND "credential_version" IS NULL
AND "claim_authentication_id" IS NULL
AND "claim_authenticated_at_ms" IS NULL
AND "claim_expires_at_ms" IS NULL
AND "claim_assurance" IS NULL
AND "claim_audit_event_id" IS NULL
)
OR (
"consumed_at_ms" >= "issued_at_ms"
AND "consumed_at_ms" < "expires_at_ms"
AND length("claim_mutation_id") = 36
AND length("claim_request_id") BETWEEN 1 AND 128
AND "claimed_subject_type" = 'user'
AND length("claimed_subject_id") BETWEEN 1 AND 255
AND length("credential_id") BETWEEN 1 AND 64
AND "credential_version" BETWEEN 1 AND 2147483647
AND length("claim_authentication_id") BETWEEN 1 AND 128
AND "claim_authenticated_at_ms" <= "consumed_at_ms"
AND "claim_expires_at_ms" > "consumed_at_ms"
AND "claim_assurance" = 'single_factor'
AND "claim_audit_event_id" = "claim_mutation_id"
)
)
)
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_challenge_issue_mutation_uidx"
ON "QingLong3LocalOwnerBootstrapChallenges" ("issue_mutation_id")
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_challenge_id_uidx"
ON "QingLong3LocalOwnerBootstrapChallenges" ("challenge_id")
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_challenge_claim_mutation_uidx"
ON "QingLong3LocalOwnerBootstrapChallenges" ("claim_mutation_id")
WHERE "claim_mutation_id" IS NOT NULL
`,
`
CREATE INDEX "ql3_local_owner_challenge_current_idx"
ON "QingLong3LocalOwnerBootstrapChallenges" (
"project_id", "version" DESC
)
`,
`
CREATE INDEX "ql3_local_owner_challenge_expiry_idx"
ON "QingLong3LocalOwnerBootstrapChallenges" (
"project_id", "expires_at_ms", "version" DESC
)
WHERE "consumed_at_ms" IS NULL
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0014CapabilityV7Migration = defineLocalSqliteMigration({
id: '0014-capability-v7',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 7,
migration_id = '0013-local-owner-bootstrap',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 6
AND migration_id = '0011-local-identity-credential'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1}'
`,
],
});
@@ -0,0 +1,73 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0015LocalOwnerDeliveryAcknowledgementsMigration =
defineLocalSqliteMigration({
id: '0015-local-owner-delivery-acknowledgements',
statements: [
`
CREATE TABLE "QingLong3LocalOwnerDeliveryAcknowledgements" (
"mutation_id" TEXT PRIMARY KEY NOT NULL,
"kind" TEXT NOT NULL,
"request_id" TEXT NOT NULL,
"project_id" TEXT,
"subject_id" TEXT,
"credential_id" TEXT,
"challenge_id" TEXT,
"fact_digest" TEXT NOT NULL,
"delivery_digest" TEXT NOT NULL,
"ttl_ms" INTEGER NOT NULL,
"acknowledged_at_ms" INTEGER NOT NULL,
"provisioning_mutation_id" TEXT,
"challenge_mutation_id" TEXT,
FOREIGN KEY ("provisioning_mutation_id")
REFERENCES "QingLong3LocalIdentityProvisionings" ("mutation_id")
ON DELETE RESTRICT,
FOREIGN KEY ("challenge_mutation_id")
REFERENCES "QingLong3LocalOwnerBootstrapChallenges" ("issue_mutation_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_local_owner_delivery_ack_mutation_check CHECK (
length("mutation_id") = 36
),
CONSTRAINT ql3_local_owner_delivery_ack_request_check CHECK (
length("request_id") BETWEEN 1 AND 128
),
CONSTRAINT ql3_local_owner_delivery_ack_digest_check CHECK (
length("fact_digest") = 64
AND "fact_digest" NOT GLOB '*[^0-9a-f]*'
AND length("delivery_digest") = 64
AND "delivery_digest" NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_local_owner_delivery_ack_time_check CHECK (
"ttl_ms" > 0 AND "acknowledged_at_ms" >= 0
),
CONSTRAINT ql3_local_owner_delivery_ack_shape_check CHECK (
(
"kind" = 'credential'
AND "project_id" IS NULL
AND length("subject_id") = 26
AND "subject_id" GLOB 'usr_*'
AND "subject_id" NOT GLOB '*[^A-Za-z0-9_-]*'
AND length("credential_id") = 26
AND "credential_id" GLOB 'own_*'
AND "credential_id" NOT GLOB '*[^A-Za-z0-9_-]*'
AND "challenge_id" IS NULL
AND "provisioning_mutation_id" = "mutation_id"
AND "challenge_mutation_id" IS NULL
AND "ttl_ms" BETWEEN 600000 AND 604800000
)
OR (
"kind" = 'challenge'
AND length("project_id") BETWEEN 1 AND 128
AND "subject_id" IS NULL
AND "credential_id" IS NULL
AND length("challenge_id") = 22
AND "challenge_id" NOT GLOB '*[^A-Za-z0-9_-]*'
AND "provisioning_mutation_id" IS NULL
AND "challenge_mutation_id" = "mutation_id"
AND "ttl_ms" BETWEEN 60000 AND 1800000
)
)
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0016CapabilityV8Migration = defineLocalSqliteMigration({
id: '0016-capability-v8',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 8,
migration_id = '0015-local-owner-delivery-acknowledgements',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 7
AND migration_id = '0013-local-owner-bootstrap'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1}'
`,
],
});
@@ -0,0 +1,36 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0017ApiCredentialPepperBindingsMigration =
defineLocalSqliteMigration({
id: '0017-api-credential-pepper-bindings',
statements: [
`
CREATE TABLE "QingLong3ApiCredentialPepperBindings" (
"credential_id" TEXT NOT NULL,
"credential_version" INTEGER NOT NULL,
"pepper_key_id" TEXT NOT NULL,
PRIMARY KEY ("credential_id", "credential_version"),
FOREIGN KEY ("credential_id", "credential_version")
REFERENCES "QingLong3ApiCredentials" ("credential_id", "version")
ON DELETE RESTRICT,
CONSTRAINT ql3_local_credential_pepper_key_id_check CHECK (
length("pepper_key_id") BETWEEN 1 AND 64
AND "pepper_key_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
)
)
`,
`
INSERT INTO "QingLong3ApiCredentialPepperBindings" (
"credential_id", "credential_version", "pepper_key_id"
)
SELECT "credential_id", "version", 'legacy-v1'
FROM "QingLong3ApiCredentials"
`,
`
CREATE INDEX "ql3_local_credential_pepper_key_idx"
ON "QingLong3ApiCredentialPepperBindings" (
"pepper_key_id", "credential_id", "credential_version"
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0018CapabilityV9Migration = defineLocalSqliteMigration({
id: '0018-capability-v9',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 9,
migration_id = '0017-api-credential-pepper-bindings',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 8
AND migration_id = '0015-local-owner-delivery-acknowledgements'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1}'
`,
],
});
@@ -0,0 +1,182 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0019LocalOwnerPepperCatalogMigration =
defineLocalSqliteMigration({
id: '0019-local-owner-pepper-catalog',
statements: [
`
CREATE TABLE "QingLong3LocalOwnerPepperKeys" (
"pepper_key_id" TEXT PRIMARY KEY,
"material_digest" TEXT,
"backup_digest" TEXT,
"state" TEXT NOT NULL,
"version" INTEGER NOT NULL,
"register_mutation_id" TEXT,
"activate_mutation_id" TEXT,
"retire_mutation_id" TEXT,
"registered_at_ms" INTEGER NOT NULL,
"activated_at_ms" INTEGER,
"retired_at_ms" INTEGER,
CONSTRAINT ql3_local_owner_pepper_key_id_check CHECK (
length("pepper_key_id") BETWEEN 1 AND 64
AND "pepper_key_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
),
CONSTRAINT ql3_local_owner_pepper_digest_check CHECK (
("material_digest" IS NULL AND "backup_digest" IS NULL)
OR (
length("material_digest") = 64
AND "material_digest" NOT GLOB '*[^0-9a-f]*'
AND length("backup_digest") = 64
AND "backup_digest" NOT GLOB '*[^0-9a-f]*'
)
),
CONSTRAINT ql3_local_owner_pepper_state_check CHECK (
"state" IN ('recovery_required', 'staged', 'active', 'retired')
),
CONSTRAINT ql3_local_owner_pepper_version_check CHECK (
"version" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_local_owner_pepper_mutation_check CHECK (
("register_mutation_id" IS NULL OR length("register_mutation_id") = 36)
AND ("activate_mutation_id" IS NULL OR length("activate_mutation_id") = 36)
AND ("retire_mutation_id" IS NULL OR length("retire_mutation_id") = 36)
),
CONSTRAINT ql3_local_owner_pepper_time_check CHECK (
"registered_at_ms" >= 0
AND ("activated_at_ms" IS NULL OR "activated_at_ms" >= "registered_at_ms")
AND ("retired_at_ms" IS NULL OR "retired_at_ms" >= "activated_at_ms")
),
CONSTRAINT ql3_local_owner_pepper_shape_check CHECK (
("state" = 'recovery_required' AND "material_digest" IS NULL
AND "backup_digest" IS NULL AND "register_mutation_id" IS NULL
AND "activate_mutation_id" IS NULL AND "retire_mutation_id" IS NULL
AND "activated_at_ms" IS NULL AND "retired_at_ms" IS NULL)
OR ("state" = 'staged' AND "material_digest" IS NOT NULL
AND "backup_digest" IS NOT NULL AND "register_mutation_id" IS NOT NULL
AND "activate_mutation_id" IS NULL AND "retire_mutation_id" IS NULL
AND "activated_at_ms" IS NULL AND "retired_at_ms" IS NULL)
OR ("state" = 'active' AND "material_digest" IS NOT NULL
AND "backup_digest" IS NOT NULL AND "register_mutation_id" IS NOT NULL
AND "activate_mutation_id" IS NOT NULL AND "retire_mutation_id" IS NULL
AND "activated_at_ms" IS NOT NULL AND "retired_at_ms" IS NULL)
OR ("state" = 'retired' AND "material_digest" IS NOT NULL
AND "backup_digest" IS NOT NULL AND "register_mutation_id" IS NOT NULL
AND "activate_mutation_id" IS NOT NULL AND "retire_mutation_id" IS NOT NULL
AND "activated_at_ms" IS NOT NULL AND "retired_at_ms" IS NOT NULL)
)
)
`,
`
INSERT INTO "QingLong3LocalOwnerPepperKeys" (
"pepper_key_id", "state", "version", "registered_at_ms"
)
SELECT 'legacy-v1', 'recovery_required', 1, 0
WHERE EXISTS (SELECT 1 FROM "QingLong3ApiCredentialPepperBindings" LIMIT 1)
`,
`
ALTER TABLE "QingLong3ApiCredentialPepperBindings"
RENAME TO "QingLong3ApiCredentialPepperBindingsBeforeCatalog"
`,
`
CREATE TABLE "QingLong3ApiCredentialPepperBindings" (
"credential_id" TEXT NOT NULL,
"credential_version" INTEGER NOT NULL,
"pepper_key_id" TEXT NOT NULL,
PRIMARY KEY ("credential_id", "credential_version"),
FOREIGN KEY ("credential_id", "credential_version")
REFERENCES "QingLong3ApiCredentials" ("credential_id", "version")
ON DELETE RESTRICT,
FOREIGN KEY ("pepper_key_id")
REFERENCES "QingLong3LocalOwnerPepperKeys" ("pepper_key_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_local_credential_pepper_key_id_check CHECK (
length("pepper_key_id") BETWEEN 1 AND 64
AND "pepper_key_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
)
)
`,
`
INSERT INTO "QingLong3ApiCredentialPepperBindings" (
"credential_id", "credential_version", "pepper_key_id"
)
SELECT "credential_id", "credential_version", "pepper_key_id"
FROM "QingLong3ApiCredentialPepperBindingsBeforeCatalog"
`,
`
DROP TABLE "QingLong3ApiCredentialPepperBindingsBeforeCatalog"
`,
`
CREATE INDEX "ql3_local_credential_pepper_key_idx"
ON "QingLong3ApiCredentialPepperBindings" (
"pepper_key_id", "credential_id", "credential_version"
)
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_pepper_register_mutation_uidx"
ON "QingLong3LocalOwnerPepperKeys" ("register_mutation_id")
WHERE "register_mutation_id" IS NOT NULL
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_pepper_single_active_uidx"
ON "QingLong3LocalOwnerPepperKeys" ("state")
WHERE "state" = 'active'
`,
`
CREATE INDEX "ql3_local_owner_pepper_state_idx"
ON "QingLong3LocalOwnerPepperKeys" ("state", "pepper_key_id")
`,
`
CREATE TABLE "QingLong3LocalOwnerPepperActivations" (
"generation" INTEGER PRIMARY KEY,
"mutation_id" TEXT NOT NULL,
"expected_generation" INTEGER NOT NULL,
"previous_pepper_key_id" TEXT,
"active_pepper_key_id" TEXT NOT NULL,
"material_digest" TEXT NOT NULL,
"backup_digest" TEXT NOT NULL,
"activated_at_ms" INTEGER NOT NULL,
CONSTRAINT ql3_local_owner_pepper_activation_generation_check CHECK (
"generation" BETWEEN 1 AND 2147483647
AND "expected_generation" = "generation" - 1
),
CONSTRAINT ql3_local_owner_pepper_activation_mutation_check CHECK (
length("mutation_id") = 36
),
CONSTRAINT ql3_local_owner_pepper_activation_key_check CHECK (
length("active_pepper_key_id") BETWEEN 1 AND 64
AND "active_pepper_key_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
AND ("previous_pepper_key_id" IS NULL OR (
length("previous_pepper_key_id") BETWEEN 1 AND 64
AND "previous_pepper_key_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
AND "previous_pepper_key_id" <> "active_pepper_key_id"
))
),
CONSTRAINT ql3_local_owner_pepper_activation_digest_check CHECK (
length("material_digest") = 64
AND "material_digest" NOT GLOB '*[^0-9a-f]*'
AND length("backup_digest") = 64
AND "backup_digest" NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_local_owner_pepper_activation_time_check CHECK (
"activated_at_ms" >= 0
),
FOREIGN KEY ("previous_pepper_key_id")
REFERENCES "QingLong3LocalOwnerPepperKeys" ("pepper_key_id")
ON DELETE RESTRICT,
FOREIGN KEY ("active_pepper_key_id")
REFERENCES "QingLong3LocalOwnerPepperKeys" ("pepper_key_id")
ON DELETE RESTRICT
)
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_pepper_activation_mutation_uidx"
ON "QingLong3LocalOwnerPepperActivations" ("mutation_id")
`,
`
CREATE INDEX "ql3_local_owner_pepper_activation_key_idx"
ON "QingLong3LocalOwnerPepperActivations" (
"active_pepper_key_id", "generation" DESC
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0020CapabilityV10Migration = defineLocalSqliteMigration({
id: '0020-capability-v10',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 10,
migration_id = '0019-local-owner-pepper-catalog',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 9
AND migration_id = '0017-api-credential-pepper-bindings'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1}'
`,
],
});
@@ -0,0 +1,140 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0021LocalOwnerCredentialRecoveryMigration =
defineLocalSqliteMigration({
id: '0021-local-owner-credential-recovery',
statements: [
`
CREATE TABLE "QingLong3LocalOwnerCredentialRecoveries" (
"issue_mutation_id" TEXT PRIMARY KEY NOT NULL,
"issue_request_id" TEXT NOT NULL,
"subject_type" TEXT NOT NULL,
"subject_id" TEXT NOT NULL,
"previous_credential_id" TEXT NOT NULL,
"previous_credential_version" INTEGER NOT NULL,
"replacement_credential_id" TEXT NOT NULL,
"replacement_credential_version" INTEGER NOT NULL,
"state" TEXT NOT NULL,
"issued_at_ms" INTEGER NOT NULL,
"issue_audit_event_id" TEXT NOT NULL,
"delivery_digest" TEXT,
"acknowledged_at_ms" INTEGER,
"complete_mutation_id" TEXT,
"complete_request_id" TEXT,
"revoked_credential_version" INTEGER,
"completed_at_ms" INTEGER,
"complete_audit_event_id" TEXT,
FOREIGN KEY ("subject_type", "subject_id")
REFERENCES "QingLong3IdentitySubjects" ("subject_type", "subject_id")
ON DELETE RESTRICT,
FOREIGN KEY ("previous_credential_id", "previous_credential_version")
REFERENCES "QingLong3ApiCredentials" ("credential_id", "version")
ON DELETE RESTRICT,
FOREIGN KEY ("replacement_credential_id", "replacement_credential_version")
REFERENCES "QingLong3ApiCredentials" ("credential_id", "version")
ON DELETE RESTRICT,
FOREIGN KEY ("previous_credential_id", "revoked_credential_version")
REFERENCES "QingLong3ApiCredentials" ("credential_id", "version")
ON DELETE RESTRICT,
FOREIGN KEY ("issue_audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
FOREIGN KEY ("complete_audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_local_owner_recovery_mutation_check CHECK (
length("issue_mutation_id") = 36
AND "issue_audit_event_id" = "issue_mutation_id"
AND ("complete_mutation_id" IS NULL OR (
length("complete_mutation_id") = 36
AND "complete_audit_event_id" = "complete_mutation_id"
AND "complete_mutation_id" <> "issue_mutation_id"
))
),
CONSTRAINT ql3_local_owner_recovery_request_check CHECK (
length("issue_request_id") BETWEEN 1 AND 128
AND ("complete_request_id" IS NULL
OR length("complete_request_id") BETWEEN 1 AND 128)
),
CONSTRAINT ql3_local_owner_recovery_identity_check CHECK (
"subject_type" = 'user'
AND length("subject_id") = 26
AND "subject_id" GLOB 'usr_*'
AND "subject_id" NOT GLOB '*[^A-Za-z0-9_-]*'
),
CONSTRAINT ql3_local_owner_recovery_credential_check CHECK (
length("previous_credential_id") BETWEEN 1 AND 64
AND "previous_credential_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
AND "previous_credential_version" BETWEEN 1 AND 2147483646
AND length("replacement_credential_id") BETWEEN 1 AND 64
AND "replacement_credential_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
AND "replacement_credential_id" <> "previous_credential_id"
AND "replacement_credential_version" = 1
AND ("revoked_credential_version" IS NULL
OR "revoked_credential_version" = "previous_credential_version" + 1)
),
CONSTRAINT ql3_local_owner_recovery_digest_check CHECK (
"delivery_digest" IS NULL OR (
length("delivery_digest") = 64
AND "delivery_digest" NOT GLOB '*[^0-9a-f]*'
)
),
CONSTRAINT ql3_local_owner_recovery_time_check CHECK (
"issued_at_ms" >= 0
AND ("acknowledged_at_ms" IS NULL
OR "acknowledged_at_ms" >= "issued_at_ms")
AND ("completed_at_ms" IS NULL
OR "completed_at_ms" >= "acknowledged_at_ms")
),
CONSTRAINT ql3_local_owner_recovery_shape_check CHECK (
("state" = 'issued'
AND "delivery_digest" IS NULL
AND "acknowledged_at_ms" IS NULL
AND "complete_mutation_id" IS NULL
AND "complete_request_id" IS NULL
AND "revoked_credential_version" IS NULL
AND "completed_at_ms" IS NULL
AND "complete_audit_event_id" IS NULL)
OR ("state" = 'acknowledged'
AND "delivery_digest" IS NOT NULL
AND "acknowledged_at_ms" IS NOT NULL
AND "complete_mutation_id" IS NULL
AND "complete_request_id" IS NULL
AND "revoked_credential_version" IS NULL
AND "completed_at_ms" IS NULL
AND "complete_audit_event_id" IS NULL)
OR ("state" = 'completed'
AND "delivery_digest" IS NOT NULL
AND "acknowledged_at_ms" IS NOT NULL
AND "complete_mutation_id" IS NOT NULL
AND "complete_request_id" IS NOT NULL
AND "revoked_credential_version" IS NOT NULL
AND "completed_at_ms" IS NOT NULL
AND "complete_audit_event_id" IS NOT NULL)
)
)
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_recovery_open_subject_uidx"
ON "QingLong3LocalOwnerCredentialRecoveries" ("subject_id")
WHERE "state" <> 'completed'
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_recovery_replacement_uidx"
ON "QingLong3LocalOwnerCredentialRecoveries" (
"replacement_credential_id", "replacement_credential_version"
)
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_recovery_complete_mutation_uidx"
ON "QingLong3LocalOwnerCredentialRecoveries" ("complete_mutation_id")
WHERE "complete_mutation_id" IS NOT NULL
`,
`
CREATE INDEX "ql3_local_owner_recovery_previous_idx"
ON "QingLong3LocalOwnerCredentialRecoveries" (
"previous_credential_id", "previous_credential_version", "state"
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0022CapabilityV11Migration = defineLocalSqliteMigration({
id: '0022-capability-v11',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 11,
migration_id = '0021-local-owner-credential-recovery',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 10
AND migration_id = '0019-local-owner-pepper-catalog'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1}'
`,
],
});
@@ -0,0 +1,133 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0023LocalOwnerPepperMaterialGcMigration =
defineLocalSqliteMigration({
id: '0023-local-owner-pepper-material-gc',
statements: [
`
CREATE TABLE "QingLong3LocalOwnerPepperMaterialGc" (
"prepare_mutation_id" TEXT PRIMARY KEY NOT NULL,
"prepare_request_id" TEXT NOT NULL,
"pepper_key_id" TEXT NOT NULL,
"material_digest" TEXT NOT NULL,
"backup_material_digest" TEXT NOT NULL,
"active_pepper_key_id" TEXT NOT NULL,
"active_generation" INTEGER NOT NULL,
"active_material_digest" TEXT NOT NULL,
"retention_policy_version" INTEGER NOT NULL,
"acknowledgement_retention_ms" INTEGER NOT NULL,
"audit_retention_ms" INTEGER NOT NULL,
"backup_retention_ms" INTEGER NOT NULL,
"retention_policy_digest" TEXT NOT NULL,
"references_inspected_at_ms" INTEGER NOT NULL,
"retention_eligible_at_ms" INTEGER NOT NULL,
"prepared_at_ms" INTEGER NOT NULL,
"prepare_audit_event_id" TEXT NOT NULL,
"state" TEXT NOT NULL,
"complete_mutation_id" TEXT,
"complete_request_id" TEXT,
"destruction_proof_digest" TEXT,
"completed_at_ms" INTEGER,
"complete_audit_event_id" TEXT,
FOREIGN KEY ("pepper_key_id")
REFERENCES "QingLong3LocalOwnerPepperKeys" ("pepper_key_id")
ON DELETE RESTRICT,
FOREIGN KEY ("active_pepper_key_id")
REFERENCES "QingLong3LocalOwnerPepperKeys" ("pepper_key_id")
ON DELETE RESTRICT,
FOREIGN KEY ("active_generation")
REFERENCES "QingLong3LocalOwnerPepperActivations" ("generation")
ON DELETE RESTRICT,
FOREIGN KEY ("prepare_audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
FOREIGN KEY ("complete_audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_local_owner_pepper_gc_mutation_check CHECK (
length("prepare_mutation_id") = 36
AND "prepare_audit_event_id" = "prepare_mutation_id"
AND ("complete_mutation_id" IS NULL OR (
length("complete_mutation_id") = 36
AND "complete_mutation_id" <> "prepare_mutation_id"
AND "complete_audit_event_id" = "complete_mutation_id"
))
),
CONSTRAINT ql3_local_owner_pepper_gc_request_check CHECK (
length("prepare_request_id") BETWEEN 1 AND 128
AND ("complete_request_id" IS NULL
OR length("complete_request_id") BETWEEN 1 AND 128)
),
CONSTRAINT ql3_local_owner_pepper_gc_key_check CHECK (
length("pepper_key_id") BETWEEN 1 AND 64
AND "pepper_key_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
AND length("active_pepper_key_id") BETWEEN 1 AND 64
AND "active_pepper_key_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
AND "pepper_key_id" <> "active_pepper_key_id"
AND "active_generation" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_local_owner_pepper_gc_digest_check CHECK (
length("material_digest") = 64
AND "material_digest" NOT GLOB '*[^0-9a-f]*'
AND length("backup_material_digest") = 64
AND "backup_material_digest" NOT GLOB '*[^0-9a-f]*'
AND length("active_material_digest") = 64
AND "active_material_digest" NOT GLOB '*[^0-9a-f]*'
AND length("retention_policy_digest") = 64
AND "retention_policy_digest" NOT GLOB '*[^0-9a-f]*'
AND ("destruction_proof_digest" IS NULL OR (
length("destruction_proof_digest") = 64
AND "destruction_proof_digest" NOT GLOB '*[^0-9a-f]*'
))
),
CONSTRAINT ql3_local_owner_pepper_gc_retention_check CHECK (
"retention_policy_version" = 1
AND "acknowledgement_retention_ms" BETWEEN 604800000 AND 315360000000
AND "audit_retention_ms" BETWEEN 2592000000 AND 315360000000
AND "backup_retention_ms" BETWEEN 2592000000 AND 315360000000
),
CONSTRAINT ql3_local_owner_pepper_gc_time_check CHECK (
"references_inspected_at_ms" = "prepared_at_ms"
AND "retention_eligible_at_ms" <= "prepared_at_ms"
AND "prepared_at_ms" >= 0
AND ("completed_at_ms" IS NULL
OR "completed_at_ms" >= "prepared_at_ms")
),
CONSTRAINT ql3_local_owner_pepper_gc_shape_check CHECK (
("state" = 'prepared'
AND "complete_mutation_id" IS NULL
AND "complete_request_id" IS NULL
AND "destruction_proof_digest" IS NULL
AND "completed_at_ms" IS NULL
AND "complete_audit_event_id" IS NULL)
OR ("state" = 'completed'
AND "complete_mutation_id" IS NOT NULL
AND "complete_request_id" IS NOT NULL
AND "destruction_proof_digest" IS NOT NULL
AND "completed_at_ms" IS NOT NULL
AND "complete_audit_event_id" IS NOT NULL)
)
)
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_pepper_gc_key_uidx"
ON "QingLong3LocalOwnerPepperMaterialGc" ("pepper_key_id")
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_pepper_gc_open_uidx"
ON "QingLong3LocalOwnerPepperMaterialGc" ("state")
WHERE "state" = 'prepared'
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_pepper_gc_complete_mutation_uidx"
ON "QingLong3LocalOwnerPepperMaterialGc" ("complete_mutation_id")
WHERE "complete_mutation_id" IS NOT NULL
`,
`
CREATE INDEX "ql3_local_owner_pepper_gc_state_idx"
ON "QingLong3LocalOwnerPepperMaterialGc" (
"state", "retention_eligible_at_ms", "pepper_key_id"
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0024CapabilityV12Migration = defineLocalSqliteMigration({
id: '0024-capability-v12',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 12,
migration_id = '0023-local-owner-pepper-material-gc',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 11
AND migration_id = '0021-local-owner-credential-recovery'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1}'
`,
],
});
@@ -0,0 +1,91 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0025LocalOwnerDeliveryAcknowledgementGcMigration =
defineLocalSqliteMigration({
id: '0025-local-owner-delivery-acknowledgement-gc',
statements: [
`
CREATE TABLE "QingLong3LocalOwnerDeliveryAcknowledgementGc" (
"gc_mutation_id" TEXT PRIMARY KEY NOT NULL,
"gc_request_id" TEXT NOT NULL,
"acknowledgement_mutation_id" TEXT NOT NULL,
"acknowledgement_kind" TEXT NOT NULL,
"delivery_digest" TEXT NOT NULL,
"acknowledged_at_ms" INTEGER NOT NULL,
"acknowledgement_semantic_digest" TEXT NOT NULL,
"bridge_clear_evidence_digest" TEXT NOT NULL,
"retention_policy_version" INTEGER NOT NULL,
"replay_retention_ms" INTEGER NOT NULL,
"audit_retention_ms" INTEGER NOT NULL,
"retention_policy_digest" TEXT NOT NULL,
"retention_eligible_at_ms" INTEGER NOT NULL,
"compacted_at_ms" INTEGER NOT NULL,
"audit_event_id" TEXT NOT NULL,
"provisioning_mutation_id" TEXT,
"challenge_mutation_id" TEXT,
FOREIGN KEY ("audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
FOREIGN KEY ("provisioning_mutation_id")
REFERENCES "QingLong3LocalIdentityProvisionings" ("mutation_id")
ON DELETE RESTRICT,
FOREIGN KEY ("challenge_mutation_id")
REFERENCES "QingLong3LocalOwnerBootstrapChallenges" ("issue_mutation_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_local_owner_delivery_ack_gc_mutation_check CHECK (
length("gc_mutation_id") = 36
AND "audit_event_id" = "gc_mutation_id"
AND length("acknowledgement_mutation_id") = 36
AND "acknowledgement_mutation_id" <> "gc_mutation_id"
),
CONSTRAINT ql3_local_owner_delivery_ack_gc_request_check CHECK (
length("gc_request_id") BETWEEN 1 AND 128
),
CONSTRAINT ql3_local_owner_delivery_ack_gc_digest_check CHECK (
length("delivery_digest") = 64
AND "delivery_digest" NOT GLOB '*[^0-9a-f]*'
AND length("acknowledgement_semantic_digest") = 64
AND "acknowledgement_semantic_digest" NOT GLOB '*[^0-9a-f]*'
AND length("bridge_clear_evidence_digest") = 64
AND "bridge_clear_evidence_digest" NOT GLOB '*[^0-9a-f]*'
AND length("retention_policy_digest") = 64
AND "retention_policy_digest" NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_local_owner_delivery_ack_gc_retention_check CHECK (
"retention_policy_version" = 1
AND "replay_retention_ms" BETWEEN 2592000000 AND 315360000000
AND "audit_retention_ms" BETWEEN 2592000000 AND 315360000000
),
CONSTRAINT ql3_local_owner_delivery_ack_gc_time_check CHECK (
"acknowledged_at_ms" >= 0
AND "retention_eligible_at_ms" <= "compacted_at_ms"
AND "compacted_at_ms" >= "acknowledged_at_ms"
),
CONSTRAINT ql3_local_owner_delivery_ack_gc_shape_check CHECK (
(
"acknowledgement_kind" = 'credential'
AND "provisioning_mutation_id" = "acknowledgement_mutation_id"
AND "challenge_mutation_id" IS NULL
)
OR (
"acknowledgement_kind" = 'challenge'
AND "provisioning_mutation_id" IS NULL
AND "challenge_mutation_id" = "acknowledgement_mutation_id"
)
)
)
`,
`
CREATE UNIQUE INDEX "ql3_local_owner_delivery_ack_gc_ack_uidx"
ON "QingLong3LocalOwnerDeliveryAcknowledgementGc" (
"acknowledgement_mutation_id"
)
`,
`
CREATE INDEX "ql3_local_owner_delivery_ack_gc_compacted_idx"
ON "QingLong3LocalOwnerDeliveryAcknowledgementGc" (
"acknowledgement_kind", "compacted_at_ms", "acknowledgement_mutation_id"
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0026CapabilityV13Migration = defineLocalSqliteMigration({
id: '0026-capability-v13',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 13,
migration_id = '0025-local-owner-delivery-acknowledgement-gc',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 12
AND migration_id = '0023-local-owner-pepper-material-gc'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1}'
`,
],
});
@@ -0,0 +1,100 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0027TaskDefinitionsMigration = defineLocalSqliteMigration({
id: '0027-task-definitions',
statements: [
`
CREATE TABLE "QingLong3TaskDefinitions" (
"project_id" TEXT NOT NULL,
"task_id" TEXT NOT NULL,
"current_revision" INTEGER NOT NULL,
"created_at_ms" INTEGER NOT NULL,
"updated_at_ms" INTEGER NOT NULL,
PRIMARY KEY ("project_id", "task_id"),
FOREIGN KEY ("project_id")
REFERENCES "QingLong3Projects" ("id")
ON DELETE RESTRICT,
CONSTRAINT ql3_task_definitions_id_check CHECK (
length("project_id") BETWEEN 1 AND 128
AND length("task_id") BETWEEN 1 AND 128
AND "project_id" NOT GLOB '*[' || char(0) || '-' || char(31) || char(127) || ']*'
AND "task_id" NOT GLOB '*[' || char(0) || '-' || char(31) || char(127) || ']*'
),
CONSTRAINT ql3_task_definitions_revision_check CHECK (
"current_revision" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_task_definitions_time_check CHECK (
"created_at_ms" >= 0 AND "updated_at_ms" >= "created_at_ms"
)
)
`,
`
CREATE TABLE "QingLong3TaskDefinitionRevisions" (
"project_id" TEXT NOT NULL,
"task_id" TEXT NOT NULL,
"revision" INTEGER NOT NULL,
"mutation_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"kind" TEXT NOT NULL,
"spec_json" TEXT NOT NULL,
"labels_json" TEXT NOT NULL,
"enabled" INTEGER NOT NULL,
"content_digest" TEXT NOT NULL,
"created_at_ms" INTEGER NOT NULL,
PRIMARY KEY ("project_id", "task_id", "revision"),
FOREIGN KEY ("project_id", "task_id")
REFERENCES "QingLong3TaskDefinitions" ("project_id", "task_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_task_definition_revisions_revision_check CHECK (
"revision" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_task_definition_revisions_mutation_check CHECK (
length("mutation_id") = 36
AND substr("mutation_id", 9, 1) = '-'
AND substr("mutation_id", 14, 1) = '-'
AND substr("mutation_id", 19, 1) = '-'
AND substr("mutation_id", 24, 1) = '-'
AND replace("mutation_id", '-', '') NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_task_definition_revisions_name_check CHECK (
length("name") BETWEEN 1 AND 255
AND ("description" IS NULL OR length("description") BETWEEN 1 AND 4096)
),
CONSTRAINT ql3_task_definition_revisions_kind_check CHECK (
"kind" IN ('script','command','workflow','agent','tool')
),
CONSTRAINT ql3_task_definition_revisions_spec_check CHECK (
json_valid("spec_json")
AND json_type("spec_json") = 'object'
AND length("spec_json") BETWEEN 1 AND 65536
),
CONSTRAINT ql3_task_definition_revisions_labels_check CHECK (
json_valid("labels_json")
AND json_type("labels_json") = 'object'
AND length("labels_json") BETWEEN 2 AND 16384
),
CONSTRAINT ql3_task_definition_revisions_enabled_check CHECK (
"enabled" IN (0, 1)
),
CONSTRAINT ql3_task_definition_revisions_digest_check CHECK (
length("content_digest") = 64
AND "content_digest" NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_task_definition_revisions_created_check CHECK (
"created_at_ms" >= 0
)
)
`,
`
CREATE UNIQUE INDEX "ql3_task_definition_revisions_mutation_uidx"
ON "QingLong3TaskDefinitionRevisions" ("mutation_id")
`,
`
CREATE INDEX "ql3_task_definition_revisions_project_kind_idx"
ON "QingLong3TaskDefinitionRevisions" (
"project_id", "kind", "enabled", "task_id", "revision"
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0028CapabilityV14Migration = defineLocalSqliteMigration({
id: '0028-capability-v14',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 14,
migration_id = '0027-task-definitions',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 13
AND migration_id = '0025-local-owner-delivery-acknowledgement-gc'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1}'
`,
],
});
@@ -0,0 +1,261 @@
import {
createLocalTaskExecutionRevision,
type LocalDispatchCommand,
} from '@qinglong/runtime-core/local-dispatch';
import {
normalizeTaskDefinitionRecord,
type TaskDefinitionRecord,
} from '@qinglong/runtime-core/task-definition';
import { compileLocalCommandTaskDefinition } from '@qinglong/runtime-core/task-definition-execution-compiler';
import {
BUILT_IN_COMMAND_TASK_SPEC_SCHEMA,
createBuiltInTaskSpecSemanticRegistry,
} from '@qinglong/runtime-core/task-spec-semantic';
import { LocalSqliteDispatchDefinitionStore } from '../task-definition/dispatchDefinitionStore';
import { defineLocalSqliteProgrammaticMigration } from './sqlMigration';
const REPLACEMENT_TABLE = `
CREATE TABLE "QingLong3LocalTaskExecutionRevisions_v15" (
"project_id" TEXT NOT NULL,
"task_id" TEXT NOT NULL,
"task_revision" TEXT NOT NULL,
"executor_type" TEXT NOT NULL,
"command_json" TEXT NOT NULL,
"working_directory" TEXT,
"timeout_ms" INTEGER,
"context_ref" TEXT NOT NULL,
"content_digest" TEXT NOT NULL,
"created_at_ms" INTEGER NOT NULL,
PRIMARY KEY ("project_id", "task_id", "task_revision"),
CONSTRAINT ql3_local_revision_executor_check CHECK (
"executor_type" = 'local_process'
),
CONSTRAINT ql3_local_revision_command_check CHECK (
json_valid("command_json")
AND json_type("command_json") = 'object'
AND length("command_json") BETWEEN 1 AND 131072
),
CONSTRAINT ql3_local_revision_working_directory_check CHECK (
"working_directory" IS NULL
OR (length("working_directory") BETWEEN 1 AND 4096
AND substr("working_directory", 1, 1) = '/')
),
CONSTRAINT ql3_local_revision_timeout_check CHECK (
"timeout_ms" IS NULL OR "timeout_ms" BETWEEN 1 AND 31536000000
),
CONSTRAINT ql3_local_revision_digest_check CHECK (
length("content_digest") = 64
AND "content_digest" NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_local_revision_created_check CHECK ("created_at_ms" >= 0),
FOREIGN KEY ("context_ref")
REFERENCES "QingLong3LocalExecutionContextRecipes" ("context_ref")
ON DELETE RESTRICT
)
`;
type RevisionRow = Record<string, unknown>;
function requiredText(row: RevisionRow, key: string): string {
const value = row[key];
if (typeof value !== 'string') {
throw new TypeError(`Local execution revision ${key} is invalid`);
}
return value;
}
function optionalText(row: RevisionRow, key: string): string | undefined {
const value = row[key];
if (value === null) return undefined;
return requiredText(row, key);
}
function requiredInteger(row: RevisionRow, key: string): number {
const value = row[key];
if (!Number.isSafeInteger(value)) {
throw new TypeError(`Local execution revision ${key} is invalid`);
}
return value as number;
}
function optionalInteger(row: RevisionRow, key: string): number | undefined {
const value = row[key];
if (value === null) return undefined;
return requiredInteger(row, key);
}
function command(row: RevisionRow): LocalDispatchCommand {
try {
return JSON.parse(requiredText(row, 'commandJson')) as LocalDispatchCommand;
} catch {
throw new TypeError('Local execution revision commandJson is invalid');
}
}
function requiredJson(row: RevisionRow, key: string): unknown {
try {
return JSON.parse(requiredText(row, key)) as unknown;
} catch {
throw new TypeError(`TaskDefinition ${key} is invalid`);
}
}
function taskDefinition(row: RevisionRow): TaskDefinitionRecord {
const description = optionalText(row, 'description');
const enabled = requiredInteger(row, 'enabled');
if (enabled !== 0 && enabled !== 1) {
throw new TypeError('TaskDefinition enabled is invalid');
}
return normalizeTaskDefinitionRecord({
projectId: requiredText(row, 'projectId'),
taskId: requiredText(row, 'taskId'),
revision: requiredInteger(row, 'revision'),
mutationId: requiredText(row, 'mutationId'),
name: requiredText(row, 'name'),
...(description === undefined ? {} : { description }),
kind: requiredText(row, 'kind') as TaskDefinitionRecord['kind'],
spec: requiredJson(row, 'specJson') as TaskDefinitionRecord['spec'],
labels: requiredJson(row, 'labelsJson') as TaskDefinitionRecord['labels'],
enabled: enabled === 1,
contentDigest: requiredText(row, 'taskContentDigest'),
createdAtMs: requiredInteger(row, 'taskCreatedAtMs'),
updatedAtMs: requiredInteger(row, 'taskUpdatedAtMs'),
});
}
export const local0029LocalExecutionRevisionDigestMigration =
defineLocalSqliteProgrammaticMigration({
id: '0029-local-execution-revision-digest',
program: [
REPLACEMENT_TABLE,
`Read every legacy revision in primary-key order without buffering.
Parse and normalize command_json through
createLocalTaskExecutionRevision using digest domain
qinglong.local-task-execution-revision.v1 NUL. The digest covers
project, task, revision, executor, canonical command, optional working
directory, optional timeout and context ref; created_at_ms is excluded
so an exact semantic replay preserves the first observed timestamp.`,
`Insert every normalized row and computed lowercase SHA-256 digest into
QingLong3LocalTaskExecutionRevisions_v15. Reject malformed legacy rows,
count drift, constraint failures or digest failures so the enclosing
BEGIN IMMEDIATE transaction rolls the entire migration back.`,
`After the digest table replacement, read every historical enabled
qinglong/command@v1 TaskDefinition revision in primary-key order,
revalidate its stored TaskDefinition content digest and built-in
semantics, compile the deterministic local plan, and append its context
recipe and execution revision through exact-content replay. Skip
disabled and non-built-in revisions; reject missing Projects, corrupt
built-in records or pre-existing derived identity conflicts.`,
'DROP TABLE "QingLong3LocalTaskExecutionRevisions"',
`ALTER TABLE "QingLong3LocalTaskExecutionRevisions_v15"
RENAME TO "QingLong3LocalTaskExecutionRevisions"`,
],
up({ client }) {
client.exec(REPLACEMENT_TABLE);
const rows = client.prepare(
`SELECT "project_id" AS "projectId", "task_id" AS "taskId",
"task_revision" AS "taskRevision",
"executor_type" AS "executorType",
"command_json" AS "commandJson",
"working_directory" AS "workingDirectory",
"timeout_ms" AS "timeoutMs", "context_ref" AS "contextRef",
"created_at_ms" AS "createdAtMs"
FROM "QingLong3LocalTaskExecutionRevisions"
ORDER BY "project_id", "task_id", "task_revision"`,
);
const insert = client.prepare(
`INSERT INTO "QingLong3LocalTaskExecutionRevisions_v15" (
"project_id", "task_id", "task_revision", "executor_type",
"command_json", "working_directory", "timeout_ms", "context_ref",
"content_digest", "created_at_ms"
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
);
let backfilled = 0;
for (const value of rows.iterate() as Iterable<RevisionRow>) {
const executorType = requiredText(value, 'executorType');
if (executorType !== 'local_process') {
throw new TypeError('Local execution revision executorType is invalid');
}
const workingDirectory = optionalText(value, 'workingDirectory');
const timeoutMs = optionalInteger(value, 'timeoutMs');
const revision = createLocalTaskExecutionRevision({
projectId: requiredText(value, 'projectId'),
taskId: requiredText(value, 'taskId'),
taskRevision: requiredText(value, 'taskRevision'),
executorType,
command: command(value),
...(workingDirectory === undefined ? {} : { workingDirectory }),
...(timeoutMs === undefined ? {} : { timeoutMs }),
contextRef: requiredText(value, 'contextRef'),
createdAtMs: requiredInteger(value, 'createdAtMs'),
});
insert.run(
revision.projectId,
revision.taskId,
revision.taskRevision,
revision.executorType,
JSON.stringify(revision.command),
revision.workingDirectory ?? null,
revision.timeoutMs ?? null,
revision.contextRef,
revision.contentDigest,
revision.createdAtMs,
);
backfilled += 1;
}
const sourceCount = client
.prepare(
'SELECT COUNT(*) AS "count" FROM "QingLong3LocalTaskExecutionRevisions"',
)
.get() as { count?: unknown } | undefined;
if (
!sourceCount ||
!Number.isSafeInteger(sourceCount.count) ||
sourceCount.count !== backfilled
) {
throw new TypeError('Local execution revision backfill count drifted');
}
client.exec('DROP TABLE "QingLong3LocalTaskExecutionRevisions"');
client.exec(
`ALTER TABLE "QingLong3LocalTaskExecutionRevisions_v15"
RENAME TO "QingLong3LocalTaskExecutionRevisions"`,
);
const semanticRegistry = createBuiltInTaskSpecSemanticRegistry();
const definitions = client.prepare(
`SELECT revision."project_id" AS "projectId",
revision."task_id" AS "taskId",
revision."revision" AS "revision",
revision."mutation_id" AS "mutationId",
revision."name" AS "name",
revision."description" AS "description",
revision."kind" AS "kind",
revision."spec_json" AS "specJson",
revision."labels_json" AS "labelsJson",
revision."enabled" AS "enabled",
revision."content_digest" AS "taskContentDigest",
head."created_at_ms" AS "taskCreatedAtMs",
revision."created_at_ms" AS "taskUpdatedAtMs"
FROM "QingLong3TaskDefinitionRevisions" AS revision
JOIN "QingLong3TaskDefinitions" AS head
ON head."project_id" = revision."project_id"
AND head."task_id" = revision."task_id"
ORDER BY revision."project_id", revision."task_id",
revision."revision"`,
);
const dispatchDefinitions = new LocalSqliteDispatchDefinitionStore(client);
for (const value of definitions.iterate() as Iterable<RevisionRow>) {
const definition = taskDefinition(value);
if (
!definition.enabled ||
definition.kind !== 'command' ||
definition.spec.schema !== BUILT_IN_COMMAND_TASK_SPEC_SCHEMA
) {
continue;
}
dispatchDefinitions.appendPlan(
compileLocalCommandTaskDefinition(definition, semanticRegistry),
);
}
},
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0030CapabilityV15Migration = defineLocalSqliteMigration({
id: '0030-capability-v15',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 15,
migration_id = '0029-local-execution-revision-digest',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 14
AND migration_id = '0027-task-definitions'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1}'
`,
],
});
@@ -0,0 +1,111 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0031TriggerDefinitionsMigration = defineLocalSqliteMigration({
id: '0031-trigger-definitions',
statements: [
`
CREATE TABLE "QingLong3Triggers" (
"project_id" TEXT NOT NULL,
"trigger_id" TEXT NOT NULL,
"task_id" TEXT NOT NULL,
"current_revision" INTEGER NOT NULL,
"created_at_ms" INTEGER NOT NULL,
"updated_at_ms" INTEGER NOT NULL,
PRIMARY KEY ("project_id", "trigger_id"),
FOREIGN KEY ("project_id")
REFERENCES "QingLong3Projects" ("id")
ON DELETE RESTRICT,
FOREIGN KEY ("project_id", "task_id")
REFERENCES "QingLong3TaskDefinitions" ("project_id", "task_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_triggers_id_check CHECK (
length("project_id") BETWEEN 1 AND 128
AND length("trigger_id") BETWEEN 1 AND 128
AND length("task_id") BETWEEN 1 AND 128
AND "project_id" NOT GLOB '*[' || char(0) || '-' || char(31) || char(127) || ']*'
AND "trigger_id" NOT GLOB '*[' || char(0) || '-' || char(31) || char(127) || ']*'
AND "task_id" NOT GLOB '*[' || char(0) || '-' || char(31) || char(127) || ']*'
),
CONSTRAINT ql3_triggers_revision_check CHECK (
"current_revision" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_triggers_time_check CHECK (
"created_at_ms" >= 0 AND "updated_at_ms" >= "created_at_ms"
)
)
`,
`
CREATE UNIQUE INDEX "ql3_triggers_task_uidx"
ON "QingLong3Triggers" ("project_id", "trigger_id", "task_id")
`,
`
CREATE TABLE "QingLong3TriggerRevisions" (
"project_id" TEXT NOT NULL,
"trigger_id" TEXT NOT NULL,
"revision" INTEGER NOT NULL,
"mutation_id" TEXT NOT NULL,
"task_id" TEXT NOT NULL,
"task_revision" INTEGER NOT NULL,
"task_content_digest" TEXT NOT NULL,
"spec_json" TEXT NOT NULL,
"enabled" INTEGER NOT NULL,
"content_digest" TEXT NOT NULL,
"created_at_ms" INTEGER NOT NULL,
PRIMARY KEY ("project_id", "trigger_id", "revision"),
FOREIGN KEY ("project_id", "trigger_id", "task_id")
REFERENCES "QingLong3Triggers" ("project_id", "trigger_id", "task_id")
ON DELETE RESTRICT,
FOREIGN KEY ("project_id", "task_id", "task_revision")
REFERENCES "QingLong3TaskDefinitionRevisions" ("project_id", "task_id", "revision")
ON DELETE RESTRICT,
CONSTRAINT ql3_trigger_revisions_revision_check CHECK (
"revision" BETWEEN 1 AND 2147483647
AND "task_revision" BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_trigger_revisions_mutation_check CHECK (
length("mutation_id") = 36
AND substr("mutation_id", 9, 1) = '-'
AND substr("mutation_id", 14, 1) = '-'
AND substr("mutation_id", 19, 1) = '-'
AND substr("mutation_id", 24, 1) = '-'
AND replace("mutation_id", '-', '') NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_trigger_revisions_task_digest_check CHECK (
length("task_content_digest") = 64
AND "task_content_digest" NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_trigger_revisions_spec_check CHECK (
json_valid("spec_json")
AND json_type("spec_json") = 'object'
AND length("spec_json") BETWEEN 1 AND 16384
),
CONSTRAINT ql3_trigger_revisions_enabled_check CHECK (
"enabled" IN (0, 1)
),
CONSTRAINT ql3_trigger_revisions_digest_check CHECK (
length("content_digest") = 64
AND "content_digest" NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_trigger_revisions_created_check CHECK (
"created_at_ms" >= 0
)
)
`,
`
CREATE UNIQUE INDEX "ql3_trigger_revisions_mutation_uidx"
ON "QingLong3TriggerRevisions" ("mutation_id")
`,
`
CREATE INDEX "ql3_trigger_revisions_project_enabled_idx"
ON "QingLong3TriggerRevisions" (
"project_id", "enabled", "trigger_id", "revision"
)
`,
`
CREATE INDEX "ql3_trigger_revisions_task_idx"
ON "QingLong3TriggerRevisions" (
"project_id", "task_id", "task_revision", "trigger_id", "revision"
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0032CapabilityV16Migration = defineLocalSqliteMigration({
id: '0032-capability-v16',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 16,
migration_id = '0031-trigger-definitions',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 15
AND migration_id = '0029-local-execution-revision-digest'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1}'
`,
],
});
@@ -0,0 +1,80 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0033LegacyAdoptionLedgerMigration =
defineLocalSqliteMigration({
id: '0033-legacy-adoption-ledger',
statements: [
`
CREATE TABLE "QingLong3LegacyAdoptions" (
"mutation_id" TEXT PRIMARY KEY,
"decision_id" TEXT NOT NULL,
"project_id" TEXT NOT NULL,
"profile" TEXT NOT NULL,
"plan_digest" TEXT NOT NULL,
"inventory_digest" TEXT NOT NULL,
"decision_digest" TEXT NOT NULL,
"receipt_digest" TEXT NOT NULL,
"authorization_file_digest" TEXT NOT NULL,
"publication_digest" TEXT NOT NULL,
"row_count" INTEGER NOT NULL,
"adopted_task_count" INTEGER NOT NULL,
"adopted_trigger_count" INTEGER NOT NULL,
"skipped_count" INTEGER NOT NULL,
"audit_event_id" TEXT NOT NULL,
"created_at_ms" INTEGER NOT NULL,
FOREIGN KEY ("project_id")
REFERENCES "QingLong3Projects" ("id")
ON DELETE RESTRICT,
FOREIGN KEY ("audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_legacy_adoptions_mutation_check CHECK (
length("mutation_id") = 36
AND "audit_event_id" = "mutation_id"
),
CONSTRAINT ql3_legacy_adoptions_decision_check CHECK (
length("decision_id") = 36
AND substr("decision_id", 15, 1) = '7'
AND replace("decision_id", '-', '') NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_legacy_adoptions_profile_check CHECK (
"profile" IN ('edge', 'standalone')
),
CONSTRAINT ql3_legacy_adoptions_digest_check CHECK (
length("plan_digest") = 64
AND "plan_digest" NOT GLOB '*[^0-9a-f]*'
AND length("inventory_digest") = 64
AND "inventory_digest" NOT GLOB '*[^0-9a-f]*'
AND length("decision_digest") = 64
AND "decision_digest" NOT GLOB '*[^0-9a-f]*'
AND length("receipt_digest") = 64
AND "receipt_digest" NOT GLOB '*[^0-9a-f]*'
AND length("authorization_file_digest") = 64
AND "authorization_file_digest" NOT GLOB '*[^0-9a-f]*'
AND length("publication_digest") = 64
AND "publication_digest" NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_legacy_adoptions_count_check CHECK (
"row_count" BETWEEN 0 AND 100000
AND "adopted_task_count" BETWEEN 0 AND "row_count"
AND "skipped_count" BETWEEN 0 AND "row_count"
AND "adopted_task_count" + "skipped_count" = "row_count"
AND "adopted_trigger_count" BETWEEN 0 AND 500000
),
CONSTRAINT ql3_legacy_adoptions_created_check CHECK (
"created_at_ms" >= 0
)
)
`,
`
CREATE UNIQUE INDEX "ql3_legacy_adoptions_decision_uidx"
ON "QingLong3LegacyAdoptions" ("decision_id")
`,
`
CREATE INDEX "ql3_legacy_adoptions_project_time_idx"
ON "QingLong3LegacyAdoptions" (
"project_id", "created_at_ms" DESC, "mutation_id" DESC
)
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0034CapabilityV17Migration = defineLocalSqliteMigration({
id: '0034-capability-v17',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 17,
migration_id = '0033-legacy-adoption-ledger',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 16
AND migration_id = '0031-trigger-definitions'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1}'
`,
],
});
@@ -0,0 +1,42 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0035LocalSchedulerMigration = defineLocalSqliteMigration({
id: '0035-local-scheduler',
statements: [
`
CREATE TABLE "QingLong3LocalTriggerSchedules" (
project_id TEXT NOT NULL,
trigger_id TEXT NOT NULL,
trigger_revision INTEGER NOT NULL,
next_fire_at_ms INTEGER,
last_scheduled_at_ms INTEGER,
state_version INTEGER NOT NULL DEFAULT 0,
updated_at_ms INTEGER NOT NULL,
PRIMARY KEY (project_id, trigger_id),
CONSTRAINT ql3_local_trigger_schedules_trigger_fk
FOREIGN KEY (project_id, trigger_id)
REFERENCES "QingLong3Triggers" (project_id, trigger_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_local_trigger_schedules_revision_check
CHECK (trigger_revision BETWEEN 1 AND 2147483647),
CONSTRAINT ql3_local_trigger_schedules_state_check
CHECK (state_version >= 0),
CONSTRAINT ql3_local_trigger_schedules_time_check CHECK (
updated_at_ms >= 0 AND
(next_fire_at_ms IS NULL OR next_fire_at_ms >= 0) AND
(last_scheduled_at_ms IS NULL OR last_scheduled_at_ms >= 0)
)
)
`,
`CREATE INDEX ql3_local_trigger_schedules_due_idx ON "QingLong3LocalTriggerSchedules" (next_fire_at_ms, project_id, trigger_id) WHERE next_fire_at_ms IS NOT NULL`,
`CREATE INDEX ql3_local_trigger_schedules_initialize_idx ON "QingLong3LocalTriggerSchedules" (project_id, trigger_id) WHERE next_fire_at_ms IS NULL`,
`
INSERT INTO "QingLong3LocalTriggerSchedules" (
project_id, trigger_id, trigger_revision, next_fire_at_ms,
last_scheduled_at_ms, state_version, updated_at_ms
)
SELECT project_id, trigger_id, current_revision, NULL, NULL, 0, updated_at_ms
FROM "QingLong3Triggers"
`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0036CapabilityV18Migration = defineLocalSqliteMigration({
id: '0036-capability-v18',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 18,
migration_id = '0035-local-scheduler',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 17
AND migration_id = '0033-legacy-adoption-ledger'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1}'
`,
],
});
@@ -0,0 +1,125 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0037PluginPackageInstallsMigration =
defineLocalSqliteMigration({
id: '0037-plugin-package-installs',
statements: [
`
CREATE TABLE "QingLong3PluginPackageInstalls" (
installation_id TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
package_name TEXT NOT NULL,
package_version TEXT NOT NULL,
operation TEXT NOT NULL,
lock_digest TEXT NOT NULL,
target_generation INTEGER NOT NULL,
previous_active_lock_digest TEXT,
active_lock_digest TEXT,
state TEXT NOT NULL,
version INTEGER NOT NULL,
last_mutation_id TEXT NOT NULL,
last_mutation_digest TEXT NOT NULL,
lock_json TEXT NOT NULL,
record_json TEXT NOT NULL,
record_digest TEXT NOT NULL,
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
CONSTRAINT ql3_plugin_package_installs_project_fk
FOREIGN KEY (project_id) REFERENCES "QingLong3Projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_installs_identity_check CHECK (
length(installation_id) BETWEEN 1 AND 128 AND
length(package_name) BETWEEN 1 AND 253 AND
length(package_version) BETWEEN 1 AND 128 AND
length(last_mutation_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_plugin_package_installs_operation_check
CHECK (operation IN ('install','reinstall','upgrade','rollback')),
CONSTRAINT ql3_plugin_package_installs_state_check
CHECK (state IN ('queued','staged','activating','active','failed')),
CONSTRAINT ql3_plugin_package_installs_version_check CHECK (
target_generation BETWEEN 1 AND 2147483647 AND
version BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_plugin_package_installs_digest_check CHECK (
length(lock_digest) = 64 AND lock_digest NOT GLOB '*[^0-9a-f]*' AND
(previous_active_lock_digest IS NULL OR
(length(previous_active_lock_digest) = 64 AND
previous_active_lock_digest NOT GLOB '*[^0-9a-f]*')) AND
(active_lock_digest IS NULL OR
(length(active_lock_digest) = 64 AND
active_lock_digest NOT GLOB '*[^0-9a-f]*')) AND
length(last_mutation_digest) = 64 AND
last_mutation_digest NOT GLOB '*[^0-9a-f]*' AND
length(record_digest) = 64 AND record_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_plugin_package_installs_record_check CHECK (
length(lock_json) BETWEEN 2 AND 262144 AND
json_valid(lock_json) AND json_type(lock_json) = 'object' AND
json_extract(lock_json, '$.lockDigest') = lock_digest AND
json_extract(lock_json, '$.projectId') = project_id AND
json_extract(lock_json, '$.packageName') = package_name AND
length(record_json) BETWEEN 2 AND 262144 AND
json_valid(record_json) AND json_type(record_json) = 'object' AND
json_extract(record_json, '$.installationId') = installation_id AND
json_extract(record_json, '$.projectId') = project_id AND
json_extract(record_json, '$.packageName') = package_name AND
json_extract(record_json, '$.lockDigest') = lock_digest AND
json_extract(record_json, '$.state') = state AND
json_extract(record_json, '$.version') = version AND
json_extract(record_json, '$.recordDigest') = record_digest
),
CONSTRAINT ql3_plugin_package_installs_time_check CHECK (
created_at_ms >= 0 AND updated_at_ms >= created_at_ms
)
)
`,
`
CREATE TABLE "QingLong3PluginPackageInstallHeads" (
project_id TEXT NOT NULL,
package_name TEXT NOT NULL,
installation_id TEXT NOT NULL,
PRIMARY KEY (project_id, package_name),
CONSTRAINT ql3_plugin_package_install_heads_project_fk
FOREIGN KEY (project_id) REFERENCES "QingLong3Projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_install_heads_install_fk
FOREIGN KEY (installation_id)
REFERENCES "QingLong3PluginPackageInstalls" (installation_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_install_heads_identity_check CHECK (
length(package_name) BETWEEN 1 AND 253 AND
length(installation_id) BETWEEN 1 AND 128
)
)
`,
`CREATE UNIQUE INDEX ql3_plugin_package_install_heads_install_uidx ON "QingLong3PluginPackageInstallHeads" (installation_id)`,
`
CREATE TABLE "QingLong3PluginPackageInstallMutations" (
installation_id TEXT NOT NULL,
mutation_id TEXT NOT NULL,
mutation_digest TEXT NOT NULL,
resulting_record_digest TEXT NOT NULL,
occurred_at_ms INTEGER NOT NULL,
PRIMARY KEY (installation_id, mutation_id),
CONSTRAINT ql3_plugin_package_install_mutations_install_fk
FOREIGN KEY (installation_id)
REFERENCES "QingLong3PluginPackageInstalls" (installation_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_install_mutations_identity_check
CHECK (length(mutation_id) BETWEEN 1 AND 128),
CONSTRAINT ql3_plugin_package_install_mutations_digest_check CHECK (
length(mutation_digest) = 64 AND
mutation_digest NOT GLOB '*[^0-9a-f]*' AND
length(resulting_record_digest) = 64 AND
resulting_record_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_plugin_package_install_mutations_time_check
CHECK (occurred_at_ms >= 0)
)
`,
`CREATE INDEX ql3_plugin_package_installs_recovery_idx ON "QingLong3PluginPackageInstalls" (state, package_name, installation_id) WHERE state IN ('queued','staged','activating')`,
`CREATE INDEX ql3_plugin_package_installs_project_history_idx ON "QingLong3PluginPackageInstalls" (project_id, package_name, created_at_ms, installation_id)`,
`CREATE INDEX ql3_plugin_package_install_mutations_result_idx ON "QingLong3PluginPackageInstallMutations" (installation_id, resulting_record_digest)`,
],
});
@@ -0,0 +1,18 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0038CapabilityV19Migration = defineLocalSqliteMigration({
id: '0038-capability-v19',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 19,
migration_id = '0037-plugin-package-installs',
capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 18
AND migration_id = '0035-local-scheduler'
AND capabilities = '{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1}'
`,
],
});
@@ -0,0 +1,124 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0039ApprovedActionsMigration = defineLocalSqliteMigration({
id: '0039-approved-actions',
statements: [
`
CREATE TABLE "QingLong3ApprovalRequests" (
request_id TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
version INTEGER NOT NULL,
state TEXT NOT NULL,
action_type TEXT NOT NULL,
action_ref TEXT NOT NULL,
action_digest TEXT NOT NULL,
preview_digest TEXT NOT NULL,
requested_by_type TEXT NOT NULL,
requested_by_id TEXT NOT NULL,
decision_id TEXT,
consumption_id TEXT,
dispatch_id TEXT,
expires_at_ms INTEGER NOT NULL,
request_json TEXT NOT NULL,
request_digest TEXT NOT NULL,
updated_at_ms INTEGER NOT NULL,
CONSTRAINT ql3_approval_requests_project_fk
FOREIGN KEY (project_id) REFERENCES "QingLong3Projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_approval_requests_identity_check CHECK (
length(request_id) BETWEEN 1 AND 128 AND
length(action_type) BETWEEN 1 AND 128 AND
length(action_ref) BETWEEN 1 AND 255
),
CONSTRAINT ql3_approval_requests_state_version_check CHECK (
(state = 'pending' AND version = 1) OR
(state IN ('approved','rejected') AND version = 2) OR
(state = 'consumed' AND version = 3)
),
CONSTRAINT ql3_approval_requests_digest_check CHECK (
length(action_digest) = 64 AND action_digest NOT GLOB '*[^0-9a-f]*' AND
length(preview_digest) = 64 AND preview_digest NOT GLOB '*[^0-9a-f]*' AND
length(request_digest) = 64 AND request_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_approval_requests_subject_check CHECK (
requested_by_type IN
('user','api_app','mcp_client','agent','system','worker') AND
length(requested_by_id) BETWEEN 1 AND 255
),
CONSTRAINT ql3_approval_requests_mutation_tuple_check CHECK (
(version = 1 AND decision_id IS NULL AND consumption_id IS NULL AND
dispatch_id IS NULL) OR
(version = 2 AND decision_id IS NOT NULL AND consumption_id IS NULL AND
dispatch_id IS NULL) OR
(version = 3 AND decision_id IS NOT NULL AND consumption_id IS NOT NULL AND
dispatch_id IS NOT NULL)
),
CONSTRAINT ql3_approval_requests_json_check CHECK (
length(request_json) BETWEEN 2 AND 65536 AND
json_valid(request_json) AND json_type(request_json) = 'object' AND
json_extract(request_json, '$.id') = request_id AND
json_extract(request_json, '$.projectId') = project_id AND
json_extract(request_json, '$.version') = version AND
json_extract(request_json, '$.state') = state AND
json_extract(request_json, '$.action.actionType') = action_type AND
json_extract(request_json, '$.action.actionRef') = action_ref AND
json_extract(request_json, '$.action.actionDigest') = action_digest AND
json_extract(request_json, '$.action.previewDigest') = preview_digest
),
CONSTRAINT ql3_approval_requests_time_check
CHECK (expires_at_ms > 0 AND updated_at_ms >= 0)
)
`,
`CREATE UNIQUE INDEX ql3_approval_requests_decision_uidx ON "QingLong3ApprovalRequests" (decision_id)`,
`CREATE UNIQUE INDEX ql3_approval_requests_consumption_uidx ON "QingLong3ApprovalRequests" (consumption_id)`,
`CREATE UNIQUE INDEX ql3_approval_requests_dispatch_uidx ON "QingLong3ApprovalRequests" (dispatch_id)`,
`CREATE INDEX ql3_approval_requests_pending_idx ON "QingLong3ApprovalRequests" (expires_at_ms, request_id) WHERE state = 'pending'`,
`CREATE INDEX ql3_approval_requests_project_idx ON "QingLong3ApprovalRequests" (project_id, updated_at_ms, request_id)`,
`
CREATE TABLE "QingLong3ApprovedActionDispatches" (
dispatch_id TEXT PRIMARY KEY,
approval_request_id TEXT NOT NULL,
project_id TEXT NOT NULL,
action_type TEXT NOT NULL,
action_ref TEXT NOT NULL,
action_digest TEXT NOT NULL,
preview_digest TEXT NOT NULL,
dispatch_json TEXT NOT NULL,
dispatch_digest TEXT NOT NULL,
created_at_ms INTEGER NOT NULL,
CONSTRAINT ql3_approved_action_dispatch_request_fk
FOREIGN KEY (approval_request_id)
REFERENCES "QingLong3ApprovalRequests" (request_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_approved_action_dispatch_project_fk
FOREIGN KEY (project_id) REFERENCES "QingLong3Projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_approved_action_dispatch_identity_check CHECK (
length(dispatch_id) BETWEEN 1 AND 128 AND
length(action_type) BETWEEN 1 AND 128 AND
length(action_ref) BETWEEN 1 AND 255
),
CONSTRAINT ql3_approved_action_dispatch_digest_check CHECK (
length(action_digest) = 64 AND action_digest NOT GLOB '*[^0-9a-f]*' AND
length(preview_digest) = 64 AND preview_digest NOT GLOB '*[^0-9a-f]*' AND
length(dispatch_digest) = 64 AND dispatch_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_approved_action_dispatch_json_check CHECK (
length(dispatch_json) BETWEEN 2 AND 65536 AND
json_valid(dispatch_json) AND json_type(dispatch_json) = 'object' AND
json_extract(dispatch_json, '$.id') = dispatch_id AND
json_extract(dispatch_json, '$.approvalRequestId') = approval_request_id AND
json_extract(dispatch_json, '$.projectId') = project_id AND
json_extract(dispatch_json, '$.action.actionType') = action_type AND
json_extract(dispatch_json, '$.action.actionRef') = action_ref AND
json_extract(dispatch_json, '$.action.actionDigest') = action_digest AND
json_extract(dispatch_json, '$.action.previewDigest') = preview_digest
),
CONSTRAINT ql3_approved_action_dispatch_time_check
CHECK (created_at_ms >= 0)
)
`,
`CREATE UNIQUE INDEX ql3_approved_action_dispatch_request_uidx ON "QingLong3ApprovedActionDispatches" (approval_request_id)`,
`CREATE INDEX ql3_approved_action_dispatch_project_idx ON "QingLong3ApprovedActionDispatches" (project_id, created_at_ms, dispatch_id)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const PREVIOUS_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1}';
const NEXT_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1}';
export const local0040CapabilityV20Migration = defineLocalSqliteMigration({
id: '0040-capability-v20',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 20,
migration_id = '0039-approved-actions',
capabilities = '${NEXT_CAPABILITIES}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 19
AND migration_id = '0037-plugin-package-installs'
AND capabilities = '${PREVIOUS_CAPABILITIES}'
`,
],
});
@@ -0,0 +1,90 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0041PluginPackageAdmissionReceiptsMigration =
defineLocalSqliteMigration({
id: '0041-plugin-package-admission-receipts',
statements: [
`
CREATE TABLE "QingLong3PluginPackageAdmissionReceipts" (
dispatch_id TEXT PRIMARY KEY,
dispatch_digest TEXT NOT NULL,
approval_request_id TEXT NOT NULL,
action_ref TEXT NOT NULL,
project_id TEXT NOT NULL,
package_name TEXT NOT NULL,
installation_id TEXT NOT NULL,
lock_digest TEXT NOT NULL,
record_digest TEXT NOT NULL,
mutation_id TEXT NOT NULL,
mutation_digest TEXT NOT NULL,
audit_event_id TEXT NOT NULL,
admitted_at_ms INTEGER NOT NULL,
receipt_json TEXT NOT NULL,
receipt_digest TEXT NOT NULL,
CONSTRAINT ql3_plugin_package_admission_dispatch_fk
FOREIGN KEY (dispatch_id)
REFERENCES "QingLong3ApprovedActionDispatches" (dispatch_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_admission_request_fk
FOREIGN KEY (approval_request_id)
REFERENCES "QingLong3ApprovalRequests" (request_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_admission_project_fk
FOREIGN KEY (project_id) REFERENCES "QingLong3Projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_admission_install_fk
FOREIGN KEY (installation_id)
REFERENCES "QingLong3PluginPackageInstalls" (installation_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_admission_audit_fk
FOREIGN KEY (audit_event_id)
REFERENCES "QingLong3SecurityAuditEvents" (event_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_admission_identity_check CHECK (
length(dispatch_id) BETWEEN 1 AND 128 AND
length(approval_request_id) BETWEEN 1 AND 128 AND
length(action_ref) BETWEEN 1 AND 255 AND
length(project_id) BETWEEN 1 AND 128 AND
length(package_name) BETWEEN 1 AND 64 AND
length(installation_id) BETWEEN 1 AND 128 AND
length(mutation_id) BETWEEN 1 AND 128 AND
length(audit_event_id) = 36
),
CONSTRAINT ql3_plugin_package_admission_digest_check CHECK (
length(dispatch_digest) = 64 AND
dispatch_digest NOT GLOB '*[^0-9a-f]*' AND
length(lock_digest) = 64 AND lock_digest NOT GLOB '*[^0-9a-f]*' AND
length(record_digest) = 64 AND record_digest NOT GLOB '*[^0-9a-f]*' AND
length(mutation_digest) = 64 AND
mutation_digest NOT GLOB '*[^0-9a-f]*' AND
length(receipt_digest) = 64 AND receipt_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_plugin_package_admission_json_check CHECK (
length(receipt_json) BETWEEN 2 AND 65536 AND
json_valid(receipt_json) AND json_type(receipt_json) = 'object' AND
json_extract(receipt_json, '$.schema') =
'qinglong/plugin-package-admission-receipt@v1' AND
json_extract(receipt_json, '$.dispatchId') = dispatch_id AND
json_extract(receipt_json, '$.dispatchDigest') = dispatch_digest AND
json_extract(receipt_json, '$.approvalRequestId') = approval_request_id AND
json_extract(receipt_json, '$.actionRef') = action_ref AND
json_extract(receipt_json, '$.projectId') = project_id AND
json_extract(receipt_json, '$.packageName') = package_name AND
json_extract(receipt_json, '$.installationId') = installation_id AND
json_extract(receipt_json, '$.lockDigest') = lock_digest AND
json_extract(receipt_json, '$.recordDigest') = record_digest AND
json_extract(receipt_json, '$.mutationId') = mutation_id AND
json_extract(receipt_json, '$.mutationDigest') = mutation_digest AND
json_extract(receipt_json, '$.auditEventId') = audit_event_id AND
json_extract(receipt_json, '$.admittedAtMs') = admitted_at_ms AND
json_extract(receipt_json, '$.receiptDigest') = receipt_digest
),
CONSTRAINT ql3_plugin_package_admission_time_check
CHECK (admitted_at_ms >= 0)
)
`,
`CREATE UNIQUE INDEX ql3_plugin_package_admission_install_uidx ON "QingLong3PluginPackageAdmissionReceipts" (installation_id)`,
`CREATE UNIQUE INDEX ql3_plugin_package_admission_audit_uidx ON "QingLong3PluginPackageAdmissionReceipts" (audit_event_id)`,
`CREATE INDEX ql3_plugin_package_admission_project_idx ON "QingLong3PluginPackageAdmissionReceipts" (project_id, admitted_at_ms, dispatch_id)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const PREVIOUS_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1}';
const NEXT_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1}';
export const local0042CapabilityV21Migration = defineLocalSqliteMigration({
id: '0042-capability-v21',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 21,
migration_id = '0041-plugin-package-admission-receipts',
capabilities = '${NEXT_CAPABILITIES}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 20
AND migration_id = '0039-approved-actions'
AND capabilities = '${PREVIOUS_CAPABILITIES}'
`,
],
});
@@ -0,0 +1,149 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0043ApprovedActionExecutionsAndPackageProposalsMigration =
defineLocalSqliteMigration({
id: '0043-approved-action-executions-and-package-proposals',
statements: [
`
CREATE TABLE "QingLong3PluginPackageInstallProposals" (
action_ref TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
action_type TEXT NOT NULL,
permission TEXT NOT NULL,
action_digest TEXT NOT NULL,
preview_digest TEXT NOT NULL,
proposed_by_type TEXT NOT NULL,
proposed_by_id TEXT NOT NULL,
fence_project_version INTEGER NOT NULL,
fence_binding_version INTEGER,
created_at_ms INTEGER NOT NULL,
proposal_json TEXT NOT NULL,
proposal_digest TEXT NOT NULL,
CONSTRAINT ql3_plugin_package_proposal_project_fk
FOREIGN KEY (project_id) REFERENCES "QingLong3Projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_proposal_identity_check CHECK (
length(action_ref) BETWEEN 1 AND 255 AND
action_type = 'plugin_package.install' AND
permission = 'package.manage' AND
proposed_by_type IN
('user','api_app','mcp_client','agent','system','worker') AND
length(proposed_by_id) BETWEEN 1 AND 255
),
CONSTRAINT ql3_plugin_package_proposal_digest_check CHECK (
length(action_digest) = 64 AND action_digest NOT GLOB '*[^0-9a-f]*' AND
length(preview_digest) = 64 AND preview_digest NOT GLOB '*[^0-9a-f]*' AND
length(proposal_digest) = 64 AND
proposal_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_plugin_package_proposal_json_check CHECK (
length(proposal_json) BETWEEN 2 AND 262144 AND
json_valid(proposal_json) AND json_type(proposal_json) = 'object' AND
json_extract(proposal_json, '$.schema') =
'qinglong/plugin-package-install-proposal@v1' AND
json_extract(proposal_json, '$.actionRef') = action_ref AND
json_extract(proposal_json, '$.projectId') = project_id AND
json_extract(proposal_json, '$.actionType') = action_type AND
json_extract(proposal_json, '$.permission') = permission AND
json_extract(proposal_json, '$.actionDigest') = action_digest AND
json_extract(proposal_json, '$.previewDigest') = preview_digest AND
json_extract(proposal_json, '$.proposedBy.type') = proposed_by_type AND
json_extract(proposal_json, '$.proposedBy.id') = proposed_by_id AND
json_extract(proposal_json, '$.proposalFence.projectVersion') =
fence_project_version AND
json_extract(proposal_json, '$.proposalFence.bindingVersion')
IS fence_binding_version AND
json_extract(proposal_json, '$.createdAtMs') = created_at_ms AND
json_extract(proposal_json, '$.proposalDigest') = proposal_digest
),
CONSTRAINT ql3_plugin_package_proposal_time_check CHECK (
fence_project_version > 0 AND
(fence_binding_version IS NULL OR fence_binding_version > 0) AND
created_at_ms >= 0
)
)
`,
`CREATE INDEX ql3_plugin_package_proposal_project_idx ON "QingLong3PluginPackageInstallProposals" (project_id, created_at_ms, action_ref)`,
`
CREATE TABLE "QingLong3ApprovedActionExecutions" (
dispatch_id TEXT PRIMARY KEY,
dispatch_digest TEXT NOT NULL,
project_id TEXT NOT NULL,
status TEXT NOT NULL,
version INTEGER NOT NULL,
attempt_count INTEGER NOT NULL,
max_attempts INTEGER NOT NULL,
eligible_at_ms INTEGER,
next_attempt_at_ms INTEGER,
lease_owner TEXT,
lease_token TEXT,
lease_expires_at_ms INTEGER,
started_at_ms INTEGER,
result_mutation_id TEXT,
result_code TEXT,
result_digest TEXT,
completed_at_ms INTEGER,
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
execution_json TEXT NOT NULL,
execution_digest TEXT NOT NULL,
CONSTRAINT ql3_approved_action_execution_dispatch_fk
FOREIGN KEY (dispatch_id)
REFERENCES "QingLong3ApprovedActionDispatches" (dispatch_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_approved_action_execution_project_fk
FOREIGN KEY (project_id) REFERENCES "QingLong3Projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_approved_action_execution_state_check CHECK (
status IN
('pending','leased','executing','retry_wait','succeeded','failed','blocked')
AND version BETWEEN 0 AND 2147483647
AND attempt_count BETWEEN 0 AND 16
AND max_attempts BETWEEN 1 AND 16
AND attempt_count <= max_attempts
),
CONSTRAINT ql3_approved_action_execution_lease_check CHECK (
(lease_owner IS NULL AND lease_token IS NULL AND
lease_expires_at_ms IS NULL) OR
(length(lease_owner) BETWEEN 1 AND 128 AND
length(lease_token) BETWEEN 1 AND 128 AND
lease_expires_at_ms > updated_at_ms)
),
CONSTRAINT ql3_approved_action_execution_result_check CHECK (
(result_mutation_id IS NULL AND result_code IS NULL) OR
(length(result_mutation_id) BETWEEN 1 AND 128 AND
length(result_code) BETWEEN 1 AND 64)
),
CONSTRAINT ql3_approved_action_execution_digest_check CHECK (
length(dispatch_digest) = 64 AND
dispatch_digest NOT GLOB '*[^0-9a-f]*' AND
(result_digest IS NULL OR
(length(result_digest) = 64 AND
result_digest NOT GLOB '*[^0-9a-f]*')) AND
length(execution_digest) = 64 AND
execution_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_approved_action_execution_json_check CHECK (
length(execution_json) BETWEEN 2 AND 65536 AND
json_valid(execution_json) AND json_type(execution_json) = 'object' AND
json_extract(execution_json, '$.schema') =
'qinglong/approved-action-execution@v1' AND
json_extract(execution_json, '$.dispatchId') = dispatch_id AND
json_extract(execution_json, '$.dispatchDigest') = dispatch_digest AND
json_extract(execution_json, '$.projectId') = project_id AND
json_extract(execution_json, '$.status') = status AND
json_extract(execution_json, '$.version') = version AND
json_extract(execution_json, '$.attemptCount') = attempt_count AND
json_extract(execution_json, '$.maxAttempts') = max_attempts AND
json_extract(execution_json, '$.executionDigest') = execution_digest
),
CONSTRAINT ql3_approved_action_execution_time_check CHECK (
created_at_ms >= 0 AND updated_at_ms >= created_at_ms
)
)
`,
`CREATE INDEX ql3_approved_action_execution_due_idx ON "QingLong3ApprovedActionExecutions" (eligible_at_ms, dispatch_id) WHERE status IN ('pending','leased','retry_wait')`,
`CREATE INDEX ql3_approved_action_execution_recovery_idx ON "QingLong3ApprovedActionExecutions" (lease_expires_at_ms, dispatch_id) WHERE status = 'executing'`,
`CREATE INDEX ql3_approved_action_execution_project_idx ON "QingLong3ApprovedActionExecutions" (project_id, updated_at_ms, dispatch_id)`,
],
});
@@ -0,0 +1,24 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const PREVIOUS_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1}';
const NEXT_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1}';
export const local0044CapabilityV22Migration = defineLocalSqliteMigration({
id: '0044-capability-v22',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 22,
migration_id =
'0043-approved-action-executions-and-package-proposals',
capabilities = '${NEXT_CAPABILITIES}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 21
AND migration_id = '0041-plugin-package-admission-receipts'
AND capabilities = '${PREVIOUS_CAPABILITIES}'
`,
],
});
@@ -0,0 +1,57 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0045PluginPackageMaterializedRevisionsMigration =
defineLocalSqliteMigration({
id: '0045-plugin-package-materialized-revisions',
statements: [
`
CREATE TABLE "QingLong3PluginPackageMaterializedRevisions" (
generation_digest TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
package_name TEXT NOT NULL,
generation INTEGER NOT NULL,
lock_digest TEXT NOT NULL,
manifest_digest TEXT NOT NULL,
revision_digest TEXT NOT NULL,
revision_json TEXT NOT NULL,
created_at_ms INTEGER NOT NULL,
CONSTRAINT ql3_plugin_package_materialized_revision_project_fk
FOREIGN KEY (project_id) REFERENCES "QingLong3Projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_materialized_revision_identity_check CHECK (
length(package_name) BETWEEN 1 AND 63 AND
generation BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_plugin_package_materialized_revision_digest_check CHECK (
length(generation_digest) = 64 AND
generation_digest NOT GLOB '*[^0-9a-f]*' AND
length(lock_digest) = 64 AND
lock_digest NOT GLOB '*[^0-9a-f]*' AND
length(manifest_digest) = 64 AND
manifest_digest NOT GLOB '*[^0-9a-f]*' AND
length(revision_digest) = 64 AND
revision_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_plugin_package_materialized_revision_json_check CHECK (
length(revision_json) BETWEEN 2 AND 25165824 AND
json_valid(revision_json) AND json_type(revision_json) = 'object' AND
json_extract(revision_json, '$.schema') =
'qinglong/plugin-package-materialized-revision@v1' AND
json_extract(revision_json, '$.generation.generationDigest') =
generation_digest AND
json_extract(revision_json, '$.generation.projectId') = project_id AND
json_extract(revision_json, '$.generation.packageName') = package_name AND
json_extract(revision_json, '$.generation.generation') = generation AND
json_extract(revision_json, '$.generation.lockDigest') = lock_digest AND
json_extract(revision_json, '$.manifestDigest') = manifest_digest AND
json_extract(revision_json, '$.revisionDigest') = revision_digest
),
CONSTRAINT ql3_plugin_package_materialized_revision_time_check CHECK (
created_at_ms >= 0
)
)
`,
`CREATE UNIQUE INDEX ql3_plugin_package_materialized_revision_generation_uidx ON "QingLong3PluginPackageMaterializedRevisions" (project_id, package_name, generation)`,
`CREATE INDEX ql3_plugin_package_materialized_revision_lock_idx ON "QingLong3PluginPackageMaterializedRevisions" (lock_digest, generation_digest)`,
],
});
@@ -0,0 +1,24 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const PREVIOUS_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1}';
const NEXT_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1}';
export const local0046CapabilityV23Migration = defineLocalSqliteMigration({
id: '0046-capability-v23',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 23,
migration_id = '0045-plugin-package-materialized-revisions',
capabilities = '${NEXT_CAPABILITIES}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 22
AND migration_id =
'0043-approved-action-executions-and-package-proposals'
AND capabilities = '${PREVIOUS_CAPABILITIES}'
`,
],
});
@@ -0,0 +1,118 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0047PluginPackageTaskReconciliationsMigration =
defineLocalSqliteMigration({
id: '0047-plugin-package-task-reconciliations',
statements: [
`
CREATE TABLE "QingLong3PluginPackageTaskOwnerships" (
project_id TEXT NOT NULL,
task_id TEXT NOT NULL,
package_name TEXT NOT NULL,
claimed_generation_digest TEXT NOT NULL,
created_at_ms INTEGER NOT NULL,
PRIMARY KEY (project_id, task_id),
CONSTRAINT ql3_plugin_package_task_ownership_project_fk
FOREIGN KEY (project_id) REFERENCES "QingLong3Projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_task_ownership_task_fk
FOREIGN KEY (project_id, task_id)
REFERENCES "QingLong3TaskDefinitions" (project_id, task_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_task_ownership_identity_check CHECK (
length(task_id) BETWEEN 1 AND 128 AND
length(package_name) BETWEEN 1 AND 63
),
CONSTRAINT ql3_plugin_package_task_ownership_digest_check CHECK (
length(claimed_generation_digest) = 64 AND
claimed_generation_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_plugin_package_task_ownership_namespace_check CHECK (
task_id LIKE 'pkg:' || package_name || ':%'
),
CONSTRAINT ql3_plugin_package_task_ownership_time_check CHECK (
created_at_ms >= 0
)
)
`,
`CREATE INDEX ql3_plugin_package_task_ownership_package_idx ON "QingLong3PluginPackageTaskOwnerships" (project_id, package_name, task_id)`,
`
CREATE TABLE "QingLong3PluginPackageTaskReconciliations" (
generation_digest TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
package_name TEXT NOT NULL,
generation INTEGER NOT NULL,
materialized_revision_digest TEXT NOT NULL,
lock_digest TEXT NOT NULL,
previous_lock_digest TEXT,
receipt_digest TEXT NOT NULL,
receipt_json TEXT NOT NULL,
committed_at_ms INTEGER NOT NULL,
CONSTRAINT ql3_plugin_package_task_reconciliation_materialized_fk
FOREIGN KEY (generation_digest)
REFERENCES "QingLong3PluginPackageMaterializedRevisions" (generation_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_task_reconciliation_identity_check CHECK (
length(package_name) BETWEEN 1 AND 63 AND
generation BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_plugin_package_task_reconciliation_digest_check CHECK (
length(generation_digest) = 64 AND
generation_digest NOT GLOB '*[^0-9a-f]*' AND
length(materialized_revision_digest) = 64 AND
materialized_revision_digest NOT GLOB '*[^0-9a-f]*' AND
length(lock_digest) = 64 AND lock_digest NOT GLOB '*[^0-9a-f]*' AND
(previous_lock_digest IS NULL OR
(length(previous_lock_digest) = 64 AND
previous_lock_digest NOT GLOB '*[^0-9a-f]*')) AND
length(receipt_digest) = 64 AND receipt_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_plugin_package_task_reconciliation_json_check CHECK (
length(receipt_json) BETWEEN 2 AND 8388608 AND
json_valid(receipt_json) AND json_type(receipt_json) = 'object' AND
json_extract(receipt_json, '$.schema') =
'qinglong/plugin-package-task-reconciliation@v1' AND
json_extract(receipt_json, '$.generationDigest') = generation_digest AND
json_extract(receipt_json, '$.projectId') = project_id AND
json_extract(receipt_json, '$.packageName') = package_name AND
json_extract(receipt_json, '$.generation') = generation AND
json_extract(receipt_json, '$.materializedRevisionDigest') =
materialized_revision_digest AND
json_extract(receipt_json, '$.lockDigest') = lock_digest AND
json_extract(receipt_json, '$.receiptDigest') = receipt_digest
),
CONSTRAINT ql3_plugin_package_task_reconciliation_time_check CHECK (
committed_at_ms >= 0
)
)
`,
`CREATE UNIQUE INDEX ql3_plugin_package_task_reconciliation_generation_uidx ON "QingLong3PluginPackageTaskReconciliations" (project_id, package_name, generation)`,
`CREATE INDEX ql3_plugin_package_task_reconciliation_lock_idx ON "QingLong3PluginPackageTaskReconciliations" (lock_digest, generation_digest)`,
`
CREATE TABLE "QingLong3PluginPackageTaskReconciliationItems" (
generation_digest TEXT NOT NULL,
task_id TEXT NOT NULL,
revision INTEGER NOT NULL,
disposition TEXT NOT NULL,
content_digest TEXT NOT NULL,
PRIMARY KEY (generation_digest, task_id),
CONSTRAINT ql3_plugin_package_task_reconciliation_item_reconciliation_fk
FOREIGN KEY (generation_digest)
REFERENCES "QingLong3PluginPackageTaskReconciliations" (generation_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_task_reconciliation_item_identity_check CHECK (
length(task_id) BETWEEN 1 AND 128 AND
revision BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_plugin_package_task_reconciliation_item_disposition_check
CHECK (disposition IN (
'already_disabled','created','disabled','retained','updated'
)),
CONSTRAINT ql3_plugin_package_task_reconciliation_item_digest_check CHECK (
length(content_digest) = 64 AND content_digest NOT GLOB '*[^0-9a-f]*'
)
)
`,
`CREATE INDEX ql3_plugin_package_task_reconciliation_item_task_idx ON "QingLong3PluginPackageTaskReconciliationItems" (task_id, generation_digest)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const PREVIOUS_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1}';
const NEXT_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1}';
export const local0048CapabilityV24Migration = defineLocalSqliteMigration({
id: '0048-capability-v24',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 24,
migration_id = '0047-plugin-package-task-reconciliations',
capabilities = '${NEXT_CAPABILITIES}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 23
AND migration_id = '0045-plugin-package-materialized-revisions'
AND capabilities = '${PREVIOUS_CAPABILITIES}'
`,
],
});
@@ -0,0 +1,118 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0049ProjectToolDefinitionSnapshotsMigration =
defineLocalSqliteMigration({
id: '0049-project-tool-definition-snapshots',
statements: [
`
CREATE UNIQUE INDEX ql3_plugin_package_installs_snapshot_source_uidx
ON "QingLong3PluginPackageInstalls" (
project_id, package_name, installation_id, target_generation, lock_digest
)
`,
`
CREATE UNIQUE INDEX ql3_plugin_package_materialized_revision_snapshot_source_uidx
ON "QingLong3PluginPackageMaterializedRevisions" (
project_id, package_name, generation, generation_digest,
lock_digest, revision_digest
)
`,
`
CREATE TABLE "QingLong3ProjectToolDefinitionSnapshots" (
project_id TEXT NOT NULL,
active_vector_digest TEXT NOT NULL,
definitions_digest TEXT NOT NULL,
snapshot_digest TEXT NOT NULL,
snapshot_json TEXT NOT NULL,
committed_at_ms INTEGER NOT NULL,
PRIMARY KEY (project_id, active_vector_digest),
CONSTRAINT ql3_project_tool_definition_snapshot_project_fk
FOREIGN KEY (project_id) REFERENCES "QingLong3Projects" (id)
ON DELETE RESTRICT,
CONSTRAINT ql3_project_tool_definition_snapshot_identity_check CHECK (
length(project_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_project_tool_definition_snapshot_digest_check CHECK (
length(active_vector_digest) = 64 AND
active_vector_digest NOT GLOB '*[^0-9a-f]*' AND
length(definitions_digest) = 64 AND
definitions_digest NOT GLOB '*[^0-9a-f]*' AND
length(snapshot_digest) = 64 AND
snapshot_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_project_tool_definition_snapshot_json_check CHECK (
length(snapshot_json) BETWEEN 2 AND 8388608 AND
json_valid(snapshot_json) AND json_type(snapshot_json) = 'object' AND
json_extract(snapshot_json, '$.schema') =
'qinglong/project-tool-definition-snapshot@v1' AND
json_extract(snapshot_json, '$.projectId') = project_id AND
json_extract(snapshot_json, '$.activeVectorDigest') =
active_vector_digest AND
json_extract(snapshot_json, '$.definitionsDigest') =
definitions_digest AND
json_extract(snapshot_json, '$.snapshotDigest') = snapshot_digest
),
CONSTRAINT ql3_project_tool_definition_snapshot_time_check CHECK (
committed_at_ms >= 0
)
)
`,
`CREATE UNIQUE INDEX ql3_project_tool_definition_snapshot_digest_uidx ON "QingLong3ProjectToolDefinitionSnapshots" (snapshot_digest)`,
`CREATE INDEX ql3_project_tool_definition_snapshot_current_idx ON "QingLong3ProjectToolDefinitionSnapshots" (project_id, committed_at_ms DESC, active_vector_digest)`,
`
CREATE TABLE "QingLong3ProjectToolDefinitionSnapshotSources" (
project_id TEXT NOT NULL,
active_vector_digest TEXT NOT NULL,
package_name TEXT NOT NULL,
installation_id TEXT NOT NULL,
generation INTEGER NOT NULL,
generation_digest TEXT NOT NULL,
lock_digest TEXT NOT NULL,
revision_digest TEXT NOT NULL,
PRIMARY KEY (project_id, active_vector_digest, package_name),
CONSTRAINT ql3_project_tool_definition_snapshot_source_snapshot_fk
FOREIGN KEY (project_id, active_vector_digest)
REFERENCES "QingLong3ProjectToolDefinitionSnapshots" (
project_id, active_vector_digest
)
ON DELETE RESTRICT,
CONSTRAINT ql3_project_tool_definition_snapshot_source_install_fk
FOREIGN KEY (
project_id, package_name, installation_id, generation, lock_digest
)
REFERENCES "QingLong3PluginPackageInstalls" (
project_id, package_name, installation_id, target_generation, lock_digest
)
ON DELETE RESTRICT,
CONSTRAINT ql3_project_tool_definition_snapshot_source_revision_fk
FOREIGN KEY (
project_id, package_name, generation, generation_digest,
lock_digest, revision_digest
)
REFERENCES "QingLong3PluginPackageMaterializedRevisions" (
project_id, package_name, generation, generation_digest,
lock_digest, revision_digest
)
ON DELETE RESTRICT,
CONSTRAINT ql3_project_tool_definition_snapshot_source_identity_check
CHECK (
length(package_name) BETWEEN 1 AND 63 AND
length(installation_id) BETWEEN 1 AND 128 AND
generation BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_project_tool_definition_snapshot_source_digest_check CHECK (
length(active_vector_digest) = 64 AND
active_vector_digest NOT GLOB '*[^0-9a-f]*' AND
length(generation_digest) = 64 AND
generation_digest NOT GLOB '*[^0-9a-f]*' AND
length(lock_digest) = 64 AND
lock_digest NOT GLOB '*[^0-9a-f]*' AND
length(revision_digest) = 64 AND
revision_digest NOT GLOB '*[^0-9a-f]*'
)
)
`,
`CREATE INDEX ql3_project_tool_definition_snapshot_source_generation_idx ON "QingLong3ProjectToolDefinitionSnapshotSources" (generation_digest, project_id, package_name)`,
`CREATE INDEX ql3_project_tool_definition_snapshot_source_install_idx ON "QingLong3ProjectToolDefinitionSnapshotSources" (installation_id, active_vector_digest)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const PREVIOUS_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1}';
const NEXT_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1}';
export const local0050CapabilityV25Migration = defineLocalSqliteMigration({
id: '0050-capability-v25',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 25,
migration_id = '0049-project-tool-definition-snapshots',
capabilities = '${NEXT_CAPABILITIES}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 24
AND migration_id = '0047-plugin-package-task-reconciliations'
AND capabilities = '${PREVIOUS_CAPABILITIES}'
`,
],
});
@@ -0,0 +1,222 @@
import { defineLocalSqliteMigration } from './sqlMigration';
import { LOCAL_STEP_RUN_REFERENCE_TRIGGERS } from '../run/stepRunSchemaContract';
export const local0051StepRunsMigration = defineLocalSqliteMigration({
id: '0051-step-runs',
statements: [
`
CREATE TEMP TABLE "QingLong3StepRunReferenceUpgradeGuard" (
valid INTEGER NOT NULL CHECK (valid = 1)
)
`,
`
INSERT INTO "QingLong3StepRunReferenceUpgradeGuard" (valid)
SELECT CASE
WHEN EXISTS (
SELECT 1 FROM "RunAttempts" WHERE step_run_id IS NOT NULL
UNION ALL
SELECT 1 FROM "RunEvents" WHERE step_run_id IS NOT NULL
) THEN 0
ELSE 1
END
`,
`DROP TABLE "QingLong3StepRunReferenceUpgradeGuard"`,
`
CREATE TABLE "StepRuns" (
id TEXT PRIMARY KEY NOT NULL,
run_id TEXT NOT NULL,
parent_step_run_id TEXT,
step_key TEXT NOT NULL,
kind TEXT NOT NULL,
definition_ref TEXT NOT NULL,
definition_digest TEXT NOT NULL,
required INTEGER NOT NULL,
status TEXT NOT NULL,
version INTEGER NOT NULL,
attempt_count INTEGER NOT NULL,
input_ref TEXT,
output_ref TEXT,
approval_request_id TEXT,
ready_at_ms INTEGER,
started_at_ms INTEGER,
finished_at_ms INTEGER,
result_code TEXT,
error_summary TEXT,
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
last_mutation_id TEXT NOT NULL,
step_run_digest TEXT NOT NULL,
step_run_json TEXT NOT NULL,
CONSTRAINT ql3_step_runs_run_fk
FOREIGN KEY (run_id) REFERENCES "Runs" (id) ON DELETE CASCADE,
CONSTRAINT ql3_step_runs_parent_fk
FOREIGN KEY (run_id, parent_step_run_id)
REFERENCES "StepRuns" (run_id, id) ON DELETE RESTRICT,
CONSTRAINT ql3_step_runs_identity_check CHECK (
length(id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
(parent_step_run_id IS NULL OR
(length(parent_step_run_id) BETWEEN 1 AND 128 AND
parent_step_run_id <> id)) AND
length(step_key) BETWEEN 1 AND 128 AND
length(CAST(definition_ref AS BLOB)) BETWEEN 1 AND 512
),
CONSTRAINT ql3_step_runs_kind_check CHECK (
kind IN (
'task', 'tool', 'model', 'agent', 'condition', 'approval',
'subworkflow'
)
),
CONSTRAINT ql3_step_runs_status_check CHECK (
status IN (
'pending', 'ready', 'waiting_approval', 'running', 'lost',
'succeeded', 'failed', 'skipped', 'cancelled', 'timed_out'
)
),
CONSTRAINT ql3_step_runs_digest_check CHECK (
length(definition_digest) = 64 AND
definition_digest NOT GLOB '*[^0-9a-f]*' AND
length(step_run_digest) = 64 AND
step_run_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_step_runs_counter_check CHECK (
required IN (0, 1) AND
version BETWEEN 1 AND 2147483647 AND
attempt_count BETWEEN 0 AND 64
),
CONSTRAINT ql3_step_runs_reference_check CHECK (
(input_ref IS NULL OR
length(CAST(input_ref AS BLOB)) BETWEEN 1 AND 512) AND
(output_ref IS NULL OR
length(CAST(output_ref AS BLOB)) BETWEEN 1 AND 512) AND
(approval_request_id IS NULL OR
length(approval_request_id) BETWEEN 1 AND 128)
),
CONSTRAINT ql3_step_runs_time_check CHECK (
created_at_ms >= 0 AND updated_at_ms >= created_at_ms AND
(ready_at_ms IS NULL OR
ready_at_ms BETWEEN created_at_ms AND updated_at_ms) AND
(started_at_ms IS NULL OR
(ready_at_ms IS NOT NULL AND
started_at_ms BETWEEN ready_at_ms AND updated_at_ms)) AND
(finished_at_ms IS NULL OR
(finished_at_ms BETWEEN created_at_ms AND updated_at_ms AND
(ready_at_ms IS NULL OR finished_at_ms >= ready_at_ms) AND
(started_at_ms IS NULL OR finished_at_ms >= started_at_ms)))
),
CONSTRAINT ql3_step_runs_state_shape_check CHECK (
(status = 'pending' AND ready_at_ms IS NULL AND
started_at_ms IS NULL AND finished_at_ms IS NULL) OR
(status IN ('ready', 'waiting_approval') AND ready_at_ms IS NOT NULL AND
started_at_ms IS NULL AND finished_at_ms IS NULL) OR
(status IN ('running', 'lost') AND ready_at_ms IS NOT NULL AND
started_at_ms IS NOT NULL AND finished_at_ms IS NULL) OR
(status IN ('succeeded', 'failed', 'skipped', 'cancelled', 'timed_out')
AND finished_at_ms IS NOT NULL)
),
CONSTRAINT ql3_step_runs_result_shape_check CHECK (
(status = 'waiting_approval' AND approval_request_id IS NOT NULL) OR
status <> 'waiting_approval'
),
CONSTRAINT ql3_step_runs_result_value_check CHECK (
(output_ref IS NULL OR status = 'succeeded') AND
(status = 'succeeded' AND result_code IS NULL AND error_summary IS NULL OR
status IN ('failed', 'skipped', 'cancelled', 'timed_out', 'lost') AND
result_code IS NOT NULL OR
status IN ('pending', 'ready', 'waiting_approval', 'running') AND
result_code IS NULL AND error_summary IS NULL) AND
(result_code IS NULL OR
(length(result_code) BETWEEN 1 AND 64 AND
result_code NOT GLOB '*[^a-z0-9_]*' AND
substr(result_code, 1, 1) GLOB '[a-z]')) AND
(error_summary IS NULL OR
length(CAST(error_summary AS BLOB)) BETWEEN 1 AND 2048)
),
CONSTRAINT ql3_step_runs_mutation_identity_check CHECK (
length(last_mutation_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_step_runs_json_check CHECK (
length(CAST(step_run_json AS BLOB)) BETWEEN 2 AND 16384 AND
json_valid(step_run_json) AND json_type(step_run_json) = 'object' AND
json_extract(step_run_json, '$.schema') = 'qinglong/step-run@v1' AND
json_extract(step_run_json, '$.id') = id AND
json_extract(step_run_json, '$.runId') = run_id AND
json_extract(step_run_json, '$.parentStepRunId') IS parent_step_run_id AND
json_extract(step_run_json, '$.stepKey') = step_key AND
json_extract(step_run_json, '$.kind') = kind AND
json_extract(step_run_json, '$.definitionRef') = definition_ref AND
json_extract(step_run_json, '$.definitionDigest') = definition_digest AND
json_extract(step_run_json, '$.required') IS required AND
json_extract(step_run_json, '$.status') = status AND
json_extract(step_run_json, '$.version') = version AND
json_extract(step_run_json, '$.attemptCount') IS attempt_count AND
json_extract(step_run_json, '$.inputRef') IS input_ref AND
json_extract(step_run_json, '$.outputRef') IS output_ref AND
json_extract(step_run_json, '$.approvalRequestId') IS
approval_request_id AND
json_extract(step_run_json, '$.readyAtMs') IS ready_at_ms AND
json_extract(step_run_json, '$.startedAtMs') IS started_at_ms AND
json_extract(step_run_json, '$.finishedAtMs') IS finished_at_ms AND
json_extract(step_run_json, '$.resultCode') IS result_code AND
json_extract(step_run_json, '$.errorSummary') IS error_summary AND
json_extract(step_run_json, '$.createdAtMs') IS created_at_ms AND
json_extract(step_run_json, '$.updatedAtMs') IS updated_at_ms AND
json_extract(step_run_json, '$.lastMutationId') = last_mutation_id AND
json_extract(step_run_json, '$.stepRunDigest') = step_run_digest
)
)
`,
`CREATE UNIQUE INDEX ql3_step_runs_run_id_uidx ON "StepRuns" (run_id, id)`,
`CREATE UNIQUE INDEX ql3_step_runs_run_step_uidx ON "StepRuns" (run_id, step_key)`,
`CREATE INDEX ql3_step_runs_run_status_idx ON "StepRuns" (run_id, status, id)`,
`CREATE INDEX ql3_step_runs_recovery_idx ON "StepRuns" (status, updated_at_ms, id) WHERE status IN ('waiting_approval', 'running', 'lost')`,
`
CREATE TABLE "StepRunMutations" (
mutation_id TEXT PRIMARY KEY NOT NULL,
mutation_digest TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
step_run_digest TEXT NOT NULL,
event_id TEXT NOT NULL,
event_sequence INTEGER NOT NULL,
run_version INTEGER NOT NULL,
step_run_json TEXT NOT NULL,
committed_at_ms INTEGER NOT NULL,
CONSTRAINT ql3_step_run_mutations_step_fk
FOREIGN KEY (run_id, step_run_id)
REFERENCES "StepRuns" (run_id, id) ON DELETE CASCADE,
CONSTRAINT ql3_step_run_mutations_event_fk
FOREIGN KEY (event_id) REFERENCES "RunEvents" (id) ON DELETE CASCADE,
CONSTRAINT ql3_step_run_mutations_identity_check CHECK (
length(mutation_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(event_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_step_run_mutations_digest_check CHECK (
length(mutation_digest) = 64 AND
mutation_digest NOT GLOB '*[^0-9a-f]*' AND
length(step_run_digest) = 64 AND
step_run_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_step_run_mutations_counter_check CHECK (
event_sequence BETWEEN 1 AND 2147483647 AND
run_version BETWEEN 1 AND 2147483647 AND
committed_at_ms >= 0
),
CONSTRAINT ql3_step_run_mutations_json_check CHECK (
length(CAST(step_run_json AS BLOB)) BETWEEN 2 AND 16384 AND
json_valid(step_run_json) AND json_type(step_run_json) = 'object' AND
json_extract(step_run_json, '$.schema') = 'qinglong/step-run@v1' AND
json_extract(step_run_json, '$.id') = step_run_id AND
json_extract(step_run_json, '$.runId') = run_id AND
json_extract(step_run_json, '$.lastMutationId') = mutation_id AND
json_extract(step_run_json, '$.stepRunDigest') = step_run_digest
)
)
`,
`CREATE UNIQUE INDEX ql3_step_run_mutations_event_uidx ON "StepRunMutations" (event_id)`,
`CREATE INDEX ql3_step_run_mutations_step_idx ON "StepRunMutations" (run_id, step_run_id, event_sequence, mutation_id)`,
...LOCAL_STEP_RUN_REFERENCE_TRIGGERS.map((trigger) => trigger.sql),
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const PREVIOUS_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1}';
const NEXT_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1}';
export const local0052CapabilityV26Migration = defineLocalSqliteMigration({
id: '0052-capability-v26',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 26,
migration_id = '0051-step-runs',
capabilities = '${NEXT_CAPABILITIES}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 25
AND migration_id = '0049-project-tool-definition-snapshots'
AND capabilities = '${PREVIOUS_CAPABILITIES}'
`,
],
});
@@ -0,0 +1,169 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0053ToolExecutionEvidenceMigration =
defineLocalSqliteMigration({
id: '0053-tool-execution-evidence',
statements: [
`
CREATE TABLE "ToolExecutionTraceAnchors" (
trace_id TEXT NOT NULL,
span_id TEXT NOT NULL,
parent_span_id TEXT,
project_id TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
invocation_plan_digest TEXT NOT NULL,
binding_digest TEXT NOT NULL,
adapter_digest TEXT NOT NULL,
redaction_contract_digest TEXT NOT NULL,
audit_contract_digest TEXT NOT NULL,
created_at_ms INTEGER NOT NULL,
trace_digest TEXT NOT NULL,
trace_json TEXT NOT NULL,
CONSTRAINT tool_execution_trace_anchors_pkey
PRIMARY KEY (trace_id, span_id),
CONSTRAINT ql3_tool_execution_trace_step_fk
FOREIGN KEY (run_id, step_run_id)
REFERENCES "StepRuns" (run_id, id) ON DELETE CASCADE,
CONSTRAINT ql3_tool_execution_trace_identity_check CHECK (
length(trace_id) = 32 AND trace_id NOT GLOB '*[^0-9a-f]*' AND
length(span_id) = 16 AND span_id NOT GLOB '*[^0-9a-f]*' AND
(parent_span_id IS NULL OR (
length(parent_span_id) = 16 AND
parent_span_id NOT GLOB '*[^0-9a-f]*' AND
parent_span_id <> span_id
)) AND
length(project_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_tool_execution_trace_digest_check CHECK (
length(invocation_plan_digest) = 64 AND
invocation_plan_digest NOT GLOB '*[^0-9a-f]*' AND
length(binding_digest) = 64 AND
binding_digest NOT GLOB '*[^0-9a-f]*' AND
length(adapter_digest) = 64 AND
adapter_digest NOT GLOB '*[^0-9a-f]*' AND
length(redaction_contract_digest) = 64 AND
redaction_contract_digest NOT GLOB '*[^0-9a-f]*' AND
length(audit_contract_digest) = 64 AND
audit_contract_digest NOT GLOB '*[^0-9a-f]*' AND
length(trace_digest) = 64 AND
trace_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_execution_trace_time_check CHECK (
created_at_ms >= 0
),
CONSTRAINT ql3_tool_execution_trace_json_check CHECK (
length(CAST(trace_json AS BLOB)) BETWEEN 2 AND 16384 AND
json_valid(trace_json) AND json_type(trace_json) = 'object' AND
json_extract(trace_json, '$.schema') =
'qinglong/tool-execution-trace-anchor@v1' AND
json_extract(trace_json, '$.traceId') = trace_id AND
json_extract(trace_json, '$.spanId') = span_id AND
json_extract(trace_json, '$.parentSpanId') IS parent_span_id AND
json_extract(trace_json, '$.projectId') = project_id AND
json_extract(trace_json, '$.runId') = run_id AND
json_extract(trace_json, '$.stepRunId') = step_run_id AND
json_extract(trace_json, '$.invocationPlanDigest') =
invocation_plan_digest AND
json_extract(trace_json, '$.bindingDigest') = binding_digest AND
json_extract(trace_json, '$.adapterDigest') = adapter_digest AND
json_extract(trace_json, '$.redactionContractDigest') =
redaction_contract_digest AND
json_extract(trace_json, '$.auditContractDigest') =
audit_contract_digest AND
json_extract(trace_json, '$.createdAtMs') IS created_at_ms AND
json_extract(trace_json, '$.traceDigest') = trace_digest
)
)
`,
`CREATE INDEX ql3_tool_execution_trace_run_idx ON "ToolExecutionTraceAnchors" (run_id, created_at_ms, trace_id, span_id)`,
`CREATE INDEX ql3_tool_execution_trace_step_idx ON "ToolExecutionTraceAnchors" (run_id, step_run_id, created_at_ms, trace_id, span_id)`,
`
CREATE TABLE "ToolExecutionAuditReceipts" (
event_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
trace_id TEXT NOT NULL,
span_id TEXT NOT NULL,
trace_digest TEXT NOT NULL,
invocation_plan_digest TEXT NOT NULL,
binding_digest TEXT NOT NULL,
audit_record_digest TEXT NOT NULL,
created_at_ms INTEGER NOT NULL,
receipt_digest TEXT NOT NULL,
audit_json TEXT NOT NULL,
receipt_json TEXT NOT NULL,
CONSTRAINT ql3_tool_execution_audit_event_fk
FOREIGN KEY (event_id)
REFERENCES "QingLong3SecurityAuditEvents" (event_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_tool_execution_audit_trace_fk
FOREIGN KEY (trace_id, span_id)
REFERENCES "ToolExecutionTraceAnchors" (trace_id, span_id)
ON DELETE CASCADE,
CONSTRAINT ql3_tool_execution_audit_step_fk
FOREIGN KEY (run_id, step_run_id)
REFERENCES "StepRuns" (run_id, id) ON DELETE CASCADE,
CONSTRAINT ql3_tool_execution_audit_identity_check CHECK (
length(event_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(trace_id) = 32 AND trace_id NOT GLOB '*[^0-9a-f]*' AND
length(span_id) = 16 AND span_id NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_execution_audit_digest_check CHECK (
length(trace_digest) = 64 AND
trace_digest NOT GLOB '*[^0-9a-f]*' AND
length(invocation_plan_digest) = 64 AND
invocation_plan_digest NOT GLOB '*[^0-9a-f]*' AND
length(binding_digest) = 64 AND
binding_digest NOT GLOB '*[^0-9a-f]*' AND
length(audit_record_digest) = 64 AND
audit_record_digest NOT GLOB '*[^0-9a-f]*' AND
length(receipt_digest) = 64 AND
receipt_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_execution_audit_time_check CHECK (
created_at_ms >= 0
),
CONSTRAINT ql3_tool_execution_audit_json_check CHECK (
length(CAST(audit_json AS BLOB)) BETWEEN 2 AND 8192 AND
json_valid(audit_json) AND json_type(audit_json) = 'object' AND
json_extract(audit_json, '$.eventId') = event_id AND
json_extract(audit_json, '$.projectId') = project_id AND
json_extract(audit_json, '$.operationId') = 'tool.invoke.start' AND
json_extract(audit_json, '$.outcome') = 'allowed' AND
json_type(audit_json, '$.fence') = 'object' AND
json_extract(audit_json, '$.occurredAtMs') IS created_at_ms
),
CONSTRAINT ql3_tool_execution_audit_receipt_json_check CHECK (
length(CAST(receipt_json AS BLOB)) BETWEEN 2 AND 16384 AND
json_valid(receipt_json) AND json_type(receipt_json) = 'object' AND
json_extract(receipt_json, '$.schema') =
'qinglong/tool-execution-audit-receipt@v1' AND
json_extract(receipt_json, '$.eventId') = event_id AND
json_extract(receipt_json, '$.projectId') = project_id AND
json_extract(receipt_json, '$.runId') = run_id AND
json_extract(receipt_json, '$.stepRunId') = step_run_id AND
json_extract(receipt_json, '$.traceId') = trace_id AND
json_extract(receipt_json, '$.spanId') = span_id AND
json_extract(receipt_json, '$.traceDigest') = trace_digest AND
json_extract(receipt_json, '$.invocationPlanDigest') =
invocation_plan_digest AND
json_extract(receipt_json, '$.bindingDigest') = binding_digest AND
json_extract(receipt_json, '$.auditRecordDigest') =
audit_record_digest AND
json_extract(receipt_json, '$.createdAtMs') IS created_at_ms AND
json_extract(receipt_json, '$.receiptDigest') = receipt_digest
)
)
`,
`CREATE UNIQUE INDEX ql3_tool_execution_audit_trace_uidx ON "ToolExecutionAuditReceipts" (trace_id, span_id)`,
`CREATE INDEX ql3_tool_execution_audit_run_idx ON "ToolExecutionAuditReceipts" (run_id, created_at_ms, trace_id, span_id)`,
`CREATE INDEX ql3_tool_execution_audit_step_idx ON "ToolExecutionAuditReceipts" (run_id, step_run_id, created_at_ms, event_id)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const PREVIOUS_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1}';
const NEXT_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1}';
export const local0054CapabilityV27Migration = defineLocalSqliteMigration({
id: '0054-capability-v27',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 27,
migration_id = '0053-tool-execution-evidence',
capabilities = '${NEXT_CAPABILITIES}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 26
AND migration_id = '0051-step-runs'
AND capabilities = '${PREVIOUS_CAPABILITIES}'
`,
],
});
@@ -0,0 +1,91 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0055ToolExecutionStartBarriersMigration =
defineLocalSqliteMigration({
id: '0055-tool-execution-start-barriers',
statements: [
`
CREATE TABLE "ToolExecutionStartBarriers" (
start_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
started_step_run_version INTEGER NOT NULL,
step_run_mutation_id TEXT NOT NULL,
run_event_id TEXT NOT NULL,
trace_id TEXT NOT NULL,
span_id TEXT NOT NULL,
audit_event_id TEXT NOT NULL,
command_digest TEXT NOT NULL,
barrier_digest TEXT NOT NULL,
started_at_ms INTEGER NOT NULL,
barrier_json TEXT NOT NULL,
CONSTRAINT ql3_tool_start_step_fk
FOREIGN KEY (run_id, step_run_id)
REFERENCES "StepRuns" (run_id, id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_start_mutation_fk
FOREIGN KEY (step_run_mutation_id)
REFERENCES "StepRunMutations" (mutation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_start_event_fk
FOREIGN KEY (run_event_id)
REFERENCES "RunEvents" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_start_trace_fk
FOREIGN KEY (trace_id, span_id)
REFERENCES "ToolExecutionTraceAnchors" (trace_id, span_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_tool_start_audit_fk
FOREIGN KEY (audit_event_id)
REFERENCES "ToolExecutionAuditReceipts" (event_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_tool_start_identity_check CHECK (
length(start_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(step_run_mutation_id) BETWEEN 1 AND 128 AND
length(run_event_id) BETWEEN 1 AND 128 AND
length(trace_id) = 32 AND trace_id NOT GLOB '*[^0-9a-f]*' AND
length(span_id) = 16 AND span_id NOT GLOB '*[^0-9a-f]*' AND
length(audit_event_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_tool_start_version_time_check CHECK (
started_step_run_version BETWEEN 2 AND 2147483647 AND
started_at_ms >= 0
),
CONSTRAINT ql3_tool_start_digest_check CHECK (
length(command_digest) = 64 AND
command_digest NOT GLOB '*[^0-9a-f]*' AND
length(barrier_digest) = 64 AND
barrier_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_start_json_check CHECK (
length(CAST(barrier_json AS BLOB)) BETWEEN 2 AND 16384 AND
json_valid(barrier_json) AND json_type(barrier_json) = 'object' AND
json_extract(barrier_json, '$.schema') =
'qinglong/tool-execution-start-barrier@v1' AND
json_extract(barrier_json, '$.startId') = start_id AND
json_extract(barrier_json, '$.projectId') = project_id AND
json_extract(barrier_json, '$.runId') = run_id AND
json_extract(barrier_json, '$.stepRunId') = step_run_id AND
json_extract(barrier_json, '$.startedStepRunVersion') IS
started_step_run_version AND
json_extract(barrier_json, '$.stepRunMutationId') =
step_run_mutation_id AND
json_extract(barrier_json, '$.runEventId') = run_event_id AND
json_extract(barrier_json, '$.traceId') = trace_id AND
json_extract(barrier_json, '$.spanId') = span_id AND
json_extract(barrier_json, '$.auditEventId') = audit_event_id AND
json_extract(barrier_json, '$.commandDigest') = command_digest AND
json_extract(barrier_json, '$.barrierDigest') = barrier_digest AND
json_extract(barrier_json, '$.startedAtMs') IS started_at_ms
)
)
`,
`CREATE UNIQUE INDEX ql3_tool_start_step_version_uidx ON "ToolExecutionStartBarriers" (run_id, step_run_id, started_step_run_version)`,
`CREATE UNIQUE INDEX ql3_tool_start_mutation_uidx ON "ToolExecutionStartBarriers" (step_run_mutation_id)`,
`CREATE UNIQUE INDEX ql3_tool_start_event_uidx ON "ToolExecutionStartBarriers" (run_event_id)`,
`CREATE UNIQUE INDEX ql3_tool_start_trace_uidx ON "ToolExecutionStartBarriers" (trace_id, span_id)`,
`CREATE UNIQUE INDEX ql3_tool_start_audit_uidx ON "ToolExecutionStartBarriers" (audit_event_id)`,
`CREATE INDEX ql3_tool_start_run_time_idx ON "ToolExecutionStartBarriers" (run_id, started_at_ms, start_id)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const PREVIOUS_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1}';
const NEXT_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1}';
export const local0056CapabilityV28Migration = defineLocalSqliteMigration({
id: '0056-capability-v28',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 28,
migration_id = '0055-tool-execution-start-barriers',
capabilities = '${NEXT_CAPABILITIES}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 27
AND migration_id = '0053-tool-execution-evidence'
AND capabilities = '${PREVIOUS_CAPABILITIES}'
`,
],
});
@@ -0,0 +1,117 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0057ToolInvocationArtifactsMigration =
defineLocalSqliteMigration({
id: '0057-tool-invocation-artifacts',
statements: [
`
CREATE TABLE "ToolInvocationInputArtifacts" (
artifact_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
action_ref TEXT NOT NULL,
input_digest TEXT NOT NULL,
invocation_action_digest TEXT NOT NULL,
artifact_digest TEXT NOT NULL,
key_id TEXT NOT NULL,
algorithm TEXT NOT NULL,
plaintext_bytes INTEGER NOT NULL,
sealed_at_ms INTEGER NOT NULL,
artifact_json TEXT NOT NULL,
CONSTRAINT ql3_tool_input_artifact_project_fk
FOREIGN KEY (project_id)
REFERENCES "QingLong3Projects" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_input_artifact_identity_check CHECK (
length(artifact_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(action_ref) BETWEEN 1 AND 255 AND
length(key_id) BETWEEN 1 AND 128 AND
algorithm = 'aes-256-gcm'
),
CONSTRAINT ql3_tool_input_artifact_digest_check CHECK (
length(input_digest) = 64 AND
input_digest NOT GLOB '*[^0-9a-f]*' AND
length(invocation_action_digest) = 64 AND
invocation_action_digest NOT GLOB '*[^0-9a-f]*' AND
length(artifact_digest) = 64 AND
artifact_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_input_artifact_budget_check CHECK (
plaintext_bytes BETWEEN 0 AND 65536 AND sealed_at_ms >= 0
),
CONSTRAINT ql3_tool_input_artifact_json_check CHECK (
length(CAST(artifact_json AS BLOB)) BETWEEN 2 AND 98304 AND
json_valid(artifact_json) AND json_type(artifact_json) = 'object' AND
json_extract(artifact_json, '$.schema') =
'qinglong/tool-invocation-input-artifact@v1' AND
json_extract(artifact_json, '$.artifactId') = artifact_id AND
json_extract(artifact_json, '$.projectId') = project_id AND
json_extract(artifact_json, '$.actionRef') = action_ref AND
json_extract(artifact_json, '$.inputDigest') = input_digest AND
json_extract(artifact_json, '$.invocationActionDigest') =
invocation_action_digest AND
json_extract(artifact_json, '$.artifactDigest') = artifact_digest AND
json_extract(artifact_json, '$.keyId') = key_id AND
json_extract(artifact_json, '$.algorithm') = algorithm AND
json_extract(artifact_json, '$.plaintextBytes') IS plaintext_bytes AND
json_extract(artifact_json, '$.sealedAtMs') IS sealed_at_ms
)
)
`,
`CREATE UNIQUE INDEX ql3_tool_input_artifact_action_uidx ON "ToolInvocationInputArtifacts" (project_id, action_ref)`,
`CREATE INDEX ql3_tool_input_artifact_project_time_idx ON "ToolInvocationInputArtifacts" (project_id, sealed_at_ms, artifact_id)`,
`
CREATE TABLE "ToolInvocationPreviewArtifacts" (
artifact_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
action_ref TEXT NOT NULL,
action_digest TEXT NOT NULL,
preview_digest TEXT NOT NULL,
redaction_contract_digest TEXT NOT NULL,
artifact_digest TEXT NOT NULL,
byte_length INTEGER NOT NULL,
sealed_at_ms INTEGER NOT NULL,
artifact_json TEXT NOT NULL,
CONSTRAINT ql3_tool_preview_artifact_project_fk
FOREIGN KEY (project_id)
REFERENCES "QingLong3Projects" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_preview_artifact_identity_check CHECK (
length(artifact_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(action_ref) BETWEEN 1 AND 255
),
CONSTRAINT ql3_tool_preview_artifact_digest_check CHECK (
length(action_digest) = 64 AND
action_digest NOT GLOB '*[^0-9a-f]*' AND
length(preview_digest) = 64 AND
preview_digest NOT GLOB '*[^0-9a-f]*' AND
length(redaction_contract_digest) = 64 AND
redaction_contract_digest NOT GLOB '*[^0-9a-f]*' AND
length(artifact_digest) = 64 AND
artifact_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_preview_artifact_budget_check CHECK (
byte_length BETWEEN 2 AND 8192 AND sealed_at_ms >= 0
),
CONSTRAINT ql3_tool_preview_artifact_json_check CHECK (
length(CAST(artifact_json AS BLOB)) BETWEEN 2 AND 16384 AND
json_valid(artifact_json) AND json_type(artifact_json) = 'object' AND
json_extract(artifact_json, '$.schema') =
'qinglong/tool-invocation-preview-artifact@v1' AND
json_extract(artifact_json, '$.artifactId') = artifact_id AND
json_extract(artifact_json, '$.projectId') = project_id AND
json_extract(artifact_json, '$.actionRef') = action_ref AND
json_extract(artifact_json, '$.actionDigest') = action_digest AND
json_extract(artifact_json, '$.previewDigest') = preview_digest AND
json_extract(artifact_json, '$.redactionContractDigest') =
redaction_contract_digest AND
json_extract(artifact_json, '$.artifactDigest') = artifact_digest AND
json_extract(artifact_json, '$.byteLength') IS byte_length AND
json_extract(artifact_json, '$.sealedAtMs') IS sealed_at_ms
)
)
`,
`CREATE UNIQUE INDEX ql3_tool_preview_artifact_action_uidx ON "ToolInvocationPreviewArtifacts" (project_id, action_ref)`,
`CREATE UNIQUE INDEX ql3_tool_preview_artifact_action_digest_uidx ON "ToolInvocationPreviewArtifacts" (action_digest)`,
`CREATE INDEX ql3_tool_preview_artifact_project_time_idx ON "ToolInvocationPreviewArtifacts" (project_id, sealed_at_ms, artifact_id)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const PREVIOUS_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1}';
const NEXT_CAPABILITIES =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1}';
export const local0058CapabilityV29Migration = defineLocalSqliteMigration({
id: '0058-capability-v29',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 29,
migration_id = '0057-tool-invocation-artifacts',
capabilities = '${NEXT_CAPABILITIES}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 28
AND migration_id = '0055-tool-execution-start-barriers'
AND capabilities = '${PREVIOUS_CAPABILITIES}'
`,
],
});
@@ -0,0 +1,81 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0059ToolExecutionArtifactBindingsMigration =
defineLocalSqliteMigration({
id: '0059-tool-execution-artifact-bindings',
statements: [
`
CREATE UNIQUE INDEX ql3_tool_input_artifact_start_binding_uidx
ON "ToolInvocationInputArtifacts" (
artifact_id, artifact_digest, project_id, action_ref, input_digest
)
`,
`
CREATE UNIQUE INDEX ql3_tool_preview_artifact_start_binding_uidx
ON "ToolInvocationPreviewArtifacts" (
artifact_id, artifact_digest, project_id, action_ref, action_digest,
preview_digest, redaction_contract_digest
)
`,
`
CREATE TABLE "ToolExecutionStartArtifactBindings" (
start_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
action_ref TEXT NOT NULL,
input_artifact_id TEXT NOT NULL,
input_artifact_digest TEXT NOT NULL,
input_digest TEXT NOT NULL,
preview_artifact_id TEXT NOT NULL,
preview_artifact_digest TEXT NOT NULL,
action_digest TEXT NOT NULL,
preview_digest TEXT NOT NULL,
redaction_contract_digest TEXT NOT NULL,
bound_at_ms INTEGER NOT NULL,
CONSTRAINT ql3_tool_start_artifact_barrier_fk
FOREIGN KEY (start_id)
REFERENCES "ToolExecutionStartBarriers" (start_id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_start_input_artifact_fk
FOREIGN KEY (
input_artifact_id, input_artifact_digest, project_id, action_ref,
input_digest
)
REFERENCES "ToolInvocationInputArtifacts" (
artifact_id, artifact_digest, project_id, action_ref, input_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_start_preview_artifact_fk
FOREIGN KEY (
preview_artifact_id, preview_artifact_digest, project_id, action_ref,
action_digest, preview_digest, redaction_contract_digest
)
REFERENCES "ToolInvocationPreviewArtifacts" (
artifact_id, artifact_digest, project_id, action_ref, action_digest,
preview_digest, redaction_contract_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_start_artifact_identity_check CHECK (
length(start_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(action_ref) BETWEEN 1 AND 255 AND
length(input_artifact_id) BETWEEN 1 AND 128 AND
length(preview_artifact_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_tool_start_artifact_digest_check CHECK (
length(input_artifact_digest) = 64 AND
input_artifact_digest NOT GLOB '*[^0-9a-f]*' AND
length(input_digest) = 64 AND
input_digest NOT GLOB '*[^0-9a-f]*' AND
length(preview_artifact_digest) = 64 AND
preview_artifact_digest NOT GLOB '*[^0-9a-f]*' AND
length(action_digest) = 64 AND
action_digest NOT GLOB '*[^0-9a-f]*' AND
length(preview_digest) = 64 AND
preview_digest NOT GLOB '*[^0-9a-f]*' AND
length(redaction_contract_digest) = 64 AND
redaction_contract_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_start_artifact_time_check CHECK (bound_at_ms >= 0)
)
`,
`CREATE INDEX ql3_tool_start_artifact_input_idx ON "ToolExecutionStartArtifactBindings" (input_artifact_id, start_id)`,
`CREATE INDEX ql3_tool_start_artifact_preview_idx ON "ToolExecutionStartArtifactBindings" (preview_artifact_id, start_id)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const CAPABILITIES_V29 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1}';
const CAPABILITIES_V30 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1}';
export const local0060CapabilityV30Migration = defineLocalSqliteMigration({
id: '0060-capability-v30',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 30,
migration_id = '0059-tool-execution-artifact-bindings',
capabilities = '${CAPABILITIES_V30}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 29
AND migration_id = '0057-tool-invocation-artifacts'
AND capabilities = '${CAPABILITIES_V29}'
`,
],
});
@@ -0,0 +1,144 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0061ToolExecutionCompletionsMigration =
defineLocalSqliteMigration({
id: '0061-tool-execution-completions',
statements: [
`
CREATE TABLE "ToolExecutionCompletions" (
start_id TEXT PRIMARY KEY NOT NULL,
artifact_id TEXT NOT NULL,
project_id TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
started_step_run_version INTEGER NOT NULL,
completed_step_run_version INTEGER NOT NULL,
barrier_digest TEXT NOT NULL,
adapter_digest TEXT NOT NULL,
output_digest TEXT NOT NULL,
execution_result_digest TEXT NOT NULL,
artifact_digest TEXT NOT NULL,
key_id TEXT NOT NULL,
algorithm TEXT NOT NULL,
plaintext_bytes INTEGER NOT NULL,
step_run_mutation_id TEXT NOT NULL,
step_run_mutation_digest TEXT NOT NULL,
completed_step_run_digest TEXT NOT NULL,
run_event_id TEXT NOT NULL,
completed_at_ms INTEGER NOT NULL,
completion_digest TEXT NOT NULL,
artifact_json TEXT NOT NULL,
completion_json TEXT NOT NULL,
CONSTRAINT ql3_tool_completion_start_fk
FOREIGN KEY (start_id)
REFERENCES "ToolExecutionStartBarriers" (start_id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_completion_step_fk
FOREIGN KEY (run_id, step_run_id)
REFERENCES "StepRuns" (run_id, id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_completion_mutation_fk
FOREIGN KEY (step_run_mutation_id)
REFERENCES "StepRunMutations" (mutation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_completion_event_fk
FOREIGN KEY (run_event_id)
REFERENCES "RunEvents" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_completion_identity_check CHECK (
length(start_id) BETWEEN 1 AND 128 AND
length(artifact_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(key_id) BETWEEN 1 AND 128 AND
length(step_run_mutation_id) BETWEEN 1 AND 128 AND
length(run_event_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_tool_completion_version_check CHECK (
started_step_run_version BETWEEN 2 AND 2147483646 AND
completed_step_run_version = started_step_run_version + 1
),
CONSTRAINT ql3_tool_completion_digest_check CHECK (
length(barrier_digest) = 64 AND
barrier_digest NOT GLOB '*[^0-9a-f]*' AND
length(adapter_digest) = 64 AND
adapter_digest NOT GLOB '*[^0-9a-f]*' AND
length(output_digest) = 64 AND
output_digest NOT GLOB '*[^0-9a-f]*' AND
length(execution_result_digest) = 64 AND
execution_result_digest NOT GLOB '*[^0-9a-f]*' AND
length(artifact_digest) = 64 AND
artifact_digest NOT GLOB '*[^0-9a-f]*' AND
length(step_run_mutation_digest) = 64 AND
step_run_mutation_digest NOT GLOB '*[^0-9a-f]*' AND
length(completed_step_run_digest) = 64 AND
completed_step_run_digest NOT GLOB '*[^0-9a-f]*' AND
length(completion_digest) = 64 AND
completion_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_completion_budget_check CHECK (
algorithm = 'aes-256-gcm' AND
plaintext_bytes BETWEEN 0 AND 262144 AND
completed_at_ms >= 0 AND
length(CAST(artifact_json AS BLOB)) BETWEEN 2 AND 393216 AND
length(CAST(completion_json AS BLOB)) BETWEEN 2 AND 24576
),
CONSTRAINT ql3_tool_completion_json_check CHECK (
json_valid(artifact_json) AND
json_type(artifact_json) = 'object' AND
json_extract(artifact_json, '$.schema') =
'qinglong/tool-execution-result-artifact@v1' AND
json_extract(artifact_json, '$.artifactId') = artifact_id AND
json_extract(artifact_json, '$.projectId') = project_id AND
json_extract(artifact_json, '$.startId') = start_id AND
json_extract(artifact_json, '$.runId') = run_id AND
json_extract(artifact_json, '$.stepRunId') = step_run_id AND
json_extract(artifact_json, '$.barrierDigest') = barrier_digest AND
json_extract(artifact_json, '$.adapterDigest') = adapter_digest AND
json_extract(artifact_json, '$.outputDigest') = output_digest AND
json_extract(artifact_json, '$.executionResultDigest') =
execution_result_digest AND
json_extract(artifact_json, '$.artifactDigest') = artifact_digest AND
json_extract(artifact_json, '$.keyId') = key_id AND
json_extract(artifact_json, '$.algorithm') = algorithm AND
json_extract(artifact_json, '$.plaintextBytes') = plaintext_bytes AND
json_extract(artifact_json, '$.sealedAtMs') = completed_at_ms AND
json_valid(completion_json) AND
json_type(completion_json) = 'object' AND
json_extract(completion_json, '$.schema') =
'qinglong/tool-execution-completion@v1' AND
json_extract(completion_json, '$.startId') = start_id AND
json_extract(completion_json, '$.projectId') = project_id AND
json_extract(completion_json, '$.runId') = run_id AND
json_extract(completion_json, '$.stepRunId') = step_run_id AND
json_extract(completion_json, '$.startedStepRunVersion') =
started_step_run_version AND
json_extract(completion_json, '$.completedStepRunVersion') =
completed_step_run_version AND
json_extract(completion_json, '$.barrierDigest') = barrier_digest AND
json_extract(completion_json, '$.adapterDigest') = adapter_digest AND
json_extract(completion_json, '$.resultArtifact.artifactId') =
artifact_id AND
json_extract(completion_json, '$.resultArtifact.artifactDigest') =
artifact_digest AND
json_extract(completion_json, '$.resultArtifact.outputDigest') =
output_digest AND
json_extract(
completion_json, '$.resultArtifact.executionResultDigest'
) = execution_result_digest AND
json_extract(completion_json, '$.stepRunMutationId') =
step_run_mutation_id AND
json_extract(completion_json, '$.stepRunMutationDigest') =
step_run_mutation_digest AND
json_extract(completion_json, '$.completedStepRunDigest') =
completed_step_run_digest AND
json_extract(completion_json, '$.runEventId') = run_event_id AND
json_extract(completion_json, '$.completedAtMs') = completed_at_ms AND
json_extract(completion_json, '$.completionDigest') = completion_digest
)
)
`,
`CREATE UNIQUE INDEX ql3_tool_completion_artifact_uidx ON "ToolExecutionCompletions" (artifact_id)`,
`CREATE UNIQUE INDEX ql3_tool_completion_mutation_uidx ON "ToolExecutionCompletions" (step_run_mutation_id)`,
`CREATE UNIQUE INDEX ql3_tool_completion_event_uidx ON "ToolExecutionCompletions" (run_event_id)`,
`CREATE UNIQUE INDEX ql3_tool_completion_step_version_uidx ON "ToolExecutionCompletions" (run_id, step_run_id, completed_step_run_version)`,
`CREATE INDEX ql3_tool_completion_project_time_idx ON "ToolExecutionCompletions" (project_id, completed_at_ms, start_id)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const CAPABILITIES_V30 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1}';
const CAPABILITIES_V31 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1}';
export const local0062CapabilityV31Migration = defineLocalSqliteMigration({
id: '0062-capability-v31',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 31,
migration_id = '0061-tool-execution-completions',
capabilities = '${CAPABILITIES_V31}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 30
AND migration_id = '0059-tool-execution-artifact-bindings'
AND capabilities = '${CAPABILITIES_V30}'
`,
],
});
@@ -0,0 +1,113 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0063ToolExecutionFailureCompletionsMigration =
defineLocalSqliteMigration({
id: '0063-tool-execution-failure-completions',
statements: [
`
CREATE TABLE "ToolExecutionFailureCompletions" (
start_id TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
run_id TEXT NOT NULL,
step_run_id TEXT NOT NULL,
started_step_run_version INTEGER NOT NULL,
completed_step_run_version INTEGER NOT NULL,
barrier_digest TEXT NOT NULL,
adapter_digest TEXT NOT NULL,
outcome TEXT NOT NULL,
result_code TEXT NOT NULL,
error_summary TEXT NOT NULL,
step_run_mutation_id TEXT NOT NULL,
step_run_mutation_digest TEXT NOT NULL,
completed_step_run_digest TEXT NOT NULL,
run_event_id TEXT NOT NULL,
completed_at_ms INTEGER NOT NULL,
completion_digest TEXT NOT NULL,
completion_json TEXT NOT NULL,
CONSTRAINT ql3_tool_failure_completion_start_fk
FOREIGN KEY (start_id)
REFERENCES "ToolExecutionStartBarriers" (start_id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_failure_completion_step_fk
FOREIGN KEY (run_id, step_run_id)
REFERENCES "StepRuns" (run_id, id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_failure_completion_mutation_fk
FOREIGN KEY (step_run_mutation_id)
REFERENCES "StepRunMutations" (mutation_id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_failure_completion_event_fk
FOREIGN KEY (run_event_id)
REFERENCES "RunEvents" (id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_failure_completion_identity_check CHECK (
length(start_id) BETWEEN 1 AND 128 AND
length(project_id) BETWEEN 1 AND 128 AND
length(run_id) BETWEEN 1 AND 128 AND
length(step_run_id) BETWEEN 1 AND 128 AND
length(step_run_mutation_id) BETWEEN 1 AND 128 AND
length(run_event_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_tool_failure_completion_version_check CHECK (
started_step_run_version BETWEEN 2 AND 2147483646 AND
completed_step_run_version = started_step_run_version + 1
),
CONSTRAINT ql3_tool_failure_completion_digest_check CHECK (
length(barrier_digest) = 64 AND
barrier_digest NOT GLOB '*[^0-9a-f]*' AND
length(adapter_digest) = 64 AND
adapter_digest NOT GLOB '*[^0-9a-f]*' AND
length(step_run_mutation_digest) = 64 AND
step_run_mutation_digest NOT GLOB '*[^0-9a-f]*' AND
length(completed_step_run_digest) = 64 AND
completed_step_run_digest NOT GLOB '*[^0-9a-f]*' AND
length(completion_digest) = 64 AND
completion_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_failure_completion_fact_check CHECK (
(
outcome = 'failed' AND
result_code = 'tool_adapter_failed' AND
error_summary = 'Trusted Tool execution failed'
) OR (
outcome = 'timed_out' AND
result_code = 'tool_deadline_exceeded' AND
error_summary = 'Trusted Tool execution deadline exceeded'
)
),
CONSTRAINT ql3_tool_failure_completion_budget_check CHECK (
completed_at_ms >= 0 AND
length(CAST(completion_json AS BLOB)) BETWEEN 2 AND 24576
),
CONSTRAINT ql3_tool_failure_completion_json_check CHECK (
json_valid(completion_json) AND
json_type(completion_json) = 'object' AND
json_extract(completion_json, '$.schema') =
'qinglong/tool-execution-failure-completion@v1' AND
json_extract(completion_json, '$.startId') = start_id AND
json_extract(completion_json, '$.projectId') = project_id AND
json_extract(completion_json, '$.runId') = run_id AND
json_extract(completion_json, '$.stepRunId') = step_run_id AND
json_extract(completion_json, '$.startedStepRunVersion') =
started_step_run_version AND
json_extract(completion_json, '$.completedStepRunVersion') =
completed_step_run_version AND
json_extract(completion_json, '$.barrierDigest') = barrier_digest AND
json_extract(completion_json, '$.adapterDigest') = adapter_digest AND
json_extract(completion_json, '$.outcome') = outcome AND
json_extract(completion_json, '$.resultCode') = result_code AND
json_extract(completion_json, '$.errorSummary') = error_summary AND
json_extract(completion_json, '$.stepRunMutationId') =
step_run_mutation_id AND
json_extract(completion_json, '$.stepRunMutationDigest') =
step_run_mutation_digest AND
json_extract(completion_json, '$.completedStepRunDigest') =
completed_step_run_digest AND
json_extract(completion_json, '$.runEventId') = run_event_id AND
json_extract(completion_json, '$.completedAtMs') = completed_at_ms AND
json_extract(completion_json, '$.completionDigest') = completion_digest
)
)
`,
`CREATE UNIQUE INDEX ql3_tool_failure_completion_mutation_uidx ON "ToolExecutionFailureCompletions" (step_run_mutation_id)`,
`CREATE UNIQUE INDEX ql3_tool_failure_completion_event_uidx ON "ToolExecutionFailureCompletions" (run_event_id)`,
`CREATE UNIQUE INDEX ql3_tool_failure_completion_step_version_uidx ON "ToolExecutionFailureCompletions" (run_id, step_run_id, completed_step_run_version)`,
`CREATE INDEX ql3_tool_failure_completion_project_time_idx ON "ToolExecutionFailureCompletions" (project_id, completed_at_ms, start_id)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const CAPABILITIES_V31 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1}';
const CAPABILITIES_V32 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1}';
export const local0064CapabilityV32Migration = defineLocalSqliteMigration({
id: '0064-capability-v32',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 32,
migration_id = '0063-tool-execution-failure-completions',
capabilities = '${CAPABILITIES_V32}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 31
AND migration_id = '0061-tool-execution-completions'
AND capabilities = '${CAPABILITIES_V31}'
`,
],
});
@@ -0,0 +1,155 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0065ToolResultKeyCatalogMigration =
defineLocalSqliteMigration({
id: '0065-tool-result-key-catalog',
statements: [
`
CREATE TABLE "ToolResultKeyCatalogGenerations" (
authority TEXT NOT NULL,
generation INTEGER NOT NULL,
previous_generation INTEGER,
previous_catalog_digest TEXT,
active_key_id TEXT,
mutation_kind TEXT NOT NULL,
mutation_id TEXT NOT NULL,
catalog_digest TEXT NOT NULL,
command_digest TEXT NOT NULL,
committed_at_ms INTEGER NOT NULL,
catalog_json TEXT NOT NULL,
PRIMARY KEY (authority, generation),
UNIQUE (authority, generation, catalog_digest),
UNIQUE (mutation_id),
UNIQUE (catalog_digest),
CONSTRAINT ql3_tool_result_key_catalog_previous_fk
FOREIGN KEY (
authority, previous_generation, previous_catalog_digest
)
REFERENCES "ToolResultKeyCatalogGenerations" (
authority, generation, catalog_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_result_key_catalog_authority_check CHECK (
authority = 'trusted-tool-results'
),
CONSTRAINT ql3_tool_result_key_catalog_generation_check CHECK (
generation BETWEEN 1 AND 2147483647 AND
(
(
generation = 1 AND
previous_generation IS NULL AND
previous_catalog_digest IS NULL AND
mutation_kind = 'bootstrap'
) OR (
generation > 1 AND
previous_generation = generation - 1 AND
previous_catalog_digest IS NOT NULL AND
mutation_kind IN (
'rotate', 'retire', 'mark_lost', 'restore'
)
)
)
),
CONSTRAINT ql3_tool_result_key_catalog_identity_check CHECK (
active_key_id IS NULL OR
length(active_key_id) BETWEEN 1 AND 128 AND
active_key_id NOT GLOB '*[^A-Za-z0-9._-]*' AND
substr(active_key_id, 1, 1) GLOB '[A-Za-z0-9]'
),
CONSTRAINT ql3_tool_result_key_catalog_digest_check CHECK (
(
previous_catalog_digest IS NULL OR
length(previous_catalog_digest) = 64 AND
previous_catalog_digest NOT GLOB '*[^0-9a-f]*'
) AND
length(catalog_digest) = 64 AND
catalog_digest NOT GLOB '*[^0-9a-f]*' AND
length(command_digest) = 64 AND
command_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_result_key_catalog_budget_check CHECK (
committed_at_ms >= 0 AND
length(mutation_id) BETWEEN 1 AND 128 AND
length(CAST(catalog_json AS BLOB)) BETWEEN 2 AND 65536
),
CONSTRAINT ql3_tool_result_key_catalog_json_check CHECK (
json_valid(catalog_json) AND
json_type(catalog_json) = 'object' AND
json_extract(catalog_json, '$.schema') =
'qinglong/tool-result-key-catalog@v1' AND
json_extract(catalog_json, '$.generation') = generation AND
(
(
previous_catalog_digest IS NULL AND
json_type(catalog_json, '$.previousCatalogDigest') = 'null'
) OR
json_extract(catalog_json, '$.previousCatalogDigest') =
previous_catalog_digest
) AND
(
(
active_key_id IS NULL AND
json_type(catalog_json, '$.activeKeyId') = 'null'
) OR
json_extract(catalog_json, '$.activeKeyId') = active_key_id
) AND
json_extract(catalog_json, '$.mutationKind') = mutation_kind AND
json_extract(catalog_json, '$.mutationId') = mutation_id AND
json_extract(catalog_json, '$.catalogDigest') = catalog_digest AND
json_extract(catalog_json, '$.committedAtMs') = committed_at_ms AND
json_type(catalog_json, '$.keys') = 'array' AND
json_array_length(json_extract(catalog_json, '$.keys'))
BETWEEN 1 AND 64
)
)
`,
`CREATE INDEX ql3_tool_result_key_catalog_current_idx ON "ToolResultKeyCatalogGenerations" (authority, generation DESC)`,
`
CREATE TABLE "ToolExecutionResultKeyBindings" (
start_id TEXT PRIMARY KEY NOT NULL,
artifact_id TEXT NOT NULL UNIQUE,
artifact_digest TEXT NOT NULL,
catalog_authority TEXT NOT NULL,
catalog_generation INTEGER NOT NULL,
catalog_digest TEXT NOT NULL,
key_id TEXT NOT NULL,
material_proof TEXT NOT NULL,
binding_digest TEXT NOT NULL UNIQUE,
CONSTRAINT ql3_tool_result_key_binding_completion_fk
FOREIGN KEY (start_id)
REFERENCES "ToolExecutionCompletions" (start_id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_result_key_binding_artifact_fk
FOREIGN KEY (artifact_id)
REFERENCES "ToolExecutionCompletions" (artifact_id) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_result_key_binding_catalog_fk
FOREIGN KEY (
catalog_authority, catalog_generation, catalog_digest
)
REFERENCES "ToolResultKeyCatalogGenerations" (
authority, generation, catalog_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_result_key_binding_authority_check CHECK (
catalog_authority = 'trusted-tool-results'
),
CONSTRAINT ql3_tool_result_key_binding_identity_check CHECK (
length(start_id) BETWEEN 1 AND 128 AND
length(artifact_id) BETWEEN 1 AND 128 AND
length(key_id) BETWEEN 1 AND 128 AND
key_id NOT GLOB '*[^A-Za-z0-9._-]*' AND
substr(key_id, 1, 1) GLOB '[A-Za-z0-9]' AND
catalog_generation BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_tool_result_key_binding_digest_check CHECK (
length(artifact_digest) = 64 AND
artifact_digest NOT GLOB '*[^0-9a-f]*' AND
length(catalog_digest) = 64 AND
catalog_digest NOT GLOB '*[^0-9a-f]*' AND
length(material_proof) = 64 AND
material_proof NOT GLOB '*[^0-9a-f]*' AND
length(binding_digest) = 64 AND
binding_digest NOT GLOB '*[^0-9a-f]*'
)
)
`,
`CREATE INDEX ql3_tool_result_key_binding_catalog_idx ON "ToolExecutionResultKeyBindings" (catalog_generation, key_id, start_id)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const CAPABILITIES_V32 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1}';
const CAPABILITIES_V33 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1}';
export const local0066CapabilityV33Migration = defineLocalSqliteMigration({
id: '0066-capability-v33',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 33,
migration_id = '0065-tool-result-key-catalog',
capabilities = '${CAPABILITIES_V33}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 32
AND migration_id = '0063-tool-execution-failure-completions'
AND capabilities = '${CAPABILITIES_V32}'
`,
],
});
@@ -0,0 +1,238 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0067ToolResultRekeyOverlaysMigration =
defineLocalSqliteMigration({
id: '0067-tool-result-rekey-overlays',
statements: [
`
CREATE TABLE "ToolExecutionResultRekeyOverlays" (
overlay_id TEXT PRIMARY KEY NOT NULL,
artifact_id TEXT NOT NULL,
source_binding_digest TEXT NOT NULL,
revision INTEGER NOT NULL,
previous_overlay_digest TEXT,
from_key_id TEXT NOT NULL,
target_catalog_authority TEXT NOT NULL,
target_catalog_generation INTEGER NOT NULL,
target_catalog_digest TEXT NOT NULL,
target_key_id TEXT NOT NULL,
target_material_proof TEXT NOT NULL,
mutation_id TEXT NOT NULL UNIQUE,
command_digest TEXT NOT NULL,
overlay_digest TEXT NOT NULL UNIQUE,
rekeyed_at_ms INTEGER NOT NULL,
overlay_json TEXT NOT NULL,
UNIQUE (artifact_id, revision),
UNIQUE (artifact_id, revision, overlay_digest),
CONSTRAINT ql3_tool_result_rekey_artifact_fk
FOREIGN KEY (artifact_id)
REFERENCES "ToolExecutionResultKeyBindings" (artifact_id)
ON DELETE RESTRICT,
CONSTRAINT ql3_tool_result_rekey_binding_fk
FOREIGN KEY (source_binding_digest)
REFERENCES "ToolExecutionResultKeyBindings" (binding_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_tool_result_rekey_previous_fk
FOREIGN KEY (previous_overlay_digest)
REFERENCES "ToolExecutionResultRekeyOverlays" (overlay_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_tool_result_rekey_catalog_fk
FOREIGN KEY (
target_catalog_authority,
target_catalog_generation,
target_catalog_digest
)
REFERENCES "ToolResultKeyCatalogGenerations" (
authority, generation, catalog_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_result_rekey_revision_check CHECK (
revision BETWEEN 1 AND 2147483647 AND
(
(revision = 1 AND previous_overlay_digest IS NULL) OR
(revision > 1 AND previous_overlay_digest IS NOT NULL)
)
),
CONSTRAINT ql3_tool_result_rekey_authority_check CHECK (
target_catalog_authority = 'trusted-tool-results'
),
CONSTRAINT ql3_tool_result_rekey_identity_check CHECK (
length(overlay_id) BETWEEN 1 AND 128 AND
length(artifact_id) BETWEEN 1 AND 128 AND
length(mutation_id) BETWEEN 1 AND 128 AND
length(from_key_id) BETWEEN 1 AND 128 AND
from_key_id NOT GLOB '*[^A-Za-z0-9._-]*' AND
substr(from_key_id, 1, 1) GLOB '[A-Za-z0-9]' AND
length(target_key_id) BETWEEN 1 AND 128 AND
target_key_id NOT GLOB '*[^A-Za-z0-9._-]*' AND
substr(target_key_id, 1, 1) GLOB '[A-Za-z0-9]' AND
from_key_id <> target_key_id AND
target_catalog_generation BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_tool_result_rekey_digest_check CHECK (
(
previous_overlay_digest IS NULL OR
length(previous_overlay_digest) = 64 AND
previous_overlay_digest NOT GLOB '*[^0-9a-f]*'
) AND
length(source_binding_digest) = 64 AND
source_binding_digest NOT GLOB '*[^0-9a-f]*' AND
length(target_catalog_digest) = 64 AND
target_catalog_digest NOT GLOB '*[^0-9a-f]*' AND
length(target_material_proof) = 64 AND
target_material_proof NOT GLOB '*[^0-9a-f]*' AND
length(command_digest) = 64 AND
command_digest NOT GLOB '*[^0-9a-f]*' AND
length(overlay_digest) = 64 AND
overlay_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_result_rekey_budget_check CHECK (
rekeyed_at_ms >= 0 AND
length(CAST(overlay_json AS BLOB)) BETWEEN 2 AND 393216
),
CONSTRAINT ql3_tool_result_rekey_json_check CHECK (
json_valid(overlay_json) AND
json_type(overlay_json) = 'object' AND
json_extract(overlay_json, '$.schema') =
'qinglong/tool-execution-result-rekey-overlay@v1' AND
json_extract(overlay_json, '$.overlayId') = overlay_id AND
json_extract(overlay_json, '$.sourceArtifact.artifactId') =
artifact_id AND
json_extract(overlay_json, '$.sourceBindingDigest') =
source_binding_digest AND
json_extract(overlay_json, '$.revision') = revision AND
(
(
previous_overlay_digest IS NULL AND
json_type(overlay_json, '$.previousOverlayDigest') = 'null'
) OR
json_extract(overlay_json, '$.previousOverlayDigest') =
previous_overlay_digest
) AND
json_extract(overlay_json, '$.fromKeyId') = from_key_id AND
json_extract(overlay_json, '$.targetCatalogFence.generation') =
target_catalog_generation AND
json_extract(overlay_json, '$.targetCatalogFence.catalogDigest') =
target_catalog_digest AND
json_extract(overlay_json, '$.targetCatalogFence.keyId') =
target_key_id AND
json_extract(overlay_json, '$.targetCatalogFence.materialProof') =
target_material_proof AND
json_extract(overlay_json, '$.rekeyedAtMs') = rekeyed_at_ms AND
json_extract(overlay_json, '$.overlayDigest') = overlay_digest
)
)
`,
`CREATE INDEX ql3_tool_result_rekey_artifact_idx ON "ToolExecutionResultRekeyOverlays" (artifact_id, revision DESC)`,
`CREATE INDEX ql3_tool_result_rekey_target_idx ON "ToolExecutionResultRekeyOverlays" (target_key_id, artifact_id, revision DESC)`,
`
CREATE TABLE "ToolExecutionResultRekeyHeads" (
artifact_id TEXT PRIMARY KEY NOT NULL,
revision INTEGER NOT NULL,
overlay_id TEXT NOT NULL UNIQUE,
overlay_digest TEXT NOT NULL UNIQUE,
target_catalog_generation INTEGER NOT NULL,
target_catalog_digest TEXT NOT NULL,
target_key_id TEXT NOT NULL,
updated_at_ms INTEGER NOT NULL,
CONSTRAINT ql3_tool_result_rekey_head_overlay_fk
FOREIGN KEY (artifact_id, revision, overlay_digest)
REFERENCES "ToolExecutionResultRekeyOverlays" (
artifact_id, revision, overlay_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_result_rekey_head_identity_check CHECK (
revision BETWEEN 1 AND 2147483647 AND
length(artifact_id) BETWEEN 1 AND 128 AND
length(overlay_id) BETWEEN 1 AND 128 AND
length(target_key_id) BETWEEN 1 AND 128 AND
target_key_id NOT GLOB '*[^A-Za-z0-9._-]*' AND
substr(target_key_id, 1, 1) GLOB '[A-Za-z0-9]' AND
target_catalog_generation BETWEEN 1 AND 2147483647 AND
updated_at_ms >= 0
),
CONSTRAINT ql3_tool_result_rekey_head_digest_check CHECK (
length(overlay_digest) = 64 AND
overlay_digest NOT GLOB '*[^0-9a-f]*' AND
length(target_catalog_digest) = 64 AND
target_catalog_digest NOT GLOB '*[^0-9a-f]*'
)
)
`,
`CREATE INDEX ql3_tool_result_rekey_head_target_idx ON "ToolExecutionResultRekeyHeads" (target_key_id, artifact_id)`,
`
CREATE TABLE "ToolResultKeyRetirementReceipts" (
receipt_digest TEXT PRIMARY KEY NOT NULL,
catalog_authority TEXT NOT NULL,
catalog_generation INTEGER NOT NULL,
catalog_digest TEXT NOT NULL,
key_id TEXT NOT NULL,
material_proof TEXT NOT NULL,
mutation_id TEXT NOT NULL UNIQUE,
command_digest TEXT NOT NULL,
binding_count INTEGER NOT NULL,
overlay_head_count INTEGER NOT NULL,
uncovered_binding_count INTEGER NOT NULL,
uncovered_overlay_head_count INTEGER NOT NULL,
coverage_digest TEXT NOT NULL,
created_at_ms INTEGER NOT NULL,
receipt_json TEXT NOT NULL,
CONSTRAINT ql3_tool_result_key_retirement_catalog_fk
FOREIGN KEY (
catalog_authority, catalog_generation, catalog_digest
)
REFERENCES "ToolResultKeyCatalogGenerations" (
authority, generation, catalog_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_tool_result_key_retirement_authority_check CHECK (
catalog_authority = 'trusted-tool-results'
),
CONSTRAINT ql3_tool_result_key_retirement_identity_check CHECK (
catalog_generation BETWEEN 1 AND 2147483647 AND
length(key_id) BETWEEN 1 AND 128 AND
key_id NOT GLOB '*[^A-Za-z0-9._-]*' AND
substr(key_id, 1, 1) GLOB '[A-Za-z0-9]' AND
length(mutation_id) BETWEEN 1 AND 128
),
CONSTRAINT ql3_tool_result_key_retirement_count_check CHECK (
binding_count BETWEEN 0 AND 2147483647 AND
overlay_head_count BETWEEN 0 AND 2147483647 AND
uncovered_binding_count = 0 AND
uncovered_overlay_head_count = 0 AND
created_at_ms >= 0
),
CONSTRAINT ql3_tool_result_key_retirement_digest_check CHECK (
length(receipt_digest) = 64 AND
receipt_digest NOT GLOB '*[^0-9a-f]*' AND
length(catalog_digest) = 64 AND
catalog_digest NOT GLOB '*[^0-9a-f]*' AND
length(material_proof) = 64 AND
material_proof NOT GLOB '*[^0-9a-f]*' AND
length(command_digest) = 64 AND
command_digest NOT GLOB '*[^0-9a-f]*' AND
length(coverage_digest) = 64 AND
coverage_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_tool_result_key_retirement_json_check CHECK (
json_valid(receipt_json) AND
json_type(receipt_json) = 'object' AND
json_extract(receipt_json, '$.schema') =
'qinglong/tool-result-key-retirement-receipt@v1' AND
json_extract(receipt_json, '$.catalogGeneration') =
catalog_generation AND
json_extract(receipt_json, '$.catalogDigest') = catalog_digest AND
json_extract(receipt_json, '$.keyId') = key_id AND
json_extract(receipt_json, '$.materialProof') = material_proof AND
json_extract(receipt_json, '$.mutationId') = mutation_id AND
json_extract(receipt_json, '$.bindingCount') = binding_count AND
json_extract(receipt_json, '$.overlayHeadCount') =
overlay_head_count AND
json_extract(receipt_json, '$.uncoveredBindingCount') = 0 AND
json_extract(receipt_json, '$.uncoveredOverlayHeadCount') = 0 AND
json_extract(receipt_json, '$.coverageDigest') = coverage_digest AND
json_extract(receipt_json, '$.createdAtMs') = created_at_ms AND
json_extract(receipt_json, '$.receiptDigest') = receipt_digest
)
)
`,
`CREATE INDEX ql3_tool_result_key_retirement_catalog_idx ON "ToolResultKeyRetirementReceipts" (catalog_generation, key_id)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const CAPABILITIES_V33 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1}';
const CAPABILITIES_V34 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1}';
export const local0068CapabilityV34Migration = defineLocalSqliteMigration({
id: '0068-capability-v34',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 34,
migration_id = '0067-tool-result-rekey-overlays',
capabilities = '${CAPABILITIES_V34}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 33
AND migration_id = '0065-tool-result-key-catalog'
AND capabilities = '${CAPABILITIES_V33}'
`,
],
});
@@ -0,0 +1,298 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0069PluginPackageQuarantineMigration =
defineLocalSqliteMigration({
id: '0069-plugin-package-quarantine',
statements: [
`
CREATE UNIQUE INDEX ql3_plugin_package_installs_quarantine_target_uidx
ON "QingLong3PluginPackageInstalls" (
project_id, package_name, installation_id, lock_digest, record_digest
)
`,
`
CREATE UNIQUE INDEX ql3_project_tool_definition_snapshot_withdrawal_uidx
ON "QingLong3ProjectToolDefinitionSnapshots" (
project_id, active_vector_digest, snapshot_digest
)
`,
`
CREATE TABLE "QingLong3PluginPackageQuarantineEvents" (
event_digest TEXT PRIMARY KEY NOT NULL,
mutation_id TEXT NOT NULL,
revocation_receipt_digest TEXT NOT NULL,
impact_digest TEXT NOT NULL,
project_id TEXT NOT NULL,
package_name TEXT NOT NULL,
installation_id TEXT NOT NULL,
lock_digest TEXT NOT NULL,
install_state TEXT NOT NULL,
install_version INTEGER NOT NULL,
install_record_digest TEXT NOT NULL,
active_lock_digest TEXT,
proposer_type TEXT NOT NULL,
proposer_id TEXT NOT NULL,
confirmer_type TEXT NOT NULL,
confirmer_id TEXT NOT NULL,
authorization_mode TEXT NOT NULL,
reason_code TEXT NOT NULL,
occurred_at_ms INTEGER NOT NULL,
event_json TEXT NOT NULL,
CONSTRAINT ql3_plugin_package_quarantine_install_fk
FOREIGN KEY (
project_id, package_name, installation_id, lock_digest,
install_record_digest
)
REFERENCES "QingLong3PluginPackageInstalls" (
project_id, package_name, installation_id, lock_digest, record_digest
) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_plugin_package_quarantine_identity_check CHECK (
length(mutation_id) BETWEEN 1 AND 128 AND
length(package_name) BETWEEN 1 AND 63 AND
length(installation_id) BETWEEN 1 AND 128 AND
install_state IN ('queued','staged','activating','active') AND
install_version BETWEEN 1 AND 2147483647
),
CONSTRAINT ql3_plugin_package_quarantine_state_check CHECK (
(
install_state = 'active' AND active_lock_digest = lock_digest
) OR (
install_state <> 'active' AND
(active_lock_digest IS NULL OR active_lock_digest <> lock_digest)
)
),
CONSTRAINT ql3_plugin_package_quarantine_subject_check CHECK (
proposer_type IN ('user','api_app','mcp_client','agent','system','worker') AND
confirmer_type IN ('user','api_app','mcp_client','agent','system','worker') AND
length(proposer_id) BETWEEN 1 AND 255 AND
length(confirmer_id) BETWEEN 1 AND 255 AND
authorization_mode IN ('dual_control','break_glass') AND
(
authorization_mode = 'break_glass' OR
proposer_type <> confirmer_type OR proposer_id <> confirmer_id
) AND
reason_code IN (
'suspected_key_compromise','confirmed_key_compromise'
)
),
CONSTRAINT ql3_plugin_package_quarantine_digest_check CHECK (
length(event_digest) = 64 AND
event_digest NOT GLOB '*[^0-9a-f]*' AND
length(revocation_receipt_digest) = 64 AND
revocation_receipt_digest NOT GLOB '*[^0-9a-f]*' AND
length(impact_digest) = 64 AND
impact_digest NOT GLOB '*[^0-9a-f]*' AND
length(lock_digest) = 64 AND
lock_digest NOT GLOB '*[^0-9a-f]*' AND
length(install_record_digest) = 64 AND
install_record_digest NOT GLOB '*[^0-9a-f]*' AND
(
active_lock_digest IS NULL OR
length(active_lock_digest) = 64 AND
active_lock_digest NOT GLOB '*[^0-9a-f]*'
)
),
CONSTRAINT ql3_plugin_package_quarantine_json_check CHECK (
length(CAST(event_json AS BLOB)) BETWEEN 2 AND 262144 AND
json_valid(event_json) AND json_type(event_json) = 'object' AND
json_extract(event_json, '$.schema') =
'qinglong/plugin-package-quarantine-event@v1' AND
json_extract(event_json, '$.mutationId') = mutation_id AND
json_extract(event_json, '$.revocationReceiptDigest') =
revocation_receipt_digest AND
json_extract(event_json, '$.impactDigest') = impact_digest AND
json_extract(event_json, '$.target.projectId') = project_id AND
json_extract(event_json, '$.target.packageName') = package_name AND
json_extract(event_json, '$.target.installationId') = installation_id AND
json_extract(event_json, '$.target.lockDigest') = lock_digest AND
json_extract(event_json, '$.target.installState') = install_state AND
json_extract(event_json, '$.target.installVersion') = install_version AND
json_extract(event_json, '$.target.installRecordDigest') =
install_record_digest AND
(
(
active_lock_digest IS NULL AND
json_type(event_json, '$.target.activeLockDigest') = 'null'
) OR
json_extract(event_json, '$.target.activeLockDigest') =
active_lock_digest
) AND
json_extract(event_json, '$.proposer.type') = proposer_type AND
json_extract(event_json, '$.proposer.id') = proposer_id AND
json_extract(event_json, '$.confirmer.type') = confirmer_type AND
json_extract(event_json, '$.confirmer.id') = confirmer_id AND
json_extract(event_json, '$.authorizationMode') = authorization_mode AND
json_extract(event_json, '$.reasonCode') = reason_code AND
json_extract(event_json, '$.occurredAtMs') = occurred_at_ms AND
json_extract(event_json, '$.eventDigest') = event_digest
),
CONSTRAINT ql3_plugin_package_quarantine_time_check CHECK (
occurred_at_ms >= 0
)
)
`,
`CREATE UNIQUE INDEX ql3_plugin_package_quarantine_mutation_uidx ON "QingLong3PluginPackageQuarantineEvents" (mutation_id)`,
`CREATE UNIQUE INDEX ql3_plugin_package_quarantine_target_uidx ON "QingLong3PluginPackageQuarantineEvents" (project_id, package_name, installation_id, lock_digest)`,
`CREATE INDEX ql3_plugin_package_quarantine_lock_idx ON "QingLong3PluginPackageQuarantineEvents" (lock_digest, project_id, package_name)`,
`CREATE INDEX ql3_plugin_package_quarantine_project_idx ON "QingLong3PluginPackageQuarantineEvents" (project_id, package_name, occurred_at_ms, event_digest)`,
`
CREATE TABLE "QingLong3PluginPackageWithdrawalReceipts" (
event_digest TEXT PRIMARY KEY NOT NULL,
receipt_digest TEXT NOT NULL,
project_id TEXT NOT NULL,
capability_status TEXT NOT NULL,
task_count INTEGER NOT NULL,
previous_active_vector_digest TEXT,
current_active_vector_digest TEXT,
current_tool_snapshot_digest TEXT,
retained_source_count INTEGER NOT NULL,
committed_at_ms INTEGER NOT NULL,
receipt_json TEXT NOT NULL,
CONSTRAINT ql3_plugin_package_withdrawal_event_fk
FOREIGN KEY (event_digest)
REFERENCES "QingLong3PluginPackageQuarantineEvents" (event_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_withdrawal_snapshot_fk
FOREIGN KEY (
project_id, current_active_vector_digest, current_tool_snapshot_digest
)
REFERENCES "QingLong3ProjectToolDefinitionSnapshots" (
project_id, active_vector_digest, snapshot_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_withdrawal_disposition_check CHECK (
(
capability_status = 'not_active' AND
task_count = 0 AND
previous_active_vector_digest IS NULL AND
current_active_vector_digest IS NULL AND
current_tool_snapshot_digest IS NULL AND
retained_source_count = 0
) OR (
capability_status = 'withdrawn' AND
task_count BETWEEN 0 AND 128 AND
previous_active_vector_digest IS NOT NULL AND
current_active_vector_digest IS NOT NULL AND
previous_active_vector_digest <> current_active_vector_digest AND
current_tool_snapshot_digest IS NOT NULL AND
retained_source_count BETWEEN 0 AND 128
)
),
CONSTRAINT ql3_plugin_package_withdrawal_digest_check CHECK (
length(event_digest) = 64 AND
event_digest NOT GLOB '*[^0-9a-f]*' AND
length(receipt_digest) = 64 AND
receipt_digest NOT GLOB '*[^0-9a-f]*' AND
(
previous_active_vector_digest IS NULL OR
length(previous_active_vector_digest) = 64 AND
previous_active_vector_digest NOT GLOB '*[^0-9a-f]*'
) AND
(
current_active_vector_digest IS NULL OR
length(current_active_vector_digest) = 64 AND
current_active_vector_digest NOT GLOB '*[^0-9a-f]*'
) AND
(
current_tool_snapshot_digest IS NULL OR
length(current_tool_snapshot_digest) = 64 AND
current_tool_snapshot_digest NOT GLOB '*[^0-9a-f]*'
)
),
CONSTRAINT ql3_plugin_package_withdrawal_json_check CHECK (
length(CAST(receipt_json AS BLOB)) BETWEEN 2 AND 8388608 AND
json_valid(receipt_json) AND json_type(receipt_json) = 'object' AND
json_extract(receipt_json, '$.schema') =
'qinglong/plugin-package-withdrawal-receipt@v1' AND
json_extract(receipt_json, '$.eventDigest') = event_digest AND
json_extract(receipt_json, '$.target.projectId') = project_id AND
json_extract(receipt_json, '$.capability.status') = capability_status AND
json_array_length(
json_extract(receipt_json, '$.capability.taskWithdrawals')
) = task_count AND
(
(
previous_active_vector_digest IS NULL AND
json_type(
receipt_json, '$.capability.previousActiveVectorDigest'
) = 'null'
) OR
json_extract(
receipt_json, '$.capability.previousActiveVectorDigest'
) = previous_active_vector_digest
) AND
(
(
current_active_vector_digest IS NULL AND
json_type(
receipt_json, '$.capability.currentActiveVectorDigest'
) = 'null'
) OR
json_extract(
receipt_json, '$.capability.currentActiveVectorDigest'
) = current_active_vector_digest
) AND
(
(
current_tool_snapshot_digest IS NULL AND
json_type(
receipt_json, '$.capability.currentToolSnapshotDigest'
) = 'null'
) OR
json_extract(
receipt_json, '$.capability.currentToolSnapshotDigest'
) = current_tool_snapshot_digest
) AND
json_extract(receipt_json, '$.capability.retainedSourceCount') =
retained_source_count AND
json_extract(receipt_json, '$.committedAtMs') = committed_at_ms AND
json_extract(receipt_json, '$.receiptDigest') = receipt_digest
),
CONSTRAINT ql3_plugin_package_withdrawal_time_check CHECK (
committed_at_ms >= 0
)
)
`,
`CREATE UNIQUE INDEX ql3_plugin_package_withdrawal_receipt_uidx ON "QingLong3PluginPackageWithdrawalReceipts" (receipt_digest)`,
`CREATE INDEX ql3_plugin_package_withdrawal_snapshot_idx ON "QingLong3PluginPackageWithdrawalReceipts" (current_tool_snapshot_digest, event_digest)`,
`
CREATE TABLE "QingLong3PluginPackageWithdrawalTasks" (
event_digest TEXT NOT NULL,
project_id TEXT NOT NULL,
task_id TEXT NOT NULL,
previous_revision INTEGER NOT NULL,
disabled_revision INTEGER NOT NULL,
previous_content_digest TEXT NOT NULL,
disabled_content_digest TEXT NOT NULL,
PRIMARY KEY (event_digest, task_id),
CONSTRAINT ql3_plugin_package_withdrawal_task_receipt_fk
FOREIGN KEY (event_digest)
REFERENCES "QingLong3PluginPackageWithdrawalReceipts" (event_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_withdrawal_task_previous_fk
FOREIGN KEY (project_id, task_id, previous_revision)
REFERENCES "QingLong3TaskDefinitionRevisions" (
project_id, task_id, revision
) ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_withdrawal_task_disabled_fk
FOREIGN KEY (project_id, task_id, disabled_revision)
REFERENCES "QingLong3TaskDefinitionRevisions" (
project_id, task_id, revision
) ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_withdrawal_task_identity_check CHECK (
length(task_id) BETWEEN 1 AND 128 AND
previous_revision BETWEEN 1 AND 2147483646 AND
disabled_revision = previous_revision + 1
),
CONSTRAINT ql3_plugin_package_withdrawal_task_digest_check CHECK (
length(event_digest) = 64 AND
event_digest NOT GLOB '*[^0-9a-f]*' AND
length(previous_content_digest) = 64 AND
previous_content_digest NOT GLOB '*[^0-9a-f]*' AND
length(disabled_content_digest) = 64 AND
disabled_content_digest NOT GLOB '*[^0-9a-f]*'
)
)
`,
`CREATE INDEX ql3_plugin_package_withdrawal_task_task_idx ON "QingLong3PluginPackageWithdrawalTasks" (project_id, task_id, event_digest)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const CAPABILITIES_V34 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1}';
const CAPABILITIES_V35 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"plugin_package_quarantine":1}';
export const local0070CapabilityV35Migration = defineLocalSqliteMigration({
id: '0070-capability-v35',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 35,
migration_id = '0069-plugin-package-quarantine',
capabilities = '${CAPABILITIES_V35}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 34
AND migration_id = '0067-tool-result-rekey-overlays'
AND capabilities = '${CAPABILITIES_V34}'
`,
],
});
@@ -0,0 +1,221 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0071LocalIdentityCredentialAdministrationMigration =
defineLocalSqliteMigration({
id: '0071-local-identity-credential-administration',
statements: [
`
CREATE UNIQUE INDEX "ql3_local_credential_pepper_binding_triple_uidx"
ON "QingLong3ApiCredentialPepperBindings" (
"credential_id", "credential_version", "pepper_key_id"
)
`,
`
CREATE TABLE "QingLong3IdentityAdministrationMutations" (
"mutation_id" TEXT PRIMARY KEY NOT NULL,
"project_id" TEXT NOT NULL,
"operation" TEXT NOT NULL,
"subject_type" TEXT NOT NULL,
"subject_id" TEXT NOT NULL,
"subject_version" INTEGER NOT NULL,
"expected_previous_version" INTEGER NOT NULL,
"status" TEXT NOT NULL,
"changed_by_type" TEXT NOT NULL,
"changed_by_id" TEXT NOT NULL,
"audit_event_id" TEXT NOT NULL UNIQUE,
"identity_created_at_ms" INTEGER NOT NULL,
"created_at_ms" INTEGER NOT NULL,
FOREIGN KEY ("project_id")
REFERENCES "QingLong3Projects" ("id") ON DELETE RESTRICT,
FOREIGN KEY ("subject_type", "subject_id")
REFERENCES "QingLong3IdentitySubjects" ("subject_type", "subject_id")
ON DELETE RESTRICT,
FOREIGN KEY ("audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_identity_admin_mutation_id_check CHECK (
length("mutation_id") = 36
),
CONSTRAINT ql3_identity_admin_operation_check CHECK (
"operation" IN ('register','enable','disable')
),
CONSTRAINT ql3_identity_admin_subject_check CHECK (
"subject_type" IN ('user','api_app','mcp_client','agent')
AND length("subject_id") BETWEEN 1 AND 255
),
CONSTRAINT ql3_identity_admin_transition_check CHECK (
"subject_version" = "expected_previous_version" + 1
AND "subject_version" BETWEEN 1 AND 2147483647
AND "expected_previous_version" BETWEEN 0 AND 2147483646
AND (
("operation" = 'register'
AND "expected_previous_version" = 0 AND "status" = 'active')
OR ("operation" = 'enable'
AND "expected_previous_version" > 0 AND "status" = 'active')
OR ("operation" = 'disable'
AND "expected_previous_version" > 0 AND "status" = 'disabled')
)
),
CONSTRAINT ql3_identity_admin_actor_check CHECK (
"changed_by_type" = 'user'
AND length("changed_by_id") BETWEEN 1 AND 255
),
CONSTRAINT ql3_identity_admin_audit_check CHECK (
"audit_event_id" = "mutation_id"
),
CONSTRAINT ql3_identity_admin_time_check CHECK (
"identity_created_at_ms" >= 0
AND "created_at_ms" >= "identity_created_at_ms"
)
)
`,
`
CREATE INDEX "ql3_identity_admin_subject_idx"
ON "QingLong3IdentityAdministrationMutations" (
"subject_type", "subject_id", "subject_version" DESC
)
`,
`
CREATE TABLE "QingLong3ApiCredentialAdministrationMutations" (
"mutation_id" TEXT PRIMARY KEY NOT NULL,
"project_id" TEXT NOT NULL,
"operation" TEXT NOT NULL,
"credential_id" TEXT NOT NULL,
"credential_version" INTEGER NOT NULL,
"expected_previous_version" INTEGER NOT NULL,
"subject_type" TEXT NOT NULL,
"subject_id" TEXT NOT NULL,
"subject_status" TEXT NOT NULL,
"state" TEXT NOT NULL,
"pepper_key_id" TEXT NOT NULL,
"secret_digest" TEXT NOT NULL,
"not_before_at_ms" INTEGER NOT NULL,
"expires_at_ms" INTEGER NOT NULL,
"delivery_digest" TEXT,
"changed_by_type" TEXT NOT NULL,
"changed_by_id" TEXT NOT NULL,
"audit_event_id" TEXT NOT NULL UNIQUE,
"created_at_ms" INTEGER NOT NULL,
FOREIGN KEY ("project_id")
REFERENCES "QingLong3Projects" ("id") ON DELETE RESTRICT,
FOREIGN KEY ("credential_id", "credential_version")
REFERENCES "QingLong3ApiCredentials" ("credential_id", "version")
ON DELETE RESTRICT,
FOREIGN KEY ("credential_id", "credential_version", "pepper_key_id")
REFERENCES "QingLong3ApiCredentialPepperBindings" (
"credential_id", "credential_version", "pepper_key_id"
) ON DELETE RESTRICT,
FOREIGN KEY ("audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_credential_admin_mutation_id_check CHECK (
length("mutation_id") = 36
),
CONSTRAINT ql3_credential_admin_operation_check CHECK (
"operation" IN ('issue','rotate','revoke')
),
CONSTRAINT ql3_credential_admin_identity_check CHECK (
length("credential_id") BETWEEN 1 AND 64
AND "credential_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
AND "subject_type" IN ('user','api_app','mcp_client','agent')
AND length("subject_id") BETWEEN 1 AND 255
AND "subject_status" IN ('active','disabled')
),
CONSTRAINT ql3_credential_admin_transition_check CHECK (
"credential_version" = "expected_previous_version" + 1
AND "credential_version" BETWEEN 1 AND 2147483647
AND "expected_previous_version" BETWEEN 0 AND 2147483646
AND (
("operation" = 'issue'
AND "expected_previous_version" = 0 AND "state" = 'active')
OR ("operation" = 'rotate'
AND "expected_previous_version" > 0 AND "state" = 'active')
OR ("operation" = 'revoke'
AND "expected_previous_version" > 0 AND "state" = 'revoked')
)
),
CONSTRAINT ql3_credential_admin_digest_check CHECK (
length("pepper_key_id") BETWEEN 1 AND 64
AND "pepper_key_id" NOT GLOB '*[^A-Za-z0-9._:-]*'
AND length("secret_digest") = 64
AND "secret_digest" NOT GLOB '*[^0-9a-f]*'
AND (
("operation" IN ('issue','rotate')
AND length("delivery_digest") = 64
AND "delivery_digest" NOT GLOB '*[^0-9a-f]*')
OR ("operation" = 'revoke' AND "delivery_digest" IS NULL)
)
),
CONSTRAINT ql3_credential_admin_lifetime_check CHECK (
"created_at_ms" >= 0
AND "not_before_at_ms" >= "created_at_ms"
AND "expires_at_ms" > "not_before_at_ms"
),
CONSTRAINT ql3_credential_admin_actor_check CHECK (
"changed_by_type" = 'user'
AND length("changed_by_id") BETWEEN 1 AND 255
),
CONSTRAINT ql3_credential_admin_audit_check CHECK (
"audit_event_id" = "mutation_id"
)
)
`,
`
CREATE INDEX "ql3_credential_admin_credential_idx"
ON "QingLong3ApiCredentialAdministrationMutations" (
"credential_id", "credential_version" DESC
)
`,
`
CREATE INDEX "ql3_credential_admin_subject_idx"
ON "QingLong3ApiCredentialAdministrationMutations" (
"subject_type", "subject_id", "created_at_ms" DESC
)
`,
`
CREATE TABLE "QingLong3ApiCredentialDeliveryAcknowledgements" (
"credential_mutation_id" TEXT PRIMARY KEY NOT NULL,
"acknowledgement_mutation_id" TEXT NOT NULL UNIQUE,
"project_id" TEXT NOT NULL,
"delivery_digest" TEXT NOT NULL,
"acknowledged_by_type" TEXT NOT NULL,
"acknowledged_by_id" TEXT NOT NULL,
"audit_event_id" TEXT NOT NULL UNIQUE,
"acknowledged_at_ms" INTEGER NOT NULL,
FOREIGN KEY ("credential_mutation_id")
REFERENCES "QingLong3ApiCredentialAdministrationMutations" ("mutation_id")
ON DELETE RESTRICT,
FOREIGN KEY ("project_id")
REFERENCES "QingLong3Projects" ("id") ON DELETE RESTRICT,
FOREIGN KEY ("audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_credential_delivery_ack_identity_check CHECK (
length("credential_mutation_id") = 36
AND length("acknowledgement_mutation_id") = 36
AND "credential_mutation_id" <> "acknowledgement_mutation_id"
),
CONSTRAINT ql3_credential_delivery_ack_digest_check CHECK (
length("delivery_digest") = 64
AND "delivery_digest" NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_credential_delivery_ack_actor_check CHECK (
"acknowledged_by_type" = 'user'
AND length("acknowledged_by_id") BETWEEN 1 AND 255
),
CONSTRAINT ql3_credential_delivery_ack_audit_check CHECK (
"audit_event_id" = "acknowledgement_mutation_id"
),
CONSTRAINT ql3_credential_delivery_ack_time_check CHECK (
"acknowledged_at_ms" >= 0
)
)
`,
`
CREATE INDEX "ql3_credential_delivery_ack_project_idx"
ON "QingLong3ApiCredentialDeliveryAcknowledgements" (
"project_id", "acknowledged_at_ms" DESC
)
`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const CAPABILITIES_V35 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"plugin_package_quarantine":1}';
const CAPABILITIES_V36 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_identity_credential_administration":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"plugin_package_quarantine":1}';
export const local0072CapabilityV36Migration = defineLocalSqliteMigration({
id: '0072-capability-v36',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 36,
migration_id = '0071-local-identity-credential-administration',
capabilities = '${CAPABILITIES_V36}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 35
AND migration_id = '0069-plugin-package-quarantine'
AND capabilities = '${CAPABILITIES_V35}'
`,
],
});
@@ -0,0 +1,97 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0073LocalProjectAdministrationMigration =
defineLocalSqliteMigration({
id: '0073-local-project-administration',
statements: [
`
CREATE TABLE "QingLong3ProjectAdministrationMutations" (
"mutation_id" TEXT PRIMARY KEY NOT NULL,
"operation" TEXT NOT NULL,
"authority_project_id" TEXT NOT NULL,
"project_id" TEXT NOT NULL,
"project_name" TEXT NOT NULL,
"project_slug" TEXT NOT NULL,
"project_status" TEXT NOT NULL,
"project_version" INTEGER NOT NULL,
"expected_previous_version" INTEGER NOT NULL,
"changed_by_type" TEXT NOT NULL,
"changed_by_id" TEXT NOT NULL,
"initial_owner_binding_version" INTEGER,
"audit_event_id" TEXT NOT NULL UNIQUE,
"project_created_at_ms" INTEGER NOT NULL,
"created_at_ms" INTEGER NOT NULL,
FOREIGN KEY ("authority_project_id")
REFERENCES "QingLong3Projects" ("id") ON DELETE RESTRICT,
FOREIGN KEY ("project_id")
REFERENCES "QingLong3Projects" ("id") ON DELETE RESTRICT,
FOREIGN KEY ("audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_project_admin_mutation_id_check CHECK (
length("mutation_id") = 36
AND substr("mutation_id", 9, 1) = '-'
AND substr("mutation_id", 14, 1) = '-'
AND substr("mutation_id", 19, 1) = '-'
AND substr("mutation_id", 24, 1) = '-'
AND replace("mutation_id", '-', '') NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_project_admin_operation_check CHECK (
"operation" IN ('create','archive','restore')
),
CONSTRAINT ql3_project_admin_identity_check CHECK (
length("authority_project_id") BETWEEN 1 AND 128
AND length("project_id") BETWEEN 1 AND 128
AND length("project_name") BETWEEN 1 AND 255
AND length("project_slug") BETWEEN 1 AND 128
AND "project_slug" = lower("project_slug")
AND "project_slug" NOT GLOB '*[^a-z0-9-]*'
AND substr("project_slug", 1, 1) NOT GLOB '[^a-z0-9]'
AND substr("project_slug", -1, 1) NOT GLOB '[^a-z0-9]'
),
CONSTRAINT ql3_project_admin_transition_check CHECK (
"project_version" = "expected_previous_version" + 1
AND "project_version" BETWEEN 1 AND 2147483647
AND "expected_previous_version" BETWEEN 0 AND 2147483646
AND (
("operation" = 'create'
AND "expected_previous_version" = 0
AND "project_status" = 'active'
AND "initial_owner_binding_version" = 1)
OR ("operation" = 'archive'
AND "expected_previous_version" > 0
AND "project_status" = 'archived'
AND "initial_owner_binding_version" IS NULL)
OR ("operation" = 'restore'
AND "expected_previous_version" > 0
AND "project_status" = 'active'
AND "initial_owner_binding_version" IS NULL)
)
),
CONSTRAINT ql3_project_admin_actor_check CHECK (
"changed_by_type" = 'user'
AND length("changed_by_id") BETWEEN 1 AND 255
),
CONSTRAINT ql3_project_admin_audit_check CHECK (
"audit_event_id" = "mutation_id"
),
CONSTRAINT ql3_project_admin_time_check CHECK (
"project_created_at_ms" >= 0
AND "created_at_ms" >= "project_created_at_ms"
)
)
`,
`
CREATE UNIQUE INDEX "ql3_project_admin_project_version_uidx"
ON "QingLong3ProjectAdministrationMutations" (
"project_id", "project_version"
)
`,
`
CREATE INDEX "ql3_project_admin_authority_time_idx"
ON "QingLong3ProjectAdministrationMutations" (
"authority_project_id", "created_at_ms" DESC, "mutation_id" DESC
)
`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const CAPABILITIES_V36 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_identity_credential_administration":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"plugin_package_quarantine":1}';
const CAPABILITIES_V37 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_project_administration":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_identity_credential_administration":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"plugin_package_quarantine":1}';
export const local0074CapabilityV37Migration = defineLocalSqliteMigration({
id: '0074-capability-v37',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 37,
migration_id = '0073-local-project-administration',
capabilities = '${CAPABILITIES_V37}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 36
AND migration_id = '0071-local-identity-credential-administration'
AND capabilities = '${CAPABILITIES_V36}'
`,
],
});
@@ -0,0 +1,84 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0075SecurityAuditCompactionsMigration =
defineLocalSqliteMigration({
id: '0075-security-audit-compactions',
statements: [
`
CREATE TABLE "QingLong3SecurityAuditCompactions" (
"mutation_id" TEXT PRIMARY KEY NOT NULL,
"request_id" TEXT NOT NULL,
"authority_project_id" TEXT NOT NULL,
"retention_ms" INTEGER NOT NULL,
"eligible_before_ms" INTEGER NOT NULL,
"batch_limit" INTEGER NOT NULL,
"deleted_count" INTEGER NOT NULL,
"deleted_payload_bytes" INTEGER NOT NULL,
"first_occurred_at_ms" INTEGER,
"first_event_id" TEXT,
"last_occurred_at_ms" INTEGER,
"last_event_id" TEXT,
"records_digest" TEXT NOT NULL,
"audit_event_id" TEXT NOT NULL UNIQUE,
"created_at_ms" INTEGER NOT NULL,
FOREIGN KEY ("authority_project_id")
REFERENCES "QingLong3Projects" ("id") ON DELETE RESTRICT,
FOREIGN KEY ("audit_event_id")
REFERENCES "QingLong3SecurityAuditEvents" ("event_id")
ON DELETE RESTRICT,
CONSTRAINT ql3_audit_compaction_mutation_check CHECK (
length("mutation_id") = 36
AND substr("mutation_id", 9, 1) = '-'
AND substr("mutation_id", 14, 1) = '-'
AND substr("mutation_id", 19, 1) = '-'
AND substr("mutation_id", 24, 1) = '-'
AND replace("mutation_id", '-', '') NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_audit_compaction_request_check CHECK (
length("request_id") BETWEEN 1 AND 128
),
CONSTRAINT ql3_audit_compaction_authority_check CHECK (
length("authority_project_id") BETWEEN 1 AND 128
),
CONSTRAINT ql3_audit_compaction_policy_check CHECK (
"retention_ms" BETWEEN 2592000000 AND 315360000000
AND "eligible_before_ms" >= 0
AND "eligible_before_ms" + "retention_ms" <= "created_at_ms"
AND "batch_limit" BETWEEN 1 AND 512
),
CONSTRAINT ql3_audit_compaction_result_check CHECK (
"deleted_count" BETWEEN 0 AND "batch_limit"
AND "deleted_payload_bytes" BETWEEN 0 AND 16777216
AND length("records_digest") = 64
AND "records_digest" NOT GLOB '*[^0-9a-f]*'
AND (
("deleted_count" = 0
AND "deleted_payload_bytes" = 0
AND "first_occurred_at_ms" IS NULL
AND "first_event_id" IS NULL
AND "last_occurred_at_ms" IS NULL
AND "last_event_id" IS NULL)
OR ("deleted_count" > 0
AND "deleted_payload_bytes" > 0
AND "first_occurred_at_ms" >= 0
AND "last_occurred_at_ms" >= "first_occurred_at_ms"
AND length("first_event_id") = 36
AND length("last_event_id") = 36)
)
),
CONSTRAINT ql3_audit_compaction_audit_check CHECK (
"audit_event_id" = "mutation_id"
),
CONSTRAINT ql3_audit_compaction_time_check CHECK (
"created_at_ms" >= 2592000000
)
)
`,
`
CREATE INDEX "ql3_audit_compaction_authority_time_idx"
ON "QingLong3SecurityAuditCompactions" (
"authority_project_id", "created_at_ms" DESC, "mutation_id" DESC
)
`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const CAPABILITIES_V37 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_project_administration":1,"local_security_audit":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_identity_credential_administration":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"plugin_package_quarantine":1}';
const CAPABILITIES_V38 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_project_administration":1,"local_security_audit":1,"local_security_audit_compaction":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_identity_credential_administration":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"plugin_package_quarantine":1}';
export const local0076CapabilityV38Migration = defineLocalSqliteMigration({
id: '0076-capability-v38',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 38,
migration_id = '0075-security-audit-compactions',
capabilities = '${CAPABILITIES_V38}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 37
AND migration_id = '0073-local-project-administration'
AND capabilities = '${CAPABILITIES_V37}'
`,
],
});
@@ -0,0 +1,349 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0077PluginPackageLifecycleMigration =
defineLocalSqliteMigration({
id: '0077-plugin-package-lifecycle',
statements: [
`
CREATE UNIQUE INDEX ql3_approved_action_dispatch_lifecycle_uidx
ON "QingLong3ApprovedActionDispatches" (
dispatch_id, project_id, action_type, action_digest, preview_digest
)
`,
`
CREATE TABLE "QingLong3PluginPackageLifecycleEvents" (
event_digest TEXT PRIMARY KEY NOT NULL,
mutation_id TEXT NOT NULL,
dispatch_id TEXT NOT NULL,
approved_action_type TEXT NOT NULL,
action TEXT NOT NULL,
project_id TEXT NOT NULL,
package_name TEXT NOT NULL,
installation_id TEXT NOT NULL,
lock_digest TEXT NOT NULL,
install_version INTEGER NOT NULL,
install_record_digest TEXT NOT NULL,
expected_version INTEGER NOT NULL,
expected_disposition TEXT NOT NULL,
expected_event_digest TEXT,
generation_digest TEXT NOT NULL,
materialized_revision_digest TEXT NOT NULL,
current_tool_snapshot_digest TEXT NOT NULL,
reference_graph_digest TEXT NOT NULL,
impact_digest TEXT NOT NULL,
action_digest TEXT NOT NULL,
requested_by_type TEXT NOT NULL,
requested_by_id TEXT NOT NULL,
approved_by_type TEXT NOT NULL,
approved_by_id TEXT NOT NULL,
authorization_mode TEXT NOT NULL,
occurred_at_ms INTEGER NOT NULL,
event_json TEXT NOT NULL,
CONSTRAINT ql3_plugin_package_lifecycle_install_fk
FOREIGN KEY (
project_id, package_name, installation_id, lock_digest,
install_record_digest
)
REFERENCES "QingLong3PluginPackageInstalls" (
project_id, package_name, installation_id, lock_digest, record_digest
) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_dispatch_fk
FOREIGN KEY (
dispatch_id, project_id, approved_action_type, action_digest,
impact_digest
)
REFERENCES "QingLong3ApprovedActionDispatches" (
dispatch_id, project_id, action_type, action_digest, preview_digest
) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_previous_event_fk
FOREIGN KEY (expected_event_digest)
REFERENCES "QingLong3PluginPackageLifecycleEvents" (event_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_identity_check CHECK (
length(mutation_id) BETWEEN 1 AND 128 AND
length(dispatch_id) BETWEEN 1 AND 128 AND
length(package_name) BETWEEN 1 AND 63 AND
length(installation_id) BETWEEN 1 AND 128 AND
install_version BETWEEN 1 AND 2147483647 AND
action IN ('disable','enable','uninstall') AND
approved_action_type = 'plugin_package.lifecycle.' || action
),
CONSTRAINT ql3_plugin_package_lifecycle_expectation_check CHECK (
(
action = 'disable' AND expected_disposition = 'active'
) OR (
action IN ('enable','uninstall') AND
expected_disposition = 'disabled'
)
),
CONSTRAINT ql3_plugin_package_lifecycle_origin_check CHECK (
(
expected_version = 0 AND
expected_disposition = 'active' AND
expected_event_digest IS NULL
) OR (
expected_version BETWEEN 1 AND 2147483646 AND
expected_event_digest IS NOT NULL
)
),
CONSTRAINT ql3_plugin_package_lifecycle_subject_check CHECK (
requested_by_type = 'user' AND approved_by_type = 'user' AND
length(requested_by_id) BETWEEN 1 AND 255 AND
length(approved_by_id) BETWEEN 1 AND 255 AND
authorization_mode IN ('human_confirmation','separation_of_duty') AND
(
(
authorization_mode = 'human_confirmation' AND
requested_by_id = approved_by_id
) OR (
authorization_mode = 'separation_of_duty' AND
requested_by_id <> approved_by_id
)
)
),
CONSTRAINT ql3_plugin_package_lifecycle_digest_check CHECK (
length(event_digest) = 64 AND
event_digest NOT GLOB '*[^0-9a-f]*' AND
length(lock_digest) = 64 AND
lock_digest NOT GLOB '*[^0-9a-f]*' AND
length(install_record_digest) = 64 AND
install_record_digest NOT GLOB '*[^0-9a-f]*' AND
length(generation_digest) = 64 AND
generation_digest NOT GLOB '*[^0-9a-f]*' AND
length(materialized_revision_digest) = 64 AND
materialized_revision_digest NOT GLOB '*[^0-9a-f]*' AND
length(current_tool_snapshot_digest) = 64 AND
current_tool_snapshot_digest NOT GLOB '*[^0-9a-f]*' AND
length(reference_graph_digest) = 64 AND
reference_graph_digest NOT GLOB '*[^0-9a-f]*' AND
length(impact_digest) = 64 AND
impact_digest NOT GLOB '*[^0-9a-f]*' AND
length(action_digest) = 64 AND
action_digest NOT GLOB '*[^0-9a-f]*' AND
(
expected_event_digest IS NULL OR
length(expected_event_digest) = 64 AND
expected_event_digest NOT GLOB '*[^0-9a-f]*'
)
),
CONSTRAINT ql3_plugin_package_lifecycle_json_check CHECK (
length(CAST(event_json AS BLOB)) BETWEEN 2 AND 524288 AND
json_valid(event_json) AND json_type(event_json) = 'object' AND
json_extract(event_json, '$.schema') =
'qinglong/plugin-package-lifecycle-event@v1' AND
json_extract(event_json, '$.mutationId') = mutation_id AND
json_extract(event_json, '$.dispatchId') = dispatch_id AND
json_extract(event_json, '$.impact.schema') =
'qinglong/plugin-package-lifecycle-impact@v1' AND
json_extract(event_json, '$.impact.action') = action AND
json_extract(event_json, '$.impact.target.projectId') = project_id AND
json_extract(event_json, '$.impact.target.packageName') = package_name AND
json_extract(event_json, '$.impact.target.installationId') =
installation_id AND
json_extract(event_json, '$.impact.target.lockDigest') = lock_digest AND
json_extract(event_json, '$.impact.target.installVersion') =
install_version AND
json_extract(event_json, '$.impact.target.installRecordDigest') =
install_record_digest AND
json_extract(event_json, '$.impact.expected.version') = expected_version AND
json_extract(event_json, '$.impact.expected.disposition') =
expected_disposition AND
(
(
expected_event_digest IS NULL AND
json_type(event_json, '$.impact.expected.eventDigest') = 'null'
) OR
json_extract(event_json, '$.impact.expected.eventDigest') =
expected_event_digest
) AND
json_extract(event_json, '$.impact.generationDigest') =
generation_digest AND
json_extract(event_json, '$.impact.materializedRevisionDigest') =
materialized_revision_digest AND
json_extract(event_json, '$.impact.currentToolSnapshotDigest') =
current_tool_snapshot_digest AND
json_extract(event_json, '$.impact.referenceGraphDigest') =
reference_graph_digest AND
json_extract(event_json, '$.impact.impactDigest') = impact_digest AND
json_extract(event_json, '$.actionDigest') = action_digest AND
json_extract(event_json, '$.requestedBy.type') = requested_by_type AND
json_extract(event_json, '$.requestedBy.id') = requested_by_id AND
json_extract(event_json, '$.approvedBy.type') = approved_by_type AND
json_extract(event_json, '$.approvedBy.id') = approved_by_id AND
json_extract(event_json, '$.authorizationMode') = authorization_mode AND
json_extract(event_json, '$.occurredAtMs') = occurred_at_ms AND
json_extract(event_json, '$.eventDigest') = event_digest
),
CONSTRAINT ql3_plugin_package_lifecycle_time_check CHECK (
occurred_at_ms >= 0
)
)
`,
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_mutation_uidx ON "QingLong3PluginPackageLifecycleEvents" (mutation_id)`,
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_dispatch_uidx ON "QingLong3PluginPackageLifecycleEvents" (dispatch_id)`,
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_target_version_uidx ON "QingLong3PluginPackageLifecycleEvents" (project_id, package_name, installation_id, lock_digest, expected_version)`,
`CREATE INDEX ql3_plugin_package_lifecycle_project_idx ON "QingLong3PluginPackageLifecycleEvents" (project_id, package_name, occurred_at_ms, event_digest)`,
`
CREATE TABLE "QingLong3PluginPackageLifecycleHeads" (
project_id TEXT NOT NULL,
package_name TEXT NOT NULL,
installation_id TEXT NOT NULL,
lock_digest TEXT NOT NULL,
install_record_digest TEXT NOT NULL,
version INTEGER NOT NULL,
disposition TEXT NOT NULL,
event_digest TEXT NOT NULL,
updated_at_ms INTEGER NOT NULL,
PRIMARY KEY (project_id, package_name),
CONSTRAINT ql3_plugin_package_lifecycle_head_install_fk
FOREIGN KEY (
project_id, package_name, installation_id, lock_digest,
install_record_digest
)
REFERENCES "QingLong3PluginPackageInstalls" (
project_id, package_name, installation_id, lock_digest, record_digest
) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_head_event_fk
FOREIGN KEY (event_digest)
REFERENCES "QingLong3PluginPackageLifecycleEvents" (event_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_head_state_check CHECK (
version BETWEEN 1 AND 2147483647 AND
disposition IN ('active','disabled','uninstalled') AND
updated_at_ms >= 0
),
CONSTRAINT ql3_plugin_package_lifecycle_head_digest_check CHECK (
length(lock_digest) = 64 AND
lock_digest NOT GLOB '*[^0-9a-f]*' AND
length(install_record_digest) = 64 AND
install_record_digest NOT GLOB '*[^0-9a-f]*' AND
length(event_digest) = 64 AND
event_digest NOT GLOB '*[^0-9a-f]*'
)
)
`,
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_head_event_uidx ON "QingLong3PluginPackageLifecycleHeads" (event_digest)`,
`
CREATE TABLE "QingLong3PluginPackageLifecycleReceipts" (
event_digest TEXT PRIMARY KEY NOT NULL,
receipt_digest TEXT NOT NULL,
project_id TEXT NOT NULL,
action TEXT NOT NULL,
capability_status TEXT NOT NULL,
task_count INTEGER NOT NULL,
previous_active_vector_digest TEXT NOT NULL,
current_active_vector_digest TEXT NOT NULL,
current_tool_snapshot_digest TEXT NOT NULL,
retained_source_count INTEGER NOT NULL,
committed_at_ms INTEGER NOT NULL,
receipt_json TEXT NOT NULL,
CONSTRAINT ql3_plugin_package_lifecycle_receipt_event_fk
FOREIGN KEY (event_digest)
REFERENCES "QingLong3PluginPackageLifecycleEvents" (event_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_receipt_snapshot_fk
FOREIGN KEY (
project_id, current_active_vector_digest, current_tool_snapshot_digest
)
REFERENCES "QingLong3ProjectToolDefinitionSnapshots" (
project_id, active_vector_digest, snapshot_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_receipt_state_check CHECK (
(
action = 'disable' AND capability_status = 'withdrawn' AND
previous_active_vector_digest <> current_active_vector_digest
) OR (
action = 'enable' AND capability_status = 'restored' AND
previous_active_vector_digest <> current_active_vector_digest
) OR (
action = 'uninstall' AND capability_status = 'retired' AND
task_count = 0 AND
previous_active_vector_digest = current_active_vector_digest
)
),
CONSTRAINT ql3_plugin_package_lifecycle_receipt_bounds_check CHECK (
task_count BETWEEN 0 AND 128 AND
retained_source_count BETWEEN 0 AND 128 AND
committed_at_ms >= 0
),
CONSTRAINT ql3_plugin_package_lifecycle_receipt_digest_check CHECK (
length(receipt_digest) = 64 AND
receipt_digest NOT GLOB '*[^0-9a-f]*' AND
length(previous_active_vector_digest) = 64 AND
previous_active_vector_digest NOT GLOB '*[^0-9a-f]*' AND
length(current_active_vector_digest) = 64 AND
current_active_vector_digest NOT GLOB '*[^0-9a-f]*' AND
length(current_tool_snapshot_digest) = 64 AND
current_tool_snapshot_digest NOT GLOB '*[^0-9a-f]*'
),
CONSTRAINT ql3_plugin_package_lifecycle_receipt_json_check CHECK (
length(CAST(receipt_json AS BLOB)) BETWEEN 2 AND 524288 AND
json_valid(receipt_json) AND json_type(receipt_json) = 'object' AND
json_extract(receipt_json, '$.schema') =
'qinglong/plugin-package-lifecycle-receipt@v1' AND
json_extract(receipt_json, '$.eventDigest') = event_digest AND
json_extract(receipt_json, '$.action') = action AND
json_extract(receipt_json, '$.target.projectId') = project_id AND
json_extract(receipt_json, '$.capability.status') = capability_status AND
json_array_length(
json_extract(receipt_json, '$.capability.taskTransitions')
) = task_count AND
json_extract(
receipt_json, '$.capability.previousActiveVectorDigest'
) = previous_active_vector_digest AND
json_extract(
receipt_json, '$.capability.currentActiveVectorDigest'
) = current_active_vector_digest AND
json_extract(receipt_json, '$.capability.currentToolSnapshotDigest') =
current_tool_snapshot_digest AND
json_extract(receipt_json, '$.capability.retainedSourceCount') =
retained_source_count AND
json_extract(receipt_json, '$.committedAtMs') = committed_at_ms AND
json_extract(receipt_json, '$.receiptDigest') = receipt_digest
)
)
`,
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_receipt_uidx ON "QingLong3PluginPackageLifecycleReceipts" (receipt_digest)`,
`CREATE INDEX ql3_plugin_package_lifecycle_receipt_snapshot_idx ON "QingLong3PluginPackageLifecycleReceipts" (project_id, current_active_vector_digest, event_digest)`,
`
CREATE TABLE "QingLong3PluginPackageLifecycleTasks" (
event_digest TEXT NOT NULL,
project_id TEXT NOT NULL,
task_id TEXT NOT NULL,
previous_revision INTEGER NOT NULL,
current_revision INTEGER NOT NULL,
previous_content_digest TEXT NOT NULL,
current_content_digest TEXT NOT NULL,
previous_enabled INTEGER NOT NULL,
current_enabled INTEGER NOT NULL,
PRIMARY KEY (event_digest, task_id),
CONSTRAINT ql3_plugin_package_lifecycle_task_receipt_fk
FOREIGN KEY (event_digest)
REFERENCES "QingLong3PluginPackageLifecycleReceipts" (event_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_task_previous_fk
FOREIGN KEY (project_id, task_id, previous_revision)
REFERENCES "QingLong3TaskDefinitionRevisions" (
project_id, task_id, revision
) ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_task_current_fk
FOREIGN KEY (project_id, task_id, current_revision)
REFERENCES "QingLong3TaskDefinitionRevisions" (
project_id, task_id, revision
) ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_task_transition_check CHECK (
current_revision = previous_revision + 1 AND
previous_enabled IN (0, 1) AND current_enabled IN (0, 1) AND
previous_enabled <> current_enabled
),
CONSTRAINT ql3_plugin_package_lifecycle_task_digest_check CHECK (
length(previous_content_digest) = 64 AND
previous_content_digest NOT GLOB '*[^0-9a-f]*' AND
length(current_content_digest) = 64 AND
current_content_digest NOT GLOB '*[^0-9a-f]*'
)
)
`,
`CREATE INDEX ql3_plugin_package_lifecycle_task_idx ON "QingLong3PluginPackageLifecycleTasks" (project_id, task_id, event_digest)`,
],
});
@@ -0,0 +1,23 @@
import { defineLocalSqliteMigration } from './sqlMigration';
const CAPABILITIES_V38 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_project_administration":1,"local_security_audit":1,"local_security_audit_compaction":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_identity_credential_administration":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"plugin_package_quarantine":1}';
const CAPABILITIES_V39 =
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_project_administration":1,"local_security_audit":1,"local_security_audit_compaction":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_identity_credential_administration":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"plugin_package_quarantine":1,"plugin_package_lifecycle":1}';
export const local0078CapabilityV39Migration = defineLocalSqliteMigration({
id: '0078-capability-v39',
statements: [
`
UPDATE "QingLong3SchemaCapabilities"
SET contract_version = 39,
migration_id = '0077-plugin-package-lifecycle',
capabilities = '${CAPABILITIES_V39}',
updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER)
WHERE contract_name = 'local-control-core'
AND contract_version = 38
AND migration_id = '0075-security-audit-compactions'
AND capabilities = '${CAPABILITIES_V38}'
`,
],
});
@@ -0,0 +1,162 @@
import { defineLocalSqliteMigration } from './sqlMigration';
export const local0079PluginPackageAutomationPublicationsMigration =
defineLocalSqliteMigration({
id: '0079-plugin-package-automation-publications',
statements: [
`
CREATE TABLE "QingLong3PluginPackageAutomationPublications" (
publication_digest TEXT PRIMARY KEY NOT NULL,
project_id TEXT NOT NULL,
package_name TEXT NOT NULL,
installation_id TEXT NOT NULL,
lock_digest TEXT NOT NULL,
generation INTEGER NOT NULL,
generation_digest TEXT NOT NULL,
materialized_revision_digest TEXT NOT NULL,
state TEXT NOT NULL,
version INTEGER NOT NULL,
previous_publication_digest TEXT,
lifecycle_event_digest TEXT,
published_at_ms INTEGER NOT NULL,
publication_json TEXT NOT NULL,
CONSTRAINT ql3_plugin_package_automation_publication_revision_fk
FOREIGN KEY (generation_digest)
REFERENCES "QingLong3PluginPackageMaterializedRevisions" (
generation_digest
) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_plugin_package_automation_publication_previous_fk
FOREIGN KEY (previous_publication_digest)
REFERENCES "QingLong3PluginPackageAutomationPublications" (
publication_digest
) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_plugin_package_automation_publication_lifecycle_fk
FOREIGN KEY (lifecycle_event_digest)
REFERENCES "QingLong3PluginPackageLifecycleEvents" (event_digest)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_plugin_package_automation_publication_identity_check CHECK (
length(project_id) BETWEEN 1 AND 128 AND
length(package_name) BETWEEN 1 AND 63 AND
length(installation_id) BETWEEN 1 AND 128 AND
generation BETWEEN 1 AND 2147483647 AND
state IN ('active','withdrawn','absent') AND
version BETWEEN 1 AND 2147483647 AND
published_at_ms >= 0 AND
(
version = 1 AND state IN ('active','absent') AND
previous_publication_digest IS NULL AND
lifecycle_event_digest IS NULL
OR
version > 1 AND previous_publication_digest IS NOT NULL
) AND
(state <> 'withdrawn' OR lifecycle_event_digest IS NOT NULL)
),
CONSTRAINT ql3_plugin_package_automation_publication_digest_check CHECK (
length(publication_digest) = 64 AND
publication_digest NOT GLOB '*[^0-9a-f]*' AND
length(lock_digest) = 64 AND
lock_digest NOT GLOB '*[^0-9a-f]*' AND
length(generation_digest) = 64 AND
generation_digest NOT GLOB '*[^0-9a-f]*' AND
length(materialized_revision_digest) = 64 AND
materialized_revision_digest NOT GLOB '*[^0-9a-f]*' AND
(
previous_publication_digest IS NULL OR
length(previous_publication_digest) = 64 AND
previous_publication_digest NOT GLOB '*[^0-9a-f]*'
) AND
(
lifecycle_event_digest IS NULL OR
length(lifecycle_event_digest) = 64 AND
lifecycle_event_digest NOT GLOB '*[^0-9a-f]*'
)
),
CONSTRAINT ql3_plugin_package_automation_publication_json_check CHECK (
length(CAST(publication_json AS BLOB)) BETWEEN 2 AND 12582912 AND
json_valid(publication_json) AND
json_type(publication_json) = 'object' AND
json_extract(publication_json, '$.schema') =
'qinglong/plugin-package-automation-publication@v1' AND
json_extract(publication_json, '$.target.projectId') = project_id AND
json_extract(publication_json, '$.target.packageName') = package_name AND
json_extract(publication_json, '$.target.installationId') =
installation_id AND
json_extract(publication_json, '$.target.lockDigest') = lock_digest AND
json_extract(publication_json, '$.target.generation') = generation AND
json_extract(publication_json, '$.target.generationDigest') =
generation_digest AND
json_extract(publication_json, '$.target.materializedRevisionDigest') =
materialized_revision_digest AND
json_extract(publication_json, '$.state') = state AND
json_extract(publication_json, '$.version') = version AND
(
previous_publication_digest IS NULL AND
json_type(publication_json, '$.previousPublicationDigest') = 'null'
OR
json_extract(publication_json, '$.previousPublicationDigest') =
previous_publication_digest
) AND
(
lifecycle_event_digest IS NULL AND
json_type(publication_json, '$.lifecycleEventDigest') = 'null'
OR
json_extract(publication_json, '$.lifecycleEventDigest') =
lifecycle_event_digest
) AND
json_extract(publication_json, '$.publishedAtMs') = published_at_ms AND
json_extract(publication_json, '$.publicationDigest') =
publication_digest AND
json_type(publication_json, '$.definitions.workflows') = 'array' AND
json_type(publication_json, '$.definitions.prompts') = 'array' AND
(
state = 'absent' AND
json_array_length(
json_extract(publication_json, '$.definitions.workflows')
) + json_array_length(
json_extract(publication_json, '$.definitions.prompts')
) = 0
OR
state <> 'absent' AND
json_array_length(
json_extract(publication_json, '$.definitions.workflows')
) + json_array_length(
json_extract(publication_json, '$.definitions.prompts')
) > 0
)
)
)
`,
`CREATE UNIQUE INDEX ql3_plugin_package_automation_publication_version_uidx ON "QingLong3PluginPackageAutomationPublications" (project_id, package_name, version)`,
`CREATE UNIQUE INDEX ql3_plugin_package_automation_publication_previous_uidx ON "QingLong3PluginPackageAutomationPublications" (previous_publication_digest) WHERE previous_publication_digest IS NOT NULL`,
`CREATE INDEX ql3_plugin_package_automation_publication_generation_idx ON "QingLong3PluginPackageAutomationPublications" (generation_digest, publication_digest)`,
`
CREATE TABLE "QingLong3PluginPackageAutomationPublicationHeads" (
project_id TEXT NOT NULL,
package_name TEXT NOT NULL,
publication_digest TEXT NOT NULL,
generation_digest TEXT NOT NULL,
state TEXT NOT NULL,
version INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
PRIMARY KEY (project_id, package_name),
CONSTRAINT ql3_plugin_package_automation_publication_head_publication_fk
FOREIGN KEY (publication_digest)
REFERENCES "QingLong3PluginPackageAutomationPublications" (
publication_digest
) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_plugin_package_automation_publication_head_state_check CHECK (
length(project_id) BETWEEN 1 AND 128 AND
length(package_name) BETWEEN 1 AND 63 AND
length(publication_digest) = 64 AND
publication_digest NOT GLOB '*[^0-9a-f]*' AND
length(generation_digest) = 64 AND
generation_digest NOT GLOB '*[^0-9a-f]*' AND
state IN ('active','withdrawn','absent') AND
version BETWEEN 1 AND 2147483647 AND
updated_at_ms >= 0
)
)
`,
`CREATE UNIQUE INDEX ql3_plugin_package_automation_publication_head_digest_uidx ON "QingLong3PluginPackageAutomationPublicationHeads" (publication_digest)`,
],
});

Some files were not shown because too many files have changed in this diff Show More