mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 18:08:20 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import {
|
||||
POSTGRESQL_MAIN_MIGRATION_STREAM_ID,
|
||||
type PostgresMigrationContext,
|
||||
} from './postgresMigrationStreamStore';
|
||||
import type { MigrationStreamDefinition } from '@qinglong/runtime-core';
|
||||
import { pg0001SchemaCapabilityMigration } from './pg-0001-schema-capability';
|
||||
import { pg0002RunCoreMigration } from './pg-0002-run-core';
|
||||
import { pg0003RunRetryPolicyMigration } from './pg-0003-run-retry-policy';
|
||||
import { pg0004ProjectPolicyMigration } from './pg-0004-project-policy';
|
||||
import { pg0005ApiCredentialSecurityAuditMigration } from './pg-0005-api-credential-security-audit';
|
||||
import { pg0006IdentityCredentialAdministrationMigration } from './pg-0006-identity-credential-administration';
|
||||
import { pg0007ClusterRecoveryIndexesMigration } from './pg-0007-cluster-recovery-indexes';
|
||||
import { pg0008RunRecoveryClaimsMigration } from './pg-0008-run-recovery-claims';
|
||||
import { pg0009WorkerSessionRunLeaseMigration } from './pg-0009-worker-session-run-lease';
|
||||
import { pg0010WorkerIngressAttestationMigration } from './pg-0010-worker-ingress-attestation';
|
||||
import { pg0011ApiCredentialPepperBindingMigration } from './pg-0011-api-credential-pepper-binding';
|
||||
import { pg0012TaskTriggerDefinitionsMigration } from './pg-0012-task-trigger-definitions';
|
||||
import { pg0013TaskExecutionRevisionsMigration } from './pg-0013-task-execution-revisions';
|
||||
import { pg0014ClusterSchedulerAdmissionMigration } from './pg-0014-cluster-scheduler-admission';
|
||||
import { pg0015WorkerCredentialDeliveryLedgerMigration } from './pg-0015-worker-credential-delivery-ledger';
|
||||
import { pg0016WorkerCredentialStageDiscardLedgerMigration } from './pg-0016-worker-credential-stage-discard-ledger';
|
||||
import { pg0017DatabaseRoleGrantsMigration } from './pg-0017-database-role-grants';
|
||||
import { pg0018PluginPackageInstallsMigration } from './pg-0018-plugin-package-installs';
|
||||
import { pg0019ApprovedActionsMigration } from './pg-0019-approved-actions';
|
||||
import { pg0020PluginPackageAdmissionReceiptsMigration } from './pg-0020-plugin-package-admission-receipts';
|
||||
import { pg0021ApprovedActionExecutionsAndPackageProposalsMigration } from './pg-0021-approved-action-executions-and-package-proposals';
|
||||
import { pg0022PluginPackageAuthoritySplitMigration } from './pg-0022-plugin-package-authority-split';
|
||||
import { pg0023PluginPackageManagementQuotaMigration } from './pg-0023-plugin-package-management-quota';
|
||||
import { pg0024PluginPackageIdentityKeysetLedgerMigration } from './pg-0024-plugin-package-identity-keyset-ledger';
|
||||
import { pg0025PluginPackageMaterializedRevisionsMigration } from './pg-0025-plugin-package-materialized-revisions';
|
||||
import { pg0026PluginPackageTaskReconciliationsMigration } from './pg-0026-plugin-package-task-reconciliations';
|
||||
import { pg0027ProjectToolDefinitionSnapshotsMigration } from './pg-0027-project-tool-definition-snapshots';
|
||||
import { pg0028StepRunsMigration } from './pg-0028-step-runs';
|
||||
import { pg0029ToolExecutionEvidenceMigration } from './pg-0029-tool-execution-evidence';
|
||||
import { pg0030ToolExecutionStartBarriersMigration } from './pg-0030-tool-execution-start-barriers';
|
||||
import { pg0031ToolInvocationArtifactsMigration } from './pg-0031-tool-invocation-artifacts';
|
||||
import { pg0032ToolExecutionArtifactBindingsMigration } from './pg-0032-tool-execution-artifact-bindings';
|
||||
import { pg0033ToolExecutionCompletionsMigration } from './pg-0033-tool-execution-completions';
|
||||
import { pg0034ToolExecutionFailureCompletionsMigration } from './pg-0034-tool-execution-failure-completions';
|
||||
import { pg0035ToolResultKeyCatalogMigration } from './pg-0035-tool-result-key-catalog';
|
||||
import { pg0036ToolResultRekeyOverlaysMigration } from './pg-0036-tool-result-rekey-overlays';
|
||||
import { pg0037PluginPackageQuarantineMigration } from './pg-0037-plugin-package-quarantine';
|
||||
import { pg0038PluginPackagePublisherProvenanceMigration } from './pg-0038-plugin-package-publisher-provenance';
|
||||
import { pg0039PluginPackagePublisherTrustAuthorityMigration } from './pg-0039-plugin-package-publisher-trust-authority';
|
||||
import { pg0040PluginPackagePublisherTrustTransitionsMigration } from './pg-0040-plugin-package-publisher-trust-transitions';
|
||||
import { pg0041PluginPackageLifecycleMigration } from './pg-0041-plugin-package-lifecycle';
|
||||
import { pg0042PluginPackageLifecyclePlansMigration } from './pg-0042-plugin-package-lifecycle-plans';
|
||||
import { pg0043PluginPackageAutomationPublicationsMigration } from './pg-0043-plugin-package-automation-publications';
|
||||
import { pg0044PluginPackageAutomationStartGuardMigration } from './pg-0044-plugin-package-automation-start-guard';
|
||||
import { pg0045PluginPackageWorkflowAdmissionsMigration } from './pg-0045-plugin-package-workflow-admissions';
|
||||
import { pg0046PluginPackageWorkflowTaskAttemptAdmissionsMigration } from './pg-0046-plugin-package-workflow-task-attempt-admissions';
|
||||
import { pg0047WorkerCredentialManagementPlansMigration } from './pg-0047-worker-credential-management-plans';
|
||||
import { pg0048WorkerCredentialPreapprovedActivationMigration } from './pg-0048-worker-credential-preapproved-activation';
|
||||
import { pg0049WorkerCredentialExecutionReceiptsMigration } from './pg-0049-worker-credential-execution-receipts';
|
||||
import { pg0050WorkerCredentialManagementBoundaryMigration } from './pg-0050-worker-credential-management-boundary';
|
||||
import { pg0051AutomationManagementBoundaryMigration } from './pg-0051-automation-management-boundary';
|
||||
import { pg0052AutomationManagementIdentityKeysetLedgerMigration } from './pg-0052-automation-management-identity-keyset-ledger';
|
||||
import { pg0053PluginPackageWorkflowRunListIndexMigration } from './pg-0053-plugin-package-workflow-run-list-index';
|
||||
import { pg0054ApprovalManagementBoundaryMigration } from './pg-0054-approval-management-boundary';
|
||||
|
||||
export const postgresqlMainMigrationStream: MigrationStreamDefinition<PostgresMigrationContext> =
|
||||
Object.freeze({
|
||||
id: POSTGRESQL_MAIN_MIGRATION_STREAM_ID,
|
||||
dialect: 'postgresql',
|
||||
migrationIdScheme: 'postgres-prefixed',
|
||||
checksumScheme: 'sha256',
|
||||
migrations: Object.freeze([
|
||||
pg0001SchemaCapabilityMigration,
|
||||
pg0002RunCoreMigration,
|
||||
pg0003RunRetryPolicyMigration,
|
||||
pg0004ProjectPolicyMigration,
|
||||
pg0005ApiCredentialSecurityAuditMigration,
|
||||
pg0006IdentityCredentialAdministrationMigration,
|
||||
pg0007ClusterRecoveryIndexesMigration,
|
||||
pg0008RunRecoveryClaimsMigration,
|
||||
pg0009WorkerSessionRunLeaseMigration,
|
||||
pg0010WorkerIngressAttestationMigration,
|
||||
pg0011ApiCredentialPepperBindingMigration,
|
||||
pg0012TaskTriggerDefinitionsMigration,
|
||||
pg0013TaskExecutionRevisionsMigration,
|
||||
pg0014ClusterSchedulerAdmissionMigration,
|
||||
pg0015WorkerCredentialDeliveryLedgerMigration,
|
||||
pg0016WorkerCredentialStageDiscardLedgerMigration,
|
||||
pg0017DatabaseRoleGrantsMigration,
|
||||
pg0018PluginPackageInstallsMigration,
|
||||
pg0019ApprovedActionsMigration,
|
||||
pg0020PluginPackageAdmissionReceiptsMigration,
|
||||
pg0021ApprovedActionExecutionsAndPackageProposalsMigration,
|
||||
pg0022PluginPackageAuthoritySplitMigration,
|
||||
pg0023PluginPackageManagementQuotaMigration,
|
||||
pg0024PluginPackageIdentityKeysetLedgerMigration,
|
||||
pg0025PluginPackageMaterializedRevisionsMigration,
|
||||
pg0026PluginPackageTaskReconciliationsMigration,
|
||||
pg0027ProjectToolDefinitionSnapshotsMigration,
|
||||
pg0028StepRunsMigration,
|
||||
pg0029ToolExecutionEvidenceMigration,
|
||||
pg0030ToolExecutionStartBarriersMigration,
|
||||
pg0031ToolInvocationArtifactsMigration,
|
||||
pg0032ToolExecutionArtifactBindingsMigration,
|
||||
pg0033ToolExecutionCompletionsMigration,
|
||||
pg0034ToolExecutionFailureCompletionsMigration,
|
||||
pg0035ToolResultKeyCatalogMigration,
|
||||
pg0036ToolResultRekeyOverlaysMigration,
|
||||
pg0037PluginPackageQuarantineMigration,
|
||||
pg0038PluginPackagePublisherProvenanceMigration,
|
||||
pg0039PluginPackagePublisherTrustAuthorityMigration,
|
||||
pg0040PluginPackagePublisherTrustTransitionsMigration,
|
||||
pg0041PluginPackageLifecycleMigration,
|
||||
pg0042PluginPackageLifecyclePlansMigration,
|
||||
pg0043PluginPackageAutomationPublicationsMigration,
|
||||
pg0044PluginPackageAutomationStartGuardMigration,
|
||||
pg0045PluginPackageWorkflowAdmissionsMigration,
|
||||
pg0046PluginPackageWorkflowTaskAttemptAdmissionsMigration,
|
||||
pg0047WorkerCredentialManagementPlansMigration,
|
||||
pg0048WorkerCredentialPreapprovedActivationMigration,
|
||||
pg0049WorkerCredentialExecutionReceiptsMigration,
|
||||
pg0050WorkerCredentialManagementBoundaryMigration,
|
||||
pg0051AutomationManagementBoundaryMigration,
|
||||
pg0052AutomationManagementIdentityKeysetLedgerMigration,
|
||||
pg0053PluginPackageWorkflowRunListIndexMigration,
|
||||
pg0054ApprovalManagementBoundaryMigration,
|
||||
]),
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const POSTGRESQL_SCHEMA_CAPABILITY_TABLE = 'schema_capabilities';
|
||||
|
||||
export const pg0001SchemaCapabilityMigration = definePostgresSqlMigration({
|
||||
id: 'pg-0001-schema-capability',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_SCHEMA_CAPABILITY_TABLE}" (
|
||||
contract_name varchar(64) PRIMARY KEY,
|
||||
contract_version integer NOT NULL
|
||||
CONSTRAINT ql3_schema_capabilities_version_check
|
||||
CHECK (contract_version >= 0),
|
||||
migration_id varchar(128) NOT NULL,
|
||||
capabilities jsonb NOT NULL
|
||||
CONSTRAINT ql3_schema_capabilities_payload_check
|
||||
CHECK (jsonb_typeof(capabilities) = 'object'),
|
||||
updated_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_schema_capabilities_updated_at_check
|
||||
CHECK (updated_at_ms >= 0),
|
||||
CONSTRAINT ql3_schema_capabilities_migration_fk
|
||||
FOREIGN KEY (migration_id)
|
||||
REFERENCES "ql3"."schema_migrations" (migration_id)
|
||||
DEFERRABLE INITIALLY DEFERRED
|
||||
)
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,224 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const POSTGRESQL_RUN_TABLE = 'runs';
|
||||
export const POSTGRESQL_RUN_ATTEMPT_TABLE = 'run_attempts';
|
||||
export const POSTGRESQL_RUN_EVENT_TABLE = 'run_events';
|
||||
|
||||
export const pg0002RunCoreMigration = definePostgresSqlMigration({
|
||||
id: 'pg-0002-run-core',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_RUN_TABLE}" (
|
||||
id varchar(36) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
task_id varchar(255) NOT NULL,
|
||||
task_revision varchar(128) NOT NULL,
|
||||
task_name varchar(255),
|
||||
task_snapshot_ref varchar(512),
|
||||
parent_run_id varchar(36),
|
||||
retry_of_run_id varchar(36),
|
||||
trigger_id varchar(36),
|
||||
trigger_type varchar(64) NOT NULL,
|
||||
execution_origin varchar(64) NOT NULL,
|
||||
execution_owner varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_runs_execution_owner_check
|
||||
CHECK (execution_owner = 'runtime'),
|
||||
triggered_by varchar(255),
|
||||
request_id varchar(128),
|
||||
scheduled_for_ms bigint
|
||||
CONSTRAINT ql3_runs_scheduled_for_check
|
||||
CHECK (scheduled_for_ms IS NULL OR scheduled_for_ms >= 0),
|
||||
status varchar(32) NOT NULL
|
||||
CONSTRAINT ql3_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_runs_version_check
|
||||
CHECK (version >= 0),
|
||||
event_sequence integer NOT NULL DEFAULT 0
|
||||
CONSTRAINT ql3_runs_event_sequence_check
|
||||
CHECK (event_sequence >= 0),
|
||||
priority integer NOT NULL DEFAULT 0,
|
||||
idempotency_key varchar(255),
|
||||
input_ref varchar(512),
|
||||
output_ref varchar(512),
|
||||
created_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_runs_created_at_check
|
||||
CHECK (created_at_ms >= 0),
|
||||
queued_at_ms bigint
|
||||
CONSTRAINT ql3_runs_queued_at_check
|
||||
CHECK (queued_at_ms IS NULL OR queued_at_ms >= 0),
|
||||
started_at_ms bigint
|
||||
CONSTRAINT ql3_runs_started_at_check
|
||||
CHECK (started_at_ms IS NULL OR started_at_ms >= 0),
|
||||
finished_at_ms bigint
|
||||
CONSTRAINT ql3_runs_finished_at_check
|
||||
CHECK (finished_at_ms IS NULL OR finished_at_ms >= 0),
|
||||
cancel_requested_at_ms bigint
|
||||
CONSTRAINT ql3_runs_cancel_requested_at_check
|
||||
CHECK (cancel_requested_at_ms IS NULL OR cancel_requested_at_ms >= 0),
|
||||
cancel_reason varchar(16)
|
||||
CONSTRAINT ql3_runs_cancel_reason_check
|
||||
CHECK (cancel_reason IS NULL OR cancel_reason IN (
|
||||
'user', 'policy', 'shutdown', 'reconcile', 'timeout'
|
||||
)),
|
||||
error_code varchar(128),
|
||||
error_summary varchar(1024),
|
||||
CONSTRAINT ql3_runs_parent_fk
|
||||
FOREIGN KEY (parent_run_id) REFERENCES "ql3"."${POSTGRESQL_RUN_TABLE}" (id),
|
||||
CONSTRAINT ql3_runs_retry_of_fk
|
||||
FOREIGN KEY (retry_of_run_id) REFERENCES "ql3"."${POSTGRESQL_RUN_TABLE}" (id)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_runs_project_idempotency_uidx
|
||||
ON "ql3"."${POSTGRESQL_RUN_TABLE}" (project_id, idempotency_key)
|
||||
WHERE idempotency_key IS NOT NULL
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_runs_project_created_idx
|
||||
ON "ql3"."${POSTGRESQL_RUN_TABLE}" (project_id, created_at_ms, id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_runs_task_created_idx
|
||||
ON "ql3"."${POSTGRESQL_RUN_TABLE}" (task_id, created_at_ms, id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_runs_dispatch_candidates_idx
|
||||
ON "ql3"."${POSTGRESQL_RUN_TABLE}" (priority DESC, queued_at_ms, id)
|
||||
WHERE execution_owner = 'runtime'
|
||||
AND status IN ('queued', 'dispatching')
|
||||
AND cancel_requested_at_ms IS NULL
|
||||
AND queued_at_ms IS NOT NULL
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_RUN_ATTEMPT_TABLE}" (
|
||||
id varchar(36) PRIMARY KEY,
|
||||
run_id varchar(36) NOT NULL,
|
||||
step_run_id varchar(36),
|
||||
attempt integer NOT NULL
|
||||
CONSTRAINT ql3_run_attempts_attempt_check
|
||||
CHECK (attempt >= 1),
|
||||
status varchar(32) NOT NULL
|
||||
CONSTRAINT ql3_run_attempts_status_check
|
||||
CHECK (status IN (
|
||||
'claimed', 'starting', 'running', 'succeeded', 'failed',
|
||||
'cancelled', 'timed_out', 'lost'
|
||||
)),
|
||||
executor_type varchar(64) NOT NULL,
|
||||
worker_id varchar(128),
|
||||
executor_handle varchar(2048),
|
||||
pid integer
|
||||
CONSTRAINT ql3_run_attempts_pid_check
|
||||
CHECK (pid IS NULL OR pid >= 1),
|
||||
log_artifact_id varchar(36),
|
||||
lease_token varchar(128),
|
||||
lease_expires_at_ms bigint
|
||||
CONSTRAINT ql3_run_attempts_lease_expiry_check
|
||||
CHECK (lease_expires_at_ms IS NULL OR lease_expires_at_ms >= 0),
|
||||
deadline_at_ms bigint
|
||||
CONSTRAINT ql3_run_attempts_deadline_check
|
||||
CHECK (deadline_at_ms IS NULL OR deadline_at_ms >= 0),
|
||||
callback_token_hash varchar(128),
|
||||
callback_sequence integer NOT NULL DEFAULT 0
|
||||
CONSTRAINT ql3_run_attempts_callback_sequence_check
|
||||
CHECK (callback_sequence >= 0),
|
||||
created_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_run_attempts_created_at_check
|
||||
CHECK (created_at_ms >= 0),
|
||||
started_at_ms bigint
|
||||
CONSTRAINT ql3_run_attempts_started_at_check
|
||||
CHECK (started_at_ms IS NULL OR started_at_ms >= 0),
|
||||
finished_at_ms bigint
|
||||
CONSTRAINT ql3_run_attempts_finished_at_check
|
||||
CHECK (finished_at_ms IS NULL OR finished_at_ms >= 0),
|
||||
exit_code integer,
|
||||
error_code varchar(128),
|
||||
error_summary varchar(1024),
|
||||
CONSTRAINT ql3_run_attempts_run_fk
|
||||
FOREIGN KEY (run_id) REFERENCES "ql3"."${POSTGRESQL_RUN_TABLE}" (id)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_run_attempts_run_attempt_uidx
|
||||
ON "ql3"."${POSTGRESQL_RUN_ATTEMPT_TABLE}" (run_id, attempt)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_run_attempts_dispatch_candidates_idx
|
||||
ON "ql3"."${POSTGRESQL_RUN_ATTEMPT_TABLE}" (status, run_id, created_at_ms, id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_run_attempts_lease_idx
|
||||
ON "ql3"."${POSTGRESQL_RUN_ATTEMPT_TABLE}" (lease_expires_at_ms, id)
|
||||
WHERE lease_expires_at_ms IS NOT NULL
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_RUN_EVENT_TABLE}" (
|
||||
id varchar(36) PRIMARY KEY,
|
||||
run_id varchar(36) NOT NULL,
|
||||
sequence integer NOT NULL
|
||||
CONSTRAINT ql3_run_events_sequence_check
|
||||
CHECK (sequence >= 1),
|
||||
type varchar(128) NOT NULL,
|
||||
dedupe_key varchar(255),
|
||||
actor_type varchar(64) NOT NULL
|
||||
CONSTRAINT ql3_run_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 varchar(255),
|
||||
attempt_id varchar(36),
|
||||
step_run_id varchar(36),
|
||||
payload jsonb NOT NULL
|
||||
CONSTRAINT ql3_run_events_payload_check
|
||||
CHECK (jsonb_typeof(payload) = 'object'),
|
||||
created_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_run_events_created_at_check
|
||||
CHECK (created_at_ms >= 0),
|
||||
CONSTRAINT ql3_run_events_run_fk
|
||||
FOREIGN KEY (run_id) REFERENCES "ql3"."${POSTGRESQL_RUN_TABLE}" (id),
|
||||
CONSTRAINT ql3_run_events_attempt_fk
|
||||
FOREIGN KEY (attempt_id) REFERENCES "ql3"."${POSTGRESQL_RUN_ATTEMPT_TABLE}" (id)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_run_events_run_sequence_uidx
|
||||
ON "ql3"."${POSTGRESQL_RUN_EVENT_TABLE}" (run_id, sequence)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_run_events_run_dedupe_uidx
|
||||
ON "ql3"."${POSTGRESQL_RUN_EVENT_TABLE}" (run_id, dedupe_key)
|
||||
WHERE dedupe_key IS NOT NULL
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_run_events_run_created_idx
|
||||
ON "ql3"."${POSTGRESQL_RUN_EVENT_TABLE}" (run_id, created_at_ms, id)
|
||||
`.trim(),
|
||||
`
|
||||
INSERT INTO "ql3"."schema_capabilities" (
|
||||
contract_name,
|
||||
contract_version,
|
||||
migration_id,
|
||||
capabilities,
|
||||
updated_at_ms
|
||||
)
|
||||
VALUES (
|
||||
'control-core',
|
||||
1,
|
||||
'pg-0002-run-core',
|
||||
'{"run_core":1}'::jsonb,
|
||||
floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint
|
||||
)
|
||||
ON CONFLICT (contract_name) DO UPDATE
|
||||
SET contract_version = EXCLUDED.contract_version,
|
||||
migration_id = EXCLUDED.migration_id,
|
||||
capabilities = EXCLUDED.capabilities,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms
|
||||
WHERE "schema_capabilities".contract_version < EXCLUDED.contract_version
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const POSTGRESQL_RUN_RETRY_POLICY_TABLE = 'run_retry_policies';
|
||||
|
||||
export const pg0003RunRetryPolicyMigration = definePostgresSqlMigration({
|
||||
id: 'pg-0003-run-retry-policy',
|
||||
statements: [
|
||||
`
|
||||
ALTER TABLE "ql3"."runs"
|
||||
ADD COLUMN legacy_cron_id integer
|
||||
CONSTRAINT ql3_runs_legacy_cron_id_check
|
||||
CHECK (legacy_cron_id IS NULL OR legacy_cron_id >= 1)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_RUN_RETRY_POLICY_TABLE}" (
|
||||
run_id varchar(36) PRIMARY KEY,
|
||||
max_attempts integer NOT NULL
|
||||
CONSTRAINT ql3_run_retry_policies_max_attempts_check
|
||||
CHECK (max_attempts BETWEEN 1 AND 16),
|
||||
retry_on_lost boolean NOT NULL,
|
||||
safety varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_run_retry_policies_safety_check
|
||||
CHECK (safety IN ('unknown', 'idempotent', 'deduplicated')),
|
||||
backoff_base_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_run_retry_policies_backoff_base_check
|
||||
CHECK (backoff_base_ms BETWEEN 0 AND 86400000),
|
||||
backoff_max_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_run_retry_policies_backoff_max_check
|
||||
CHECK (backoff_max_ms BETWEEN backoff_base_ms AND 86400000),
|
||||
next_attempt_at_ms bigint
|
||||
CONSTRAINT ql3_run_retry_policies_next_attempt_check
|
||||
CHECK (next_attempt_at_ms IS NULL OR next_attempt_at_ms >= 0),
|
||||
version integer NOT NULL DEFAULT 0
|
||||
CONSTRAINT ql3_run_retry_policies_version_check
|
||||
CHECK (version >= 0),
|
||||
created_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_run_retry_policies_created_at_check
|
||||
CHECK (created_at_ms >= 0),
|
||||
updated_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_run_retry_policies_updated_at_check
|
||||
CHECK (updated_at_ms >= created_at_ms),
|
||||
CONSTRAINT ql3_run_retry_policies_run_fk
|
||||
FOREIGN KEY (run_id) REFERENCES "ql3"."runs" (id) ON DELETE CASCADE
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_run_retry_policies_due_idx
|
||||
ON "ql3"."${POSTGRESQL_RUN_RETRY_POLICY_TABLE}" (next_attempt_at_ms, run_id)
|
||||
WHERE next_attempt_at_ms IS NOT NULL
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_runs_lost_retry_idx
|
||||
ON "ql3"."runs" (execution_owner, status, id)
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 2,
|
||||
migration_id = 'pg-0003-run-retry-policy',
|
||||
capabilities = '{"run_core":1,"run_retry_policy":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 1
|
||||
AND migration_id = 'pg-0002-run-core'
|
||||
AND capabilities = '{"run_core":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 1'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const POSTGRESQL_PROJECT_TABLE = 'projects';
|
||||
export const POSTGRESQL_PROJECT_ROLE_BINDING_TABLE = 'project_role_bindings';
|
||||
|
||||
export const pg0004ProjectPolicyMigration = definePostgresSqlMigration({
|
||||
id: 'pg-0004-project-policy',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_PROJECT_TABLE}" (
|
||||
id varchar(128) PRIMARY KEY
|
||||
CONSTRAINT ql3_projects_id_check CHECK (char_length(id) >= 1),
|
||||
name varchar(255) NOT NULL
|
||||
CONSTRAINT ql3_projects_name_check CHECK (char_length(name) >= 1),
|
||||
slug varchar(128) NOT NULL
|
||||
CONSTRAINT ql3_projects_slug_check
|
||||
CHECK (slug ~ '^[a-z0-9](?:[a-z0-9-]{0,126}[a-z0-9])?$'),
|
||||
status varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_projects_status_check
|
||||
CHECK (status IN ('active', 'archived')),
|
||||
version integer NOT NULL
|
||||
CONSTRAINT ql3_projects_version_check
|
||||
CHECK (version BETWEEN 1 AND 2147483647),
|
||||
created_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_projects_created_at_check CHECK (created_at_ms >= 0),
|
||||
updated_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_projects_updated_at_check
|
||||
CHECK (updated_at_ms >= created_at_ms)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_projects_slug_uidx
|
||||
ON "ql3"."${POSTGRESQL_PROJECT_TABLE}" (slug)
|
||||
`.trim(),
|
||||
`
|
||||
INSERT INTO "ql3"."${POSTGRESQL_PROJECT_TABLE}" (
|
||||
id, name, slug, status, version, created_at_ms, updated_at_ms
|
||||
)
|
||||
VALUES ('default', 'Default', 'default', 'active', 1, 0, 0)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_PROJECT_ROLE_BINDING_TABLE}" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
subject_type varchar(32) NOT NULL
|
||||
CONSTRAINT ql3_project_role_bindings_subject_type_check
|
||||
CHECK (subject_type IN ('user', 'api_app', 'mcp_client', 'agent', 'system', 'worker')),
|
||||
subject_id varchar(255) NOT NULL
|
||||
CONSTRAINT ql3_project_role_bindings_subject_id_check
|
||||
CHECK (char_length(subject_id) >= 1),
|
||||
version integer NOT NULL
|
||||
CONSTRAINT ql3_project_role_bindings_version_check
|
||||
CHECK (version BETWEEN 1 AND 2147483647),
|
||||
state varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_project_role_bindings_state_check
|
||||
CHECK (state IN ('active', 'revoked')),
|
||||
role varchar(16),
|
||||
mutation_id varchar(64) NOT NULL
|
||||
CONSTRAINT ql3_project_role_bindings_mutation_id_check
|
||||
CHECK (mutation_id ~ '^[A-Za-z0-9._:-]{1,64}$'),
|
||||
changed_by_type varchar(32) NOT NULL
|
||||
CONSTRAINT ql3_project_role_bindings_changed_by_type_check
|
||||
CHECK (changed_by_type IN ('user', 'api_app', 'mcp_client', 'agent', 'system', 'worker')),
|
||||
changed_by_id varchar(255) NOT NULL
|
||||
CONSTRAINT ql3_project_role_bindings_changed_by_id_check
|
||||
CHECK (char_length(changed_by_id) >= 1),
|
||||
created_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_project_role_bindings_created_at_check
|
||||
CHECK (created_at_ms >= 0),
|
||||
CONSTRAINT project_role_bindings_pkey
|
||||
PRIMARY KEY (project_id, subject_type, subject_id, version),
|
||||
CONSTRAINT ql3_project_role_bindings_role_state_check
|
||||
CHECK (
|
||||
(state = 'active' AND role IN ('owner', 'admin', 'operator', 'viewer'))
|
||||
OR (state = 'revoked' AND role IS NULL)
|
||||
),
|
||||
CONSTRAINT ql3_project_role_bindings_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."${POSTGRESQL_PROJECT_TABLE}" (id)
|
||||
ON DELETE CASCADE
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_project_role_bindings_current_idx
|
||||
ON "ql3"."${POSTGRESQL_PROJECT_ROLE_BINDING_TABLE}"
|
||||
(project_id, subject_type, subject_id, version DESC)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_project_role_bindings_mutation_uidx
|
||||
ON "ql3"."${POSTGRESQL_PROJECT_ROLE_BINDING_TABLE}"
|
||||
(project_id, mutation_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_project_role_bindings_subject_idx
|
||||
ON "ql3"."${POSTGRESQL_PROJECT_ROLE_BINDING_TABLE}"
|
||||
(subject_type, subject_id, project_id, version DESC)
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 3,
|
||||
migration_id = 'pg-0004-project-policy',
|
||||
capabilities = '{"project_policy":1,"run_core":1,"run_retry_policy":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 2
|
||||
AND migration_id = 'pg-0003-run-retry-policy'
|
||||
AND capabilities = '{"run_core":1,"run_retry_policy":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 2'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const POSTGRESQL_IDENTITY_SUBJECT_TABLE = 'identity_subjects';
|
||||
export const POSTGRESQL_API_CREDENTIAL_TABLE = 'api_credentials';
|
||||
export const POSTGRESQL_SECURITY_AUDIT_EVENT_TABLE = 'security_audit_events';
|
||||
|
||||
export const pg0005ApiCredentialSecurityAuditMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0005-api-credential-security-audit',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_IDENTITY_SUBJECT_TABLE}" (
|
||||
subject_type varchar(32) NOT NULL
|
||||
CONSTRAINT ql3_identity_subjects_type_check
|
||||
CHECK (subject_type IN ('user', 'api_app', 'mcp_client', 'agent', 'system', 'worker')),
|
||||
subject_id varchar(255) NOT NULL
|
||||
CONSTRAINT ql3_identity_subjects_id_check
|
||||
CHECK (char_length(subject_id) >= 1),
|
||||
status varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_identity_subjects_status_check
|
||||
CHECK (status IN ('active', 'disabled')),
|
||||
version integer NOT NULL
|
||||
CONSTRAINT ql3_identity_subjects_version_check
|
||||
CHECK (version BETWEEN 1 AND 2147483647),
|
||||
created_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_identity_subjects_created_at_check
|
||||
CHECK (created_at_ms >= 0),
|
||||
updated_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_identity_subjects_updated_at_check
|
||||
CHECK (updated_at_ms >= created_at_ms),
|
||||
CONSTRAINT identity_subjects_pkey
|
||||
PRIMARY KEY (subject_type, subject_id)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_identity_subjects_status_idx
|
||||
ON "ql3"."${POSTGRESQL_IDENTITY_SUBJECT_TABLE}"
|
||||
(status, subject_type, subject_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_API_CREDENTIAL_TABLE}" (
|
||||
credential_id varchar(64) NOT NULL
|
||||
CONSTRAINT ql3_api_credentials_id_check
|
||||
CHECK (credential_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$'),
|
||||
version integer NOT NULL
|
||||
CONSTRAINT ql3_api_credentials_version_check
|
||||
CHECK (version BETWEEN 1 AND 2147483647),
|
||||
state varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_api_credentials_state_check
|
||||
CHECK (state IN ('active', 'revoked')),
|
||||
subject_type varchar(32) NOT NULL
|
||||
CONSTRAINT ql3_api_credentials_subject_type_check
|
||||
CHECK (subject_type IN ('user', 'api_app', 'mcp_client', 'agent')),
|
||||
subject_id varchar(255) NOT NULL
|
||||
CONSTRAINT ql3_api_credentials_subject_id_check
|
||||
CHECK (char_length(subject_id) >= 1),
|
||||
secret_digest char(64) NOT NULL
|
||||
CONSTRAINT ql3_api_credentials_secret_digest_check
|
||||
CHECK (secret_digest ~ '^[a-f0-9]{64}$'),
|
||||
created_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_api_credentials_created_at_check
|
||||
CHECK (created_at_ms >= 0),
|
||||
not_before_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_api_credentials_not_before_check
|
||||
CHECK (not_before_at_ms >= created_at_ms),
|
||||
expires_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_api_credentials_expires_at_check
|
||||
CHECK (expires_at_ms > not_before_at_ms),
|
||||
CONSTRAINT api_credentials_pkey
|
||||
PRIMARY KEY (credential_id, version),
|
||||
CONSTRAINT ql3_api_credentials_subject_fk
|
||||
FOREIGN KEY (subject_type, subject_id)
|
||||
REFERENCES "ql3"."${POSTGRESQL_IDENTITY_SUBJECT_TABLE}"
|
||||
(subject_type, subject_id)
|
||||
ON DELETE RESTRICT
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_api_credentials_current_idx
|
||||
ON "ql3"."${POSTGRESQL_API_CREDENTIAL_TABLE}"
|
||||
(credential_id, version DESC)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_api_credentials_subject_idx
|
||||
ON "ql3"."${POSTGRESQL_API_CREDENTIAL_TABLE}"
|
||||
(subject_type, subject_id, credential_id, version DESC)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_SECURITY_AUDIT_EVENT_TABLE}" (
|
||||
event_id uuid PRIMARY KEY,
|
||||
request_id varchar(128) NOT NULL
|
||||
CONSTRAINT ql3_security_audit_events_request_id_check
|
||||
CHECK (request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'),
|
||||
operation_id varchar(128) NOT NULL
|
||||
CONSTRAINT ql3_security_audit_events_operation_id_check
|
||||
CHECK (operation_id ~ '^[a-z][a-z0-9_.:-]{0,127}$'),
|
||||
project_id varchar(128)
|
||||
CONSTRAINT ql3_security_audit_events_project_id_check
|
||||
CHECK (
|
||||
project_id IS NULL
|
||||
OR project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
subject_type varchar(32)
|
||||
CONSTRAINT ql3_security_audit_events_subject_type_check
|
||||
CHECK (
|
||||
subject_type IS NULL
|
||||
OR subject_type IN ('user', 'api_app', 'mcp_client', 'agent', 'system', 'worker')
|
||||
),
|
||||
subject_id varchar(255)
|
||||
CONSTRAINT ql3_security_audit_events_subject_id_check
|
||||
CHECK (subject_id IS NULL OR char_length(subject_id) >= 1),
|
||||
authentication_id varchar(128)
|
||||
CONSTRAINT ql3_security_audit_events_authentication_id_check
|
||||
CHECK (
|
||||
authentication_id IS NULL
|
||||
OR authentication_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
outcome varchar(32) NOT NULL
|
||||
CONSTRAINT ql3_security_audit_events_outcome_check
|
||||
CHECK (
|
||||
outcome IN (
|
||||
'authentication_rejected',
|
||||
'authentication_unavailable',
|
||||
'authorization_unavailable',
|
||||
'denied',
|
||||
'approval_required',
|
||||
'allowed'
|
||||
)
|
||||
),
|
||||
reasons jsonb NOT NULL
|
||||
CONSTRAINT ql3_security_audit_events_reasons_check
|
||||
CHECK (
|
||||
jsonb_typeof(reasons) = 'array'
|
||||
AND jsonb_array_length(reasons) BETWEEN 1 AND 8
|
||||
AND octet_length(reasons::text) <= 1024
|
||||
),
|
||||
project_version integer
|
||||
CONSTRAINT ql3_security_audit_events_project_version_check
|
||||
CHECK (project_version IS NULL OR project_version >= 1),
|
||||
binding_version integer
|
||||
CONSTRAINT ql3_security_audit_events_binding_version_check
|
||||
CHECK (binding_version IS NULL OR binding_version >= 1),
|
||||
occurred_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_security_audit_events_occurred_at_check
|
||||
CHECK (occurred_at_ms >= 0),
|
||||
CONSTRAINT ql3_security_audit_events_identity_check
|
||||
CHECK (
|
||||
(
|
||||
outcome IN ('authentication_rejected', 'authentication_unavailable')
|
||||
AND subject_type IS NULL
|
||||
AND subject_id IS NULL
|
||||
AND authentication_id IS NULL
|
||||
)
|
||||
OR (
|
||||
outcome NOT IN ('authentication_rejected', 'authentication_unavailable')
|
||||
AND subject_type IS NOT NULL
|
||||
AND subject_id IS NOT NULL
|
||||
AND authentication_id IS NOT NULL
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_security_audit_events_fence_check
|
||||
CHECK (project_version IS NOT NULL OR binding_version IS NULL)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_security_audit_events_occurred_idx
|
||||
ON "ql3"."${POSTGRESQL_SECURITY_AUDIT_EVENT_TABLE}"
|
||||
(occurred_at_ms DESC, event_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_security_audit_events_subject_idx
|
||||
ON "ql3"."${POSTGRESQL_SECURITY_AUDIT_EVENT_TABLE}"
|
||||
(subject_type, subject_id, occurred_at_ms DESC, event_id)
|
||||
WHERE subject_type IS NOT NULL
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_security_audit_events_project_idx
|
||||
ON "ql3"."${POSTGRESQL_SECURITY_AUDIT_EVENT_TABLE}"
|
||||
(project_id, occurred_at_ms DESC, event_id)
|
||||
WHERE project_id IS NOT NULL
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 4,
|
||||
migration_id = 'pg-0005-api-credential-security-audit',
|
||||
capabilities = '{"api_credential":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 3
|
||||
AND migration_id = 'pg-0004-project-policy'
|
||||
AND capabilities = '{"project_policy":1,"run_core":1,"run_retry_policy":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 3'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const POSTGRESQL_IDENTITY_SUBJECT_MUTATION_TABLE =
|
||||
'identity_subject_mutations';
|
||||
export const POSTGRESQL_API_CREDENTIAL_MUTATION_TABLE =
|
||||
'api_credential_mutations';
|
||||
|
||||
export const pg0006IdentityCredentialAdministrationMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0006-identity-credential-administration',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_IDENTITY_SUBJECT_MUTATION_TABLE}" (
|
||||
mutation_id uuid PRIMARY KEY,
|
||||
operation varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_identity_subject_mutations_operation_check
|
||||
CHECK (operation IN ('import', 'register', 'enable', 'disable')),
|
||||
subject_type varchar(32) NOT NULL
|
||||
CONSTRAINT ql3_identity_subject_mutations_subject_type_check
|
||||
CHECK (subject_type IN ('user', 'api_app', 'mcp_client', 'agent', 'system', 'worker')),
|
||||
subject_id varchar(255) NOT NULL
|
||||
CONSTRAINT ql3_identity_subject_mutations_subject_id_check
|
||||
CHECK (char_length(subject_id) >= 1),
|
||||
subject_version integer NOT NULL
|
||||
CONSTRAINT ql3_identity_subject_mutations_version_check
|
||||
CHECK (subject_version BETWEEN 1 AND 2147483647),
|
||||
expected_previous_version integer NOT NULL
|
||||
CONSTRAINT ql3_identity_subject_mutations_previous_version_check
|
||||
CHECK (expected_previous_version BETWEEN 0 AND 2147483646),
|
||||
status varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_identity_subject_mutations_status_check
|
||||
CHECK (status IN ('active', 'disabled')),
|
||||
changed_by_type varchar(32) NOT NULL
|
||||
CONSTRAINT ql3_identity_subject_mutations_changed_by_type_check
|
||||
CHECK (changed_by_type IN ('user', 'system')),
|
||||
changed_by_id varchar(255) NOT NULL
|
||||
CONSTRAINT ql3_identity_subject_mutations_changed_by_id_check
|
||||
CHECK (char_length(changed_by_id) >= 1),
|
||||
audit_event_id uuid NOT NULL,
|
||||
identity_created_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_identity_subject_mutations_identity_created_at_check
|
||||
CHECK (identity_created_at_ms >= 0),
|
||||
created_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_identity_subject_mutations_created_at_check
|
||||
CHECK (created_at_ms >= 0),
|
||||
CONSTRAINT ql3_identity_subject_mutations_version_fence_check
|
||||
CHECK (subject_version = expected_previous_version + 1),
|
||||
CONSTRAINT ql3_identity_subject_mutations_transition_check
|
||||
CHECK (
|
||||
operation = 'import'
|
||||
OR (operation = 'register' AND expected_previous_version = 0 AND status = 'active')
|
||||
OR (operation = 'enable' AND expected_previous_version >= 1 AND status = 'active')
|
||||
OR (operation = 'disable' AND expected_previous_version >= 1 AND status = 'disabled')
|
||||
),
|
||||
CONSTRAINT ql3_identity_subject_mutations_audit_identity_check
|
||||
CHECK (mutation_id = audit_event_id),
|
||||
CONSTRAINT ql3_identity_subject_mutations_subject_fk
|
||||
FOREIGN KEY (subject_type, subject_id)
|
||||
REFERENCES "ql3"."identity_subjects" (subject_type, subject_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_identity_subject_mutations_audit_fk
|
||||
FOREIGN KEY (audit_event_id)
|
||||
REFERENCES "ql3"."security_audit_events" (event_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_identity_subject_mutations_subject_version_uidx
|
||||
UNIQUE (subject_type, subject_id, subject_version)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_identity_subject_mutations_actor_idx
|
||||
ON "ql3"."${POSTGRESQL_IDENTITY_SUBJECT_MUTATION_TABLE}"
|
||||
(changed_by_type, changed_by_id, created_at_ms DESC, mutation_id)
|
||||
`.trim(),
|
||||
`
|
||||
WITH source AS (
|
||||
SELECT
|
||||
subject.*,
|
||||
md5('ql3-identity-import:' || subject.subject_type || ':' || subject.subject_id) AS digest
|
||||
FROM "ql3"."identity_subjects" AS subject
|
||||
), imported AS (
|
||||
SELECT
|
||||
source.*,
|
||||
(
|
||||
substr(digest, 1, 8) || '-' || substr(digest, 9, 4) || '-4' ||
|
||||
substr(digest, 14, 3) || '-8' || substr(digest, 18, 3) || '-' ||
|
||||
substr(digest, 21, 12)
|
||||
)::uuid AS mutation_id
|
||||
FROM source
|
||||
)
|
||||
INSERT INTO "ql3"."security_audit_events" (
|
||||
event_id, request_id, operation_id, project_id, subject_type, subject_id,
|
||||
authentication_id, outcome, reasons, project_version, binding_version,
|
||||
occurred_at_ms
|
||||
)
|
||||
SELECT
|
||||
mutation_id,
|
||||
'migration:' || mutation_id::text,
|
||||
'identity.import',
|
||||
NULL,
|
||||
'system',
|
||||
'pg-0006',
|
||||
'migration:pg-0006',
|
||||
'allowed',
|
||||
'["identity_admin"]'::jsonb,
|
||||
NULL,
|
||||
NULL,
|
||||
created_at_ms
|
||||
FROM imported
|
||||
`.trim(),
|
||||
`
|
||||
WITH source AS (
|
||||
SELECT
|
||||
subject.*,
|
||||
md5('ql3-identity-import:' || subject.subject_type || ':' || subject.subject_id) AS digest
|
||||
FROM "ql3"."identity_subjects" AS subject
|
||||
), imported AS (
|
||||
SELECT
|
||||
source.*,
|
||||
(
|
||||
substr(digest, 1, 8) || '-' || substr(digest, 9, 4) || '-4' ||
|
||||
substr(digest, 14, 3) || '-8' || substr(digest, 18, 3) || '-' ||
|
||||
substr(digest, 21, 12)
|
||||
)::uuid AS mutation_id
|
||||
FROM source
|
||||
)
|
||||
INSERT INTO "ql3"."${POSTGRESQL_IDENTITY_SUBJECT_MUTATION_TABLE}" (
|
||||
mutation_id, operation, subject_type, subject_id, subject_version,
|
||||
expected_previous_version, status, changed_by_type, changed_by_id,
|
||||
audit_event_id, identity_created_at_ms, created_at_ms
|
||||
)
|
||||
SELECT
|
||||
mutation_id,
|
||||
'import',
|
||||
subject_type,
|
||||
subject_id,
|
||||
version,
|
||||
version - 1,
|
||||
status,
|
||||
'system',
|
||||
'pg-0006',
|
||||
mutation_id,
|
||||
created_at_ms,
|
||||
created_at_ms
|
||||
FROM imported
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."${POSTGRESQL_API_CREDENTIAL_MUTATION_TABLE}" (
|
||||
mutation_id uuid PRIMARY KEY,
|
||||
operation varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_api_credential_mutations_operation_check
|
||||
CHECK (operation IN ('import', 'issue', 'rotate', 'revoke')),
|
||||
credential_id varchar(64) NOT NULL
|
||||
CONSTRAINT ql3_api_credential_mutations_id_check
|
||||
CHECK (credential_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$'),
|
||||
credential_version integer NOT NULL
|
||||
CONSTRAINT ql3_api_credential_mutations_version_check
|
||||
CHECK (credential_version BETWEEN 1 AND 2147483647),
|
||||
expected_previous_version integer NOT NULL
|
||||
CONSTRAINT ql3_api_credential_mutations_previous_version_check
|
||||
CHECK (expected_previous_version BETWEEN 0 AND 2147483646),
|
||||
state varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_api_credential_mutations_state_check
|
||||
CHECK (state IN ('active', 'revoked')),
|
||||
subject_type varchar(32) NOT NULL
|
||||
CONSTRAINT ql3_api_credential_mutations_subject_type_check
|
||||
CHECK (subject_type IN ('user', 'api_app', 'mcp_client', 'agent')),
|
||||
subject_id varchar(255) NOT NULL
|
||||
CONSTRAINT ql3_api_credential_mutations_subject_id_check
|
||||
CHECK (char_length(subject_id) >= 1),
|
||||
subject_status varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_api_credential_mutations_subject_status_check
|
||||
CHECK (subject_status IN ('active', 'disabled')),
|
||||
changed_by_type varchar(32) NOT NULL
|
||||
CONSTRAINT ql3_api_credential_mutations_changed_by_type_check
|
||||
CHECK (changed_by_type IN ('user', 'system')),
|
||||
changed_by_id varchar(255) NOT NULL
|
||||
CONSTRAINT ql3_api_credential_mutations_changed_by_id_check
|
||||
CHECK (char_length(changed_by_id) >= 1),
|
||||
audit_event_id uuid NOT NULL,
|
||||
created_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_api_credential_mutations_created_at_check
|
||||
CHECK (created_at_ms >= 0),
|
||||
CONSTRAINT ql3_api_credential_mutations_version_fence_check
|
||||
CHECK (credential_version = expected_previous_version + 1),
|
||||
CONSTRAINT ql3_api_credential_mutations_transition_check
|
||||
CHECK (
|
||||
operation = 'import'
|
||||
OR (operation = 'issue' AND expected_previous_version = 0 AND state = 'active')
|
||||
OR (operation = 'rotate' AND expected_previous_version >= 1 AND state = 'active')
|
||||
OR (operation = 'revoke' AND expected_previous_version >= 1 AND state = 'revoked')
|
||||
),
|
||||
CONSTRAINT ql3_api_credential_mutations_audit_identity_check
|
||||
CHECK (mutation_id = audit_event_id),
|
||||
CONSTRAINT ql3_api_credential_mutations_credential_fk
|
||||
FOREIGN KEY (credential_id, credential_version)
|
||||
REFERENCES "ql3"."api_credentials" (credential_id, version)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_api_credential_mutations_subject_fk
|
||||
FOREIGN KEY (subject_type, subject_id)
|
||||
REFERENCES "ql3"."identity_subjects" (subject_type, subject_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_api_credential_mutations_audit_fk
|
||||
FOREIGN KEY (audit_event_id)
|
||||
REFERENCES "ql3"."security_audit_events" (event_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_api_credential_mutations_credential_version_uidx
|
||||
UNIQUE (credential_id, credential_version)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_api_credential_mutations_actor_idx
|
||||
ON "ql3"."${POSTGRESQL_API_CREDENTIAL_MUTATION_TABLE}"
|
||||
(changed_by_type, changed_by_id, created_at_ms DESC, mutation_id)
|
||||
`.trim(),
|
||||
`
|
||||
WITH source AS (
|
||||
SELECT
|
||||
credential.*,
|
||||
md5('ql3-credential-import:' || credential.credential_id || ':' || credential.version::text) AS digest
|
||||
FROM "ql3"."api_credentials" AS credential
|
||||
), imported AS (
|
||||
SELECT
|
||||
source.*,
|
||||
(
|
||||
substr(digest, 1, 8) || '-' || substr(digest, 9, 4) || '-4' ||
|
||||
substr(digest, 14, 3) || '-8' || substr(digest, 18, 3) || '-' ||
|
||||
substr(digest, 21, 12)
|
||||
)::uuid AS mutation_id
|
||||
FROM source
|
||||
)
|
||||
INSERT INTO "ql3"."security_audit_events" (
|
||||
event_id, request_id, operation_id, project_id, subject_type, subject_id,
|
||||
authentication_id, outcome, reasons, project_version, binding_version,
|
||||
occurred_at_ms
|
||||
)
|
||||
SELECT
|
||||
mutation_id,
|
||||
'migration:' || mutation_id::text,
|
||||
'credential.import',
|
||||
NULL,
|
||||
'system',
|
||||
'pg-0006',
|
||||
'migration:pg-0006',
|
||||
'allowed',
|
||||
'["credential_admin"]'::jsonb,
|
||||
NULL,
|
||||
NULL,
|
||||
created_at_ms
|
||||
FROM imported
|
||||
`.trim(),
|
||||
`
|
||||
WITH source AS (
|
||||
SELECT
|
||||
credential.*,
|
||||
md5('ql3-credential-import:' || credential.credential_id || ':' || credential.version::text) AS digest
|
||||
FROM "ql3"."api_credentials" AS credential
|
||||
), imported AS (
|
||||
SELECT
|
||||
source.*,
|
||||
(
|
||||
substr(digest, 1, 8) || '-' || substr(digest, 9, 4) || '-4' ||
|
||||
substr(digest, 14, 3) || '-8' || substr(digest, 18, 3) || '-' ||
|
||||
substr(digest, 21, 12)
|
||||
)::uuid AS mutation_id
|
||||
FROM source
|
||||
)
|
||||
INSERT INTO "ql3"."${POSTGRESQL_API_CREDENTIAL_MUTATION_TABLE}" (
|
||||
mutation_id, operation, credential_id, credential_version,
|
||||
expected_previous_version, state, subject_type, subject_id,
|
||||
subject_status, changed_by_type, changed_by_id, audit_event_id, created_at_ms
|
||||
)
|
||||
SELECT
|
||||
mutation_id,
|
||||
'import',
|
||||
credential_id,
|
||||
version,
|
||||
version - 1,
|
||||
state,
|
||||
subject_type,
|
||||
subject_id,
|
||||
(
|
||||
SELECT identity.status
|
||||
FROM "ql3"."identity_subjects" AS identity
|
||||
WHERE identity.subject_type = imported.subject_type
|
||||
AND identity.subject_id = imported.subject_id
|
||||
),
|
||||
'system',
|
||||
'pg-0006',
|
||||
mutation_id,
|
||||
created_at_ms
|
||||
FROM imported
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 5,
|
||||
migration_id = 'pg-0006-identity-credential-administration',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 4
|
||||
AND migration_id = 'pg-0005-api-credential-security-audit'
|
||||
AND capabilities = '{"api_credential":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 4'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0007ClusterRecoveryIndexesMigration = definePostgresSqlMigration(
|
||||
{
|
||||
id: 'pg-0007-cluster-recovery-indexes',
|
||||
statements: [
|
||||
`
|
||||
CREATE INDEX ql3_runs_runtime_recovery_idx
|
||||
ON "ql3"."runs" (created_at_ms, id)
|
||||
WHERE execution_owner = 'runtime'
|
||||
AND status IN ('created', 'dispatching', 'running')
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_run_attempts_recovery_idx
|
||||
ON "ql3"."run_attempts" (lease_expires_at_ms, created_at_ms, id)
|
||||
WHERE status IN ('claimed', 'starting', 'running')
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 6,
|
||||
migration_id = 'pg-0007-cluster-recovery-indexes',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"cluster_recovery":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 5
|
||||
AND migration_id = 'pg-0006-identity-credential-administration'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 5'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,148 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0008RunRecoveryClaimsMigration = definePostgresSqlMigration({
|
||||
id: 'pg-0008-run-recovery-claims',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."run_recovery_controls" (
|
||||
target_kind varchar(16) NOT NULL,
|
||||
target_id varchar(36) NOT NULL,
|
||||
run_id varchar(36) NOT NULL,
|
||||
attempt_id varchar(36),
|
||||
target_status varchar(32) NOT NULL,
|
||||
target_created_at_ms bigint NOT NULL,
|
||||
observed_at_ms bigint NOT NULL,
|
||||
state varchar(16) NOT NULL,
|
||||
claim_owner varchar(128),
|
||||
claim_token varchar(64),
|
||||
claim_version integer NOT NULL DEFAULT 0,
|
||||
claim_expires_at_ms bigint,
|
||||
next_claim_at_ms bigint,
|
||||
failure_count integer NOT NULL DEFAULT 0,
|
||||
created_at_ms bigint NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT run_recovery_controls_pkey
|
||||
PRIMARY KEY (target_kind, target_id),
|
||||
CONSTRAINT ql3_run_recovery_controls_target_kind_check
|
||||
CHECK (target_kind IN ('run', 'attempt')),
|
||||
CONSTRAINT ql3_run_recovery_controls_target_id_check
|
||||
CHECK (char_length(target_id) >= 1),
|
||||
CONSTRAINT ql3_run_recovery_controls_target_shape_check
|
||||
CHECK (
|
||||
(
|
||||
target_kind = 'run'
|
||||
AND target_id = run_id
|
||||
AND attempt_id IS NULL
|
||||
AND target_status IN ('created', 'dispatching', 'running')
|
||||
)
|
||||
OR (
|
||||
target_kind = 'attempt'
|
||||
AND target_id = attempt_id
|
||||
AND attempt_id IS NOT NULL
|
||||
AND target_status IN ('claimed', 'starting', 'running')
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_run_recovery_controls_target_created_at_check
|
||||
CHECK (target_created_at_ms >= 0),
|
||||
CONSTRAINT ql3_run_recovery_controls_observed_at_check
|
||||
CHECK (observed_at_ms >= 0),
|
||||
CONSTRAINT ql3_run_recovery_controls_state_check
|
||||
CHECK (state IN ('available', 'claimed', 'retry', 'manual', 'resolved')),
|
||||
CONSTRAINT ql3_run_recovery_controls_claim_owner_check
|
||||
CHECK (
|
||||
claim_owner IS NULL
|
||||
OR claim_owner ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_run_recovery_controls_claim_token_check
|
||||
CHECK (
|
||||
claim_token IS NULL
|
||||
OR claim_token ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{15,63}$'
|
||||
),
|
||||
CONSTRAINT ql3_run_recovery_controls_claim_version_check
|
||||
CHECK (claim_version BETWEEN 0 AND 2147483647),
|
||||
CONSTRAINT ql3_run_recovery_controls_claim_expires_at_check
|
||||
CHECK (claim_expires_at_ms IS NULL OR claim_expires_at_ms >= 0),
|
||||
CONSTRAINT ql3_run_recovery_controls_next_claim_at_check
|
||||
CHECK (next_claim_at_ms IS NULL OR next_claim_at_ms >= 0),
|
||||
CONSTRAINT ql3_run_recovery_controls_failure_count_check
|
||||
CHECK (failure_count BETWEEN 0 AND 2147483647),
|
||||
CONSTRAINT ql3_run_recovery_controls_created_at_check
|
||||
CHECK (created_at_ms >= 0),
|
||||
CONSTRAINT ql3_run_recovery_controls_updated_at_check
|
||||
CHECK (updated_at_ms >= created_at_ms),
|
||||
CONSTRAINT ql3_run_recovery_controls_state_shape_check
|
||||
CHECK (
|
||||
(
|
||||
state = 'claimed'
|
||||
AND claim_owner IS NOT NULL
|
||||
AND claim_token IS NOT NULL
|
||||
AND claim_expires_at_ms IS NOT NULL
|
||||
AND next_claim_at_ms IS NULL
|
||||
)
|
||||
OR (
|
||||
state = 'retry'
|
||||
AND claim_owner IS NULL
|
||||
AND claim_token IS NULL
|
||||
AND claim_expires_at_ms IS NULL
|
||||
AND next_claim_at_ms IS NOT NULL
|
||||
)
|
||||
OR (
|
||||
state IN ('available', 'manual', 'resolved')
|
||||
AND claim_owner IS NULL
|
||||
AND claim_token IS NULL
|
||||
AND claim_expires_at_ms IS NULL
|
||||
AND next_claim_at_ms IS NULL
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_run_recovery_controls_run_fk
|
||||
FOREIGN KEY (run_id)
|
||||
REFERENCES "ql3"."runs" (id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT ql3_run_recovery_controls_attempt_fk
|
||||
FOREIGN KEY (attempt_id)
|
||||
REFERENCES "ql3"."run_attempts" (id)
|
||||
ON DELETE CASCADE
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_run_recovery_controls_available_idx
|
||||
ON "ql3"."run_recovery_controls"
|
||||
(target_created_at_ms, target_kind, target_id)
|
||||
WHERE state IN ('available', 'resolved')
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_run_recovery_controls_retry_idx
|
||||
ON "ql3"."run_recovery_controls"
|
||||
(next_claim_at_ms, target_created_at_ms, target_kind, target_id)
|
||||
WHERE state = 'retry'
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_run_recovery_controls_claim_expiry_idx
|
||||
ON "ql3"."run_recovery_controls"
|
||||
(claim_expires_at_ms, target_created_at_ms, target_kind, target_id)
|
||||
WHERE state = 'claimed'
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 7,
|
||||
migration_id = 'pg-0008-run-recovery-claims',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 6
|
||||
AND migration_id = 'pg-0007-cluster-recovery-indexes'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"cluster_recovery":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 6'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,210 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0009WorkerSessionRunLeaseMigration = definePostgresSqlMigration({
|
||||
id: 'pg-0009-worker-session-run-lease',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."worker_sessions" (
|
||||
worker_id varchar(128) PRIMARY KEY,
|
||||
session_id varchar(36) NOT NULL,
|
||||
generation integer NOT NULL,
|
||||
status varchar(16) NOT NULL,
|
||||
version integer NOT NULL,
|
||||
capabilities_json varchar(16384) NOT NULL,
|
||||
capabilities_hash char(64) NOT NULL,
|
||||
max_concurrent_runs integer NOT NULL,
|
||||
available_slots integer NOT NULL,
|
||||
registered_at_ms bigint NOT NULL,
|
||||
last_heartbeat_at_ms bigint NOT NULL,
|
||||
lease_expires_at_ms bigint NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT ql3_worker_sessions_worker_id_check
|
||||
CHECK (worker_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'),
|
||||
CONSTRAINT ql3_worker_sessions_session_id_check
|
||||
CHECK (session_id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
|
||||
CONSTRAINT ql3_worker_sessions_generation_check
|
||||
CHECK (generation BETWEEN 1 AND 2147483647),
|
||||
CONSTRAINT ql3_worker_sessions_status_check
|
||||
CHECK (status IN ('online', 'draining', 'offline')),
|
||||
CONSTRAINT ql3_worker_sessions_version_check
|
||||
CHECK (version BETWEEN 0 AND 2147483647),
|
||||
CONSTRAINT ql3_worker_sessions_capabilities_check
|
||||
CHECK (octet_length(capabilities_json) BETWEEN 2 AND 16384),
|
||||
CONSTRAINT ql3_worker_sessions_capabilities_hash_check
|
||||
CHECK (capabilities_hash ~ '^[0-9a-f]{64}$'),
|
||||
CONSTRAINT ql3_worker_sessions_concurrency_check
|
||||
CHECK (
|
||||
max_concurrent_runs BETWEEN 1 AND 1024
|
||||
AND available_slots BETWEEN 0 AND max_concurrent_runs
|
||||
),
|
||||
CONSTRAINT ql3_worker_sessions_timestamps_check
|
||||
CHECK (
|
||||
registered_at_ms >= 0
|
||||
AND last_heartbeat_at_ms >= registered_at_ms
|
||||
AND updated_at_ms >= last_heartbeat_at_ms
|
||||
AND (
|
||||
(status = 'offline' AND lease_expires_at_ms >= last_heartbeat_at_ms)
|
||||
OR (status <> 'offline' AND lease_expires_at_ms > last_heartbeat_at_ms)
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_worker_sessions_status_capacity_check
|
||||
CHECK (status = 'online' OR available_slots = 0)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_worker_sessions_available_idx
|
||||
ON "ql3"."worker_sessions" (worker_id)
|
||||
WHERE status = 'online' AND available_slots > 0
|
||||
`.trim(),
|
||||
`
|
||||
ALTER TABLE "ql3"."run_attempts"
|
||||
ADD COLUMN worker_session_id varchar(36),
|
||||
ADD COLUMN worker_generation integer,
|
||||
ADD COLUMN lease_generation integer,
|
||||
ADD COLUMN lease_version integer,
|
||||
ADD COLUMN lease_token_digest char(64),
|
||||
ADD COLUMN offer_id varchar(128),
|
||||
ADD CONSTRAINT ql3_run_attempts_worker_session_id_check
|
||||
CHECK (
|
||||
worker_session_id IS NULL
|
||||
OR worker_session_id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
||||
),
|
||||
ADD CONSTRAINT ql3_run_attempts_worker_generation_check
|
||||
CHECK (worker_generation IS NULL OR worker_generation BETWEEN 1 AND 2147483647),
|
||||
ADD CONSTRAINT ql3_run_attempts_lease_generation_check
|
||||
CHECK (lease_generation IS NULL OR lease_generation BETWEEN 1 AND 2147483647),
|
||||
ADD CONSTRAINT ql3_run_attempts_lease_version_check
|
||||
CHECK (lease_version IS NULL OR lease_version BETWEEN 0 AND 2147483647),
|
||||
ADD CONSTRAINT ql3_run_attempts_lease_token_digest_check
|
||||
CHECK (lease_token_digest IS NULL OR lease_token_digest ~ '^[0-9a-f]{64}$'),
|
||||
ADD CONSTRAINT ql3_run_attempts_offer_id_check
|
||||
CHECK (offer_id IS NULL OR char_length(offer_id) BETWEEN 1 AND 128),
|
||||
ADD CONSTRAINT ql3_run_attempts_remote_fence_shape_check
|
||||
CHECK (
|
||||
(
|
||||
worker_session_id IS NULL
|
||||
AND worker_generation IS NULL
|
||||
AND lease_generation IS NULL
|
||||
AND lease_version IS NULL
|
||||
AND lease_token_digest IS NULL
|
||||
AND offer_id IS NULL
|
||||
)
|
||||
OR (
|
||||
worker_id IS NOT NULL
|
||||
AND worker_session_id IS NOT NULL
|
||||
AND worker_generation IS NOT NULL
|
||||
AND lease_generation IS NOT NULL
|
||||
AND lease_version IS NOT NULL
|
||||
AND lease_token_digest IS NOT NULL
|
||||
AND offer_id IS NOT NULL
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."run_dispatch_leases" (
|
||||
attempt_id varchar(36) PRIMARY KEY,
|
||||
run_id varchar(36) NOT NULL,
|
||||
status varchar(16) NOT NULL,
|
||||
version integer NOT NULL,
|
||||
lease_generation integer NOT NULL,
|
||||
worker_id varchar(128) NOT NULL,
|
||||
worker_session_id varchar(36) NOT NULL,
|
||||
worker_generation integer NOT NULL,
|
||||
lease_token_digest char(64) NOT NULL,
|
||||
offer_id varchar(128) NOT NULL,
|
||||
acquired_at_ms bigint NOT NULL,
|
||||
renewed_at_ms bigint NOT NULL,
|
||||
expires_at_ms bigint NOT NULL,
|
||||
released_at_ms bigint,
|
||||
release_reason varchar(32),
|
||||
completed_at_ms bigint,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT ql3_run_dispatch_leases_status_check
|
||||
CHECK (status IN ('leased', 'released', 'completed')),
|
||||
CONSTRAINT ql3_run_dispatch_leases_version_check
|
||||
CHECK (version BETWEEN 0 AND 2147483647),
|
||||
CONSTRAINT ql3_run_dispatch_leases_generation_check
|
||||
CHECK (lease_generation BETWEEN 1 AND 2147483647),
|
||||
CONSTRAINT ql3_run_dispatch_leases_worker_generation_check
|
||||
CHECK (worker_generation BETWEEN 1 AND 2147483647),
|
||||
CONSTRAINT ql3_run_dispatch_leases_token_digest_check
|
||||
CHECK (lease_token_digest ~ '^[0-9a-f]{64}$'),
|
||||
CONSTRAINT ql3_run_dispatch_leases_offer_id_check
|
||||
CHECK (char_length(offer_id) BETWEEN 1 AND 128),
|
||||
CONSTRAINT ql3_run_dispatch_leases_timestamps_check
|
||||
CHECK (
|
||||
acquired_at_ms >= 0
|
||||
AND renewed_at_ms >= acquired_at_ms
|
||||
AND expires_at_ms > renewed_at_ms
|
||||
AND updated_at_ms >= acquired_at_ms
|
||||
),
|
||||
CONSTRAINT ql3_run_dispatch_leases_state_shape_check
|
||||
CHECK (
|
||||
(
|
||||
status = 'leased'
|
||||
AND released_at_ms IS NULL
|
||||
AND release_reason IS NULL
|
||||
AND completed_at_ms IS NULL
|
||||
)
|
||||
OR (
|
||||
status = 'released'
|
||||
AND released_at_ms IS NOT NULL
|
||||
AND release_reason IN (
|
||||
'declined', 'shutdown', 'start_failed', 'capacity_changed', 'lease_expired'
|
||||
)
|
||||
AND completed_at_ms IS NULL
|
||||
)
|
||||
OR (
|
||||
status = 'completed'
|
||||
AND released_at_ms IS NULL
|
||||
AND release_reason IS NULL
|
||||
AND completed_at_ms IS NOT NULL
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_run_dispatch_leases_attempt_fk
|
||||
FOREIGN KEY (attempt_id) REFERENCES "ql3"."run_attempts" (id) ON DELETE CASCADE,
|
||||
CONSTRAINT ql3_run_dispatch_leases_run_fk
|
||||
FOREIGN KEY (run_id) REFERENCES "ql3"."runs" (id) ON DELETE CASCADE,
|
||||
CONSTRAINT ql3_run_dispatch_leases_worker_fk
|
||||
FOREIGN KEY (worker_id) REFERENCES "ql3"."worker_sessions" (worker_id)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_run_dispatch_leases_offer_uidx
|
||||
ON "ql3"."run_dispatch_leases" (offer_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_run_dispatch_leases_worker_active_idx
|
||||
ON "ql3"."run_dispatch_leases"
|
||||
(worker_id, worker_session_id, worker_generation, expires_at_ms, attempt_id)
|
||||
WHERE status = 'leased'
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_run_dispatch_leases_expiry_idx
|
||||
ON "ql3"."run_dispatch_leases" (expires_at_ms, attempt_id)
|
||||
WHERE status = 'leased'
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 8,
|
||||
migration_id = 'pg-0009-worker-session-run-lease',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 7
|
||||
AND migration_id = 'pg-0008-run-recovery-claims'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 7'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,167 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0010WorkerIngressAttestationMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0010-worker-ingress-attestation',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."worker_credentials" (
|
||||
credential_id varchar(64) NOT NULL,
|
||||
version integer NOT NULL,
|
||||
state varchar(16) NOT NULL,
|
||||
worker_id varchar(128) NOT NULL,
|
||||
secret_digest char(64) NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
not_before_at_ms bigint NOT NULL,
|
||||
expires_at_ms bigint NOT NULL,
|
||||
PRIMARY KEY (credential_id, version),
|
||||
CONSTRAINT ql3_worker_credentials_id_check
|
||||
CHECK (credential_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$'),
|
||||
CONSTRAINT ql3_worker_credentials_version_check
|
||||
CHECK (version BETWEEN 1 AND 2147483647),
|
||||
CONSTRAINT ql3_worker_credentials_state_check
|
||||
CHECK (state IN ('active', 'revoked')),
|
||||
CONSTRAINT ql3_worker_credentials_worker_id_check
|
||||
CHECK (worker_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'),
|
||||
CONSTRAINT ql3_worker_credentials_digest_check
|
||||
CHECK (secret_digest ~ '^[0-9a-f]{64}$'),
|
||||
CONSTRAINT ql3_worker_credentials_lifetime_check
|
||||
CHECK (
|
||||
created_at_ms >= 0
|
||||
AND not_before_at_ms >= created_at_ms
|
||||
AND expires_at_ms > not_before_at_ms
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_worker_credentials_latest_idx
|
||||
ON "ql3"."worker_credentials" (credential_id, version DESC)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."worker_credential_mutations" (
|
||||
mutation_id varchar(36) PRIMARY KEY,
|
||||
operation varchar(16) NOT NULL,
|
||||
credential_id varchar(64) NOT NULL,
|
||||
credential_version integer NOT NULL,
|
||||
expected_previous_version integer NOT NULL,
|
||||
changed_by_type varchar(32) NOT NULL,
|
||||
changed_by_id varchar(255) NOT NULL,
|
||||
audit_event_id uuid NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
CONSTRAINT ql3_worker_credential_mutations_id_check
|
||||
CHECK (mutation_id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
|
||||
CONSTRAINT ql3_worker_credential_mutations_operation_check
|
||||
CHECK (operation IN ('issue', 'rotate', 'revoke')),
|
||||
CONSTRAINT ql3_worker_credential_mutations_version_check
|
||||
CHECK (
|
||||
credential_version BETWEEN 1 AND 2147483647
|
||||
AND expected_previous_version BETWEEN 0 AND 2147483646
|
||||
AND credential_version = expected_previous_version + 1
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_mutations_actor_check
|
||||
CHECK (
|
||||
changed_by_type IN ('user', 'system')
|
||||
AND char_length(changed_by_id) BETWEEN 1 AND 255
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_mutations_created_at_check
|
||||
CHECK (created_at_ms >= 0),
|
||||
CONSTRAINT ql3_worker_credential_mutations_credential_fk
|
||||
FOREIGN KEY (credential_id, credential_version)
|
||||
REFERENCES "ql3"."worker_credentials" (credential_id, version),
|
||||
CONSTRAINT ql3_worker_credential_mutations_audit_fk
|
||||
FOREIGN KEY (audit_event_id)
|
||||
REFERENCES "ql3"."security_audit_events" (event_id)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_worker_credential_mutations_audit_uidx
|
||||
ON "ql3"."worker_credential_mutations" (audit_event_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."worker_execution_attestations" (
|
||||
attestation_id varchar(36) PRIMARY KEY,
|
||||
run_id varchar(36) NOT NULL,
|
||||
attempt_id varchar(36) NOT NULL,
|
||||
sequence integer NOT NULL,
|
||||
state varchar(16) NOT NULL,
|
||||
worker_id varchar(128) NOT NULL,
|
||||
worker_session_id varchar(36) NOT NULL,
|
||||
worker_generation integer NOT NULL,
|
||||
lease_token_digest char(64) NOT NULL,
|
||||
lease_generation integer NOT NULL,
|
||||
lease_version integer NOT NULL,
|
||||
offer_id varchar(128) NOT NULL,
|
||||
callback_sequence integer NOT NULL,
|
||||
executor_handle varchar(512) NOT NULL,
|
||||
journal_revision integer NOT NULL,
|
||||
received_at_ms bigint NOT NULL,
|
||||
CONSTRAINT ql3_worker_execution_attestations_id_check
|
||||
CHECK (attestation_id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
|
||||
CONSTRAINT ql3_worker_execution_attestations_sequence_check
|
||||
CHECK (sequence BETWEEN 1 AND 2147483647),
|
||||
CONSTRAINT ql3_worker_execution_attestations_state_check
|
||||
CHECK (state IN ('running', 'stopped')),
|
||||
CONSTRAINT ql3_worker_execution_attestations_worker_id_check
|
||||
CHECK (worker_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'),
|
||||
CONSTRAINT ql3_worker_execution_attestations_session_id_check
|
||||
CHECK (worker_session_id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'),
|
||||
CONSTRAINT ql3_worker_execution_attestations_generation_check
|
||||
CHECK (
|
||||
worker_generation BETWEEN 1 AND 2147483647
|
||||
AND lease_generation BETWEEN 1 AND 2147483647
|
||||
AND lease_version BETWEEN 0 AND 2147483647
|
||||
),
|
||||
CONSTRAINT ql3_worker_execution_attestations_digest_check
|
||||
CHECK (lease_token_digest ~ '^[0-9a-f]{64}$'),
|
||||
CONSTRAINT ql3_worker_execution_attestations_offer_check
|
||||
CHECK (char_length(offer_id) BETWEEN 1 AND 128),
|
||||
CONSTRAINT ql3_worker_execution_attestations_callback_check
|
||||
CHECK (callback_sequence BETWEEN 0 AND 2147483647),
|
||||
CONSTRAINT ql3_worker_execution_attestations_handle_check
|
||||
CHECK (char_length(executor_handle) BETWEEN 1 AND 512),
|
||||
CONSTRAINT ql3_worker_execution_attestations_journal_check
|
||||
CHECK (journal_revision BETWEEN 1 AND 2147483647),
|
||||
CONSTRAINT ql3_worker_execution_attestations_received_at_check
|
||||
CHECK (received_at_ms >= 0),
|
||||
CONSTRAINT ql3_worker_execution_attestations_run_fk
|
||||
FOREIGN KEY (run_id) REFERENCES "ql3"."runs" (id) ON DELETE CASCADE,
|
||||
CONSTRAINT ql3_worker_execution_attestations_attempt_fk
|
||||
FOREIGN KEY (attempt_id) REFERENCES "ql3"."run_attempts" (id) ON DELETE CASCADE,
|
||||
CONSTRAINT ql3_worker_execution_attestations_worker_fk
|
||||
FOREIGN KEY (worker_id) REFERENCES "ql3"."worker_sessions" (worker_id)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_worker_execution_attestations_sequence_uidx
|
||||
ON "ql3"."worker_execution_attestations"
|
||||
(attempt_id, lease_generation, sequence)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_worker_execution_attestations_exact_idx
|
||||
ON "ql3"."worker_execution_attestations"
|
||||
(attempt_id, lease_generation, sequence DESC)
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 9,
|
||||
migration_id = 'pg-0010-worker-ingress-attestation',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"worker_attestation":1,"worker_credential":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 8
|
||||
AND migration_id = 'pg-0009-worker-session-run-lease'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 8'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0011ApiCredentialPepperBindingMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0011-api-credential-pepper-binding',
|
||||
statements: [
|
||||
`
|
||||
ALTER TABLE "ql3"."api_credentials"
|
||||
ADD COLUMN pepper_key_id varchar(64) NOT NULL DEFAULT 'legacy-v1'
|
||||
`.trim(),
|
||||
`
|
||||
ALTER TABLE "ql3"."api_credentials"
|
||||
ADD CONSTRAINT ql3_api_credentials_pepper_key_id_check
|
||||
CHECK (pepper_key_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$')
|
||||
`.trim(),
|
||||
`
|
||||
ALTER TABLE "ql3"."api_credentials"
|
||||
ALTER COLUMN pepper_key_id DROP DEFAULT
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 10,
|
||||
migration_id = 'pg-0011-api-credential-pepper-binding',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"worker_attestation":1,"worker_credential":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 9
|
||||
AND migration_id = 'pg-0010-worker-ingress-attestation'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"worker_attestation":1,"worker_credential":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 9'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,189 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0012TaskTriggerDefinitionsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0012-task-trigger-definitions',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."task_definitions" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
task_id varchar(128) NOT NULL,
|
||||
current_revision integer NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT task_definitions_pkey PRIMARY KEY (project_id, task_id),
|
||||
CONSTRAINT ql3_task_definitions_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_task_definitions_id_check CHECK (
|
||||
char_length(project_id) BETWEEN 1 AND 128
|
||||
AND char_length(task_id) BETWEEN 1 AND 128
|
||||
AND project_id !~ '[[:cntrl:]]'
|
||||
AND task_id !~ '[[:cntrl:]]'
|
||||
),
|
||||
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)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."task_definition_revisions" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
task_id varchar(128) NOT NULL,
|
||||
revision integer NOT NULL,
|
||||
mutation_id uuid NOT NULL,
|
||||
name varchar(255) NOT NULL,
|
||||
description varchar(4096),
|
||||
kind varchar(16) NOT NULL,
|
||||
spec_json jsonb NOT NULL,
|
||||
labels_json jsonb NOT NULL,
|
||||
enabled boolean NOT NULL,
|
||||
content_digest char(64) NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
CONSTRAINT task_definition_revisions_pkey
|
||||
PRIMARY KEY (project_id, task_id, revision),
|
||||
CONSTRAINT ql3_task_definition_revisions_head_fk
|
||||
FOREIGN KEY (project_id, task_id)
|
||||
REFERENCES "ql3"."task_definitions" (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_name_check CHECK (
|
||||
char_length(name) BETWEEN 1 AND 255
|
||||
AND (description IS NULL OR char_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 (
|
||||
jsonb_typeof(spec_json) = 'object'
|
||||
AND octet_length(spec_json::text) BETWEEN 2 AND 65536
|
||||
),
|
||||
CONSTRAINT ql3_task_definition_revisions_labels_check CHECK (
|
||||
jsonb_typeof(labels_json) = 'object'
|
||||
AND octet_length(labels_json::text) BETWEEN 2 AND 16384
|
||||
),
|
||||
CONSTRAINT ql3_task_definition_revisions_digest_check
|
||||
CHECK (content_digest ~ '^[0-9a-f]{64}$'),
|
||||
CONSTRAINT ql3_task_definition_revisions_created_check
|
||||
CHECK (created_at_ms >= 0)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_task_definition_revisions_mutation_uidx
|
||||
ON "ql3"."task_definition_revisions" (mutation_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_task_definition_revisions_project_kind_idx
|
||||
ON "ql3"."task_definition_revisions"
|
||||
(project_id, kind, enabled, task_id, revision)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."triggers" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
trigger_id varchar(128) NOT NULL,
|
||||
task_id varchar(128) NOT NULL,
|
||||
current_revision integer NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT triggers_pkey PRIMARY KEY (project_id, trigger_id),
|
||||
CONSTRAINT ql3_triggers_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_triggers_task_fk
|
||||
FOREIGN KEY (project_id, task_id)
|
||||
REFERENCES "ql3"."task_definitions" (project_id, task_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_triggers_id_check CHECK (
|
||||
char_length(project_id) BETWEEN 1 AND 128
|
||||
AND char_length(trigger_id) BETWEEN 1 AND 128
|
||||
AND char_length(task_id) BETWEEN 1 AND 128
|
||||
AND project_id !~ '[[:cntrl:]]'
|
||||
AND trigger_id !~ '[[:cntrl:]]'
|
||||
AND task_id !~ '[[:cntrl:]]'
|
||||
),
|
||||
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)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_triggers_task_uidx
|
||||
ON "ql3"."triggers" (project_id, trigger_id, task_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."trigger_revisions" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
trigger_id varchar(128) NOT NULL,
|
||||
revision integer NOT NULL,
|
||||
mutation_id uuid NOT NULL,
|
||||
task_id varchar(128) NOT NULL,
|
||||
task_revision integer NOT NULL,
|
||||
task_content_digest char(64) NOT NULL,
|
||||
spec_json jsonb NOT NULL,
|
||||
enabled boolean NOT NULL,
|
||||
content_digest char(64) NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
CONSTRAINT trigger_revisions_pkey
|
||||
PRIMARY KEY (project_id, trigger_id, revision),
|
||||
CONSTRAINT ql3_trigger_revisions_head_fk
|
||||
FOREIGN KEY (project_id, trigger_id, task_id)
|
||||
REFERENCES "ql3"."triggers" (project_id, trigger_id, task_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_trigger_revisions_task_revision_fk
|
||||
FOREIGN KEY (project_id, task_id, task_revision)
|
||||
REFERENCES "ql3"."task_definition_revisions" (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_task_digest_check
|
||||
CHECK (task_content_digest ~ '^[0-9a-f]{64}$'),
|
||||
CONSTRAINT ql3_trigger_revisions_spec_check CHECK (
|
||||
jsonb_typeof(spec_json) = 'object'
|
||||
AND octet_length(spec_json::text) BETWEEN 2 AND 16384
|
||||
),
|
||||
CONSTRAINT ql3_trigger_revisions_digest_check
|
||||
CHECK (content_digest ~ '^[0-9a-f]{64}$'),
|
||||
CONSTRAINT ql3_trigger_revisions_created_check
|
||||
CHECK (created_at_ms >= 0)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_trigger_revisions_mutation_uidx
|
||||
ON "ql3"."trigger_revisions" (mutation_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_trigger_revisions_project_enabled_idx
|
||||
ON "ql3"."trigger_revisions"
|
||||
(project_id, enabled, trigger_id, revision)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_trigger_revisions_task_idx
|
||||
ON "ql3"."trigger_revisions"
|
||||
(project_id, task_id, task_revision, trigger_id, revision)
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 11,
|
||||
migration_id = 'pg-0012-task-trigger-definitions',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 10
|
||||
AND migration_id = 'pg-0011-api-credential-pepper-binding'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"worker_attestation":1,"worker_credential":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 10'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0013TaskExecutionRevisionsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0013-task-execution-revisions',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."task_execution_revisions" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
task_id varchar(128) NOT NULL,
|
||||
source_revision integer NOT NULL,
|
||||
task_revision varchar(96) NOT NULL,
|
||||
source_content_digest char(64) NOT NULL,
|
||||
executor_type varchar(32) NOT NULL,
|
||||
plan_schema varchar(64) NOT NULL,
|
||||
plan_json jsonb NOT NULL,
|
||||
content_digest char(64) NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
CONSTRAINT task_execution_revisions_pkey
|
||||
PRIMARY KEY (project_id, task_id, source_revision, executor_type),
|
||||
CONSTRAINT ql3_task_execution_revisions_source_fk
|
||||
FOREIGN KEY (project_id, task_id, source_revision)
|
||||
REFERENCES "ql3"."task_definition_revisions"
|
||||
(project_id, task_id, revision)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_task_execution_revisions_identity_check CHECK (
|
||||
task_revision = concat(
|
||||
'qltd:v1:', source_revision::text, ':', source_content_digest
|
||||
)
|
||||
AND source_content_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_task_execution_revisions_executor_check
|
||||
CHECK (executor_type = 'remote_worker'),
|
||||
CONSTRAINT ql3_task_execution_revisions_plan_check CHECK (
|
||||
plan_schema = 'qinglong/command-execution@v1'
|
||||
AND jsonb_typeof(plan_json) = 'object'
|
||||
AND octet_length(plan_json::text) BETWEEN 2 AND 98304
|
||||
),
|
||||
CONSTRAINT ql3_task_execution_revisions_digest_check
|
||||
CHECK (content_digest ~ '^[0-9a-f]{64}$'),
|
||||
CONSTRAINT ql3_task_execution_revisions_created_check
|
||||
CHECK (created_at_ms >= 0)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_task_execution_revisions_ref_uidx
|
||||
ON "ql3"."task_execution_revisions"
|
||||
(project_id, task_id, task_revision, executor_type)
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 12,
|
||||
migration_id = 'pg-0013-task-execution-revisions',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 11
|
||||
AND migration_id = 'pg-0012-task-trigger-definitions'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 11'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0014ClusterSchedulerAdmissionMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0014-cluster-scheduler-admission',
|
||||
statements: [
|
||||
`
|
||||
ALTER TABLE "ql3"."runs"
|
||||
ALTER COLUMN trigger_id TYPE varchar(128)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."trigger_schedules" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
trigger_id varchar(128) NOT NULL,
|
||||
trigger_revision integer NOT NULL,
|
||||
next_fire_at_ms bigint,
|
||||
last_scheduled_at_ms bigint,
|
||||
state_version integer NOT NULL,
|
||||
claim_owner varchar(128),
|
||||
claim_token uuid,
|
||||
claim_version integer NOT NULL,
|
||||
claim_expires_at_ms bigint,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT trigger_schedules_pkey PRIMARY KEY (project_id, trigger_id),
|
||||
CONSTRAINT ql3_trigger_schedules_revision_fk
|
||||
FOREIGN KEY (project_id, trigger_id, trigger_revision)
|
||||
REFERENCES "ql3"."trigger_revisions" (project_id, trigger_id, revision)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_trigger_schedules_revision_check
|
||||
CHECK (trigger_revision BETWEEN 1 AND 2147483647),
|
||||
CONSTRAINT ql3_trigger_schedules_cursor_check CHECK (
|
||||
next_fire_at_ms IS NULL OR next_fire_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_trigger_schedules_last_check CHECK (
|
||||
last_scheduled_at_ms IS NULL OR last_scheduled_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_trigger_schedules_version_check CHECK (
|
||||
state_version BETWEEN 0 AND 2147483647
|
||||
AND claim_version BETWEEN 0 AND 2147483647
|
||||
),
|
||||
CONSTRAINT ql3_trigger_schedules_claim_owner_check CHECK (
|
||||
claim_owner IS NULL
|
||||
OR (
|
||||
char_length(claim_owner) BETWEEN 1 AND 128
|
||||
AND claim_owner ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_trigger_schedules_claim_shape_check CHECK (
|
||||
(
|
||||
claim_owner IS NULL
|
||||
AND claim_token IS NULL
|
||||
AND claim_expires_at_ms IS NULL
|
||||
)
|
||||
OR (
|
||||
claim_owner IS NOT NULL
|
||||
AND claim_token IS NOT NULL
|
||||
AND claim_version >= 1
|
||||
AND claim_expires_at_ms IS NOT NULL
|
||||
AND claim_expires_at_ms > updated_at_ms
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_trigger_schedules_updated_check CHECK (updated_at_ms >= 0)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
INSERT INTO "ql3"."trigger_schedules" (
|
||||
project_id, trigger_id, trigger_revision, next_fire_at_ms,
|
||||
last_scheduled_at_ms, state_version, claim_owner, claim_token,
|
||||
claim_version, claim_expires_at_ms, updated_at_ms
|
||||
)
|
||||
SELECT
|
||||
project_id, trigger_id, current_revision, NULL, NULL, 0,
|
||||
NULL, NULL, 0, NULL, updated_at_ms
|
||||
FROM "ql3"."triggers"
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_trigger_schedules_due_idx
|
||||
ON "ql3"."trigger_schedules"
|
||||
(next_fire_at_ms NULLS FIRST, claim_expires_at_ms, project_id, trigger_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_trigger_schedules_claim_expiry_idx
|
||||
ON "ql3"."trigger_schedules"
|
||||
(claim_expires_at_ms, project_id, trigger_id)
|
||||
WHERE claim_token IS NOT NULL
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 13,
|
||||
migration_id = 'pg-0014-cluster-scheduler-admission',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 12
|
||||
AND migration_id = 'pg-0013-task-execution-revisions'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 12'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0015WorkerCredentialDeliveryLedgerMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0015-worker-credential-delivery-ledger',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."worker_credential_deliveries" (
|
||||
delivery_id varchar(36) NOT NULL,
|
||||
version integer NOT NULL,
|
||||
state varchar(32) NOT NULL,
|
||||
worker_id varchar(128) NOT NULL,
|
||||
credential_id varchar(64) NOT NULL,
|
||||
credential_version integer NOT NULL,
|
||||
previous_credential_id varchar(64),
|
||||
secret_digest char(64) NOT NULL,
|
||||
token_digest char(64) NOT NULL,
|
||||
deployment_target_digest char(64) NOT NULL,
|
||||
deployment_generation varchar(128) NOT NULL,
|
||||
staged_at_ms bigint NOT NULL,
|
||||
credential_committed_at_ms bigint NOT NULL,
|
||||
published_at_ms bigint,
|
||||
publication_digest char(64),
|
||||
observed_at_ms bigint,
|
||||
observed_session_id varchar(36),
|
||||
observed_session_version integer,
|
||||
previous_revoked_at_ms bigint,
|
||||
CONSTRAINT worker_credential_deliveries_pkey
|
||||
PRIMARY KEY (delivery_id, version),
|
||||
CONSTRAINT ql3_worker_credential_deliveries_mutation_fk
|
||||
FOREIGN KEY (delivery_id)
|
||||
REFERENCES "ql3"."worker_credential_mutations" (mutation_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_worker_credential_deliveries_credential_fk
|
||||
FOREIGN KEY (credential_id, credential_version)
|
||||
REFERENCES "ql3"."worker_credentials" (credential_id, version)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_worker_credential_deliveries_version_check CHECK (
|
||||
version BETWEEN 1 AND 4
|
||||
AND credential_version = 1
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_deliveries_id_check CHECK (
|
||||
delivery_id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_deliveries_state_check CHECK (
|
||||
state IN (
|
||||
'credential_committed', 'published', 'observed', 'previous_revoked'
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_deliveries_worker_check CHECK (
|
||||
char_length(worker_id) BETWEEN 1 AND 128
|
||||
AND worker_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_deliveries_credential_check CHECK (
|
||||
char_length(credential_id) BETWEEN 1 AND 64
|
||||
AND credential_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$'
|
||||
AND (
|
||||
previous_credential_id IS NULL
|
||||
OR (
|
||||
char_length(previous_credential_id) BETWEEN 1 AND 64
|
||||
AND previous_credential_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$'
|
||||
AND previous_credential_id <> credential_id
|
||||
)
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_deliveries_digest_check CHECK (
|
||||
secret_digest ~ '^[0-9a-f]{64}$'
|
||||
AND token_digest ~ '^[0-9a-f]{64}$'
|
||||
AND deployment_target_digest ~ '^[0-9a-f]{64}$'
|
||||
AND (
|
||||
publication_digest IS NULL
|
||||
OR publication_digest ~ '^[0-9a-f]{64}$'
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_deliveries_generation_check CHECK (
|
||||
char_length(deployment_generation) BETWEEN 1 AND 128
|
||||
AND deployment_generation ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_deliveries_session_check CHECK (
|
||||
observed_session_id IS NULL
|
||||
OR observed_session_id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_deliveries_time_check CHECK (
|
||||
staged_at_ms >= 0
|
||||
AND credential_committed_at_ms >= staged_at_ms
|
||||
AND (published_at_ms IS NULL OR published_at_ms >= credential_committed_at_ms)
|
||||
AND (observed_at_ms IS NULL OR observed_at_ms >= published_at_ms)
|
||||
AND (
|
||||
previous_revoked_at_ms IS NULL
|
||||
OR previous_revoked_at_ms >= observed_at_ms
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_deliveries_state_shape_check CHECK (
|
||||
(
|
||||
version = 1
|
||||
AND state = 'credential_committed'
|
||||
AND published_at_ms IS NULL
|
||||
AND publication_digest IS NULL
|
||||
AND observed_at_ms IS NULL
|
||||
AND observed_session_id IS NULL
|
||||
AND observed_session_version IS NULL
|
||||
AND previous_revoked_at_ms IS NULL
|
||||
)
|
||||
OR (
|
||||
version = 2
|
||||
AND state = 'published'
|
||||
AND published_at_ms IS NOT NULL
|
||||
AND publication_digest IS NOT NULL
|
||||
AND observed_at_ms IS NULL
|
||||
AND observed_session_id IS NULL
|
||||
AND observed_session_version IS NULL
|
||||
AND previous_revoked_at_ms IS NULL
|
||||
)
|
||||
OR (
|
||||
version = 3
|
||||
AND state = 'observed'
|
||||
AND published_at_ms IS NOT NULL
|
||||
AND publication_digest IS NOT NULL
|
||||
AND observed_at_ms IS NOT NULL
|
||||
AND observed_session_id IS NOT NULL
|
||||
AND observed_session_version BETWEEN 1 AND 2147483647
|
||||
AND previous_revoked_at_ms IS NULL
|
||||
)
|
||||
OR (
|
||||
version = 4
|
||||
AND state = 'previous_revoked'
|
||||
AND previous_credential_id IS NOT NULL
|
||||
AND published_at_ms IS NOT NULL
|
||||
AND publication_digest IS NOT NULL
|
||||
AND observed_at_ms IS NOT NULL
|
||||
AND observed_session_id IS NOT NULL
|
||||
AND observed_session_version BETWEEN 1 AND 2147483647
|
||||
AND previous_revoked_at_ms IS NOT NULL
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_worker_credential_deliveries_recovery_idx
|
||||
ON "ql3"."worker_credential_deliveries"
|
||||
(state, delivery_id, version DESC)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_worker_credential_deliveries_credential_uidx
|
||||
ON "ql3"."worker_credential_deliveries"
|
||||
(credential_id, credential_version)
|
||||
WHERE version = 1
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_worker_credential_deliveries_credential_idx
|
||||
ON "ql3"."worker_credential_deliveries"
|
||||
(credential_id, credential_version, delivery_id, version)
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 14,
|
||||
migration_id = 'pg-0015-worker-credential-delivery-ledger',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 13
|
||||
AND migration_id = 'pg-0014-cluster-scheduler-admission'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 13'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0016WorkerCredentialStageDiscardLedgerMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0016-worker-credential-stage-discard-ledger',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."worker_credential_stage_discards" (
|
||||
delivery_id varchar(36) NOT NULL,
|
||||
version integer NOT NULL,
|
||||
state varchar(32) NOT NULL,
|
||||
worker_id varchar(128) NOT NULL,
|
||||
credential_id varchar(64) NOT NULL,
|
||||
credential_version integer NOT NULL,
|
||||
previous_credential_id varchar(64),
|
||||
secret_digest char(64) NOT NULL,
|
||||
token_digest char(64) NOT NULL,
|
||||
deployment_target_digest char(64) NOT NULL,
|
||||
deployment_generation varchar(128) NOT NULL,
|
||||
staged_at_ms bigint NOT NULL,
|
||||
authorized_at_ms bigint NOT NULL,
|
||||
discarded_at_ms bigint,
|
||||
CONSTRAINT worker_credential_stage_discards_pkey
|
||||
PRIMARY KEY (delivery_id, version),
|
||||
CONSTRAINT ql3_worker_credential_stage_discards_version_check CHECK (
|
||||
version BETWEEN 1 AND 2
|
||||
AND credential_version = 1
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_stage_discards_id_check CHECK (
|
||||
delivery_id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_stage_discards_state_check CHECK (
|
||||
state IN ('discard_authorized', 'discarded')
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_stage_discards_worker_check CHECK (
|
||||
char_length(worker_id) BETWEEN 1 AND 128
|
||||
AND worker_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_stage_discards_credential_check CHECK (
|
||||
char_length(credential_id) BETWEEN 1 AND 64
|
||||
AND credential_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$'
|
||||
AND (
|
||||
previous_credential_id IS NULL
|
||||
OR (
|
||||
char_length(previous_credential_id) BETWEEN 1 AND 64
|
||||
AND previous_credential_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$'
|
||||
AND previous_credential_id <> credential_id
|
||||
)
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_stage_discards_digest_check CHECK (
|
||||
secret_digest ~ '^[0-9a-f]{64}$'
|
||||
AND token_digest ~ '^[0-9a-f]{64}$'
|
||||
AND deployment_target_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_stage_discards_generation_check CHECK (
|
||||
char_length(deployment_generation) BETWEEN 1 AND 128
|
||||
AND deployment_generation ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_stage_discards_time_check CHECK (
|
||||
staged_at_ms >= 0
|
||||
AND authorized_at_ms >= 0
|
||||
AND (discarded_at_ms IS NULL OR discarded_at_ms >= authorized_at_ms)
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_stage_discards_state_shape_check CHECK (
|
||||
(
|
||||
version = 1
|
||||
AND state = 'discard_authorized'
|
||||
AND discarded_at_ms IS NULL
|
||||
)
|
||||
OR (
|
||||
version = 2
|
||||
AND state = 'discarded'
|
||||
AND discarded_at_ms IS NOT NULL
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_worker_credential_stage_discards_recovery_idx
|
||||
ON "ql3"."worker_credential_stage_discards"
|
||||
(state, delivery_id, version DESC)
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 15,
|
||||
migration_id = 'pg-0016-worker-credential-stage-discard-ledger',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 14
|
||||
AND migration_id = 'pg-0015-worker-credential-delivery-ledger'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 14'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0017DatabaseRoleGrantsMigration = definePostgresSqlMigration({
|
||||
id: 'pg-0017-database-role-grants',
|
||||
statements: [
|
||||
`
|
||||
DO $ql3$
|
||||
DECLARE
|
||||
invalid_roles text[];
|
||||
BEGIN
|
||||
SELECT array_agg(expected.role_name ORDER BY expected.role_name)
|
||||
INTO invalid_roles
|
||||
FROM (
|
||||
VALUES
|
||||
('ql3_migration'),
|
||||
('ql3_runtime'),
|
||||
('ql3_admin'),
|
||||
('ql3_worker_ingress')
|
||||
) AS expected(role_name)
|
||||
LEFT JOIN pg_catalog.pg_roles roles
|
||||
ON roles.rolname = expected.role_name
|
||||
WHERE roles.rolname IS NULL
|
||||
OR roles.rolcanlogin IS NOT TRUE
|
||||
OR roles.rolsuper IS NOT FALSE
|
||||
OR roles.rolcreatedb IS NOT FALSE
|
||||
OR roles.rolcreaterole IS NOT FALSE
|
||||
OR roles.rolreplication IS NOT FALSE
|
||||
OR roles.rolbypassrls IS NOT FALSE;
|
||||
|
||||
IF invalid_roles IS NOT NULL THEN
|
||||
RAISE EXCEPTION 'required QingLong database roles are missing or privileged: %',
|
||||
invalid_roles
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
'REVOKE ALL ON SCHEMA "ql3" FROM PUBLIC',
|
||||
'REVOKE ALL ON ALL TABLES IN SCHEMA "ql3" FROM PUBLIC',
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
EXECUTE format(
|
||||
'REVOKE CONNECT ON DATABASE %I FROM PUBLIC',
|
||||
current_database()
|
||||
);
|
||||
EXECUTE format(
|
||||
'GRANT CONNECT ON DATABASE %I TO ql3_migration, ql3_runtime, ql3_admin, ql3_worker_ingress',
|
||||
current_database()
|
||||
);
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
'GRANT USAGE ON SCHEMA "ql3" TO ql3_runtime',
|
||||
'GRANT SELECT ON "ql3"."schema_migrations", "ql3"."schema_capabilities" TO ql3_runtime',
|
||||
'GRANT SELECT, INSERT, UPDATE ON "ql3"."runs", "ql3"."run_attempts", "ql3"."worker_sessions", "ql3"."run_dispatch_leases", "ql3"."run_recovery_controls", "ql3"."run_retry_policies" TO ql3_runtime',
|
||||
'GRANT SELECT, INSERT ON "ql3"."run_events" TO ql3_runtime',
|
||||
'GRANT SELECT, INSERT, UPDATE ON "ql3"."projects" TO ql3_runtime',
|
||||
'GRANT SELECT, INSERT ON "ql3"."project_role_bindings" TO ql3_runtime',
|
||||
'GRANT SELECT ON "ql3"."identity_subjects", "ql3"."api_credentials", "ql3"."worker_execution_attestations", "ql3"."task_definitions", "ql3"."task_definition_revisions", "ql3"."task_execution_revisions", "ql3"."triggers", "ql3"."trigger_revisions" TO ql3_runtime',
|
||||
'GRANT SELECT, UPDATE ON "ql3"."trigger_schedules" TO ql3_runtime',
|
||||
'GRANT INSERT ON "ql3"."security_audit_events" TO ql3_runtime',
|
||||
'GRANT USAGE ON SCHEMA "ql3" TO ql3_admin',
|
||||
'GRANT SELECT ON "ql3"."schema_migrations", "ql3"."schema_capabilities" TO ql3_admin',
|
||||
'GRANT SELECT ON "ql3"."projects" TO ql3_admin',
|
||||
'GRANT SELECT, INSERT, UPDATE ON "ql3"."identity_subjects" TO ql3_admin',
|
||||
'GRANT SELECT, INSERT ON "ql3"."api_credentials", "ql3"."security_audit_events", "ql3"."identity_subject_mutations", "ql3"."api_credential_mutations" TO ql3_admin',
|
||||
'GRANT SELECT, INSERT, UPDATE ON "ql3"."worker_credentials" TO ql3_admin',
|
||||
'GRANT SELECT, INSERT ON "ql3"."worker_credential_mutations" TO ql3_admin',
|
||||
'GRANT SELECT, INSERT ON "ql3"."worker_credential_deliveries", "ql3"."worker_credential_stage_discards" TO ql3_admin',
|
||||
'GRANT SELECT, INSERT, UPDATE ON "ql3"."task_definitions", "ql3"."triggers", "ql3"."trigger_schedules" TO ql3_admin',
|
||||
'GRANT SELECT, INSERT ON "ql3"."task_definition_revisions", "ql3"."task_execution_revisions", "ql3"."trigger_revisions" TO ql3_admin',
|
||||
'GRANT USAGE ON SCHEMA "ql3" TO ql3_worker_ingress',
|
||||
'GRANT SELECT ON "ql3"."schema_migrations", "ql3"."schema_capabilities", "ql3"."run_attempts", "ql3"."run_dispatch_leases", "ql3"."worker_credentials" TO ql3_worker_ingress',
|
||||
'GRANT SELECT, INSERT, UPDATE ON "ql3"."worker_sessions" TO ql3_worker_ingress',
|
||||
'GRANT SELECT, INSERT ON "ql3"."worker_credential_deliveries" TO ql3_worker_ingress',
|
||||
'GRANT SELECT, INSERT ON "ql3"."worker_execution_attestations" TO ql3_worker_ingress',
|
||||
'GRANT INSERT ON "ql3"."security_audit_events" TO ql3_worker_ingress',
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 16,
|
||||
migration_id = 'pg-0017-database-role-grants',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 15
|
||||
AND migration_id = 'pg-0016-worker-credential-stage-discard-ledger'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 15'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,212 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0018PluginPackageInstallsMigration = definePostgresSqlMigration({
|
||||
id: 'pg-0018-plugin-package-installs',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_installs" (
|
||||
installation_id varchar(128) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
package_version varchar(128) NOT NULL,
|
||||
operation varchar(16) NOT NULL,
|
||||
lock_digest char(64) NOT NULL,
|
||||
target_generation integer NOT NULL,
|
||||
previous_active_lock_digest char(64),
|
||||
active_lock_digest char(64),
|
||||
state varchar(16) NOT NULL,
|
||||
version integer NOT NULL,
|
||||
last_mutation_id varchar(128) NOT NULL,
|
||||
last_mutation_digest char(64) NOT NULL,
|
||||
lock_json jsonb NOT NULL,
|
||||
record_json jsonb NOT NULL,
|
||||
record_digest char(64) NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_installs_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_installs_identity_check CHECK (
|
||||
installation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
AND package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$'
|
||||
AND char_length(package_version) BETWEEN 1 AND 128
|
||||
AND last_mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
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 (
|
||||
lock_digest ~ '^[0-9a-f]{64}$'
|
||||
AND (
|
||||
previous_active_lock_digest IS NULL
|
||||
OR previous_active_lock_digest ~ '^[0-9a-f]{64}$'
|
||||
)
|
||||
AND (
|
||||
active_lock_digest IS NULL
|
||||
OR active_lock_digest ~ '^[0-9a-f]{64}$'
|
||||
)
|
||||
AND last_mutation_digest ~ '^[0-9a-f]{64}$'
|
||||
AND record_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_installs_record_check CHECK (
|
||||
jsonb_typeof(lock_json) = 'object'
|
||||
AND octet_length(lock_json::text) BETWEEN 2 AND 262144
|
||||
AND lock_json @> jsonb_build_object(
|
||||
'lockDigest', lock_digest,
|
||||
'projectId', project_id,
|
||||
'packageName', package_name
|
||||
)
|
||||
AND jsonb_typeof(record_json) = 'object'
|
||||
AND octet_length(record_json::text) BETWEEN 2 AND 262144
|
||||
AND record_json @> jsonb_build_object(
|
||||
'installationId', installation_id,
|
||||
'projectId', project_id,
|
||||
'packageName', package_name,
|
||||
'lockDigest', lock_digest,
|
||||
'state', state,
|
||||
'version', version,
|
||||
'recordDigest', record_digest
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_installs_time_check CHECK (
|
||||
created_at_ms >= 0
|
||||
AND updated_at_ms >= created_at_ms
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_install_heads" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
installation_id varchar(128) NOT NULL,
|
||||
CONSTRAINT plugin_package_install_heads_pkey
|
||||
PRIMARY KEY (project_id, package_name),
|
||||
CONSTRAINT ql3_plugin_package_install_heads_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_install_heads_install_fk
|
||||
FOREIGN KEY (installation_id)
|
||||
REFERENCES "ql3"."plugin_package_installs" (installation_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_install_heads_identity_check CHECK (
|
||||
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$'
|
||||
AND installation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_plugin_package_install_heads_install_uidx
|
||||
ON "ql3"."plugin_package_install_heads" (installation_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_install_mutations" (
|
||||
installation_id varchar(128) NOT NULL,
|
||||
mutation_id varchar(128) NOT NULL,
|
||||
mutation_digest char(64) NOT NULL,
|
||||
resulting_record_digest char(64) NOT NULL,
|
||||
occurred_at_ms bigint NOT NULL,
|
||||
CONSTRAINT plugin_package_install_mutations_pkey
|
||||
PRIMARY KEY (installation_id, mutation_id),
|
||||
CONSTRAINT ql3_plugin_package_install_mutations_install_fk
|
||||
FOREIGN KEY (installation_id)
|
||||
REFERENCES "ql3"."plugin_package_installs" (installation_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_install_mutations_identity_check CHECK (
|
||||
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_install_mutations_digest_check CHECK (
|
||||
mutation_digest ~ '^[0-9a-f]{64}$'
|
||||
AND resulting_record_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_install_mutations_time_check CHECK (
|
||||
occurred_at_ms >= 0
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_plugin_package_installs_recovery_idx
|
||||
ON "ql3"."plugin_package_installs" (state, package_name, installation_id)
|
||||
WHERE state IN ('queued', 'staged', 'activating')
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_plugin_package_installs_project_history_idx
|
||||
ON "ql3"."plugin_package_installs"
|
||||
(project_id, package_name, created_at_ms, installation_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_plugin_package_install_mutations_result_idx
|
||||
ON "ql3"."plugin_package_install_mutations"
|
||||
(installation_id, resulting_record_digest)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE FUNCTION "ql3"."lock_active_plugin_package_project"(varchar)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
DECLARE
|
||||
project_is_active boolean;
|
||||
BEGIN
|
||||
SELECT projects.status = 'active'
|
||||
INTO project_is_active
|
||||
FROM "ql3"."projects" AS projects
|
||||
WHERE projects.id = $1
|
||||
FOR SHARE;
|
||||
|
||||
RETURN COALESCE(project_is_active, false);
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON FUNCTION "ql3"."lock_active_plugin_package_project"(varchar)
|
||||
FROM PUBLIC
|
||||
`.trim(),
|
||||
`
|
||||
GRANT EXECUTE
|
||||
ON FUNCTION "ql3"."lock_active_plugin_package_project"(varchar)
|
||||
TO ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT, UPDATE
|
||||
ON "ql3"."plugin_package_installs", "ql3"."plugin_package_install_heads"
|
||||
TO ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."plugin_package_install_mutations"
|
||||
TO ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 17,
|
||||
migration_id = 'pg-0018-plugin-package-installs',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_install":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 16
|
||||
AND migration_id = 'pg-0017-database-role-grants'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 16'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,253 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0019ApprovedActionsMigration = definePostgresSqlMigration({
|
||||
id: 'pg-0019-approved-actions',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."approval_requests" (
|
||||
request_id varchar(128) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
version integer NOT NULL,
|
||||
state varchar(16) NOT NULL,
|
||||
action_type varchar(128) NOT NULL,
|
||||
action_ref varchar(255) NOT NULL,
|
||||
action_digest char(64) NOT NULL,
|
||||
preview_digest char(64) NOT NULL,
|
||||
requested_by_type varchar(32) NOT NULL,
|
||||
requested_by_id varchar(255) NOT NULL,
|
||||
decision_id varchar(128),
|
||||
consumption_id varchar(128),
|
||||
dispatch_id varchar(128),
|
||||
expires_at_ms bigint NOT NULL,
|
||||
request_json jsonb NOT NULL,
|
||||
request_digest char(64) NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT ql3_approval_requests_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_approval_requests_identity_check CHECK (
|
||||
request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
AND action_type ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
AND action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$'
|
||||
),
|
||||
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 (
|
||||
action_digest ~ '^[0-9a-f]{64}$'
|
||||
AND preview_digest ~ '^[0-9a-f]{64}$'
|
||||
AND request_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_approval_requests_subject_check CHECK (
|
||||
requested_by_type IN
|
||||
('user','api_app','mcp_client','agent','system','worker')
|
||||
AND char_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 (
|
||||
jsonb_typeof(request_json) = 'object'
|
||||
AND octet_length(request_json::text) BETWEEN 2 AND 65536
|
||||
AND request_json @> jsonb_build_object(
|
||||
'id', request_id,
|
||||
'projectId', project_id,
|
||||
'version', version,
|
||||
'state', state,
|
||||
'action', jsonb_build_object(
|
||||
'actionType', action_type,
|
||||
'actionRef', action_ref,
|
||||
'actionDigest', action_digest,
|
||||
'previewDigest', preview_digest
|
||||
)
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_approval_requests_time_check CHECK (
|
||||
expires_at_ms > 0 AND updated_at_ms >= 0
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_approval_requests_decision_uidx
|
||||
ON "ql3"."approval_requests" (decision_id)
|
||||
WHERE decision_id IS NOT NULL
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_approval_requests_consumption_uidx
|
||||
ON "ql3"."approval_requests" (consumption_id)
|
||||
WHERE consumption_id IS NOT NULL
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_approval_requests_dispatch_uidx
|
||||
ON "ql3"."approval_requests" (dispatch_id)
|
||||
WHERE dispatch_id IS NOT NULL
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_approval_requests_pending_idx
|
||||
ON "ql3"."approval_requests" (expires_at_ms, request_id)
|
||||
WHERE state = 'pending'
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_approval_requests_project_idx
|
||||
ON "ql3"."approval_requests" (project_id, updated_at_ms, request_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."approved_action_dispatches" (
|
||||
dispatch_id varchar(128) PRIMARY KEY,
|
||||
approval_request_id varchar(128) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
action_type varchar(128) NOT NULL,
|
||||
action_ref varchar(255) NOT NULL,
|
||||
action_digest char(64) NOT NULL,
|
||||
preview_digest char(64) NOT NULL,
|
||||
dispatch_json jsonb NOT NULL,
|
||||
dispatch_digest char(64) NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
CONSTRAINT ql3_approved_action_dispatch_request_fk
|
||||
FOREIGN KEY (approval_request_id)
|
||||
REFERENCES "ql3"."approval_requests" (request_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_approved_action_dispatch_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_approved_action_dispatch_identity_check CHECK (
|
||||
dispatch_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
AND action_type ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
AND action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$'
|
||||
),
|
||||
CONSTRAINT ql3_approved_action_dispatch_digest_check CHECK (
|
||||
action_digest ~ '^[0-9a-f]{64}$'
|
||||
AND preview_digest ~ '^[0-9a-f]{64}$'
|
||||
AND dispatch_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_approved_action_dispatch_json_check CHECK (
|
||||
jsonb_typeof(dispatch_json) = 'object'
|
||||
AND octet_length(dispatch_json::text) BETWEEN 2 AND 65536
|
||||
AND dispatch_json @> jsonb_build_object(
|
||||
'id', dispatch_id,
|
||||
'approvalRequestId', approval_request_id,
|
||||
'projectId', project_id,
|
||||
'action', jsonb_build_object(
|
||||
'actionType', action_type,
|
||||
'actionRef', action_ref,
|
||||
'actionDigest', action_digest,
|
||||
'previewDigest', preview_digest
|
||||
)
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_approved_action_dispatch_time_check CHECK (
|
||||
created_at_ms >= 0
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_approved_action_dispatch_request_uidx
|
||||
ON "ql3"."approved_action_dispatches" (approval_request_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_approved_action_dispatch_project_idx
|
||||
ON "ql3"."approved_action_dispatches"
|
||||
(project_id, created_at_ms, dispatch_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE FUNCTION "ql3"."lock_approval_policy_fence"(
|
||||
varchar,
|
||||
varchar,
|
||||
varchar,
|
||||
integer,
|
||||
integer
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
DECLARE
|
||||
current_project_version integer;
|
||||
current_project_status varchar;
|
||||
current_binding_version integer;
|
||||
BEGIN
|
||||
SELECT project.version, project.status
|
||||
INTO current_project_version, current_project_status
|
||||
FROM "ql3"."projects" AS project
|
||||
WHERE project.id = $1
|
||||
FOR SHARE;
|
||||
|
||||
SELECT binding.version
|
||||
INTO current_binding_version
|
||||
FROM "ql3"."project_role_bindings" AS binding
|
||||
WHERE binding.project_id = $1
|
||||
AND binding.subject_type = $2
|
||||
AND binding.subject_id = $3
|
||||
ORDER BY binding.version DESC
|
||||
LIMIT 1
|
||||
FOR SHARE;
|
||||
|
||||
RETURN current_project_status = 'active'
|
||||
AND current_project_version = $4
|
||||
AND current_binding_version IS NOT DISTINCT FROM $5;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON FUNCTION "ql3"."lock_approval_policy_fence"(
|
||||
varchar, varchar, varchar, integer, integer
|
||||
)
|
||||
FROM PUBLIC
|
||||
`.trim(),
|
||||
`
|
||||
GRANT EXECUTE
|
||||
ON FUNCTION "ql3"."lock_approval_policy_fence"(
|
||||
varchar, varchar, varchar, integer, integer
|
||||
)
|
||||
TO ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT, UPDATE
|
||||
ON "ql3"."approval_requests"
|
||||
TO ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."approved_action_dispatches"
|
||||
TO ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 18,
|
||||
migration_id = 'pg-0019-approved-actions',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_install":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 17
|
||||
AND migration_id = 'pg-0018-plugin-package-installs'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_install":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 17'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0020PluginPackageAdmissionReceiptsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0020-plugin-package-admission-receipts',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_admission_receipts" (
|
||||
dispatch_id varchar(128) PRIMARY KEY,
|
||||
dispatch_digest char(64) NOT NULL,
|
||||
approval_request_id varchar(128) NOT NULL,
|
||||
action_ref varchar(255) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(64) NOT NULL,
|
||||
installation_id varchar(128) NOT NULL,
|
||||
lock_digest char(64) NOT NULL,
|
||||
record_digest char(64) NOT NULL,
|
||||
mutation_id varchar(128) NOT NULL,
|
||||
mutation_digest char(64) NOT NULL,
|
||||
audit_event_id uuid NOT NULL,
|
||||
admitted_at_ms bigint NOT NULL,
|
||||
receipt_json jsonb NOT NULL,
|
||||
receipt_digest char(64) NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_admission_dispatch_fk
|
||||
FOREIGN KEY (dispatch_id)
|
||||
REFERENCES "ql3"."approved_action_dispatches" (dispatch_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_admission_request_fk
|
||||
FOREIGN KEY (approval_request_id)
|
||||
REFERENCES "ql3"."approval_requests" (request_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_admission_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_admission_install_fk
|
||||
FOREIGN KEY (installation_id)
|
||||
REFERENCES "ql3"."plugin_package_installs" (installation_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_admission_audit_fk
|
||||
FOREIGN KEY (audit_event_id)
|
||||
REFERENCES "ql3"."security_audit_events" (event_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_admission_identity_check CHECK (
|
||||
dispatch_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
AND approval_request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
AND action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$'
|
||||
AND project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
AND package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$'
|
||||
AND installation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
AND mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_admission_digest_check CHECK (
|
||||
dispatch_digest ~ '^[0-9a-f]{64}$'
|
||||
AND lock_digest ~ '^[0-9a-f]{64}$'
|
||||
AND record_digest ~ '^[0-9a-f]{64}$'
|
||||
AND mutation_digest ~ '^[0-9a-f]{64}$'
|
||||
AND receipt_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_admission_json_check CHECK (
|
||||
jsonb_typeof(receipt_json) = 'object'
|
||||
AND octet_length(receipt_json::text) BETWEEN 2 AND 65536
|
||||
AND receipt_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-admission-receipt@v1',
|
||||
'dispatchId', dispatch_id,
|
||||
'dispatchDigest', dispatch_digest,
|
||||
'approvalRequestId', approval_request_id,
|
||||
'actionRef', action_ref,
|
||||
'projectId', project_id,
|
||||
'packageName', package_name,
|
||||
'installationId', installation_id,
|
||||
'lockDigest', lock_digest,
|
||||
'recordDigest', record_digest,
|
||||
'mutationId', mutation_id,
|
||||
'mutationDigest', mutation_digest,
|
||||
'auditEventId', audit_event_id::text,
|
||||
'admittedAtMs', admitted_at_ms,
|
||||
'receiptDigest', receipt_digest
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_admission_time_check CHECK (
|
||||
admitted_at_ms >= 0
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_plugin_package_admission_install_uidx
|
||||
ON "ql3"."plugin_package_admission_receipts" (installation_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_plugin_package_admission_audit_uidx
|
||||
ON "ql3"."plugin_package_admission_receipts" (audit_event_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_plugin_package_admission_project_idx
|
||||
ON "ql3"."plugin_package_admission_receipts"
|
||||
(project_id, admitted_at_ms, dispatch_id)
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."plugin_package_admission_receipts"
|
||||
TO ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 19,
|
||||
migration_id = 'pg-0020-plugin-package-admission-receipts',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_install":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 18
|
||||
AND migration_id = 'pg-0019-approved-actions'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_install":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 18'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const pg0021ApprovedActionExecutionsAndPackageProposalsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0021-approved-action-executions-and-package-proposals',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_install_proposals" (
|
||||
action_ref varchar(255) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
action_type varchar(128) NOT NULL,
|
||||
permission varchar(128) NOT NULL,
|
||||
action_digest char(64) NOT NULL,
|
||||
preview_digest char(64) NOT NULL,
|
||||
proposed_by_type varchar(32) NOT NULL,
|
||||
proposed_by_id varchar(255) NOT NULL,
|
||||
fence_project_version integer NOT NULL,
|
||||
fence_binding_version integer,
|
||||
created_at_ms bigint NOT NULL,
|
||||
proposal_json jsonb NOT NULL,
|
||||
proposal_digest char(64) NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_proposal_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_proposal_identity_check CHECK (
|
||||
action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$'
|
||||
AND action_type = 'plugin_package.install'
|
||||
AND permission = 'package.manage'
|
||||
AND proposed_by_type IN
|
||||
('user','api_app','mcp_client','agent','system','worker')
|
||||
AND char_length(proposed_by_id) BETWEEN 1 AND 255
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_proposal_digest_check CHECK (
|
||||
action_digest ~ '^[0-9a-f]{64}$'
|
||||
AND preview_digest ~ '^[0-9a-f]{64}$'
|
||||
AND proposal_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_proposal_json_check CHECK (
|
||||
jsonb_typeof(proposal_json) = 'object'
|
||||
AND octet_length(proposal_json::text) BETWEEN 2 AND 262144
|
||||
AND proposal_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-install-proposal@v1',
|
||||
'actionRef', action_ref,
|
||||
'projectId', project_id,
|
||||
'actionType', action_type,
|
||||
'permission', permission,
|
||||
'actionDigest', action_digest,
|
||||
'previewDigest', preview_digest,
|
||||
'proposedBy', jsonb_build_object(
|
||||
'type', proposed_by_type,
|
||||
'id', proposed_by_id
|
||||
),
|
||||
'proposalFence', jsonb_build_object(
|
||||
'projectVersion', fence_project_version,
|
||||
'bindingVersion', fence_binding_version
|
||||
),
|
||||
'createdAtMs', created_at_ms,
|
||||
'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
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_plugin_package_proposal_project_idx
|
||||
ON "ql3"."plugin_package_install_proposals"
|
||||
(project_id, created_at_ms, action_ref)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."approved_action_executions" (
|
||||
dispatch_id varchar(128) PRIMARY KEY,
|
||||
dispatch_digest char(64) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
status varchar(16) NOT NULL,
|
||||
version integer NOT NULL,
|
||||
attempt_count integer NOT NULL,
|
||||
max_attempts integer NOT NULL,
|
||||
eligible_at_ms bigint,
|
||||
next_attempt_at_ms bigint,
|
||||
lease_owner varchar(128),
|
||||
lease_token varchar(128),
|
||||
lease_expires_at_ms bigint,
|
||||
started_at_ms bigint,
|
||||
result_mutation_id varchar(128),
|
||||
result_code varchar(64),
|
||||
result_digest char(64),
|
||||
completed_at_ms bigint,
|
||||
created_at_ms bigint NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
execution_json jsonb NOT NULL,
|
||||
execution_digest char(64) NOT NULL,
|
||||
CONSTRAINT ql3_approved_action_execution_dispatch_fk
|
||||
FOREIGN KEY (dispatch_id)
|
||||
REFERENCES "ql3"."approved_action_dispatches" (dispatch_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_approved_action_execution_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (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 (
|
||||
char_length(lease_owner) BETWEEN 1 AND 128
|
||||
AND char_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 (
|
||||
char_length(result_mutation_id) BETWEEN 1 AND 128
|
||||
AND char_length(result_code) BETWEEN 1 AND 64
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_approved_action_execution_digest_check CHECK (
|
||||
dispatch_digest ~ '^[0-9a-f]{64}$'
|
||||
AND (result_digest IS NULL OR result_digest ~ '^[0-9a-f]{64}$')
|
||||
AND execution_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_approved_action_execution_json_check CHECK (
|
||||
jsonb_typeof(execution_json) = 'object'
|
||||
AND octet_length(execution_json::text) BETWEEN 2 AND 65536
|
||||
AND execution_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/approved-action-execution@v1',
|
||||
'dispatchId', dispatch_id,
|
||||
'dispatchDigest', dispatch_digest,
|
||||
'projectId', project_id,
|
||||
'status', status,
|
||||
'version', version,
|
||||
'attemptCount', attempt_count,
|
||||
'maxAttempts', max_attempts,
|
||||
'executionDigest', execution_digest
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_approved_action_execution_time_check CHECK (
|
||||
created_at_ms >= 0 AND updated_at_ms >= created_at_ms
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_approved_action_execution_due_idx
|
||||
ON "ql3"."approved_action_executions" (eligible_at_ms, dispatch_id)
|
||||
WHERE status IN ('pending','leased','retry_wait')
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_approved_action_execution_recovery_idx
|
||||
ON "ql3"."approved_action_executions" (lease_expires_at_ms, dispatch_id)
|
||||
WHERE status = 'executing'
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_approved_action_execution_project_idx
|
||||
ON "ql3"."approved_action_executions"
|
||||
(project_id, updated_at_ms, dispatch_id)
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."plugin_package_install_proposals"
|
||||
TO ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT, UPDATE
|
||||
ON "ql3"."approved_action_executions"
|
||||
TO ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 20,
|
||||
migration_id =
|
||||
'pg-0021-approved-action-executions-and-package-proposals',
|
||||
capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_install":1,"plugin_package_proposal":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 19
|
||||
AND migration_id = 'pg-0020-plugin-package-admission-receipts'
|
||||
AND capabilities = '{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_install":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 19'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V20 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_install":1,"plugin_package_proposal":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V21 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_install":1,"plugin_package_proposal":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0022PluginPackageAuthoritySplitMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0022-plugin-package-authority-split',
|
||||
statements: [
|
||||
`
|
||||
DO $ql3$
|
||||
DECLARE
|
||||
invalid_roles text[];
|
||||
BEGIN
|
||||
SELECT array_agg(expected.role_name ORDER BY expected.role_name)
|
||||
INTO invalid_roles
|
||||
FROM (
|
||||
VALUES
|
||||
('ql3_package_manager'),
|
||||
('ql3_package_executor')
|
||||
) AS expected(role_name)
|
||||
LEFT JOIN pg_catalog.pg_roles roles
|
||||
ON roles.rolname = expected.role_name
|
||||
WHERE roles.rolname IS NULL
|
||||
OR roles.rolcanlogin IS NOT TRUE
|
||||
OR roles.rolsuper IS NOT FALSE
|
||||
OR roles.rolcreatedb IS NOT FALSE
|
||||
OR roles.rolcreaterole IS NOT FALSE
|
||||
OR roles.rolreplication IS NOT FALSE
|
||||
OR roles.rolbypassrls IS NOT FALSE;
|
||||
|
||||
IF invalid_roles IS NOT NULL THEN
|
||||
RAISE EXCEPTION
|
||||
'required QingLong Package database roles are missing or privileged: %',
|
||||
invalid_roles
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
EXECUTE format(
|
||||
'GRANT CONNECT ON DATABASE %I TO ql3_package_manager, ql3_package_executor',
|
||||
current_database()
|
||||
);
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
'GRANT USAGE ON SCHEMA "ql3" TO ql3_package_manager, ql3_package_executor',
|
||||
'GRANT SELECT ON "ql3"."schema_migrations", "ql3"."schema_capabilities" TO ql3_package_manager, ql3_package_executor',
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."plugin_package_install_proposals",
|
||||
"ql3"."approval_requests",
|
||||
"ql3"."approved_action_dispatches",
|
||||
"ql3"."approved_action_executions",
|
||||
"ql3"."plugin_package_installs",
|
||||
"ql3"."plugin_package_install_heads",
|
||||
"ql3"."plugin_package_install_mutations",
|
||||
"ql3"."plugin_package_admission_receipts"
|
||||
FROM ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE EXECUTE
|
||||
ON FUNCTION "ql3"."lock_approval_policy_fence"(
|
||||
varchar, varchar, varchar, integer, integer
|
||||
)
|
||||
FROM ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE EXECUTE
|
||||
ON FUNCTION "ql3"."lock_active_plugin_package_project"(varchar)
|
||||
FROM ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT
|
||||
ON "ql3"."projects", "ql3"."project_role_bindings"
|
||||
TO ql3_package_manager, ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."security_audit_events"
|
||||
TO ql3_package_manager, ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."plugin_package_install_proposals"
|
||||
TO ql3_package_manager
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT, UPDATE
|
||||
ON "ql3"."approval_requests"
|
||||
TO ql3_package_manager
|
||||
`.trim(),
|
||||
`
|
||||
GRANT EXECUTE
|
||||
ON FUNCTION "ql3"."lock_approval_policy_fence"(
|
||||
varchar, varchar, varchar, integer, integer
|
||||
)
|
||||
TO ql3_package_manager, ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT
|
||||
ON "ql3"."plugin_package_install_proposals"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, UPDATE
|
||||
ON "ql3"."approval_requests"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."approved_action_dispatches"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT, UPDATE
|
||||
ON "ql3"."approved_action_executions",
|
||||
"ql3"."plugin_package_installs",
|
||||
"ql3"."plugin_package_install_heads"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."plugin_package_install_mutations",
|
||||
"ql3"."plugin_package_admission_receipts"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT EXECUTE
|
||||
ON FUNCTION "ql3"."lock_active_plugin_package_project"(varchar)
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 21,
|
||||
migration_id = 'pg-0022-plugin-package-authority-split',
|
||||
capabilities = '${CAPABILITIES_V21}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 20
|
||||
AND migration_id =
|
||||
'pg-0021-approved-action-executions-and-package-proposals'
|
||||
AND capabilities = '${CAPABILITIES_V20}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 20'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V21 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_install":1,"plugin_package_proposal":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V22 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_proposal":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0023PluginPackageManagementQuotaMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0023-plugin-package-management-quota',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_management_quota_buckets" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
subject_type varchar(32) NOT NULL,
|
||||
subject_id varchar(255) NOT NULL,
|
||||
operation varchar(64) NOT NULL,
|
||||
window_started_at_ms bigint NOT NULL,
|
||||
consumed_count integer NOT NULL,
|
||||
receipt_ids jsonb NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT plugin_package_management_quota_buckets_pkey
|
||||
PRIMARY KEY (project_id, subject_type, subject_id, operation),
|
||||
CONSTRAINT ql3_plugin_package_management_quota_project_fk
|
||||
FOREIGN KEY (project_id)
|
||||
REFERENCES "ql3"."projects"(id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_management_quota_identity_check
|
||||
CHECK (
|
||||
subject_type = 'user'
|
||||
AND char_length(subject_id) BETWEEN 1 AND 255
|
||||
AND subject_id !~ '[[:cntrl:]]'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_management_quota_operation_check
|
||||
CHECK (
|
||||
operation IN (
|
||||
'plugin-package.propose',
|
||||
'plugin-package.decide',
|
||||
'plugin-package.inspect'
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_management_quota_window_check
|
||||
CHECK (
|
||||
window_started_at_ms >= 0
|
||||
AND consumed_count BETWEEN 1 AND 1000
|
||||
AND updated_at_ms >= window_started_at_ms
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_management_quota_receipts_check
|
||||
CHECK (
|
||||
jsonb_typeof(receipt_ids) = 'array'
|
||||
AND jsonb_array_length(receipt_ids) = consumed_count
|
||||
AND octet_length(receipt_ids::text) BETWEEN 3 AND 262144
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."plugin_package_management_quota_buckets"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT, UPDATE
|
||||
ON "ql3"."plugin_package_management_quota_buckets"
|
||||
TO ql3_package_manager
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 22,
|
||||
migration_id = 'pg-0023-plugin-package-management-quota',
|
||||
capabilities = '${CAPABILITIES_V22}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 21
|
||||
AND migration_id = 'pg-0022-plugin-package-authority-split'
|
||||
AND capabilities = '${CAPABILITIES_V21}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 21'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V22 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_proposal":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V23 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_proposal":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0024PluginPackageIdentityKeysetLedgerMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0024-plugin-package-identity-keyset-ledger',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_identity_keyset_ledger" (
|
||||
authority varchar(64) NOT NULL,
|
||||
generation bigint NOT NULL,
|
||||
digest varchar(43) NOT NULL,
|
||||
issuer varchar(512) NOT NULL,
|
||||
audience varchar(256) NOT NULL,
|
||||
active_key_ids jsonb NOT NULL,
|
||||
revoked_key_ids jsonb NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT plugin_package_identity_keyset_ledger_pkey
|
||||
PRIMARY KEY (authority),
|
||||
CONSTRAINT ql3_plugin_package_identity_keyset_authority_check
|
||||
CHECK (authority = 'plugin-package-management'),
|
||||
CONSTRAINT ql3_plugin_package_identity_keyset_generation_check
|
||||
CHECK (generation >= 1 AND updated_at_ms >= 0),
|
||||
CONSTRAINT ql3_plugin_package_identity_keyset_digest_check
|
||||
CHECK (digest ~ '^[A-Za-z0-9_-]{43}$'),
|
||||
CONSTRAINT ql3_plugin_package_identity_keyset_trust_domain_check
|
||||
CHECK (
|
||||
char_length(issuer) BETWEEN 1 AND 512
|
||||
AND issuer !~ '[[:cntrl:]]'
|
||||
AND char_length(audience) BETWEEN 1 AND 256
|
||||
AND audience !~ '[[:cntrl:]]'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_identity_keyset_keys_check
|
||||
CHECK (
|
||||
jsonb_typeof(active_key_ids) = 'array'
|
||||
AND jsonb_array_length(active_key_ids) BETWEEN 1 AND 8
|
||||
AND octet_length(active_key_ids::text) BETWEEN 3 AND 8192
|
||||
AND jsonb_typeof(revoked_key_ids) = 'array'
|
||||
AND jsonb_array_length(revoked_key_ids) BETWEEN 0 AND 64
|
||||
AND octet_length(revoked_key_ids::text) BETWEEN 2 AND 16384
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."plugin_package_identity_keyset_ledger"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT, UPDATE
|
||||
ON "ql3"."plugin_package_identity_keyset_ledger"
|
||||
TO ql3_package_manager
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 23,
|
||||
migration_id = 'pg-0024-plugin-package-identity-keyset-ledger',
|
||||
capabilities = '${CAPABILITIES_V23}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 22
|
||||
AND migration_id = 'pg-0023-plugin-package-management-quota'
|
||||
AND capabilities = '${CAPABILITIES_V22}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 22'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V23 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_proposal":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V24 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0025PluginPackageMaterializedRevisionsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0025-plugin-package-materialized-revisions',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_materialized_revisions" (
|
||||
generation_digest char(64) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
generation integer NOT NULL,
|
||||
lock_digest char(64) NOT NULL,
|
||||
manifest_digest char(64) NOT NULL,
|
||||
revision_digest char(64) NOT NULL,
|
||||
revision_json jsonb NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
CONSTRAINT plugin_package_materialized_revisions_pkey
|
||||
PRIMARY KEY (generation_digest),
|
||||
CONSTRAINT ql3_plugin_package_materialized_revision_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_materialized_revision_identity_check CHECK (
|
||||
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' AND
|
||||
generation BETWEEN 1 AND 2147483647
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_materialized_revision_digest_check CHECK (
|
||||
generation_digest ~ '^[0-9a-f]{64}$' AND
|
||||
lock_digest ~ '^[0-9a-f]{64}$' AND
|
||||
manifest_digest ~ '^[0-9a-f]{64}$' AND
|
||||
revision_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_materialized_revision_json_check CHECK (
|
||||
jsonb_typeof(revision_json) = 'object' AND
|
||||
octet_length(revision_json::text) BETWEEN 2 AND 25165824 AND
|
||||
revision_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-materialized-revision@v1',
|
||||
'generation', jsonb_build_object(
|
||||
'generationDigest', generation_digest,
|
||||
'projectId', project_id,
|
||||
'packageName', package_name,
|
||||
'generation', generation,
|
||||
'lockDigest', lock_digest
|
||||
),
|
||||
'manifestDigest', manifest_digest,
|
||||
'revisionDigest', revision_digest
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_materialized_revision_time_check CHECK (
|
||||
created_at_ms >= 0
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_plugin_package_materialized_revision_generation_uidx
|
||||
ON "ql3"."plugin_package_materialized_revisions"
|
||||
(project_id, package_name, generation)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_plugin_package_materialized_revision_lock_idx
|
||||
ON "ql3"."plugin_package_materialized_revisions"
|
||||
(lock_digest, generation_digest)
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."plugin_package_materialized_revisions"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."plugin_package_materialized_revisions"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 24,
|
||||
migration_id = 'pg-0025-plugin-package-materialized-revisions',
|
||||
capabilities = '${CAPABILITIES_V24}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 23
|
||||
AND migration_id = 'pg-0024-plugin-package-identity-keyset-ledger'
|
||||
AND capabilities = '${CAPABILITIES_V23}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 23'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+622
@@ -0,0 +1,622 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V24 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V25 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0026PluginPackageTaskReconciliationsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0026-plugin-package-task-reconciliations',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_task_ownerships" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
task_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
claimed_generation_digest char(64) NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
CONSTRAINT plugin_package_task_ownerships_pkey
|
||||
PRIMARY KEY (project_id, task_id),
|
||||
CONSTRAINT ql3_plugin_package_task_ownership_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_task_ownership_task_fk
|
||||
FOREIGN KEY (project_id, task_id)
|
||||
REFERENCES "ql3"."task_definitions" (project_id, task_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_task_ownership_identity_check CHECK (
|
||||
char_length(task_id) BETWEEN 1 AND 128 AND
|
||||
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_task_ownership_digest_check CHECK (
|
||||
claimed_generation_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
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
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_plugin_package_task_ownership_package_idx
|
||||
ON "ql3"."plugin_package_task_ownerships"
|
||||
(project_id, package_name, task_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_task_reconciliations" (
|
||||
generation_digest char(64) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
generation integer NOT NULL,
|
||||
materialized_revision_digest char(64) NOT NULL,
|
||||
lock_digest char(64) NOT NULL,
|
||||
previous_lock_digest char(64),
|
||||
receipt_digest char(64) NOT NULL,
|
||||
receipt_json jsonb NOT NULL,
|
||||
committed_at_ms bigint NOT NULL,
|
||||
CONSTRAINT plugin_package_task_reconciliations_pkey
|
||||
PRIMARY KEY (generation_digest),
|
||||
CONSTRAINT ql3_plugin_package_task_reconciliation_materialized_fk
|
||||
FOREIGN KEY (generation_digest)
|
||||
REFERENCES "ql3"."plugin_package_materialized_revisions"
|
||||
(generation_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_task_reconciliation_identity_check CHECK (
|
||||
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' AND
|
||||
generation BETWEEN 1 AND 2147483647
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_task_reconciliation_digest_check CHECK (
|
||||
generation_digest ~ '^[0-9a-f]{64}$' AND
|
||||
materialized_revision_digest ~ '^[0-9a-f]{64}$' AND
|
||||
lock_digest ~ '^[0-9a-f]{64}$' AND
|
||||
(previous_lock_digest IS NULL OR
|
||||
previous_lock_digest ~ '^[0-9a-f]{64}$') AND
|
||||
receipt_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_task_reconciliation_json_check CHECK (
|
||||
jsonb_typeof(receipt_json) = 'object' AND
|
||||
octet_length(receipt_json::text) BETWEEN 2 AND 8388608 AND
|
||||
receipt_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-task-reconciliation@v1',
|
||||
'generationDigest', generation_digest,
|
||||
'projectId', project_id,
|
||||
'packageName', package_name,
|
||||
'generation', generation,
|
||||
'materializedRevisionDigest', materialized_revision_digest,
|
||||
'lockDigest', lock_digest,
|
||||
'receiptDigest', receipt_digest
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_task_reconciliation_time_check CHECK (
|
||||
committed_at_ms >= 0
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_plugin_package_task_reconciliation_generation_uidx
|
||||
ON "ql3"."plugin_package_task_reconciliations"
|
||||
(project_id, package_name, generation)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_plugin_package_task_reconciliation_lock_idx
|
||||
ON "ql3"."plugin_package_task_reconciliations"
|
||||
(lock_digest, generation_digest)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_task_reconciliation_items" (
|
||||
generation_digest char(64) NOT NULL,
|
||||
task_id varchar(128) NOT NULL,
|
||||
revision integer NOT NULL,
|
||||
disposition varchar(24) NOT NULL,
|
||||
content_digest char(64) NOT NULL,
|
||||
CONSTRAINT plugin_package_task_reconciliation_items_pkey
|
||||
PRIMARY KEY (generation_digest, task_id),
|
||||
CONSTRAINT ql3_plugin_package_task_reconciliation_item_reconciliation_fk
|
||||
FOREIGN KEY (generation_digest)
|
||||
REFERENCES "ql3"."plugin_package_task_reconciliations"
|
||||
(generation_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_task_reconciliation_item_identity_check CHECK (
|
||||
char_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 (
|
||||
content_digest ~ '^[0-9a-f]{64}$'
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_plugin_package_task_reconciliation_item_task_idx
|
||||
ON "ql3"."plugin_package_task_reconciliation_items"
|
||||
(task_id, generation_digest)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE FUNCTION "ql3"."commit_plugin_package_task_reconciliation"(
|
||||
p_generation_digest char(64),
|
||||
p_materialized_revision_digest char(64),
|
||||
p_receipt jsonb,
|
||||
p_writes jsonb,
|
||||
p_execution_revisions jsonb
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
DECLARE
|
||||
materialized "ql3"."plugin_package_materialized_revisions"%ROWTYPE;
|
||||
install_record "ql3"."plugin_package_installs"%ROWTYPE;
|
||||
previous_receipt jsonb;
|
||||
expected_ids text[];
|
||||
receipt_ids text[];
|
||||
write_ids text[];
|
||||
execution_ids text[];
|
||||
task_id_value text;
|
||||
item jsonb;
|
||||
write_value jsonb;
|
||||
definition jsonb;
|
||||
execution_value jsonb;
|
||||
resource_value jsonb;
|
||||
current_head "ql3"."task_definitions"%ROWTYPE;
|
||||
current_revision "ql3"."task_definition_revisions"%ROWTYPE;
|
||||
owner_package varchar(63);
|
||||
now_ms bigint;
|
||||
BEGIN
|
||||
IF NOT pg_has_role(session_user, 'ql3_package_executor', 'member') THEN
|
||||
RAISE EXCEPTION 'Package Task reconciliation authority is required'
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
IF p_generation_digest !~ '^[0-9a-f]{64}$'
|
||||
OR p_materialized_revision_digest !~ '^[0-9a-f]{64}$'
|
||||
OR jsonb_typeof(p_receipt) <> 'object'
|
||||
OR jsonb_typeof(p_writes) <> 'array'
|
||||
OR jsonb_typeof(p_execution_revisions) <> 'array'
|
||||
OR jsonb_array_length(p_writes) > 512
|
||||
OR jsonb_array_length(p_execution_revisions) > 512 THEN
|
||||
RAISE EXCEPTION 'invalid Package Task reconciliation input'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
SELECT * INTO materialized
|
||||
FROM "ql3"."plugin_package_materialized_revisions"
|
||||
WHERE generation_digest = p_generation_digest
|
||||
FOR KEY SHARE;
|
||||
IF NOT FOUND
|
||||
OR materialized.revision_digest <> p_materialized_revision_digest THEN
|
||||
RAISE EXCEPTION 'materialized revision fence changed'
|
||||
USING ERRCODE = 'serialization_failure';
|
||||
END IF;
|
||||
|
||||
SELECT install.* INTO install_record
|
||||
FROM "ql3"."plugin_package_install_heads" AS head
|
||||
JOIN "ql3"."plugin_package_installs" AS install
|
||||
ON install.installation_id = head.installation_id
|
||||
WHERE head.project_id = materialized.project_id
|
||||
AND head.package_name = materialized.package_name
|
||||
FOR UPDATE OF install;
|
||||
IF NOT FOUND
|
||||
OR install_record.state <> 'active'
|
||||
OR install_record.installation_id <>
|
||||
materialized.revision_json #>> '{generation,installationId}'
|
||||
OR install_record.target_generation <> materialized.generation
|
||||
OR install_record.lock_digest <> materialized.lock_digest
|
||||
OR install_record.previous_active_lock_digest IS DISTINCT FROM
|
||||
NULLIF(materialized.revision_json #>> '{generation,previousActiveLockDigest}', '')
|
||||
THEN
|
||||
RAISE EXCEPTION 'active Package install fence changed'
|
||||
USING ERRCODE = 'serialization_failure';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM "ql3"."plugin_package_task_reconciliations"
|
||||
WHERE generation_digest = p_generation_digest
|
||||
AND receipt_json = p_receipt
|
||||
) THEN
|
||||
RETURN false;
|
||||
ELSIF EXISTS (
|
||||
SELECT 1 FROM "ql3"."plugin_package_task_reconciliations"
|
||||
WHERE generation_digest = p_generation_digest
|
||||
) THEN
|
||||
RAISE EXCEPTION 'generation receipt identity is already bound'
|
||||
USING ERRCODE = 'unique_violation';
|
||||
END IF;
|
||||
|
||||
IF materialized.generation = 1 THEN
|
||||
IF materialized.revision_json #>> '{generation,previousActiveLockDigest}'
|
||||
IS NOT NULL THEN
|
||||
RAISE EXCEPTION 'first generation has a previous lock'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
previous_receipt := NULL;
|
||||
ELSE
|
||||
SELECT receipt_json INTO previous_receipt
|
||||
FROM "ql3"."plugin_package_task_reconciliations"
|
||||
WHERE project_id = materialized.project_id
|
||||
AND package_name = materialized.package_name
|
||||
AND generation = materialized.generation - 1
|
||||
FOR KEY SHARE;
|
||||
IF NOT FOUND
|
||||
OR previous_receipt ->> 'lockDigest' <>
|
||||
materialized.revision_json #>>
|
||||
'{generation,previousActiveLockDigest}' THEN
|
||||
RAISE EXCEPTION 'previous generation receipt is missing'
|
||||
USING ERRCODE = 'serialization_failure';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
IF p_receipt ->> 'schema' <>
|
||||
'qinglong/plugin-package-task-reconciliation@v1'
|
||||
OR p_receipt ->> 'generationDigest' <> p_generation_digest
|
||||
OR p_receipt ->> 'materializedRevisionDigest' <>
|
||||
p_materialized_revision_digest
|
||||
OR p_receipt ->> 'projectId' <> materialized.project_id
|
||||
OR p_receipt ->> 'packageName' <> materialized.package_name
|
||||
OR (p_receipt ->> 'generation')::integer <> materialized.generation
|
||||
OR p_receipt ->> 'lockDigest' <> materialized.lock_digest
|
||||
OR jsonb_typeof(p_receipt -> 'items') <> 'array'
|
||||
OR jsonb_array_length(p_receipt -> 'items') > 512 THEN
|
||||
RAISE EXCEPTION 'receipt does not match materialized generation'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
SELECT array_agg(value ORDER BY value) INTO expected_ids
|
||||
FROM (
|
||||
SELECT 'pkg:' || materialized.package_name || ':' ||
|
||||
(resource_json -> 'value' ->> 'id') AS value
|
||||
FROM jsonb_array_elements(materialized.revision_json -> 'resources')
|
||||
AS resources(resource_json)
|
||||
WHERE resource_json ->> 'kind' = 'task'
|
||||
UNION
|
||||
SELECT previous_item_json ->> 'taskId'
|
||||
FROM jsonb_array_elements(COALESCE(previous_receipt -> 'items', '[]'::jsonb))
|
||||
AS previous_items(previous_item_json)
|
||||
) AS expected;
|
||||
expected_ids := COALESCE(expected_ids, ARRAY[]::text[]);
|
||||
|
||||
SELECT array_agg(value ORDER BY value), count(DISTINCT value)
|
||||
INTO receipt_ids, now_ms
|
||||
FROM (
|
||||
SELECT receipt_item_json ->> 'taskId' AS value
|
||||
FROM jsonb_array_elements(p_receipt -> 'items')
|
||||
AS receipt_items(receipt_item_json)
|
||||
) AS receipt_values;
|
||||
receipt_ids := COALESCE(receipt_ids, ARRAY[]::text[]);
|
||||
IF receipt_ids <> expected_ids
|
||||
OR now_ms <> cardinality(receipt_ids) THEN
|
||||
RAISE EXCEPTION 'receipt does not exactly cover the generation task set'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
SELECT array_agg(value ORDER BY value), count(DISTINCT value)
|
||||
INTO write_ids, now_ms
|
||||
FROM (
|
||||
SELECT write_entry_json -> 'definition' ->> 'taskId' AS value
|
||||
FROM jsonb_array_elements(p_writes) AS writes(write_entry_json)
|
||||
) AS write_values;
|
||||
write_ids := COALESCE(write_ids, ARRAY[]::text[]);
|
||||
IF now_ms <> cardinality(write_ids)
|
||||
OR NOT write_ids <@ expected_ids THEN
|
||||
RAISE EXCEPTION 'writes are duplicated or outside the generation task set'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
SELECT array_agg(value ORDER BY value), count(DISTINCT value)
|
||||
INTO execution_ids, now_ms
|
||||
FROM (
|
||||
SELECT execution_entry_json ->> 'taskId' AS value
|
||||
FROM jsonb_array_elements(p_execution_revisions)
|
||||
AS executions(execution_entry_json)
|
||||
) AS execution_values;
|
||||
execution_ids := COALESCE(execution_ids, ARRAY[]::text[]);
|
||||
IF now_ms <> cardinality(execution_ids)
|
||||
OR NOT execution_ids <@ write_ids THEN
|
||||
RAISE EXCEPTION 'execution revisions are duplicated or unbound'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
FOREACH task_id_value IN ARRAY expected_ids LOOP
|
||||
SELECT receipt_item_json INTO item
|
||||
FROM jsonb_array_elements(p_receipt -> 'items')
|
||||
AS receipt_items(receipt_item_json)
|
||||
WHERE receipt_item_json ->> 'taskId' = task_id_value;
|
||||
SELECT write_entry_json INTO write_value
|
||||
FROM jsonb_array_elements(p_writes) AS writes(write_entry_json)
|
||||
WHERE write_entry_json -> 'definition' ->> 'taskId' = task_id_value;
|
||||
definition := write_value -> 'definition';
|
||||
SELECT resource_json -> 'value' INTO resource_value
|
||||
FROM jsonb_array_elements(materialized.revision_json -> 'resources')
|
||||
AS resources(resource_json)
|
||||
WHERE resource_json ->> 'kind' = 'task'
|
||||
AND 'pkg:' || materialized.package_name || ':' ||
|
||||
(resource_json -> 'value' ->> 'id') = task_id_value;
|
||||
|
||||
SELECT * INTO current_head
|
||||
FROM "ql3"."task_definitions"
|
||||
WHERE project_id = materialized.project_id AND task_id = task_id_value
|
||||
FOR UPDATE;
|
||||
IF FOUND THEN
|
||||
SELECT * INTO current_revision
|
||||
FROM "ql3"."task_definition_revisions"
|
||||
WHERE project_id = current_head.project_id
|
||||
AND task_id = current_head.task_id
|
||||
AND revision = current_head.current_revision;
|
||||
SELECT package_name INTO owner_package
|
||||
FROM "ql3"."plugin_package_task_ownerships"
|
||||
WHERE project_id = current_head.project_id
|
||||
AND task_id = current_head.task_id;
|
||||
IF owner_package IS DISTINCT FROM materialized.package_name THEN
|
||||
RAISE EXCEPTION 'TaskDefinition ownership collision'
|
||||
USING ERRCODE = 'unique_violation';
|
||||
END IF;
|
||||
ELSE
|
||||
current_revision := NULL;
|
||||
owner_package := NULL;
|
||||
END IF;
|
||||
|
||||
IF item ->> 'disposition' IN ('created','updated','disabled') THEN
|
||||
IF write_value IS NULL
|
||||
OR item ->> 'contentDigest' <> definition ->> 'contentDigest'
|
||||
OR (item ->> 'revision')::integer <>
|
||||
(definition ->> 'revision')::integer
|
||||
OR write_value -> 'command' ->> 'projectId' <>
|
||||
materialized.project_id
|
||||
OR write_value -> 'command' ->> 'taskId' <> task_id_value
|
||||
OR write_value -> 'command' ->> 'mutationId' <>
|
||||
definition ->> 'mutationId' THEN
|
||||
RAISE EXCEPTION 'receipt write binding is invalid'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
ELSIF write_value IS NOT NULL THEN
|
||||
RAISE EXCEPTION 'non-mutating receipt item has a write'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
IF resource_value IS NOT NULL THEN
|
||||
IF item ->> 'disposition' NOT IN ('created','retained','updated') THEN
|
||||
RAISE EXCEPTION 'active materialized Task has an invalid disposition'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
IF write_value IS NOT NULL AND (
|
||||
definition ->> 'projectId' <> materialized.project_id OR
|
||||
definition ->> 'taskId' <> task_id_value OR
|
||||
definition ->> 'name' <> resource_value ->> 'name' OR
|
||||
definition -> 'description' IS DISTINCT FROM
|
||||
resource_value -> 'description' OR
|
||||
definition ->> 'kind' <> resource_value ->> 'kind' OR
|
||||
definition -> 'spec' IS DISTINCT FROM resource_value -> 'spec' OR
|
||||
definition -> 'labels' IS DISTINCT FROM resource_value -> 'labels' OR
|
||||
definition -> 'enabled' IS DISTINCT FROM resource_value -> 'enabled'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Task write differs from materialized resource'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
ELSE
|
||||
IF item ->> 'disposition' NOT IN ('already_disabled','disabled') THEN
|
||||
RAISE EXCEPTION 'removed Task has an invalid disposition'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
IF item ->> 'disposition' = 'disabled' AND (
|
||||
current_revision IS NULL OR
|
||||
definition ->> 'name' <> current_revision.name OR
|
||||
definition -> 'description' IS DISTINCT FROM
|
||||
to_jsonb(current_revision.description) OR
|
||||
definition ->> 'kind' <> current_revision.kind OR
|
||||
definition -> 'spec' IS DISTINCT FROM current_revision.spec_json OR
|
||||
definition -> 'labels' IS DISTINCT FROM current_revision.labels_json OR
|
||||
definition -> 'enabled' <> 'false'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'removed Task disable write is invalid'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
IF write_value IS NULL THEN
|
||||
IF current_revision IS NULL
|
||||
OR (item ->> 'revision')::integer <> current_revision.revision
|
||||
OR item ->> 'contentDigest' <> current_revision.content_digest THEN
|
||||
RAISE EXCEPTION 'retained Task head changed'
|
||||
USING ERRCODE = 'serialization_failure';
|
||||
END IF;
|
||||
ELSIF current_revision IS NULL THEN
|
||||
IF item ->> 'disposition' <> 'created'
|
||||
OR write_value -> 'command' -> 'expectedRevision' <> 'null'::jsonb
|
||||
OR (definition ->> 'revision')::integer <> 1 THEN
|
||||
RAISE EXCEPTION 'Task create CAS is invalid'
|
||||
USING ERRCODE = 'serialization_failure';
|
||||
END IF;
|
||||
ELSIF item ->> 'disposition' = 'created'
|
||||
OR (write_value -> 'command' ->> 'expectedRevision')::integer <>
|
||||
current_revision.revision
|
||||
OR (definition ->> 'revision')::integer <>
|
||||
current_revision.revision + 1 THEN
|
||||
RAISE EXCEPTION 'Task update CAS is invalid'
|
||||
USING ERRCODE = 'serialization_failure';
|
||||
END IF;
|
||||
|
||||
execution_value := NULL;
|
||||
SELECT execution_entry_json INTO execution_value
|
||||
FROM jsonb_array_elements(p_execution_revisions)
|
||||
AS executions(execution_entry_json)
|
||||
WHERE execution_entry_json ->> 'taskId' = task_id_value;
|
||||
IF write_value IS NOT NULL
|
||||
AND definition -> 'enabled' = 'true'::jsonb
|
||||
AND definition ->> 'kind' = 'command'
|
||||
AND definition #>> '{spec,schema}' = 'qinglong/command@v1' THEN
|
||||
IF execution_value IS NULL
|
||||
OR (execution_value ->> 'sourceRevision')::integer <>
|
||||
(definition ->> 'revision')::integer
|
||||
OR execution_value ->> 'sourceContentDigest' <>
|
||||
definition ->> 'contentDigest' THEN
|
||||
RAISE EXCEPTION 'required execution revision is missing'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
ELSIF execution_value IS NOT NULL THEN
|
||||
RAISE EXCEPTION 'unexpected execution revision'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
now_ms := floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint;
|
||||
FOREACH task_id_value IN ARRAY write_ids LOOP
|
||||
SELECT write_entry_json INTO write_value
|
||||
FROM jsonb_array_elements(p_writes) AS writes(write_entry_json)
|
||||
WHERE write_entry_json -> 'definition' ->> 'taskId' = task_id_value;
|
||||
definition := write_value -> 'definition';
|
||||
IF write_value -> 'command' -> 'expectedRevision' = 'null'::jsonb THEN
|
||||
INSERT INTO "ql3"."task_definitions" (
|
||||
project_id, task_id, current_revision, created_at_ms, updated_at_ms
|
||||
) VALUES (
|
||||
definition ->> 'projectId', definition ->> 'taskId', 1,
|
||||
(definition ->> 'createdAtMs')::bigint,
|
||||
(definition ->> 'updatedAtMs')::bigint
|
||||
);
|
||||
INSERT INTO "ql3"."plugin_package_task_ownerships" (
|
||||
project_id, task_id, package_name,
|
||||
claimed_generation_digest, created_at_ms
|
||||
) VALUES (
|
||||
materialized.project_id, task_id_value, materialized.package_name,
|
||||
p_generation_digest, now_ms
|
||||
);
|
||||
END IF;
|
||||
INSERT INTO "ql3"."task_definition_revisions" (
|
||||
project_id, task_id, revision, mutation_id, name, description, kind,
|
||||
spec_json, labels_json, enabled, content_digest, created_at_ms
|
||||
) VALUES (
|
||||
definition ->> 'projectId', definition ->> 'taskId',
|
||||
(definition ->> 'revision')::integer,
|
||||
(definition ->> 'mutationId')::uuid,
|
||||
definition ->> 'name', definition ->> 'description',
|
||||
definition ->> 'kind', definition -> 'spec', definition -> 'labels',
|
||||
(definition ->> 'enabled')::boolean, definition ->> 'contentDigest',
|
||||
(definition ->> 'updatedAtMs')::bigint
|
||||
);
|
||||
SELECT execution_entry_json INTO execution_value
|
||||
FROM jsonb_array_elements(p_execution_revisions)
|
||||
AS executions(execution_entry_json)
|
||||
WHERE execution_entry_json ->> 'taskId' = task_id_value;
|
||||
IF execution_value IS NOT NULL THEN
|
||||
INSERT INTO "ql3"."task_execution_revisions" (
|
||||
project_id, task_id, source_revision, task_revision,
|
||||
source_content_digest, executor_type, plan_schema, plan_json,
|
||||
content_digest, created_at_ms
|
||||
) VALUES (
|
||||
execution_value ->> 'projectId', execution_value ->> 'taskId',
|
||||
(execution_value ->> 'sourceRevision')::integer,
|
||||
execution_value ->> 'taskRevision',
|
||||
execution_value ->> 'sourceContentDigest',
|
||||
execution_value ->> 'executorType',
|
||||
execution_value ->> 'planSchema',
|
||||
execution_value -> 'planJson',
|
||||
execution_value ->> 'contentDigest',
|
||||
(execution_value ->> 'createdAtMs')::bigint
|
||||
);
|
||||
END IF;
|
||||
IF write_value -> 'command' -> 'expectedRevision' <> 'null'::jsonb THEN
|
||||
UPDATE "ql3"."task_definitions" AS task_head
|
||||
SET current_revision = (definition ->> 'revision')::integer,
|
||||
updated_at_ms = (definition ->> 'updatedAtMs')::bigint
|
||||
WHERE task_head.project_id = materialized.project_id
|
||||
AND task_head.task_id = task_id_value
|
||||
AND task_head.current_revision =
|
||||
(write_value -> 'command' ->> 'expectedRevision')::integer;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'Task head changed while committing'
|
||||
USING ERRCODE = 'serialization_failure';
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
INSERT INTO "ql3"."plugin_package_task_reconciliations" (
|
||||
generation_digest, project_id, package_name, generation,
|
||||
materialized_revision_digest, lock_digest, previous_lock_digest,
|
||||
receipt_digest, receipt_json, committed_at_ms
|
||||
) VALUES (
|
||||
p_generation_digest, materialized.project_id, materialized.package_name,
|
||||
materialized.generation, p_materialized_revision_digest,
|
||||
materialized.lock_digest,
|
||||
NULLIF(p_receipt ->> 'previousLockDigest', ''),
|
||||
p_receipt ->> 'receiptDigest', p_receipt,
|
||||
(p_receipt ->> 'committedAtMs')::bigint
|
||||
);
|
||||
INSERT INTO "ql3"."plugin_package_task_reconciliation_items" (
|
||||
generation_digest, task_id, revision, disposition, content_digest
|
||||
)
|
||||
SELECT p_generation_digest, receipt_item_json ->> 'taskId',
|
||||
(receipt_item_json ->> 'revision')::integer,
|
||||
receipt_item_json ->> 'disposition',
|
||||
receipt_item_json ->> 'contentDigest'
|
||||
FROM jsonb_array_elements(p_receipt -> 'items')
|
||||
AS receipt_items(receipt_item_json);
|
||||
RETURN true;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."plugin_package_task_ownerships",
|
||||
"ql3"."plugin_package_task_reconciliations",
|
||||
"ql3"."plugin_package_task_reconciliation_items"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT
|
||||
ON "ql3"."plugin_package_task_ownerships"
|
||||
TO ql3_admin, ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT
|
||||
ON "ql3"."plugin_package_task_reconciliations",
|
||||
"ql3"."plugin_package_task_reconciliation_items",
|
||||
"ql3"."task_definitions",
|
||||
"ql3"."task_definition_revisions",
|
||||
"ql3"."task_execution_revisions"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON FUNCTION "ql3"."commit_plugin_package_task_reconciliation"(
|
||||
char(64), char(64), jsonb, jsonb, jsonb
|
||||
)
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT EXECUTE
|
||||
ON FUNCTION "ql3"."commit_plugin_package_task_reconciliation"(
|
||||
char(64), char(64), jsonb, jsonb, jsonb
|
||||
)
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 25,
|
||||
migration_id = 'pg-0026-plugin-package-task-reconciliations',
|
||||
capabilities = '${CAPABILITIES_V25}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 24
|
||||
AND migration_id = 'pg-0025-plugin-package-materialized-revisions'
|
||||
AND capabilities = '${CAPABILITIES_V24}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 24'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V25 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V26 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0027ProjectToolDefinitionSnapshotsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0027-project-tool-definition-snapshots',
|
||||
statements: [
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_plugin_package_installs_snapshot_source_uidx
|
||||
ON "ql3"."plugin_package_installs"
|
||||
(project_id, package_name, installation_id, target_generation, lock_digest)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_plugin_package_materialized_revision_snapshot_source_uidx
|
||||
ON "ql3"."plugin_package_materialized_revisions"
|
||||
(project_id, package_name, generation, generation_digest,
|
||||
lock_digest, revision_digest)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."project_tool_definition_snapshots" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
active_vector_digest char(64) NOT NULL,
|
||||
definitions_digest char(64) NOT NULL,
|
||||
snapshot_digest char(64) NOT NULL,
|
||||
snapshot_json jsonb NOT NULL,
|
||||
committed_at_ms bigint NOT NULL,
|
||||
CONSTRAINT project_tool_definition_snapshots_pkey
|
||||
PRIMARY KEY (project_id, active_vector_digest),
|
||||
CONSTRAINT ql3_project_tool_definition_snapshot_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_project_tool_definition_snapshot_identity_check CHECK (
|
||||
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_project_tool_definition_snapshot_digest_check CHECK (
|
||||
active_vector_digest ~ '^[0-9a-f]{64}$' AND
|
||||
definitions_digest ~ '^[0-9a-f]{64}$' AND
|
||||
snapshot_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_project_tool_definition_snapshot_json_check CHECK (
|
||||
jsonb_typeof(snapshot_json) = 'object' AND
|
||||
octet_length(snapshot_json::text) BETWEEN 2 AND 8388608 AND
|
||||
snapshot_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/project-tool-definition-snapshot@v1',
|
||||
'projectId', project_id,
|
||||
'activeVectorDigest', active_vector_digest,
|
||||
'definitionsDigest', definitions_digest,
|
||||
'snapshotDigest', snapshot_digest
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_project_tool_definition_snapshot_time_check CHECK (
|
||||
committed_at_ms >= 0
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_project_tool_definition_snapshot_digest_uidx
|
||||
ON "ql3"."project_tool_definition_snapshots" (snapshot_digest)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_project_tool_definition_snapshot_current_idx
|
||||
ON "ql3"."project_tool_definition_snapshots"
|
||||
(project_id, committed_at_ms DESC, active_vector_digest)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."project_tool_definition_snapshot_sources" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
active_vector_digest char(64) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
installation_id varchar(128) NOT NULL,
|
||||
generation integer NOT NULL,
|
||||
generation_digest char(64) NOT NULL,
|
||||
lock_digest char(64) NOT NULL,
|
||||
revision_digest char(64) NOT NULL,
|
||||
CONSTRAINT project_tool_definition_snapshot_sources_pkey
|
||||
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 "ql3"."project_tool_definition_snapshots"
|
||||
(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 "ql3"."plugin_package_installs" (
|
||||
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 "ql3"."plugin_package_materialized_revisions" (
|
||||
project_id, package_name, generation, generation_digest,
|
||||
lock_digest, revision_digest
|
||||
)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_project_tool_definition_snapshot_source_identity_check CHECK (
|
||||
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' AND
|
||||
installation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
generation BETWEEN 1 AND 2147483647
|
||||
),
|
||||
CONSTRAINT ql3_project_tool_definition_snapshot_source_digest_check CHECK (
|
||||
active_vector_digest ~ '^[0-9a-f]{64}$' AND
|
||||
generation_digest ~ '^[0-9a-f]{64}$' AND
|
||||
lock_digest ~ '^[0-9a-f]{64}$' AND
|
||||
revision_digest ~ '^[0-9a-f]{64}$'
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_project_tool_definition_snapshot_source_generation_idx
|
||||
ON "ql3"."project_tool_definition_snapshot_sources"
|
||||
(generation_digest, project_id, package_name)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_project_tool_definition_snapshot_source_install_idx
|
||||
ON "ql3"."project_tool_definition_snapshot_sources"
|
||||
(installation_id, active_vector_digest)
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."project_tool_definition_snapshots",
|
||||
"ql3"."project_tool_definition_snapshot_sources"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."project_tool_definition_snapshots",
|
||||
"ql3"."project_tool_definition_snapshot_sources"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 26,
|
||||
migration_id = 'pg-0027-project-tool-definition-snapshots',
|
||||
capabilities = '${CAPABILITIES_V26}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 25
|
||||
AND migration_id = 'pg-0026-plugin-package-task-reconciliations'
|
||||
AND capabilities = '${CAPABILITIES_V25}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 25'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,299 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V26 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V27 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0028StepRunsMigration = definePostgresSqlMigration({
|
||||
id: 'pg-0028-step-runs',
|
||||
statements: [
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM "ql3"."run_attempts" WHERE step_run_id IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT 1 FROM "ql3"."run_events" WHERE step_run_id IS NOT NULL
|
||||
) THEN
|
||||
RAISE EXCEPTION
|
||||
'legacy step_run_id cannot be upgraded without StepRun authority'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
ALTER TABLE "ql3"."run_attempts"
|
||||
ALTER COLUMN step_run_id TYPE varchar(128)
|
||||
`.trim(),
|
||||
`
|
||||
ALTER TABLE "ql3"."run_events"
|
||||
ALTER COLUMN step_run_id TYPE varchar(128)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."step_runs" (
|
||||
id varchar(128) PRIMARY KEY,
|
||||
run_id varchar(36) NOT NULL,
|
||||
parent_step_run_id varchar(128),
|
||||
step_key varchar(128) NOT NULL,
|
||||
kind varchar(32) NOT NULL,
|
||||
definition_ref varchar(512) NOT NULL,
|
||||
definition_digest char(64) NOT NULL,
|
||||
required boolean NOT NULL,
|
||||
status varchar(32) NOT NULL,
|
||||
version integer NOT NULL,
|
||||
attempt_count integer NOT NULL,
|
||||
input_ref varchar(512),
|
||||
output_ref varchar(512),
|
||||
approval_request_id varchar(128),
|
||||
ready_at_ms bigint,
|
||||
started_at_ms bigint,
|
||||
finished_at_ms bigint,
|
||||
result_code varchar(64),
|
||||
error_summary varchar(2048),
|
||||
created_at_ms bigint NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
last_mutation_id varchar(128) NOT NULL,
|
||||
step_run_digest char(64) NOT NULL,
|
||||
step_run_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_step_runs_run_fk
|
||||
FOREIGN KEY (run_id) REFERENCES "ql3"."runs" (id) ON DELETE CASCADE,
|
||||
CONSTRAINT ql3_step_runs_identity_check CHECK (
|
||||
id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
(parent_step_run_id IS NULL OR
|
||||
(parent_step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
parent_step_run_id <> id)) AND
|
||||
step_key ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
octet_length(definition_ref) 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 (
|
||||
definition_digest ~ '^[0-9a-f]{64}$' AND
|
||||
step_run_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_step_runs_counter_check CHECK (
|
||||
version BETWEEN 1 AND 2147483647 AND
|
||||
attempt_count BETWEEN 0 AND 64
|
||||
),
|
||||
CONSTRAINT ql3_step_runs_reference_check CHECK (
|
||||
(input_ref IS NULL OR octet_length(input_ref) BETWEEN 1 AND 512) AND
|
||||
(output_ref IS NULL OR octet_length(output_ref) BETWEEN 1 AND 512) AND
|
||||
(approval_request_id IS NULL OR
|
||||
approval_request_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$')
|
||||
),
|
||||
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_approval_shape_check CHECK (
|
||||
status <> 'waiting_approval' OR approval_request_id IS NOT NULL
|
||||
),
|
||||
CONSTRAINT ql3_step_runs_result_shape_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 result_code ~ '^[a-z][a-z0-9_]{0,63}$') AND
|
||||
(error_summary IS NULL OR octet_length(error_summary) BETWEEN 1 AND 2048)
|
||||
),
|
||||
CONSTRAINT ql3_step_runs_mutation_identity_check CHECK (
|
||||
last_mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_step_runs_json_check CHECK (
|
||||
jsonb_typeof(step_run_json) = 'object' AND
|
||||
octet_length(step_run_json::text) BETWEEN 2 AND 16384 AND
|
||||
step_run_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/step-run@v1',
|
||||
'id', id,
|
||||
'runId', run_id,
|
||||
'parentStepRunId', parent_step_run_id,
|
||||
'stepKey', step_key,
|
||||
'kind', kind,
|
||||
'definitionRef', definition_ref,
|
||||
'definitionDigest', definition_digest,
|
||||
'required', required,
|
||||
'status', status,
|
||||
'version', version,
|
||||
'attemptCount', attempt_count,
|
||||
'inputRef', input_ref,
|
||||
'outputRef', output_ref,
|
||||
'approvalRequestId', approval_request_id,
|
||||
'readyAtMs', ready_at_ms,
|
||||
'startedAtMs', started_at_ms,
|
||||
'finishedAtMs', finished_at_ms,
|
||||
'resultCode', result_code,
|
||||
'errorSummary', error_summary,
|
||||
'createdAtMs', created_at_ms,
|
||||
'updatedAtMs', updated_at_ms,
|
||||
'lastMutationId', last_mutation_id,
|
||||
'stepRunDigest', step_run_digest
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_step_runs_run_id_uidx
|
||||
ON "ql3"."step_runs" (run_id, id)
|
||||
`.trim(),
|
||||
`
|
||||
ALTER TABLE "ql3"."step_runs"
|
||||
ADD CONSTRAINT ql3_step_runs_parent_fk
|
||||
FOREIGN KEY (run_id, parent_step_run_id)
|
||||
REFERENCES "ql3"."step_runs" (run_id, id)
|
||||
ON DELETE RESTRICT
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_step_runs_run_step_uidx
|
||||
ON "ql3"."step_runs" (run_id, step_key)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_step_runs_run_status_idx
|
||||
ON "ql3"."step_runs" (run_id, status, id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_step_runs_recovery_idx
|
||||
ON "ql3"."step_runs" (status, updated_at_ms, id)
|
||||
WHERE status IN ('waiting_approval', 'running', 'lost')
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."step_run_mutations" (
|
||||
mutation_id varchar(128) PRIMARY KEY,
|
||||
mutation_digest char(64) NOT NULL,
|
||||
run_id varchar(36) NOT NULL,
|
||||
step_run_id varchar(128) NOT NULL,
|
||||
step_run_digest char(64) NOT NULL,
|
||||
event_id varchar(36) NOT NULL,
|
||||
event_sequence integer NOT NULL,
|
||||
run_version integer NOT NULL,
|
||||
step_run_json jsonb NOT NULL,
|
||||
committed_at_ms bigint NOT NULL,
|
||||
CONSTRAINT ql3_step_run_mutations_step_fk
|
||||
FOREIGN KEY (run_id, step_run_id)
|
||||
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE CASCADE,
|
||||
CONSTRAINT ql3_step_run_mutations_event_fk
|
||||
FOREIGN KEY (event_id) REFERENCES "ql3"."run_events" (id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT ql3_step_run_mutations_identity_check CHECK (
|
||||
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_step_run_mutations_digest_check CHECK (
|
||||
mutation_digest ~ '^[0-9a-f]{64}$' AND
|
||||
step_run_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
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 (
|
||||
jsonb_typeof(step_run_json) = 'object' AND
|
||||
octet_length(step_run_json::text) BETWEEN 2 AND 16384 AND
|
||||
step_run_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/step-run@v1',
|
||||
'id', step_run_id,
|
||||
'runId', run_id,
|
||||
'lastMutationId', mutation_id,
|
||||
'stepRunDigest', step_run_digest
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_step_run_mutations_event_uidx
|
||||
ON "ql3"."step_run_mutations" (event_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_step_run_mutations_step_idx
|
||||
ON "ql3"."step_run_mutations"
|
||||
(run_id, step_run_id, event_sequence, mutation_id)
|
||||
`.trim(),
|
||||
`
|
||||
ALTER TABLE "ql3"."run_attempts"
|
||||
ADD CONSTRAINT ql3_run_attempts_step_run_fk
|
||||
FOREIGN KEY (run_id, step_run_id)
|
||||
REFERENCES "ql3"."step_runs" (run_id, id)
|
||||
ON DELETE RESTRICT
|
||||
`.trim(),
|
||||
`
|
||||
ALTER TABLE "ql3"."run_events"
|
||||
ADD CONSTRAINT ql3_run_events_step_run_fk
|
||||
FOREIGN KEY (run_id, step_run_id)
|
||||
REFERENCES "ql3"."step_runs" (run_id, id)
|
||||
ON DELETE RESTRICT
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."step_runs", "ql3"."step_run_mutations"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT, UPDATE
|
||||
ON "ql3"."step_runs"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."step_run_mutations"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 27,
|
||||
migration_id = 'pg-0028-step-runs',
|
||||
capabilities = '${CAPABILITIES_V27}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 26
|
||||
AND migration_id = 'pg-0027-project-tool-definition-snapshots'
|
||||
AND capabilities = '${CAPABILITIES_V26}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 26'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,213 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V27 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V28 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_evidence":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0029ToolExecutionEvidenceMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0029-tool-execution-evidence',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_execution_trace_anchors" (
|
||||
trace_id char(32) NOT NULL,
|
||||
span_id char(16) NOT NULL,
|
||||
parent_span_id char(16),
|
||||
project_id varchar(128) NOT NULL,
|
||||
run_id varchar(36) NOT NULL,
|
||||
step_run_id varchar(128) NOT NULL,
|
||||
invocation_plan_digest char(64) NOT NULL,
|
||||
binding_digest char(64) NOT NULL,
|
||||
adapter_digest char(64) NOT NULL,
|
||||
redaction_contract_digest char(64) NOT NULL,
|
||||
audit_contract_digest char(64) NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
trace_digest char(64) NOT NULL,
|
||||
trace_json jsonb 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 "ql3"."step_runs" (run_id, id) ON DELETE CASCADE,
|
||||
CONSTRAINT ql3_tool_execution_trace_identity_check CHECK (
|
||||
trace_id ~ '^[0-9a-f]{32}$' AND
|
||||
span_id ~ '^[0-9a-f]{16}$' AND
|
||||
(parent_span_id IS NULL OR (
|
||||
parent_span_id ~ '^[0-9a-f]{16}$' AND parent_span_id <> span_id
|
||||
)) AND
|
||||
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_tool_execution_trace_digest_check CHECK (
|
||||
invocation_plan_digest ~ '^[0-9a-f]{64}$' AND
|
||||
binding_digest ~ '^[0-9a-f]{64}$' AND
|
||||
adapter_digest ~ '^[0-9a-f]{64}$' AND
|
||||
redaction_contract_digest ~ '^[0-9a-f]{64}$' AND
|
||||
audit_contract_digest ~ '^[0-9a-f]{64}$' AND
|
||||
trace_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_tool_execution_trace_time_check CHECK (
|
||||
created_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_tool_execution_trace_json_check CHECK (
|
||||
jsonb_typeof(trace_json) = 'object' AND
|
||||
octet_length(trace_json::text) BETWEEN 2 AND 16384 AND
|
||||
trace_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/tool-execution-trace-anchor@v1',
|
||||
'traceId', trace_id,
|
||||
'spanId', span_id,
|
||||
'parentSpanId', parent_span_id,
|
||||
'projectId', project_id,
|
||||
'runId', run_id,
|
||||
'stepRunId', step_run_id,
|
||||
'invocationPlanDigest', invocation_plan_digest,
|
||||
'bindingDigest', binding_digest,
|
||||
'adapterDigest', adapter_digest,
|
||||
'redactionContractDigest', redaction_contract_digest,
|
||||
'auditContractDigest', audit_contract_digest,
|
||||
'createdAtMs', created_at_ms,
|
||||
'traceDigest', trace_digest
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_execution_trace_run_idx
|
||||
ON "ql3"."tool_execution_trace_anchors"
|
||||
(run_id, created_at_ms, trace_id, span_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_execution_trace_step_idx
|
||||
ON "ql3"."tool_execution_trace_anchors"
|
||||
(run_id, step_run_id, created_at_ms, trace_id, span_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_execution_audit_receipts" (
|
||||
event_id uuid PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
run_id varchar(36) NOT NULL,
|
||||
step_run_id varchar(128) NOT NULL,
|
||||
trace_id char(32) NOT NULL,
|
||||
span_id char(16) NOT NULL,
|
||||
trace_digest char(64) NOT NULL,
|
||||
invocation_plan_digest char(64) NOT NULL,
|
||||
binding_digest char(64) NOT NULL,
|
||||
audit_record_digest char(64) NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
receipt_digest char(64) NOT NULL,
|
||||
audit_json jsonb NOT NULL,
|
||||
receipt_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_tool_execution_audit_event_fk
|
||||
FOREIGN KEY (event_id)
|
||||
REFERENCES "ql3"."security_audit_events" (event_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_execution_audit_trace_fk
|
||||
FOREIGN KEY (trace_id, span_id)
|
||||
REFERENCES "ql3"."tool_execution_trace_anchors" (trace_id, span_id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT ql3_tool_execution_audit_step_fk
|
||||
FOREIGN KEY (run_id, step_run_id)
|
||||
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE CASCADE,
|
||||
CONSTRAINT ql3_tool_execution_audit_identity_check CHECK (
|
||||
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
trace_id ~ '^[0-9a-f]{32}$' AND
|
||||
span_id ~ '^[0-9a-f]{16}$'
|
||||
),
|
||||
CONSTRAINT ql3_tool_execution_audit_digest_check CHECK (
|
||||
trace_digest ~ '^[0-9a-f]{64}$' AND
|
||||
invocation_plan_digest ~ '^[0-9a-f]{64}$' AND
|
||||
binding_digest ~ '^[0-9a-f]{64}$' AND
|
||||
audit_record_digest ~ '^[0-9a-f]{64}$' AND
|
||||
receipt_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_tool_execution_audit_time_check CHECK (
|
||||
created_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_tool_execution_audit_json_check CHECK (
|
||||
jsonb_typeof(audit_json) = 'object' AND
|
||||
octet_length(audit_json::text) BETWEEN 2 AND 8192 AND
|
||||
audit_json @> jsonb_build_object(
|
||||
'eventId', event_id,
|
||||
'projectId', project_id,
|
||||
'operationId', 'tool.invoke.start',
|
||||
'outcome', 'allowed',
|
||||
'occurredAtMs', created_at_ms
|
||||
) AND
|
||||
jsonb_typeof(audit_json -> 'fence') = 'object'
|
||||
),
|
||||
CONSTRAINT ql3_tool_execution_audit_receipt_json_check CHECK (
|
||||
jsonb_typeof(receipt_json) = 'object' AND
|
||||
octet_length(receipt_json::text) BETWEEN 2 AND 16384 AND
|
||||
receipt_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/tool-execution-audit-receipt@v1',
|
||||
'eventId', event_id,
|
||||
'projectId', project_id,
|
||||
'runId', run_id,
|
||||
'stepRunId', step_run_id,
|
||||
'traceId', trace_id,
|
||||
'spanId', span_id,
|
||||
'traceDigest', trace_digest,
|
||||
'invocationPlanDigest', invocation_plan_digest,
|
||||
'bindingDigest', binding_digest,
|
||||
'auditRecordDigest', audit_record_digest,
|
||||
'createdAtMs', created_at_ms,
|
||||
'receiptDigest', receipt_digest
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_execution_audit_trace_uidx
|
||||
ON "ql3"."tool_execution_audit_receipts" (trace_id, span_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_execution_audit_run_idx
|
||||
ON "ql3"."tool_execution_audit_receipts"
|
||||
(run_id, created_at_ms, trace_id, span_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_execution_audit_step_idx
|
||||
ON "ql3"."tool_execution_audit_receipts"
|
||||
(run_id, step_run_id, created_at_ms, event_id)
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."tool_execution_trace_anchors",
|
||||
"ql3"."tool_execution_audit_receipts"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."tool_execution_trace_anchors",
|
||||
"ql3"."tool_execution_audit_receipts"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 28,
|
||||
migration_id = 'pg-0029-tool-execution-evidence',
|
||||
capabilities = '${CAPABILITIES_V28}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 27
|
||||
AND migration_id = 'pg-0028-step-runs'
|
||||
AND capabilities = '${CAPABILITIES_V27}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 27'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V28 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_evidence":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V29 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0030ToolExecutionStartBarriersMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0030-tool-execution-start-barriers',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_execution_start_barriers" (
|
||||
start_id varchar(128) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
run_id varchar(36) NOT NULL,
|
||||
step_run_id varchar(128) NOT NULL,
|
||||
started_step_run_version integer NOT NULL,
|
||||
step_run_mutation_id varchar(128) NOT NULL,
|
||||
run_event_id varchar(128) NOT NULL,
|
||||
trace_id char(32) NOT NULL,
|
||||
span_id char(16) NOT NULL,
|
||||
audit_event_id uuid NOT NULL,
|
||||
command_digest char(64) NOT NULL,
|
||||
barrier_digest char(64) NOT NULL,
|
||||
started_at_ms bigint NOT NULL,
|
||||
barrier_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_tool_start_step_fk
|
||||
FOREIGN KEY (run_id, step_run_id)
|
||||
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_start_mutation_fk
|
||||
FOREIGN KEY (step_run_mutation_id)
|
||||
REFERENCES "ql3"."step_run_mutations" (mutation_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_start_event_fk
|
||||
FOREIGN KEY (run_event_id)
|
||||
REFERENCES "ql3"."run_events" (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_start_trace_fk
|
||||
FOREIGN KEY (trace_id, span_id)
|
||||
REFERENCES "ql3"."tool_execution_trace_anchors" (trace_id, span_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_start_audit_fk
|
||||
FOREIGN KEY (audit_event_id)
|
||||
REFERENCES "ql3"."tool_execution_audit_receipts" (event_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_start_identity_check CHECK (
|
||||
start_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
step_run_mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
run_event_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
trace_id ~ '^[0-9a-f]{32}$' AND span_id ~ '^[0-9a-f]{16}$'
|
||||
),
|
||||
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 (
|
||||
command_digest ~ '^[0-9a-f]{64}$' AND
|
||||
barrier_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_tool_start_json_check CHECK (
|
||||
jsonb_typeof(barrier_json) = 'object' AND
|
||||
octet_length(barrier_json::text) BETWEEN 2 AND 16384 AND
|
||||
barrier_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/tool-execution-start-barrier@v1',
|
||||
'startId', start_id,
|
||||
'projectId', project_id,
|
||||
'runId', run_id,
|
||||
'stepRunId', step_run_id,
|
||||
'startedStepRunVersion', started_step_run_version,
|
||||
'stepRunMutationId', step_run_mutation_id,
|
||||
'runEventId', run_event_id,
|
||||
'traceId', trace_id,
|
||||
'spanId', span_id,
|
||||
'auditEventId', audit_event_id,
|
||||
'commandDigest', command_digest,
|
||||
'barrierDigest', barrier_digest,
|
||||
'startedAtMs', started_at_ms
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_start_step_version_uidx
|
||||
ON "ql3"."tool_execution_start_barriers"
|
||||
(run_id, step_run_id, started_step_run_version)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_start_mutation_uidx
|
||||
ON "ql3"."tool_execution_start_barriers" (step_run_mutation_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_start_event_uidx
|
||||
ON "ql3"."tool_execution_start_barriers" (run_event_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_start_trace_uidx
|
||||
ON "ql3"."tool_execution_start_barriers" (trace_id, span_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_start_audit_uidx
|
||||
ON "ql3"."tool_execution_start_barriers" (audit_event_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_start_run_time_idx
|
||||
ON "ql3"."tool_execution_start_barriers"
|
||||
(run_id, started_at_ms, start_id)
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."tool_execution_start_barriers"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."tool_execution_start_barriers"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 29,
|
||||
migration_id = 'pg-0030-tool-execution-start-barriers',
|
||||
capabilities = '${CAPABILITIES_V29}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 28
|
||||
AND migration_id = 'pg-0029-tool-execution-evidence'
|
||||
AND capabilities = '${CAPABILITIES_V28}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 28'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,167 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V29 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V30 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0031ToolInvocationArtifactsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0031-tool-invocation-artifacts',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_invocation_input_artifacts" (
|
||||
artifact_id varchar(128) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
action_ref varchar(255) NOT NULL,
|
||||
input_digest char(64) NOT NULL,
|
||||
invocation_action_digest char(64) NOT NULL,
|
||||
artifact_digest char(64) NOT NULL,
|
||||
key_id varchar(128) NOT NULL,
|
||||
algorithm varchar(32) NOT NULL,
|
||||
plaintext_bytes integer NOT NULL,
|
||||
sealed_at_ms bigint NOT NULL,
|
||||
artifact_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_tool_input_artifact_project_fk
|
||||
FOREIGN KEY (project_id)
|
||||
REFERENCES "ql3"."projects" (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_input_artifact_identity_check CHECK (
|
||||
artifact_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$' AND
|
||||
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
|
||||
algorithm = 'aes-256-gcm'
|
||||
),
|
||||
CONSTRAINT ql3_tool_input_artifact_digest_check CHECK (
|
||||
input_digest ~ '^[0-9a-f]{64}$' AND
|
||||
invocation_action_digest ~ '^[0-9a-f]{64}$' AND
|
||||
artifact_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
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 (
|
||||
jsonb_typeof(artifact_json) = 'object' AND
|
||||
octet_length(artifact_json::text) BETWEEN 2 AND 98304 AND
|
||||
artifact_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/tool-invocation-input-artifact@v1',
|
||||
'artifactId', artifact_id,
|
||||
'projectId', project_id,
|
||||
'actionRef', action_ref,
|
||||
'inputDigest', input_digest,
|
||||
'invocationActionDigest', invocation_action_digest,
|
||||
'artifactDigest', artifact_digest,
|
||||
'keyId', key_id,
|
||||
'algorithm', algorithm,
|
||||
'plaintextBytes', plaintext_bytes,
|
||||
'sealedAtMs', sealed_at_ms
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_input_artifact_action_uidx
|
||||
ON "ql3"."tool_invocation_input_artifacts" (project_id, action_ref)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_input_artifact_project_time_idx
|
||||
ON "ql3"."tool_invocation_input_artifacts"
|
||||
(project_id, sealed_at_ms, artifact_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_invocation_preview_artifacts" (
|
||||
artifact_id varchar(128) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
action_ref varchar(255) NOT NULL,
|
||||
action_digest char(64) NOT NULL,
|
||||
preview_digest char(64) NOT NULL,
|
||||
redaction_contract_digest char(64) NOT NULL,
|
||||
artifact_digest char(64) NOT NULL,
|
||||
byte_length integer NOT NULL,
|
||||
sealed_at_ms bigint NOT NULL,
|
||||
artifact_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_tool_preview_artifact_project_fk
|
||||
FOREIGN KEY (project_id)
|
||||
REFERENCES "ql3"."projects" (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_preview_artifact_identity_check CHECK (
|
||||
artifact_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$'
|
||||
),
|
||||
CONSTRAINT ql3_tool_preview_artifact_digest_check CHECK (
|
||||
action_digest ~ '^[0-9a-f]{64}$' AND
|
||||
preview_digest ~ '^[0-9a-f]{64}$' AND
|
||||
redaction_contract_digest ~ '^[0-9a-f]{64}$' AND
|
||||
artifact_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
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 (
|
||||
jsonb_typeof(artifact_json) = 'object' AND
|
||||
octet_length(artifact_json::text) BETWEEN 2 AND 16384 AND
|
||||
artifact_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/tool-invocation-preview-artifact@v1',
|
||||
'artifactId', artifact_id,
|
||||
'projectId', project_id,
|
||||
'actionRef', action_ref,
|
||||
'actionDigest', action_digest,
|
||||
'previewDigest', preview_digest,
|
||||
'redactionContractDigest', redaction_contract_digest,
|
||||
'artifactDigest', artifact_digest,
|
||||
'byteLength', byte_length,
|
||||
'sealedAtMs', sealed_at_ms
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_preview_artifact_action_uidx
|
||||
ON "ql3"."tool_invocation_preview_artifacts" (project_id, action_ref)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_preview_artifact_action_digest_uidx
|
||||
ON "ql3"."tool_invocation_preview_artifacts" (action_digest)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_preview_artifact_project_time_idx
|
||||
ON "ql3"."tool_invocation_preview_artifacts"
|
||||
(project_id, sealed_at_ms, artifact_id)
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."tool_invocation_input_artifacts",
|
||||
"ql3"."tool_invocation_preview_artifacts"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."tool_invocation_input_artifacts",
|
||||
"ql3"."tool_invocation_preview_artifacts"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 30,
|
||||
migration_id = 'pg-0031-tool-invocation-artifacts',
|
||||
capabilities = '${CAPABILITIES_V30}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 29
|
||||
AND migration_id = 'pg-0030-tool-execution-start-barriers'
|
||||
AND capabilities = '${CAPABILITIES_V29}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 29'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V30 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V31 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0032ToolExecutionArtifactBindingsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0032-tool-execution-artifact-bindings',
|
||||
statements: [
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_input_artifact_start_binding_uidx
|
||||
ON "ql3"."tool_invocation_input_artifacts" (
|
||||
artifact_id, artifact_digest, project_id, action_ref, input_digest
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_preview_artifact_start_binding_uidx
|
||||
ON "ql3"."tool_invocation_preview_artifacts" (
|
||||
artifact_id, artifact_digest, project_id, action_ref, action_digest,
|
||||
preview_digest, redaction_contract_digest
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_execution_start_artifact_bindings" (
|
||||
start_id varchar(128) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
action_ref varchar(255) NOT NULL,
|
||||
input_artifact_id varchar(128) NOT NULL,
|
||||
input_artifact_digest char(64) NOT NULL,
|
||||
input_digest char(64) NOT NULL,
|
||||
preview_artifact_id varchar(128) NOT NULL,
|
||||
preview_artifact_digest char(64) NOT NULL,
|
||||
action_digest char(64) NOT NULL,
|
||||
preview_digest char(64) NOT NULL,
|
||||
redaction_contract_digest char(64) NOT NULL,
|
||||
bound_at_ms bigint NOT NULL,
|
||||
CONSTRAINT ql3_tool_start_artifact_barrier_fk
|
||||
FOREIGN KEY (start_id)
|
||||
REFERENCES "ql3"."tool_execution_start_barriers" (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 "ql3"."tool_invocation_input_artifacts" (
|
||||
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 "ql3"."tool_invocation_preview_artifacts" (
|
||||
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 (
|
||||
start_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$' AND
|
||||
input_artifact_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
preview_artifact_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_tool_start_artifact_digest_check CHECK (
|
||||
input_artifact_digest ~ '^[0-9a-f]{64}$' AND
|
||||
input_digest ~ '^[0-9a-f]{64}$' AND
|
||||
preview_artifact_digest ~ '^[0-9a-f]{64}$' AND
|
||||
action_digest ~ '^[0-9a-f]{64}$' AND
|
||||
preview_digest ~ '^[0-9a-f]{64}$' AND
|
||||
redaction_contract_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_tool_start_artifact_time_check CHECK (bound_at_ms >= 0)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_start_artifact_input_idx
|
||||
ON "ql3"."tool_execution_start_artifact_bindings"
|
||||
(input_artifact_id, start_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_start_artifact_preview_idx
|
||||
ON "ql3"."tool_execution_start_artifact_bindings"
|
||||
(preview_artifact_id, start_id)
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."tool_execution_start_artifact_bindings"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."tool_execution_start_artifact_bindings"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 31,
|
||||
migration_id = 'pg-0032-tool-execution-artifact-bindings',
|
||||
capabilities = '${CAPABILITIES_V31}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 30
|
||||
AND migration_id = 'pg-0031-tool-invocation-artifacts'
|
||||
AND capabilities = '${CAPABILITIES_V30}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 30'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V31 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V32 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0033ToolExecutionCompletionsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0033-tool-execution-completions',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_execution_completions" (
|
||||
start_id varchar(128) PRIMARY KEY,
|
||||
artifact_id varchar(128) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
run_id varchar(128) NOT NULL,
|
||||
step_run_id varchar(128) NOT NULL,
|
||||
started_step_run_version integer NOT NULL,
|
||||
completed_step_run_version integer NOT NULL,
|
||||
barrier_digest char(64) NOT NULL,
|
||||
adapter_digest char(64) NOT NULL,
|
||||
output_digest char(64) NOT NULL,
|
||||
execution_result_digest char(64) NOT NULL,
|
||||
artifact_digest char(64) NOT NULL,
|
||||
key_id varchar(128) NOT NULL,
|
||||
algorithm varchar(32) NOT NULL,
|
||||
plaintext_bytes integer NOT NULL,
|
||||
step_run_mutation_id varchar(128) NOT NULL,
|
||||
step_run_mutation_digest char(64) NOT NULL,
|
||||
completed_step_run_digest char(64) NOT NULL,
|
||||
run_event_id varchar(128) NOT NULL,
|
||||
completed_at_ms bigint NOT NULL,
|
||||
completion_digest char(64) NOT NULL,
|
||||
artifact_json jsonb NOT NULL,
|
||||
completion_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_tool_completion_start_fk
|
||||
FOREIGN KEY (start_id)
|
||||
REFERENCES "ql3"."tool_execution_start_barriers" (start_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_completion_step_fk
|
||||
FOREIGN KEY (run_id, step_run_id)
|
||||
REFERENCES "ql3"."step_runs" (run_id, id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_completion_mutation_fk
|
||||
FOREIGN KEY (step_run_mutation_id)
|
||||
REFERENCES "ql3"."step_run_mutations" (mutation_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_completion_event_fk
|
||||
FOREIGN KEY (run_event_id)
|
||||
REFERENCES "ql3"."run_events" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_completion_identity_check CHECK (
|
||||
start_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
artifact_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
|
||||
step_run_mutation_id ~
|
||||
'^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
run_event_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
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 (
|
||||
barrier_digest ~ '^[0-9a-f]{64}$' AND
|
||||
adapter_digest ~ '^[0-9a-f]{64}$' AND
|
||||
output_digest ~ '^[0-9a-f]{64}$' AND
|
||||
execution_result_digest ~ '^[0-9a-f]{64}$' AND
|
||||
artifact_digest ~ '^[0-9a-f]{64}$' AND
|
||||
step_run_mutation_digest ~ '^[0-9a-f]{64}$' AND
|
||||
completed_step_run_digest ~ '^[0-9a-f]{64}$' AND
|
||||
completion_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_tool_completion_budget_check CHECK (
|
||||
algorithm = 'aes-256-gcm' AND
|
||||
plaintext_bytes BETWEEN 0 AND 262144 AND
|
||||
completed_at_ms >= 0 AND
|
||||
octet_length(artifact_json::text) BETWEEN 2 AND 393216 AND
|
||||
octet_length(completion_json::text) BETWEEN 2 AND 24576
|
||||
),
|
||||
CONSTRAINT ql3_tool_completion_json_check CHECK (
|
||||
jsonb_typeof(artifact_json) = 'object' AND
|
||||
artifact_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/tool-execution-result-artifact@v1',
|
||||
'artifactId', artifact_id,
|
||||
'projectId', project_id,
|
||||
'startId', start_id,
|
||||
'runId', run_id,
|
||||
'stepRunId', step_run_id,
|
||||
'barrierDigest', barrier_digest,
|
||||
'adapterDigest', adapter_digest,
|
||||
'outputDigest', output_digest,
|
||||
'executionResultDigest', execution_result_digest,
|
||||
'artifactDigest', artifact_digest,
|
||||
'keyId', key_id,
|
||||
'algorithm', algorithm,
|
||||
'plaintextBytes', plaintext_bytes,
|
||||
'sealedAtMs', completed_at_ms
|
||||
) AND
|
||||
jsonb_typeof(completion_json) = 'object' AND
|
||||
completion_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/tool-execution-completion@v1',
|
||||
'startId', start_id,
|
||||
'projectId', project_id,
|
||||
'runId', run_id,
|
||||
'stepRunId', step_run_id,
|
||||
'startedStepRunVersion', started_step_run_version,
|
||||
'completedStepRunVersion', completed_step_run_version,
|
||||
'barrierDigest', barrier_digest,
|
||||
'adapterDigest', adapter_digest,
|
||||
'resultArtifact', jsonb_build_object(
|
||||
'artifactId', artifact_id,
|
||||
'artifactDigest', artifact_digest,
|
||||
'outputDigest', output_digest,
|
||||
'executionResultDigest', execution_result_digest
|
||||
),
|
||||
'stepRunMutationId', step_run_mutation_id,
|
||||
'stepRunMutationDigest', step_run_mutation_digest,
|
||||
'completedStepRunDigest', completed_step_run_digest,
|
||||
'runEventId', run_event_id,
|
||||
'completedAtMs', completed_at_ms,
|
||||
'completionDigest', completion_digest
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_completion_artifact_uidx
|
||||
ON "ql3"."tool_execution_completions" (artifact_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_completion_mutation_uidx
|
||||
ON "ql3"."tool_execution_completions" (step_run_mutation_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_completion_event_uidx
|
||||
ON "ql3"."tool_execution_completions" (run_event_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_completion_step_version_uidx
|
||||
ON "ql3"."tool_execution_completions"
|
||||
(run_id, step_run_id, completed_step_run_version)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_completion_project_time_idx
|
||||
ON "ql3"."tool_execution_completions"
|
||||
(project_id, completed_at_ms, start_id)
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."tool_execution_completions"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."tool_execution_completions"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 32,
|
||||
migration_id = 'pg-0033-tool-execution-completions',
|
||||
capabilities = '${CAPABILITIES_V32}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 31
|
||||
AND migration_id = 'pg-0032-tool-execution-artifact-bindings'
|
||||
AND capabilities = '${CAPABILITIES_V31}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 31'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V32 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V33 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0034ToolExecutionFailureCompletionsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0034-tool-execution-failure-completions',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_execution_failure_completions" (
|
||||
start_id varchar(128) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
run_id varchar(128) NOT NULL,
|
||||
step_run_id varchar(128) NOT NULL,
|
||||
started_step_run_version integer NOT NULL,
|
||||
completed_step_run_version integer NOT NULL,
|
||||
barrier_digest char(64) NOT NULL,
|
||||
adapter_digest char(64) NOT NULL,
|
||||
outcome varchar(16) NOT NULL,
|
||||
result_code varchar(64) NOT NULL,
|
||||
error_summary varchar(128) NOT NULL,
|
||||
step_run_mutation_id varchar(128) NOT NULL,
|
||||
step_run_mutation_digest char(64) NOT NULL,
|
||||
completed_step_run_digest char(64) NOT NULL,
|
||||
run_event_id varchar(128) NOT NULL,
|
||||
completed_at_ms bigint NOT NULL,
|
||||
completion_digest char(64) NOT NULL,
|
||||
completion_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_tool_failure_completion_start_fk
|
||||
FOREIGN KEY (start_id)
|
||||
REFERENCES "ql3"."tool_execution_start_barriers" (start_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_failure_completion_step_fk
|
||||
FOREIGN KEY (run_id, step_run_id)
|
||||
REFERENCES "ql3"."step_runs" (run_id, id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_failure_completion_mutation_fk
|
||||
FOREIGN KEY (step_run_mutation_id)
|
||||
REFERENCES "ql3"."step_run_mutations" (mutation_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_failure_completion_event_fk
|
||||
FOREIGN KEY (run_event_id)
|
||||
REFERENCES "ql3"."run_events" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_failure_completion_identity_check CHECK (
|
||||
start_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
step_run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
step_run_mutation_id ~
|
||||
'^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
run_event_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
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 (
|
||||
barrier_digest ~ '^[0-9a-f]{64}$' AND
|
||||
adapter_digest ~ '^[0-9a-f]{64}$' AND
|
||||
step_run_mutation_digest ~ '^[0-9a-f]{64}$' AND
|
||||
completed_step_run_digest ~ '^[0-9a-f]{64}$' AND
|
||||
completion_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
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
|
||||
octet_length(completion_json::text) BETWEEN 2 AND 24576
|
||||
),
|
||||
CONSTRAINT ql3_tool_failure_completion_json_check CHECK (
|
||||
jsonb_typeof(completion_json) = 'object' AND
|
||||
completion_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/tool-execution-failure-completion@v1',
|
||||
'startId', start_id,
|
||||
'projectId', project_id,
|
||||
'runId', run_id,
|
||||
'stepRunId', step_run_id,
|
||||
'startedStepRunVersion', started_step_run_version,
|
||||
'completedStepRunVersion', completed_step_run_version,
|
||||
'barrierDigest', barrier_digest,
|
||||
'adapterDigest', adapter_digest,
|
||||
'outcome', outcome,
|
||||
'resultCode', result_code,
|
||||
'errorSummary', error_summary,
|
||||
'stepRunMutationId', step_run_mutation_id,
|
||||
'stepRunMutationDigest', step_run_mutation_digest,
|
||||
'completedStepRunDigest', completed_step_run_digest,
|
||||
'runEventId', run_event_id,
|
||||
'completedAtMs', completed_at_ms,
|
||||
'completionDigest', completion_digest
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_failure_completion_mutation_uidx
|
||||
ON "ql3"."tool_execution_failure_completions" (step_run_mutation_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_failure_completion_event_uidx
|
||||
ON "ql3"."tool_execution_failure_completions" (run_event_id)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_tool_failure_completion_step_version_uidx
|
||||
ON "ql3"."tool_execution_failure_completions"
|
||||
(run_id, step_run_id, completed_step_run_version)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_failure_completion_project_time_idx
|
||||
ON "ql3"."tool_execution_failure_completions"
|
||||
(project_id, completed_at_ms, start_id)
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."tool_execution_failure_completions"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."tool_execution_failure_completions"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 33,
|
||||
migration_id = 'pg-0034-tool-execution-failure-completions',
|
||||
capabilities = '${CAPABILITIES_V33}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 32
|
||||
AND migration_id = 'pg-0033-tool-execution-completions'
|
||||
AND capabilities = '${CAPABILITIES_V32}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 32'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,195 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V33 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V34 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0035ToolResultKeyCatalogMigration = definePostgresSqlMigration({
|
||||
id: 'pg-0035-tool-result-key-catalog',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_result_key_catalog_generations" (
|
||||
authority varchar(64) NOT NULL,
|
||||
generation integer NOT NULL,
|
||||
previous_generation integer,
|
||||
previous_catalog_digest char(64),
|
||||
active_key_id varchar(128),
|
||||
mutation_kind varchar(16) NOT NULL,
|
||||
mutation_id varchar(128) NOT NULL,
|
||||
catalog_digest char(64) NOT NULL,
|
||||
command_digest char(64) NOT NULL,
|
||||
committed_at_ms bigint NOT NULL,
|
||||
catalog_json jsonb NOT NULL,
|
||||
PRIMARY KEY (authority, generation),
|
||||
CONSTRAINT ql3_tool_result_key_catalog_generation_digest_key
|
||||
UNIQUE (authority, generation, catalog_digest),
|
||||
CONSTRAINT ql3_tool_result_key_catalog_mutation_key
|
||||
UNIQUE (mutation_id),
|
||||
CONSTRAINT ql3_tool_result_key_catalog_digest_key
|
||||
UNIQUE (catalog_digest),
|
||||
CONSTRAINT ql3_tool_result_key_catalog_previous_fk
|
||||
FOREIGN KEY (
|
||||
authority, previous_generation, previous_catalog_digest
|
||||
)
|
||||
REFERENCES "ql3"."tool_result_key_catalog_generations" (
|
||||
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
|
||||
active_key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_tool_result_key_catalog_digest_check CHECK (
|
||||
(
|
||||
previous_catalog_digest IS NULL OR
|
||||
previous_catalog_digest ~ '^[0-9a-f]{64}$'
|
||||
) AND
|
||||
catalog_digest ~ '^[0-9a-f]{64}$' AND
|
||||
command_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_tool_result_key_catalog_budget_check CHECK (
|
||||
committed_at_ms >= 0 AND
|
||||
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
octet_length(catalog_json::text) BETWEEN 2 AND 65536
|
||||
),
|
||||
CONSTRAINT ql3_tool_result_key_catalog_json_check CHECK (
|
||||
jsonb_typeof(catalog_json) = 'object' AND
|
||||
catalog_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/tool-result-key-catalog@v1',
|
||||
'generation', generation,
|
||||
'previousCatalogDigest', previous_catalog_digest,
|
||||
'activeKeyId', active_key_id,
|
||||
'mutationKind', mutation_kind,
|
||||
'mutationId', mutation_id,
|
||||
'catalogDigest', catalog_digest,
|
||||
'committedAtMs', committed_at_ms
|
||||
) AND
|
||||
jsonb_typeof(catalog_json -> 'keys') = 'array' AND
|
||||
jsonb_array_length(catalog_json -> 'keys') BETWEEN 1 AND 64
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_result_key_catalog_current_idx
|
||||
ON "ql3"."tool_result_key_catalog_generations"
|
||||
(authority, generation DESC)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_execution_result_key_bindings" (
|
||||
start_id varchar(128) PRIMARY KEY,
|
||||
artifact_id varchar(128) NOT NULL,
|
||||
artifact_digest char(64) NOT NULL,
|
||||
catalog_authority varchar(64) NOT NULL,
|
||||
catalog_generation integer NOT NULL,
|
||||
catalog_digest char(64) NOT NULL,
|
||||
key_id varchar(128) NOT NULL,
|
||||
material_proof char(64) NOT NULL,
|
||||
binding_digest char(64) NOT NULL,
|
||||
CONSTRAINT ql3_tool_result_key_binding_artifact_key
|
||||
UNIQUE (artifact_id),
|
||||
CONSTRAINT ql3_tool_result_key_binding_digest_key
|
||||
UNIQUE (binding_digest),
|
||||
CONSTRAINT ql3_tool_result_key_binding_completion_fk
|
||||
FOREIGN KEY (start_id)
|
||||
REFERENCES "ql3"."tool_execution_completions" (start_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_result_key_binding_artifact_fk
|
||||
FOREIGN KEY (artifact_id)
|
||||
REFERENCES "ql3"."tool_execution_completions" (artifact_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_tool_result_key_binding_catalog_fk
|
||||
FOREIGN KEY (
|
||||
catalog_authority, catalog_generation, catalog_digest
|
||||
)
|
||||
REFERENCES "ql3"."tool_result_key_catalog_generations" (
|
||||
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 (
|
||||
start_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
artifact_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
|
||||
catalog_generation BETWEEN 1 AND 2147483647
|
||||
),
|
||||
CONSTRAINT ql3_tool_result_key_binding_digest_check CHECK (
|
||||
artifact_digest ~ '^[0-9a-f]{64}$' AND
|
||||
catalog_digest ~ '^[0-9a-f]{64}$' AND
|
||||
material_proof ~ '^[0-9a-f]{64}$' AND
|
||||
binding_digest ~ '^[0-9a-f]{64}$'
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_tool_result_key_binding_catalog_idx
|
||||
ON "ql3"."tool_execution_result_key_bindings"
|
||||
(catalog_generation, key_id, start_id)
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."tool_result_key_catalog_generations",
|
||||
"ql3"."tool_execution_result_key_bindings"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT
|
||||
ON "ql3"."tool_result_key_catalog_generations"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."tool_result_key_catalog_generations"
|
||||
TO ql3_admin
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."tool_execution_result_key_bindings"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 34,
|
||||
migration_id = 'pg-0035-tool-result-key-catalog',
|
||||
capabilities = '${CAPABILITIES_V34}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 33
|
||||
AND migration_id = 'pg-0034-tool-execution-failure-completions'
|
||||
AND capabilities = '${CAPABILITIES_V33}'::jsonb;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 33'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,234 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V34 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V35 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0036ToolResultRekeyOverlaysMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0036-tool-result-rekey-overlays',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_execution_result_rekey_overlays" (
|
||||
overlay_id varchar(128) PRIMARY KEY,
|
||||
artifact_id varchar(128) NOT NULL,
|
||||
source_binding_digest char(64) NOT NULL,
|
||||
revision integer NOT NULL,
|
||||
previous_overlay_digest char(64),
|
||||
from_key_id varchar(128) NOT NULL,
|
||||
target_catalog_authority varchar(64) NOT NULL,
|
||||
target_catalog_generation integer NOT NULL,
|
||||
target_catalog_digest char(64) NOT NULL,
|
||||
target_key_id varchar(128) NOT NULL,
|
||||
target_material_proof char(64) NOT NULL,
|
||||
mutation_id varchar(128) NOT NULL,
|
||||
command_digest char(64) NOT NULL,
|
||||
overlay_digest char(64) NOT NULL,
|
||||
rekeyed_at_ms bigint NOT NULL,
|
||||
overlay_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_result_rekey_digest_key UNIQUE (overlay_digest),
|
||||
CONSTRAINT ql3_result_rekey_artifact_fk FOREIGN KEY (artifact_id)
|
||||
REFERENCES "ql3"."tool_execution_result_key_bindings" (artifact_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_result_rekey_binding_fk FOREIGN KEY (source_binding_digest)
|
||||
REFERENCES "ql3"."tool_execution_result_key_bindings" (binding_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_result_rekey_previous_fk FOREIGN KEY (previous_overlay_digest)
|
||||
REFERENCES "ql3"."tool_execution_result_rekey_overlays" (overlay_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_result_rekey_catalog_fk FOREIGN KEY (
|
||||
target_catalog_authority, target_catalog_generation,
|
||||
target_catalog_digest
|
||||
) REFERENCES "ql3"."tool_result_key_catalog_generations" (
|
||||
authority, generation, catalog_digest
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_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_result_rekey_authority_check CHECK (
|
||||
target_catalog_authority = 'trusted-tool-results'
|
||||
),
|
||||
CONSTRAINT ql3_result_rekey_identity_check CHECK (
|
||||
overlay_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
artifact_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
from_key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
|
||||
target_key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
|
||||
from_key_id <> target_key_id AND
|
||||
target_catalog_generation BETWEEN 1 AND 2147483647
|
||||
),
|
||||
CONSTRAINT ql3_result_rekey_digest_check CHECK (
|
||||
(previous_overlay_digest IS NULL OR
|
||||
previous_overlay_digest ~ '^[0-9a-f]{64}$') AND
|
||||
source_binding_digest ~ '^[0-9a-f]{64}$' AND
|
||||
target_catalog_digest ~ '^[0-9a-f]{64}$' AND
|
||||
target_material_proof ~ '^[0-9a-f]{64}$' AND
|
||||
command_digest ~ '^[0-9a-f]{64}$' AND
|
||||
overlay_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_result_rekey_budget_check CHECK (
|
||||
rekeyed_at_ms >= 0 AND
|
||||
octet_length(overlay_json::text) BETWEEN 2 AND 393216
|
||||
),
|
||||
CONSTRAINT ql3_result_rekey_json_check CHECK (
|
||||
jsonb_typeof(overlay_json) = 'object' AND
|
||||
overlay_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/tool-execution-result-rekey-overlay@v1',
|
||||
'overlayId', overlay_id,
|
||||
'sourceBindingDigest', source_binding_digest,
|
||||
'revision', revision,
|
||||
'previousOverlayDigest', previous_overlay_digest,
|
||||
'fromKeyId', from_key_id,
|
||||
'rekeyedAtMs', rekeyed_at_ms,
|
||||
'overlayDigest', overlay_digest
|
||||
) AND
|
||||
overlay_json -> 'sourceArtifact' ->> 'artifactId' = artifact_id AND
|
||||
overlay_json -> 'targetCatalogFence' @> jsonb_build_object(
|
||||
'generation', target_catalog_generation,
|
||||
'catalogDigest', target_catalog_digest,
|
||||
'keyId', target_key_id,
|
||||
'materialProof', target_material_proof
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_result_rekey_mutation_key ON "ql3"."tool_execution_result_rekey_overlays" (mutation_id)`,
|
||||
`CREATE UNIQUE INDEX ql3_result_rekey_artifact_revision_key ON "ql3"."tool_execution_result_rekey_overlays" (artifact_id, revision)`,
|
||||
`CREATE UNIQUE INDEX ql3_result_rekey_artifact_revision_digest_key ON "ql3"."tool_execution_result_rekey_overlays" (artifact_id, revision, overlay_digest)`,
|
||||
`CREATE INDEX ql3_result_rekey_artifact_idx ON "ql3"."tool_execution_result_rekey_overlays" (artifact_id, revision DESC)`,
|
||||
`CREATE INDEX ql3_result_rekey_target_idx ON "ql3"."tool_execution_result_rekey_overlays" (target_key_id, artifact_id, revision DESC)`,
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_execution_result_rekey_heads" (
|
||||
artifact_id varchar(128) PRIMARY KEY,
|
||||
revision integer NOT NULL,
|
||||
overlay_id varchar(128) NOT NULL,
|
||||
overlay_digest char(64) NOT NULL,
|
||||
target_catalog_generation integer NOT NULL,
|
||||
target_catalog_digest char(64) NOT NULL,
|
||||
target_key_id varchar(128) NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT ql3_result_rekey_head_overlay_fk FOREIGN KEY (
|
||||
artifact_id, revision, overlay_digest
|
||||
) REFERENCES "ql3"."tool_execution_result_rekey_overlays" (
|
||||
artifact_id, revision, overlay_digest
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_result_rekey_head_identity_check CHECK (
|
||||
revision BETWEEN 1 AND 2147483647 AND
|
||||
artifact_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
overlay_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
target_key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
|
||||
target_catalog_generation BETWEEN 1 AND 2147483647 AND
|
||||
updated_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_result_rekey_head_digest_check CHECK (
|
||||
overlay_digest ~ '^[0-9a-f]{64}$' AND
|
||||
target_catalog_digest ~ '^[0-9a-f]{64}$'
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_result_rekey_head_overlay_key ON "ql3"."tool_execution_result_rekey_heads" (overlay_id)`,
|
||||
`CREATE UNIQUE INDEX ql3_result_rekey_head_digest_key ON "ql3"."tool_execution_result_rekey_heads" (overlay_digest)`,
|
||||
`CREATE INDEX ql3_result_rekey_head_target_idx ON "ql3"."tool_execution_result_rekey_heads" (target_key_id, artifact_id)`,
|
||||
`
|
||||
CREATE TABLE "ql3"."tool_result_key_retirement_receipts" (
|
||||
receipt_digest char(64) PRIMARY KEY,
|
||||
catalog_authority varchar(64) NOT NULL,
|
||||
catalog_generation integer NOT NULL,
|
||||
catalog_digest char(64) NOT NULL,
|
||||
key_id varchar(128) NOT NULL,
|
||||
material_proof char(64) NOT NULL,
|
||||
mutation_id varchar(128) NOT NULL,
|
||||
command_digest char(64) 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 char(64) NOT NULL,
|
||||
created_at_ms bigint NOT NULL,
|
||||
receipt_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_result_retirement_catalog_fk FOREIGN KEY (
|
||||
catalog_authority, catalog_generation, catalog_digest
|
||||
) REFERENCES "ql3"."tool_result_key_catalog_generations" (
|
||||
authority, generation, catalog_digest
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_result_retirement_authority_check CHECK (
|
||||
catalog_authority = 'trusted-tool-results'
|
||||
),
|
||||
CONSTRAINT ql3_result_retirement_identity_check CHECK (
|
||||
catalog_generation BETWEEN 1 AND 2147483647 AND
|
||||
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' AND
|
||||
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_result_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_result_retirement_digest_check CHECK (
|
||||
receipt_digest ~ '^[0-9a-f]{64}$' AND
|
||||
catalog_digest ~ '^[0-9a-f]{64}$' AND
|
||||
material_proof ~ '^[0-9a-f]{64}$' AND
|
||||
command_digest ~ '^[0-9a-f]{64}$' AND
|
||||
coverage_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_result_retirement_json_check CHECK (
|
||||
jsonb_typeof(receipt_json) = 'object' AND
|
||||
receipt_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/tool-result-key-retirement-receipt@v1',
|
||||
'catalogGeneration', catalog_generation,
|
||||
'catalogDigest', catalog_digest,
|
||||
'keyId', key_id,
|
||||
'materialProof', material_proof,
|
||||
'mutationId', mutation_id,
|
||||
'bindingCount', binding_count,
|
||||
'overlayHeadCount', overlay_head_count,
|
||||
'uncoveredBindingCount', 0,
|
||||
'uncoveredOverlayHeadCount', 0,
|
||||
'coverageDigest', coverage_digest,
|
||||
'createdAtMs', created_at_ms,
|
||||
'receiptDigest', receipt_digest
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_result_retirement_mutation_key ON "ql3"."tool_result_key_retirement_receipts" (mutation_id)`,
|
||||
`CREATE INDEX ql3_result_retirement_catalog_idx ON "ql3"."tool_result_key_retirement_receipts" (catalog_generation, key_id)`,
|
||||
`
|
||||
REVOKE ALL ON
|
||||
"ql3"."tool_execution_result_rekey_overlays",
|
||||
"ql3"."tool_execution_result_rekey_heads",
|
||||
"ql3"."tool_result_key_retirement_receipts"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`GRANT SELECT ON "ql3"."tool_execution_result_rekey_overlays", "ql3"."tool_execution_result_rekey_heads" TO ql3_runtime`,
|
||||
`GRANT SELECT, INSERT ON "ql3"."tool_execution_result_rekey_overlays", "ql3"."tool_result_key_retirement_receipts" TO ql3_admin`,
|
||||
`GRANT SELECT, INSERT, UPDATE ON "ql3"."tool_execution_result_rekey_heads" TO ql3_admin`,
|
||||
`GRANT SELECT ON "ql3"."tool_execution_completions", "ql3"."tool_execution_result_key_bindings" TO ql3_admin`,
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 35,
|
||||
migration_id = 'pg-0036-tool-result-rekey-overlays',
|
||||
capabilities = '${CAPABILITIES_V35}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 34
|
||||
AND migration_id = 'pg-0035-tool-result-key-catalog'
|
||||
AND capabilities = '${CAPABILITIES_V34}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 34'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,779 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V35 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V36 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0037PluginPackageQuarantineMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0037-plugin-package-quarantine',
|
||||
statements: [
|
||||
`
|
||||
ALTER TABLE "ql3"."plugin_package_installs"
|
||||
ADD CONSTRAINT ql3_plugin_package_installs_quarantine_target_key
|
||||
UNIQUE (
|
||||
project_id, package_name, installation_id, lock_digest, record_digest
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
ALTER TABLE "ql3"."project_tool_definition_snapshots"
|
||||
ADD CONSTRAINT ql3_project_tool_snapshot_withdrawal_key
|
||||
UNIQUE (project_id, active_vector_digest, snapshot_digest)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_quarantine_events" (
|
||||
event_digest char(64) PRIMARY KEY,
|
||||
mutation_id varchar(128) NOT NULL,
|
||||
revocation_receipt_digest char(64) NOT NULL,
|
||||
impact_digest char(64) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
installation_id varchar(128) NOT NULL,
|
||||
lock_digest char(64) NOT NULL,
|
||||
install_state varchar(16) NOT NULL,
|
||||
install_version integer NOT NULL,
|
||||
install_record_digest char(64) NOT NULL,
|
||||
active_lock_digest char(64),
|
||||
proposer_type varchar(16) NOT NULL,
|
||||
proposer_id varchar(255) NOT NULL,
|
||||
confirmer_type varchar(16) NOT NULL,
|
||||
confirmer_id varchar(255) NOT NULL,
|
||||
authorization_mode varchar(16) NOT NULL,
|
||||
reason_code varchar(32) NOT NULL,
|
||||
occurred_at_ms bigint NOT NULL,
|
||||
event_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_quarantine_install_fk FOREIGN KEY (
|
||||
project_id, package_name, installation_id, lock_digest,
|
||||
install_record_digest
|
||||
) REFERENCES "ql3"."plugin_package_installs" (
|
||||
project_id, package_name, installation_id, lock_digest, record_digest
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_quarantine_identity_check CHECK (
|
||||
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' AND
|
||||
installation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' 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
|
||||
octet_length(proposer_id) BETWEEN 1 AND 255 AND
|
||||
octet_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 (
|
||||
event_digest ~ '^[0-9a-f]{64}$' AND
|
||||
revocation_receipt_digest ~ '^[0-9a-f]{64}$' AND
|
||||
impact_digest ~ '^[0-9a-f]{64}$' AND
|
||||
lock_digest ~ '^[0-9a-f]{64}$' AND
|
||||
install_record_digest ~ '^[0-9a-f]{64}$' AND
|
||||
(active_lock_digest IS NULL OR
|
||||
active_lock_digest ~ '^[0-9a-f]{64}$')
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_quarantine_json_check CHECK (
|
||||
jsonb_typeof(event_json) = 'object' AND
|
||||
octet_length(event_json::text) BETWEEN 2 AND 262144 AND
|
||||
event_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-quarantine-event@v1',
|
||||
'mutationId', mutation_id,
|
||||
'revocationReceiptDigest', revocation_receipt_digest,
|
||||
'impactDigest', impact_digest,
|
||||
'authorizationMode', authorization_mode,
|
||||
'reasonCode', reason_code,
|
||||
'occurredAtMs', occurred_at_ms,
|
||||
'eventDigest', event_digest
|
||||
) AND
|
||||
event_json -> 'target' @> jsonb_build_object(
|
||||
'projectId', project_id,
|
||||
'packageName', package_name,
|
||||
'installationId', installation_id,
|
||||
'lockDigest', lock_digest,
|
||||
'installState', install_state,
|
||||
'installVersion', install_version,
|
||||
'installRecordDigest', install_record_digest,
|
||||
'activeLockDigest', active_lock_digest
|
||||
) AND
|
||||
event_json -> 'proposer' @> jsonb_build_object(
|
||||
'type', proposer_type, 'id', proposer_id
|
||||
) AND
|
||||
event_json -> 'confirmer' @> jsonb_build_object(
|
||||
'type', confirmer_type, 'id', confirmer_id
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_quarantine_time_check CHECK (
|
||||
occurred_at_ms >= 0
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_quarantine_mutation_key ON "ql3"."plugin_package_quarantine_events" (mutation_id)`,
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_quarantine_target_key ON "ql3"."plugin_package_quarantine_events" (project_id, package_name, installation_id, lock_digest)`,
|
||||
`CREATE INDEX ql3_plugin_package_quarantine_lock_idx ON "ql3"."plugin_package_quarantine_events" (lock_digest, project_id, package_name)`,
|
||||
`CREATE INDEX ql3_plugin_package_quarantine_project_idx ON "ql3"."plugin_package_quarantine_events" (project_id, package_name, occurred_at_ms, event_digest)`,
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_withdrawal_receipts" (
|
||||
event_digest char(64) PRIMARY KEY,
|
||||
receipt_digest char(64) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
capability_status varchar(16) NOT NULL,
|
||||
task_count integer NOT NULL,
|
||||
previous_active_vector_digest char(64),
|
||||
current_active_vector_digest char(64),
|
||||
current_tool_snapshot_digest char(64),
|
||||
retained_source_count integer NOT NULL,
|
||||
committed_at_ms bigint NOT NULL,
|
||||
receipt_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_withdrawal_event_fk FOREIGN KEY (
|
||||
event_digest
|
||||
) REFERENCES "ql3"."plugin_package_quarantine_events" (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 "ql3"."project_tool_definition_snapshots" (
|
||||
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 (
|
||||
event_digest ~ '^[0-9a-f]{64}$' AND
|
||||
receipt_digest ~ '^[0-9a-f]{64}$' AND
|
||||
(previous_active_vector_digest IS NULL OR
|
||||
previous_active_vector_digest ~ '^[0-9a-f]{64}$') AND
|
||||
(current_active_vector_digest IS NULL OR
|
||||
current_active_vector_digest ~ '^[0-9a-f]{64}$') AND
|
||||
(current_tool_snapshot_digest IS NULL OR
|
||||
current_tool_snapshot_digest ~ '^[0-9a-f]{64}$')
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_withdrawal_json_check CHECK (
|
||||
jsonb_typeof(receipt_json) = 'object' AND
|
||||
octet_length(receipt_json::text) BETWEEN 2 AND 8388608 AND
|
||||
receipt_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-withdrawal-receipt@v1',
|
||||
'eventDigest', event_digest,
|
||||
'committedAtMs', committed_at_ms,
|
||||
'receiptDigest', receipt_digest
|
||||
) AND
|
||||
receipt_json -> 'target' ->> 'projectId' = project_id AND
|
||||
receipt_json -> 'capability' @> jsonb_build_object(
|
||||
'status', capability_status,
|
||||
'previousActiveVectorDigest', previous_active_vector_digest,
|
||||
'currentActiveVectorDigest', current_active_vector_digest,
|
||||
'currentToolSnapshotDigest', current_tool_snapshot_digest,
|
||||
'retainedSourceCount', retained_source_count
|
||||
) AND
|
||||
jsonb_array_length(
|
||||
receipt_json -> 'capability' -> 'taskWithdrawals'
|
||||
) = task_count
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_withdrawal_time_check CHECK (
|
||||
committed_at_ms >= 0
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_withdrawal_receipt_key ON "ql3"."plugin_package_withdrawal_receipts" (receipt_digest)`,
|
||||
`CREATE INDEX ql3_plugin_package_withdrawal_snapshot_idx ON "ql3"."plugin_package_withdrawal_receipts" (current_tool_snapshot_digest, event_digest)`,
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_withdrawal_tasks" (
|
||||
event_digest char(64) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
task_id varchar(128) NOT NULL,
|
||||
previous_revision integer NOT NULL,
|
||||
disabled_revision integer NOT NULL,
|
||||
previous_content_digest char(64) NOT NULL,
|
||||
disabled_content_digest char(64) NOT NULL,
|
||||
PRIMARY KEY (event_digest, task_id),
|
||||
CONSTRAINT ql3_plugin_package_withdrawal_task_receipt_fk FOREIGN KEY (
|
||||
event_digest
|
||||
) REFERENCES "ql3"."plugin_package_withdrawal_receipts" (event_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_withdrawal_task_previous_fk FOREIGN KEY (
|
||||
project_id, task_id, previous_revision
|
||||
) REFERENCES "ql3"."task_definition_revisions" (
|
||||
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 "ql3"."task_definition_revisions" (
|
||||
project_id, task_id, revision
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_withdrawal_task_identity_check CHECK (
|
||||
task_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
previous_revision BETWEEN 1 AND 2147483646 AND
|
||||
disabled_revision = previous_revision + 1
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_withdrawal_task_digest_check CHECK (
|
||||
event_digest ~ '^[0-9a-f]{64}$' AND
|
||||
previous_content_digest ~ '^[0-9a-f]{64}$' AND
|
||||
disabled_content_digest ~ '^[0-9a-f]{64}$'
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE INDEX ql3_plugin_package_withdrawal_task_idx ON "ql3"."plugin_package_withdrawal_tasks" (project_id, task_id, event_digest)`,
|
||||
`
|
||||
CREATE FUNCTION "ql3"."plugin_package_run_start_allowed"(
|
||||
p_project_id varchar,
|
||||
p_task_id varchar,
|
||||
p_task_revision varchar
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
DECLARE
|
||||
project_exists boolean;
|
||||
BEGIN
|
||||
IF NOT pg_has_role(session_user, 'ql3_runtime', 'member') THEN
|
||||
RAISE EXCEPTION 'Runtime authority is required'
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
|
||||
SELECT true INTO project_exists
|
||||
FROM "ql3"."projects"
|
||||
WHERE id = p_project_id
|
||||
FOR SHARE;
|
||||
|
||||
IF NOT COALESCE(project_exists, false) THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
RETURN NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_quarantine_events" AS quarantine
|
||||
JOIN "ql3"."plugin_package_task_reconciliations" AS reconciliation
|
||||
ON reconciliation.project_id = quarantine.project_id
|
||||
AND reconciliation.package_name = quarantine.package_name
|
||||
AND reconciliation.lock_digest = quarantine.lock_digest
|
||||
JOIN "ql3"."plugin_package_task_reconciliation_items" AS item
|
||||
ON item.generation_digest = reconciliation.generation_digest
|
||||
AND item.task_id = p_task_id
|
||||
AND 'qltd:v1:' || item.revision || ':' || item.content_digest =
|
||||
p_task_revision
|
||||
WHERE quarantine.project_id = p_project_id
|
||||
);
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
CREATE FUNCTION "ql3"."plugin_package_tool_start_allowed"(
|
||||
p_project_id varchar,
|
||||
p_definition_ref varchar,
|
||||
p_definition_digest char(64)
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
DECLARE
|
||||
project_exists boolean;
|
||||
BEGIN
|
||||
IF NOT pg_has_role(session_user, 'ql3_runtime', 'member') THEN
|
||||
RAISE EXCEPTION 'Runtime authority is required'
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
|
||||
SELECT true INTO project_exists
|
||||
FROM "ql3"."projects"
|
||||
WHERE id = p_project_id
|
||||
FOR SHARE;
|
||||
|
||||
IF NOT COALESCE(project_exists, false) THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
RETURN NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_quarantine_events" AS quarantine
|
||||
JOIN "ql3"."project_tool_definition_snapshot_sources" AS source
|
||||
ON source.project_id = quarantine.project_id
|
||||
AND source.package_name = quarantine.package_name
|
||||
AND source.installation_id = quarantine.installation_id
|
||||
AND source.lock_digest = quarantine.lock_digest
|
||||
JOIN "ql3"."project_tool_definition_snapshots" AS snapshot
|
||||
ON snapshot.project_id = source.project_id
|
||||
AND snapshot.active_vector_digest = source.active_vector_digest
|
||||
CROSS JOIN LATERAL jsonb_array_elements(
|
||||
snapshot.snapshot_json -> 'definitions'
|
||||
) AS definition(item)
|
||||
WHERE quarantine.project_id = p_project_id
|
||||
AND definition.item ->> 'packageName' = quarantine.package_name
|
||||
AND 'tool:' ||
|
||||
(definition.item -> 'definition' ->> 'name') || '@' ||
|
||||
(definition.item -> 'definition' ->> 'version') = p_definition_ref
|
||||
AND definition.item ->> 'definitionDigest' = p_definition_digest
|
||||
);
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
CREATE FUNCTION "ql3"."commit_plugin_package_quarantine"(
|
||||
p_event jsonb,
|
||||
p_receipt jsonb,
|
||||
p_task_writes jsonb,
|
||||
p_snapshot jsonb
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
DECLARE
|
||||
event_digest_value char(64);
|
||||
project_id_value varchar(128);
|
||||
package_name_value varchar(63);
|
||||
installation_id_value varchar(128);
|
||||
lock_digest_value char(64);
|
||||
install_record "ql3"."plugin_package_installs"%ROWTYPE;
|
||||
existing_event jsonb;
|
||||
existing_receipt jsonb;
|
||||
task_write jsonb;
|
||||
previous_task "ql3"."task_definition_revisions"%ROWTYPE;
|
||||
disabled_task jsonb;
|
||||
withdrawal jsonb;
|
||||
enabled_task_count integer;
|
||||
source_count integer;
|
||||
inserted_snapshot integer;
|
||||
BEGIN
|
||||
IF NOT pg_has_role(session_user, 'ql3_package_executor', 'member') THEN
|
||||
RAISE EXCEPTION 'Package executor authority is required'
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
IF jsonb_typeof(p_event) <> 'object'
|
||||
OR jsonb_typeof(p_receipt) <> 'object'
|
||||
OR jsonb_typeof(p_task_writes) <> 'array'
|
||||
OR jsonb_array_length(p_task_writes) > 128
|
||||
OR (p_snapshot <> 'null'::jsonb AND
|
||||
jsonb_typeof(p_snapshot) <> 'object') THEN
|
||||
RAISE EXCEPTION 'invalid Package quarantine input'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
event_digest_value := p_event ->> 'eventDigest';
|
||||
project_id_value := p_event -> 'target' ->> 'projectId';
|
||||
package_name_value := p_event -> 'target' ->> 'packageName';
|
||||
installation_id_value := p_event -> 'target' ->> 'installationId';
|
||||
lock_digest_value := p_event -> 'target' ->> 'lockDigest';
|
||||
|
||||
IF p_receipt ->> 'eventDigest' IS DISTINCT FROM event_digest_value
|
||||
OR p_receipt -> 'target' IS DISTINCT FROM p_event -> 'target'
|
||||
OR (p_receipt ->> 'committedAtMs')::bigint <
|
||||
(p_event ->> 'occurredAtMs')::bigint THEN
|
||||
RAISE EXCEPTION 'Package quarantine receipt does not bind the event'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
PERFORM 1 FROM "ql3"."projects"
|
||||
WHERE id = project_id_value
|
||||
FOR UPDATE;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'Package quarantine Project is absent'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
SELECT event_json INTO existing_event
|
||||
FROM "ql3"."plugin_package_quarantine_events"
|
||||
WHERE event_digest = event_digest_value;
|
||||
IF FOUND THEN
|
||||
SELECT receipt_json INTO existing_receipt
|
||||
FROM "ql3"."plugin_package_withdrawal_receipts"
|
||||
WHERE event_digest = event_digest_value;
|
||||
IF NOT FOUND
|
||||
OR existing_event IS DISTINCT FROM p_event
|
||||
OR existing_receipt IS DISTINCT FROM p_receipt THEN
|
||||
RAISE EXCEPTION 'Package quarantine replay conflicts'
|
||||
USING ERRCODE = 'unique_violation';
|
||||
END IF;
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
SELECT * INTO install_record
|
||||
FROM "ql3"."plugin_package_installs"
|
||||
WHERE installation_id = installation_id_value
|
||||
FOR UPDATE;
|
||||
IF NOT FOUND
|
||||
OR install_record.project_id <> project_id_value
|
||||
OR install_record.package_name <> package_name_value
|
||||
OR install_record.lock_digest <> lock_digest_value
|
||||
OR install_record.state <> p_event -> 'target' ->> 'installState'
|
||||
OR install_record.version <>
|
||||
(p_event -> 'target' ->> 'installVersion')::integer
|
||||
OR install_record.record_digest <>
|
||||
p_event -> 'target' ->> 'installRecordDigest'
|
||||
OR install_record.active_lock_digest IS DISTINCT FROM
|
||||
p_event -> 'target' ->> 'activeLockDigest' THEN
|
||||
RAISE EXCEPTION 'Package quarantine target drifted'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
IF install_record.state = 'active' AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_install_heads" AS head
|
||||
JOIN "ql3"."plugin_package_installs" AS head_install
|
||||
ON head_install.installation_id = head.installation_id
|
||||
JOIN "ql3"."plugin_package_materialized_revisions" AS revision
|
||||
ON revision.project_id = install_record.project_id
|
||||
AND revision.package_name = install_record.package_name
|
||||
AND revision.generation = install_record.target_generation
|
||||
AND revision.lock_digest = install_record.lock_digest
|
||||
WHERE head.project_id = install_record.project_id
|
||||
AND head.package_name = install_record.package_name
|
||||
AND head.installation_id = install_record.installation_id
|
||||
AND head_install.active_lock_digest = install_record.lock_digest
|
||||
) THEN
|
||||
RAISE EXCEPTION 'active Package quarantine target is not materialized'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
INSERT INTO "ql3"."plugin_package_quarantine_events" (
|
||||
event_digest, mutation_id, revocation_receipt_digest, impact_digest,
|
||||
project_id, package_name, installation_id, lock_digest,
|
||||
install_state, install_version, install_record_digest,
|
||||
active_lock_digest, proposer_type, proposer_id, confirmer_type,
|
||||
confirmer_id, authorization_mode, reason_code, occurred_at_ms, event_json
|
||||
) VALUES (
|
||||
event_digest_value,
|
||||
p_event ->> 'mutationId',
|
||||
p_event ->> 'revocationReceiptDigest',
|
||||
p_event ->> 'impactDigest',
|
||||
project_id_value,
|
||||
package_name_value,
|
||||
installation_id_value,
|
||||
lock_digest_value,
|
||||
p_event -> 'target' ->> 'installState',
|
||||
(p_event -> 'target' ->> 'installVersion')::integer,
|
||||
p_event -> 'target' ->> 'installRecordDigest',
|
||||
p_event -> 'target' ->> 'activeLockDigest',
|
||||
p_event -> 'proposer' ->> 'type',
|
||||
p_event -> 'proposer' ->> 'id',
|
||||
p_event -> 'confirmer' ->> 'type',
|
||||
p_event -> 'confirmer' ->> 'id',
|
||||
p_event ->> 'authorizationMode',
|
||||
p_event ->> 'reasonCode',
|
||||
(p_event ->> 'occurredAtMs')::bigint,
|
||||
p_event
|
||||
);
|
||||
|
||||
IF install_record.state <> 'active' THEN
|
||||
IF p_snapshot <> 'null'::jsonb OR jsonb_array_length(p_task_writes) <> 0
|
||||
OR p_receipt -> 'capability' ->> 'status' <> 'not_active' THEN
|
||||
RAISE EXCEPTION 'inactive Package quarantine disposition is invalid'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
ELSE
|
||||
IF p_snapshot = 'null'::jsonb
|
||||
OR p_receipt -> 'capability' ->> 'status' <> 'withdrawn' THEN
|
||||
RAISE EXCEPTION 'active Package quarantine disposition is invalid'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
SELECT count(*)::integer INTO source_count
|
||||
FROM "ql3"."plugin_package_install_heads" AS head
|
||||
JOIN "ql3"."plugin_package_installs" AS head_install
|
||||
ON head_install.installation_id = head.installation_id
|
||||
JOIN "ql3"."plugin_package_installs" AS active_install
|
||||
ON active_install.project_id = head.project_id
|
||||
AND active_install.package_name = head.package_name
|
||||
AND active_install.lock_digest = head_install.active_lock_digest
|
||||
JOIN "ql3"."plugin_package_materialized_revisions" AS revision
|
||||
ON revision.project_id = active_install.project_id
|
||||
AND revision.package_name = active_install.package_name
|
||||
AND revision.generation = active_install.target_generation
|
||||
AND revision.lock_digest = active_install.lock_digest
|
||||
LEFT JOIN "ql3"."plugin_package_quarantine_events" AS quarantine
|
||||
ON quarantine.project_id = active_install.project_id
|
||||
AND quarantine.package_name = active_install.package_name
|
||||
AND quarantine.installation_id = active_install.installation_id
|
||||
AND quarantine.lock_digest = active_install.lock_digest
|
||||
WHERE head.project_id = project_id_value
|
||||
AND head_install.active_lock_digest IS NOT NULL
|
||||
AND active_install.state = 'active'
|
||||
AND quarantine.event_digest IS NULL;
|
||||
IF source_count > 128
|
||||
OR source_count <>
|
||||
jsonb_array_length(p_snapshot -> 'sources')
|
||||
OR source_count <>
|
||||
(p_receipt -> 'capability' ->> 'retainedSourceCount')::integer
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_install_heads" AS head
|
||||
JOIN "ql3"."plugin_package_installs" AS head_install
|
||||
ON head_install.installation_id = head.installation_id
|
||||
JOIN "ql3"."plugin_package_installs" AS active_install
|
||||
ON active_install.project_id = head.project_id
|
||||
AND active_install.package_name = head.package_name
|
||||
AND active_install.lock_digest = head_install.active_lock_digest
|
||||
JOIN "ql3"."plugin_package_materialized_revisions" AS revision
|
||||
ON revision.project_id = active_install.project_id
|
||||
AND revision.package_name = active_install.package_name
|
||||
AND revision.generation = active_install.target_generation
|
||||
AND revision.lock_digest = active_install.lock_digest
|
||||
LEFT JOIN "ql3"."plugin_package_quarantine_events" AS quarantine
|
||||
ON quarantine.project_id = active_install.project_id
|
||||
AND quarantine.package_name = active_install.package_name
|
||||
AND quarantine.installation_id = active_install.installation_id
|
||||
AND quarantine.lock_digest = active_install.lock_digest
|
||||
WHERE head.project_id = project_id_value
|
||||
AND head_install.active_lock_digest IS NOT NULL
|
||||
AND active_install.state = 'active'
|
||||
AND quarantine.event_digest IS NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM jsonb_array_elements(
|
||||
p_snapshot -> 'sources'
|
||||
) AS source
|
||||
WHERE source ->> 'packageName' = active_install.package_name
|
||||
AND source ->> 'installationId' =
|
||||
active_install.installation_id
|
||||
AND (source ->> 'generation')::integer =
|
||||
active_install.target_generation
|
||||
AND source ->> 'generationDigest' =
|
||||
revision.generation_digest
|
||||
AND source ->> 'lockDigest' = active_install.lock_digest
|
||||
AND source ->> 'revisionDigest' =
|
||||
revision.revision_digest
|
||||
)
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Package quarantine Tool source vector drifted'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
SELECT count(*)::integer INTO enabled_task_count
|
||||
FROM "ql3"."plugin_package_task_ownerships" AS ownership
|
||||
JOIN "ql3"."task_definitions" AS head
|
||||
ON head.project_id = ownership.project_id
|
||||
AND head.task_id = ownership.task_id
|
||||
JOIN "ql3"."task_definition_revisions" AS revision
|
||||
ON revision.project_id = head.project_id
|
||||
AND revision.task_id = head.task_id
|
||||
AND revision.revision = head.current_revision
|
||||
WHERE ownership.project_id = project_id_value
|
||||
AND ownership.package_name = package_name_value
|
||||
AND revision.enabled = true;
|
||||
IF enabled_task_count > 128
|
||||
OR enabled_task_count <> jsonb_array_length(p_task_writes)
|
||||
OR enabled_task_count <>
|
||||
jsonb_array_length(
|
||||
p_receipt -> 'capability' -> 'taskWithdrawals'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Package quarantine Task set drifted'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
FOR task_write IN SELECT value FROM jsonb_array_elements(p_task_writes)
|
||||
LOOP
|
||||
disabled_task := task_write -> 'disabled';
|
||||
withdrawal := task_write -> 'withdrawal';
|
||||
SELECT revision.* INTO previous_task
|
||||
FROM "ql3"."plugin_package_task_ownerships" AS ownership
|
||||
JOIN "ql3"."task_definitions" AS head
|
||||
ON head.project_id = ownership.project_id
|
||||
AND head.task_id = ownership.task_id
|
||||
JOIN "ql3"."task_definition_revisions" AS revision
|
||||
ON revision.project_id = head.project_id
|
||||
AND revision.task_id = head.task_id
|
||||
AND revision.revision = head.current_revision
|
||||
WHERE ownership.project_id = project_id_value
|
||||
AND ownership.package_name = package_name_value
|
||||
AND ownership.task_id = withdrawal ->> 'taskId'
|
||||
AND revision.enabled = true
|
||||
FOR UPDATE OF head;
|
||||
IF NOT FOUND
|
||||
OR previous_task.revision <>
|
||||
(withdrawal ->> 'previousRevision')::integer
|
||||
OR previous_task.content_digest <>
|
||||
withdrawal ->> 'previousContentDigest'
|
||||
OR disabled_task ->> 'projectId' <> project_id_value
|
||||
OR disabled_task ->> 'taskId' <> withdrawal ->> 'taskId'
|
||||
OR (disabled_task ->> 'revision')::integer <>
|
||||
previous_task.revision + 1
|
||||
OR (disabled_task ->> 'enabled')::boolean <> false
|
||||
OR disabled_task ->> 'contentDigest' <>
|
||||
withdrawal ->> 'disabledContentDigest' THEN
|
||||
RAISE EXCEPTION 'Package quarantine Task write drifted'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
|
||||
INSERT INTO "ql3"."task_definition_revisions" (
|
||||
project_id, task_id, revision, mutation_id, name, description,
|
||||
kind, spec_json, labels_json, enabled, content_digest, created_at_ms
|
||||
) VALUES (
|
||||
project_id_value,
|
||||
disabled_task ->> 'taskId',
|
||||
(disabled_task ->> 'revision')::integer,
|
||||
(disabled_task ->> 'mutationId')::uuid,
|
||||
disabled_task ->> 'name',
|
||||
disabled_task ->> 'description',
|
||||
disabled_task ->> 'kind',
|
||||
disabled_task -> 'spec',
|
||||
disabled_task -> 'labels',
|
||||
false,
|
||||
disabled_task ->> 'contentDigest',
|
||||
(disabled_task ->> 'updatedAtMs')::bigint
|
||||
);
|
||||
UPDATE "ql3"."task_definitions"
|
||||
SET current_revision = (disabled_task ->> 'revision')::integer,
|
||||
updated_at_ms = (disabled_task ->> 'updatedAtMs')::bigint
|
||||
WHERE project_id = project_id_value
|
||||
AND task_id = disabled_task ->> 'taskId'
|
||||
AND current_revision = previous_task.revision;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'Package quarantine Task head drifted'
|
||||
USING ERRCODE = 'serialization_failure';
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
INSERT INTO "ql3"."project_tool_definition_snapshots" (
|
||||
project_id, active_vector_digest, definitions_digest,
|
||||
snapshot_digest, snapshot_json, committed_at_ms
|
||||
) VALUES (
|
||||
project_id_value,
|
||||
p_snapshot ->> 'activeVectorDigest',
|
||||
p_snapshot ->> 'definitionsDigest',
|
||||
p_snapshot ->> 'snapshotDigest',
|
||||
p_snapshot,
|
||||
(p_receipt ->> 'committedAtMs')::bigint
|
||||
)
|
||||
ON CONFLICT (project_id, active_vector_digest) DO NOTHING;
|
||||
GET DIAGNOSTICS inserted_snapshot = ROW_COUNT;
|
||||
IF inserted_snapshot = 1 THEN
|
||||
INSERT INTO "ql3"."project_tool_definition_snapshot_sources" (
|
||||
project_id, active_vector_digest, package_name, installation_id,
|
||||
generation, generation_digest, lock_digest, revision_digest
|
||||
)
|
||||
SELECT
|
||||
project_id_value,
|
||||
p_snapshot ->> 'activeVectorDigest',
|
||||
source ->> 'packageName',
|
||||
source ->> 'installationId',
|
||||
(source ->> 'generation')::integer,
|
||||
source ->> 'generationDigest',
|
||||
source ->> 'lockDigest',
|
||||
source ->> 'revisionDigest'
|
||||
FROM jsonb_array_elements(p_snapshot -> 'sources') AS source;
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."project_tool_definition_snapshots"
|
||||
WHERE project_id = project_id_value
|
||||
AND active_vector_digest = p_snapshot ->> 'activeVectorDigest'
|
||||
AND snapshot_json = p_snapshot
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Package quarantine snapshot conflicts'
|
||||
USING ERRCODE = 'unique_violation';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
INSERT INTO "ql3"."plugin_package_withdrawal_receipts" (
|
||||
event_digest, receipt_digest, project_id, capability_status,
|
||||
task_count, previous_active_vector_digest,
|
||||
current_active_vector_digest, current_tool_snapshot_digest,
|
||||
retained_source_count, committed_at_ms, receipt_json
|
||||
) VALUES (
|
||||
event_digest_value,
|
||||
p_receipt ->> 'receiptDigest',
|
||||
project_id_value,
|
||||
p_receipt -> 'capability' ->> 'status',
|
||||
jsonb_array_length(
|
||||
p_receipt -> 'capability' -> 'taskWithdrawals'
|
||||
),
|
||||
p_receipt -> 'capability' ->> 'previousActiveVectorDigest',
|
||||
p_receipt -> 'capability' ->> 'currentActiveVectorDigest',
|
||||
p_receipt -> 'capability' ->> 'currentToolSnapshotDigest',
|
||||
(p_receipt -> 'capability' ->> 'retainedSourceCount')::integer,
|
||||
(p_receipt ->> 'committedAtMs')::bigint,
|
||||
p_receipt
|
||||
);
|
||||
|
||||
INSERT INTO "ql3"."plugin_package_withdrawal_tasks" (
|
||||
event_digest, project_id, task_id, previous_revision,
|
||||
disabled_revision, previous_content_digest, disabled_content_digest
|
||||
)
|
||||
SELECT
|
||||
event_digest_value,
|
||||
project_id_value,
|
||||
receipt_withdrawal ->> 'taskId',
|
||||
(receipt_withdrawal ->> 'previousRevision')::integer,
|
||||
(receipt_withdrawal ->> 'disabledRevision')::integer,
|
||||
receipt_withdrawal ->> 'previousContentDigest',
|
||||
receipt_withdrawal ->> 'disabledContentDigest'
|
||||
FROM jsonb_array_elements(
|
||||
p_receipt -> 'capability' -> 'taskWithdrawals'
|
||||
) AS receipt_withdrawal;
|
||||
|
||||
RETURN true;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL ON
|
||||
"ql3"."plugin_package_quarantine_events",
|
||||
"ql3"."plugin_package_withdrawal_receipts",
|
||||
"ql3"."plugin_package_withdrawal_tasks"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL ON FUNCTION
|
||||
"ql3"."plugin_package_run_start_allowed"(varchar, varchar, varchar),
|
||||
"ql3"."plugin_package_tool_start_allowed"(varchar, varchar, char(64)),
|
||||
"ql3"."commit_plugin_package_quarantine"(jsonb, jsonb, jsonb, jsonb)
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`GRANT SELECT ON "ql3"."plugin_package_quarantine_events", "ql3"."plugin_package_withdrawal_receipts", "ql3"."plugin_package_withdrawal_tasks" TO ql3_package_executor`,
|
||||
`GRANT EXECUTE ON FUNCTION "ql3"."plugin_package_run_start_allowed"(varchar, varchar, varchar), "ql3"."plugin_package_tool_start_allowed"(varchar, varchar, char(64)) TO ql3_runtime`,
|
||||
`GRANT EXECUTE ON FUNCTION "ql3"."commit_plugin_package_quarantine"(jsonb, jsonb, jsonb, jsonb) TO ql3_package_executor`,
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 36,
|
||||
migration_id = 'pg-0037-plugin-package-quarantine',
|
||||
capabilities = '${CAPABILITIES_V36}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 35
|
||||
AND migration_id = 'pg-0036-tool-result-rekey-overlays'
|
||||
AND capabilities = '${CAPABILITIES_V35}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 35'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V36 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V37 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0038PluginPackagePublisherProvenanceMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0038-plugin-package-publisher-provenance',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_publisher_provenance" (
|
||||
installation_id varchar(128) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
lock_digest char(64) NOT NULL,
|
||||
artifact_digest char(64) NOT NULL,
|
||||
manifest_digest char(64) NOT NULL,
|
||||
content_digest char(64) NOT NULL,
|
||||
stage_evidence_digest char(64) NOT NULL,
|
||||
publisher varchar(253) NOT NULL,
|
||||
key_id varchar(128) NOT NULL,
|
||||
signature_digest char(64) NOT NULL,
|
||||
key_not_before_ms bigint NOT NULL,
|
||||
key_not_after_ms bigint NOT NULL,
|
||||
verified_at_ms bigint NOT NULL,
|
||||
provenance_digest char(64) NOT NULL,
|
||||
provenance_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_publisher_provenance_install_fk
|
||||
FOREIGN KEY (installation_id)
|
||||
REFERENCES "ql3"."plugin_package_installs" (installation_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_publisher_provenance_identity_check CHECK (
|
||||
installation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' AND
|
||||
publisher ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$' AND
|
||||
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_provenance_digest_check CHECK (
|
||||
lock_digest ~ '^[0-9a-f]{64}$' AND
|
||||
artifact_digest ~ '^[0-9a-f]{64}$' AND
|
||||
manifest_digest ~ '^[0-9a-f]{64}$' AND
|
||||
content_digest ~ '^[0-9a-f]{64}$' AND
|
||||
stage_evidence_digest ~ '^[0-9a-f]{64}$' AND
|
||||
signature_digest ~ '^[0-9a-f]{64}$' AND
|
||||
provenance_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_provenance_time_check CHECK (
|
||||
key_not_before_ms >= 0 AND
|
||||
key_not_after_ms > key_not_before_ms AND
|
||||
verified_at_ms >= key_not_before_ms AND
|
||||
verified_at_ms < key_not_after_ms
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_provenance_json_check CHECK (
|
||||
jsonb_typeof(provenance_json) = 'object' AND
|
||||
octet_length(provenance_json::text) BETWEEN 2 AND 262144 AND
|
||||
provenance_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-publisher-provenance@v1',
|
||||
'projectId', project_id,
|
||||
'packageName', package_name,
|
||||
'installationId', installation_id,
|
||||
'lockDigest', lock_digest,
|
||||
'artifactDigest', artifact_digest,
|
||||
'manifestDigest', manifest_digest,
|
||||
'contentDigest', content_digest,
|
||||
'stageEvidenceDigest', stage_evidence_digest,
|
||||
'publisher', publisher,
|
||||
'keyId', key_id,
|
||||
'signatureDigest', signature_digest,
|
||||
'keyNotBeforeMs', key_not_before_ms,
|
||||
'keyNotAfterMs', key_not_after_ms,
|
||||
'verifiedAtMs', verified_at_ms,
|
||||
'provenanceDigest', provenance_digest
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_publisher_provenance_digest_key ON "ql3"."plugin_package_publisher_provenance" (provenance_digest)`,
|
||||
`CREATE INDEX ql3_plugin_package_publisher_provenance_signer_idx ON "ql3"."plugin_package_publisher_provenance" (publisher, key_id, project_id, package_name, installation_id)`,
|
||||
`CREATE INDEX ql3_plugin_package_publisher_provenance_lock_idx ON "ql3"."plugin_package_publisher_provenance" (lock_digest, installation_id)`,
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_publisher_revocation_receipts" (
|
||||
receipt_digest char(64) PRIMARY KEY,
|
||||
mutation_id varchar(128) NOT NULL,
|
||||
publisher varchar(253) NOT NULL,
|
||||
key_id varchar(128) NOT NULL,
|
||||
previous_trust_digest char(64) NOT NULL,
|
||||
current_trust_digest char(64) NOT NULL,
|
||||
proposer_type varchar(16) NOT NULL,
|
||||
proposer_id varchar(255) NOT NULL,
|
||||
confirmer_type varchar(16) NOT NULL,
|
||||
confirmer_id varchar(255) NOT NULL,
|
||||
authorization_mode varchar(16) NOT NULL,
|
||||
reason_code varchar(32) NOT NULL,
|
||||
revoked_at_ms bigint NOT NULL,
|
||||
receipt_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_publisher_revocation_identity_check CHECK (
|
||||
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
publisher ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$' AND
|
||||
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
proposer_type IN (
|
||||
'user', 'api_app', 'mcp_client', 'agent', 'system', 'worker'
|
||||
) AND
|
||||
confirmer_type IN (
|
||||
'user', 'api_app', 'mcp_client', 'agent', 'system', 'worker'
|
||||
) AND
|
||||
octet_length(proposer_id) BETWEEN 1 AND 255 AND
|
||||
octet_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_publisher_revocation_digest_check CHECK (
|
||||
receipt_digest ~ '^[0-9a-f]{64}$' AND
|
||||
previous_trust_digest ~ '^[0-9a-f]{64}$' AND
|
||||
current_trust_digest ~ '^[0-9a-f]{64}$' AND
|
||||
previous_trust_digest <> current_trust_digest
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_revocation_time_check CHECK (
|
||||
revoked_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_revocation_json_check CHECK (
|
||||
jsonb_typeof(receipt_json) = 'object' AND
|
||||
octet_length(receipt_json::text) BETWEEN 2 AND 262144 AND
|
||||
receipt_json @> jsonb_build_object(
|
||||
'schema',
|
||||
'qinglong/plugin-package-publisher-key-revocation-receipt@v1',
|
||||
'mutationId', mutation_id,
|
||||
'publisher', publisher,
|
||||
'keyId', key_id,
|
||||
'previousTrustDigest', previous_trust_digest,
|
||||
'currentTrustDigest', current_trust_digest,
|
||||
'authorizationMode', authorization_mode,
|
||||
'reasonCode', reason_code,
|
||||
'revokedAtMs', revoked_at_ms,
|
||||
'receiptDigest', receipt_digest
|
||||
) AND
|
||||
receipt_json -> 'proposer' @> jsonb_build_object(
|
||||
'type', proposer_type, 'id', proposer_id
|
||||
) AND
|
||||
receipt_json -> 'confirmer' @> jsonb_build_object(
|
||||
'type', confirmer_type, 'id', confirmer_id
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_publisher_revocation_mutation_key ON "ql3"."plugin_package_publisher_revocation_receipts" (mutation_id)`,
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_publisher_revocation_signer_key ON "ql3"."plugin_package_publisher_revocation_receipts" (publisher, key_id)`,
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_publisher_revocation_impacts" (
|
||||
revocation_receipt_digest char(64) PRIMARY KEY,
|
||||
impact_digest char(64) NOT NULL,
|
||||
item_count integer NOT NULL,
|
||||
generated_at_ms bigint NOT NULL,
|
||||
impact_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_publisher_impact_receipt_fk
|
||||
FOREIGN KEY (revocation_receipt_digest)
|
||||
REFERENCES "ql3"."plugin_package_publisher_revocation_receipts"
|
||||
(receipt_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_publisher_impact_digest_check CHECK (
|
||||
revocation_receipt_digest ~ '^[0-9a-f]{64}$' AND
|
||||
impact_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_impact_count_check CHECK (
|
||||
item_count BETWEEN 0 AND 4096 AND generated_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_impact_json_check CHECK (
|
||||
jsonb_typeof(impact_json) = 'object' AND
|
||||
octet_length(impact_json::text) BETWEEN 2 AND 8388608 AND
|
||||
impact_json @> jsonb_build_object(
|
||||
'schema',
|
||||
'qinglong/plugin-package-publisher-key-revocation-impact@v1',
|
||||
'revocationReceiptDigest', revocation_receipt_digest,
|
||||
'generatedAtMs', generated_at_ms,
|
||||
'impactDigest', impact_digest
|
||||
) AND
|
||||
jsonb_array_length(impact_json -> 'items') = item_count
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_publisher_impact_digest_key ON "ql3"."plugin_package_publisher_revocation_impacts" (impact_digest)`,
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_publisher_revocation_impact_items" (
|
||||
impact_digest char(64) NOT NULL,
|
||||
provenance_digest char(64) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
installation_id varchar(128) NOT NULL,
|
||||
lock_digest char(64) NOT NULL,
|
||||
PRIMARY KEY (impact_digest, provenance_digest),
|
||||
CONSTRAINT ql3_plugin_package_publisher_impact_item_impact_fk
|
||||
FOREIGN KEY (impact_digest)
|
||||
REFERENCES "ql3"."plugin_package_publisher_revocation_impacts"
|
||||
(impact_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_publisher_impact_item_provenance_fk
|
||||
FOREIGN KEY (provenance_digest)
|
||||
REFERENCES "ql3"."plugin_package_publisher_provenance"
|
||||
(provenance_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_publisher_impact_item_install_fk
|
||||
FOREIGN KEY (installation_id)
|
||||
REFERENCES "ql3"."plugin_package_installs" (installation_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_publisher_impact_item_identity_check CHECK (
|
||||
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' AND
|
||||
installation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
impact_digest ~ '^[0-9a-f]{64}$' AND
|
||||
provenance_digest ~ '^[0-9a-f]{64}$' AND
|
||||
lock_digest ~ '^[0-9a-f]{64}$'
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_publisher_impact_item_install_key ON "ql3"."plugin_package_publisher_revocation_impact_items" (impact_digest, installation_id)`,
|
||||
`CREATE INDEX ql3_plugin_package_publisher_impact_item_target_idx ON "ql3"."plugin_package_publisher_revocation_impact_items" (project_id, package_name, installation_id, lock_digest)`,
|
||||
`
|
||||
CREATE FUNCTION "ql3"."enforce_plugin_package_stage_provenance"()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
BEGIN
|
||||
IF OLD.state = 'queued' AND NEW.state = 'staged' AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_publisher_provenance" AS provenance
|
||||
WHERE provenance.installation_id = NEW.installation_id
|
||||
AND provenance.project_id = NEW.project_id
|
||||
AND provenance.package_name = NEW.package_name
|
||||
AND provenance.lock_digest = NEW.lock_digest
|
||||
AND provenance.artifact_digest =
|
||||
NEW.lock_json -> 'source' ->> 'artifactDigest'
|
||||
AND provenance.manifest_digest = NEW.lock_json ->> 'manifestDigest'
|
||||
AND provenance.content_digest =
|
||||
NEW.lock_json -> 'source' ->> 'contentDigest'
|
||||
AND provenance.stage_evidence_digest =
|
||||
NEW.record_json -> 'stageReceipt' ->> 'evidenceDigest'
|
||||
) THEN
|
||||
RAISE EXCEPTION
|
||||
'Plugin Package stage requires exact publisher provenance'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TRIGGER ql3_plugin_package_stage_provenance_guard
|
||||
BEFORE UPDATE ON "ql3"."plugin_package_installs"
|
||||
FOR EACH ROW EXECUTE FUNCTION
|
||||
"ql3"."enforce_plugin_package_stage_provenance"()
|
||||
`.trim(),
|
||||
`
|
||||
CREATE OR REPLACE FUNCTION "ql3"."plugin_package_run_start_allowed"(
|
||||
p_project_id varchar,
|
||||
p_task_id varchar,
|
||||
p_task_revision varchar
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
DECLARE
|
||||
project_exists boolean;
|
||||
BEGIN
|
||||
IF NOT pg_has_role(session_user, 'ql3_runtime', 'member') THEN
|
||||
RAISE EXCEPTION 'Runtime authority is required'
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
|
||||
SELECT true INTO project_exists
|
||||
FROM "ql3"."projects"
|
||||
WHERE id = p_project_id
|
||||
FOR SHARE;
|
||||
|
||||
IF NOT COALESCE(project_exists, false) THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
RETURN NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_task_reconciliations" AS reconciliation
|
||||
JOIN "ql3"."plugin_package_task_reconciliation_items" AS item
|
||||
ON item.generation_digest = reconciliation.generation_digest
|
||||
AND item.task_id = p_task_id
|
||||
AND 'qltd:v1:' || item.revision || ':' || item.content_digest =
|
||||
p_task_revision
|
||||
WHERE reconciliation.project_id = p_project_id
|
||||
AND (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_quarantine_events" AS quarantine
|
||||
WHERE quarantine.project_id = reconciliation.project_id
|
||||
AND quarantine.package_name = reconciliation.package_name
|
||||
AND quarantine.lock_digest = reconciliation.lock_digest
|
||||
) OR EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_publisher_provenance" AS provenance
|
||||
JOIN "ql3"."plugin_package_publisher_revocation_receipts" AS revoked
|
||||
ON revoked.publisher = provenance.publisher
|
||||
AND revoked.key_id = provenance.key_id
|
||||
WHERE provenance.lock_digest = reconciliation.lock_digest
|
||||
)
|
||||
)
|
||||
);
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
CREATE OR REPLACE FUNCTION "ql3"."plugin_package_tool_start_allowed"(
|
||||
p_project_id varchar,
|
||||
p_definition_ref varchar,
|
||||
p_definition_digest char(64)
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
DECLARE
|
||||
project_exists boolean;
|
||||
BEGIN
|
||||
IF NOT pg_has_role(session_user, 'ql3_runtime', 'member') THEN
|
||||
RAISE EXCEPTION 'Runtime authority is required'
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
|
||||
SELECT true INTO project_exists
|
||||
FROM "ql3"."projects"
|
||||
WHERE id = p_project_id
|
||||
FOR SHARE;
|
||||
|
||||
IF NOT COALESCE(project_exists, false) THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
RETURN NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."project_tool_definition_snapshot_sources" AS source
|
||||
JOIN "ql3"."project_tool_definition_snapshots" AS snapshot
|
||||
ON snapshot.project_id = source.project_id
|
||||
AND snapshot.active_vector_digest = source.active_vector_digest
|
||||
CROSS JOIN LATERAL jsonb_array_elements(
|
||||
snapshot.snapshot_json -> 'definitions'
|
||||
) AS definition(item)
|
||||
WHERE source.project_id = p_project_id
|
||||
AND definition.item ->> 'packageName' = source.package_name
|
||||
AND 'tool:' ||
|
||||
(definition.item -> 'definition' ->> 'name') || '@' ||
|
||||
(definition.item -> 'definition' ->> 'version') = p_definition_ref
|
||||
AND definition.item ->> 'definitionDigest' = p_definition_digest
|
||||
AND (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_quarantine_events" AS quarantine
|
||||
WHERE quarantine.project_id = source.project_id
|
||||
AND quarantine.package_name = source.package_name
|
||||
AND quarantine.installation_id = source.installation_id
|
||||
AND quarantine.lock_digest = source.lock_digest
|
||||
) OR EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_publisher_provenance" AS provenance
|
||||
JOIN "ql3"."plugin_package_publisher_revocation_receipts" AS revoked
|
||||
ON revoked.publisher = provenance.publisher
|
||||
AND revoked.key_id = provenance.key_id
|
||||
WHERE provenance.installation_id = source.installation_id
|
||||
AND provenance.lock_digest = source.lock_digest
|
||||
)
|
||||
)
|
||||
);
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL ON
|
||||
"ql3"."plugin_package_publisher_provenance",
|
||||
"ql3"."plugin_package_publisher_revocation_receipts",
|
||||
"ql3"."plugin_package_publisher_revocation_impacts",
|
||||
"ql3"."plugin_package_publisher_revocation_impact_items"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL ON FUNCTION
|
||||
"ql3"."enforce_plugin_package_stage_provenance"()
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT ON
|
||||
"ql3"."plugin_package_publisher_provenance",
|
||||
"ql3"."plugin_package_publisher_revocation_receipts",
|
||||
"ql3"."plugin_package_publisher_revocation_impacts",
|
||||
"ql3"."plugin_package_publisher_revocation_impact_items"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 37,
|
||||
migration_id = 'pg-0038-plugin-package-publisher-provenance',
|
||||
capabilities = '${CAPABILITIES_V37}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 36
|
||||
AND migration_id = 'pg-0037-plugin-package-quarantine'
|
||||
AND capabilities = '${CAPABILITIES_V36}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 36'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V37 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V38 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0039PluginPackagePublisherTrustAuthorityMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0039-plugin-package-publisher-trust-authority',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_publisher_trust_snapshots" (
|
||||
snapshot_digest char(64) PRIMARY KEY,
|
||||
key_count integer NOT NULL,
|
||||
observed_by varchar(128) NOT NULL,
|
||||
observed_at_ms bigint NOT NULL,
|
||||
snapshot_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_publisher_trust_snapshot_identity_check CHECK (
|
||||
observed_by ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_trust_snapshot_digest_check CHECK (
|
||||
snapshot_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_trust_snapshot_count_check CHECK (
|
||||
key_count BETWEEN 0 AND 32 AND observed_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_trust_snapshot_json_check CHECK (
|
||||
jsonb_typeof(snapshot_json) = 'object' AND
|
||||
octet_length(snapshot_json::text) BETWEEN 2 AND 262144 AND
|
||||
snapshot_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-publisher-trust-snapshot@v1',
|
||||
'snapshotDigest', snapshot_digest
|
||||
) AND
|
||||
jsonb_array_length(snapshot_json -> 'keys') = key_count
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_publisher_trust_heads" (
|
||||
authority_id varchar(128) PRIMARY KEY,
|
||||
generation integer NOT NULL,
|
||||
base_snapshot_digest char(64) NOT NULL,
|
||||
effective_trust_digest char(64) NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
head_digest char(64) NOT NULL,
|
||||
head_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_publisher_trust_head_snapshot_fk
|
||||
FOREIGN KEY (base_snapshot_digest)
|
||||
REFERENCES "ql3"."plugin_package_publisher_trust_snapshots"
|
||||
(snapshot_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_publisher_trust_head_effective_snapshot_fk
|
||||
FOREIGN KEY (effective_trust_digest)
|
||||
REFERENCES "ql3"."plugin_package_publisher_trust_snapshots"
|
||||
(snapshot_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_publisher_trust_head_identity_check CHECK (
|
||||
authority_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
generation BETWEEN 1 AND 2147483647
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_trust_head_digest_check CHECK (
|
||||
base_snapshot_digest ~ '^[0-9a-f]{64}$' AND
|
||||
effective_trust_digest ~ '^[0-9a-f]{64}$' AND
|
||||
head_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_trust_head_time_check CHECK (
|
||||
updated_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_trust_head_json_check CHECK (
|
||||
jsonb_typeof(head_json) = 'object' AND
|
||||
octet_length(head_json::text) BETWEEN 2 AND 65536 AND
|
||||
head_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-publisher-trust-head@v1',
|
||||
'authorityId', authority_id,
|
||||
'generation', generation,
|
||||
'baseSnapshotDigest', base_snapshot_digest,
|
||||
'effectiveTrustDigest', effective_trust_digest,
|
||||
'updatedAtMs', updated_at_ms,
|
||||
'headDigest', head_digest
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_publisher_revocation_proposals" (
|
||||
action_ref varchar(255) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
authority_id varchar(128) NOT NULL,
|
||||
trust_generation integer NOT NULL,
|
||||
publisher varchar(253) NOT NULL,
|
||||
key_id varchar(128) NOT NULL,
|
||||
previous_trust_digest char(64) NOT NULL,
|
||||
current_trust_digest char(64) NOT NULL,
|
||||
action_type varchar(128) NOT NULL,
|
||||
permission varchar(128) NOT NULL,
|
||||
action_digest char(64) NOT NULL,
|
||||
preview_digest char(64) NOT NULL,
|
||||
authorization_mode varchar(16) NOT NULL,
|
||||
reason_code varchar(32) NOT NULL,
|
||||
proposed_by_type varchar(32) NOT NULL,
|
||||
proposed_by_id varchar(255) NOT NULL,
|
||||
proposer_assurance varchar(32) NOT NULL,
|
||||
fence_project_version integer NOT NULL,
|
||||
fence_binding_version integer,
|
||||
created_at_ms bigint NOT NULL,
|
||||
proposal_json jsonb NOT NULL,
|
||||
proposal_digest char(64) NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_publisher_revocation_proposal_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_publisher_revocation_proposal_head_fk
|
||||
FOREIGN KEY (authority_id)
|
||||
REFERENCES "ql3"."plugin_package_publisher_trust_heads" (authority_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_publisher_revocation_proposal_identity_check
|
||||
CHECK (
|
||||
action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$' AND
|
||||
authority_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
publisher ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$' AND
|
||||
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
action_type = 'plugin_package.publisher_key.revoke' AND
|
||||
permission = 'package.manage' AND
|
||||
authorization_mode IN ('dual_control', 'break_glass') AND
|
||||
reason_code IN (
|
||||
'suspected_key_compromise', 'confirmed_key_compromise'
|
||||
) AND
|
||||
proposed_by_type IN (
|
||||
'user', 'api_app', 'mcp_client', 'agent', 'system', 'worker'
|
||||
) AND
|
||||
octet_length(proposed_by_id) BETWEEN 1 AND 255 AND
|
||||
proposer_assurance IN (
|
||||
'single_factor', 'multi_factor', 'service', 'hardware',
|
||||
'local_console'
|
||||
) AND
|
||||
(authorization_mode <> 'break_glass' OR
|
||||
proposer_assurance = 'hardware')
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_revocation_proposal_digest_check
|
||||
CHECK (
|
||||
previous_trust_digest ~ '^[0-9a-f]{64}$' AND
|
||||
current_trust_digest ~ '^[0-9a-f]{64}$' AND
|
||||
previous_trust_digest <> current_trust_digest AND
|
||||
action_digest ~ '^[0-9a-f]{64}$' AND
|
||||
preview_digest ~ '^[0-9a-f]{64}$' AND
|
||||
proposal_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_revocation_proposal_time_check
|
||||
CHECK (
|
||||
trust_generation BETWEEN 1 AND 2147483647 AND
|
||||
fence_project_version BETWEEN 1 AND 2147483647 AND
|
||||
(fence_binding_version IS NULL OR
|
||||
fence_binding_version BETWEEN 1 AND 2147483647) AND
|
||||
created_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_publisher_revocation_proposal_json_check
|
||||
CHECK (
|
||||
jsonb_typeof(proposal_json) = 'object' AND
|
||||
octet_length(proposal_json::text) BETWEEN 2 AND 262144 AND
|
||||
proposal_json @> jsonb_build_object(
|
||||
'schema',
|
||||
'qinglong/plugin-package-publisher-key-revocation-proposal@v1',
|
||||
'actionRef', action_ref,
|
||||
'projectId', project_id,
|
||||
'actionType', action_type,
|
||||
'permission', permission,
|
||||
'actionDigest', action_digest,
|
||||
'previewDigest', preview_digest,
|
||||
'proposerAssurance', proposer_assurance,
|
||||
'createdAtMs', created_at_ms,
|
||||
'proposalDigest', proposal_digest
|
||||
) AND
|
||||
proposal_json -> 'actionInput' @> jsonb_build_object(
|
||||
'authorityProjectId', project_id,
|
||||
'trustAuthorityId', authority_id,
|
||||
'trustGeneration', trust_generation,
|
||||
'publisher', publisher,
|
||||
'keyId', key_id,
|
||||
'previousTrustDigest', previous_trust_digest,
|
||||
'currentTrustDigest', current_trust_digest,
|
||||
'authorizationMode', authorization_mode,
|
||||
'reasonCode', reason_code
|
||||
) AND
|
||||
proposal_json -> 'proposedBy' @> jsonb_build_object(
|
||||
'type', proposed_by_type, 'id', proposed_by_id
|
||||
) AND
|
||||
proposal_json -> 'proposalFence' @> jsonb_build_object(
|
||||
'projectVersion', fence_project_version,
|
||||
'bindingVersion', fence_binding_version
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_publisher_revocation_proposal_digest_key ON "ql3"."plugin_package_publisher_revocation_proposals" (proposal_digest)`,
|
||||
`CREATE INDEX ql3_plugin_package_publisher_revocation_proposal_project_idx ON "ql3"."plugin_package_publisher_revocation_proposals" (project_id, created_at_ms, action_ref)`,
|
||||
`CREATE INDEX ql3_plugin_package_publisher_revocation_proposal_signer_idx ON "ql3"."plugin_package_publisher_revocation_proposals" (publisher, key_id, trust_generation)`,
|
||||
`
|
||||
REVOKE ALL ON
|
||||
"ql3"."plugin_package_publisher_trust_snapshots",
|
||||
"ql3"."plugin_package_publisher_trust_heads",
|
||||
"ql3"."plugin_package_publisher_revocation_proposals"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT ON
|
||||
"ql3"."plugin_package_publisher_trust_snapshots",
|
||||
"ql3"."plugin_package_publisher_trust_heads",
|
||||
"ql3"."plugin_package_publisher_revocation_proposals"
|
||||
TO ql3_package_manager
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT ON
|
||||
"ql3"."plugin_package_publisher_trust_snapshots"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT ON
|
||||
"ql3"."plugin_package_publisher_revocation_proposals"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, UPDATE ON
|
||||
"ql3"."plugin_package_publisher_trust_heads"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 38,
|
||||
migration_id =
|
||||
'pg-0039-plugin-package-publisher-trust-authority',
|
||||
capabilities = '${CAPABILITIES_V38}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 37
|
||||
AND migration_id = 'pg-0038-plugin-package-publisher-provenance'
|
||||
AND capabilities = '${CAPABILITIES_V37}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 37'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V38 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V39 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0040PluginPackagePublisherTrustTransitionsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0040-plugin-package-publisher-trust-transitions',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_publisher_trust_transition_proposals" (
|
||||
action_ref varchar(255) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
authority_id varchar(128) NOT NULL,
|
||||
trust_generation integer NOT NULL,
|
||||
mode varchar(16) NOT NULL,
|
||||
publisher varchar(253) NOT NULL,
|
||||
key_id varchar(128) NOT NULL,
|
||||
previous_trust_digest char(64) NOT NULL,
|
||||
current_trust_digest char(64) NOT NULL,
|
||||
action_type varchar(128) NOT NULL,
|
||||
permission varchar(128) NOT NULL,
|
||||
action_digest char(64) NOT NULL,
|
||||
preview_digest char(64) NOT NULL,
|
||||
proposed_by_type varchar(32) NOT NULL,
|
||||
proposed_by_id varchar(255) NOT NULL,
|
||||
proposer_assurance varchar(32) NOT NULL,
|
||||
fence_project_version integer NOT NULL,
|
||||
fence_binding_version integer,
|
||||
created_at_ms bigint NOT NULL,
|
||||
proposal_json jsonb NOT NULL,
|
||||
proposal_digest char(64) NOT NULL,
|
||||
CONSTRAINT ql3_pp_trust_transition_proposal_digest_key
|
||||
UNIQUE (proposal_digest),
|
||||
CONSTRAINT ql3_pp_trust_transition_proposal_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_pp_trust_transition_proposal_head_fk
|
||||
FOREIGN KEY (authority_id)
|
||||
REFERENCES "ql3"."plugin_package_publisher_trust_heads" (authority_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_pp_trust_transition_proposal_previous_fk
|
||||
FOREIGN KEY (previous_trust_digest)
|
||||
REFERENCES "ql3"."plugin_package_publisher_trust_snapshots"
|
||||
(snapshot_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_pp_trust_transition_proposal_current_fk
|
||||
FOREIGN KEY (current_trust_digest)
|
||||
REFERENCES "ql3"."plugin_package_publisher_trust_snapshots"
|
||||
(snapshot_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_pp_trust_transition_proposal_identity_check
|
||||
CHECK (
|
||||
action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$' AND
|
||||
authority_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
publisher ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$' AND
|
||||
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
mode IN ('overlap_add', 'safe_retire') AND
|
||||
action_type = CASE mode
|
||||
WHEN 'overlap_add'
|
||||
THEN 'plugin_package.publisher_key.overlap_add'
|
||||
WHEN 'safe_retire'
|
||||
THEN 'plugin_package.publisher_key.safe_retire'
|
||||
END AND
|
||||
permission = 'package.manage' AND
|
||||
proposed_by_type = 'user' AND
|
||||
octet_length(proposed_by_id) BETWEEN 1 AND 255 AND
|
||||
proposer_assurance IN ('multi_factor', 'hardware')
|
||||
),
|
||||
CONSTRAINT ql3_pp_trust_transition_proposal_digest_check
|
||||
CHECK (
|
||||
previous_trust_digest ~ '^[0-9a-f]{64}$' AND
|
||||
current_trust_digest ~ '^[0-9a-f]{64}$' AND
|
||||
previous_trust_digest <> current_trust_digest AND
|
||||
action_digest ~ '^[0-9a-f]{64}$' AND
|
||||
preview_digest ~ '^[0-9a-f]{64}$' AND
|
||||
proposal_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_pp_trust_transition_proposal_time_check
|
||||
CHECK (
|
||||
trust_generation BETWEEN 1 AND 2147483647 AND
|
||||
fence_project_version BETWEEN 1 AND 2147483647 AND
|
||||
(fence_binding_version IS NULL OR
|
||||
fence_binding_version BETWEEN 1 AND 2147483647) AND
|
||||
created_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_pp_trust_transition_proposal_json_check
|
||||
CHECK (
|
||||
jsonb_typeof(proposal_json) = 'object' AND
|
||||
octet_length(proposal_json::text) BETWEEN 2 AND 262144 AND
|
||||
proposal_json @> jsonb_build_object(
|
||||
'schema',
|
||||
'qinglong/plugin-package-publisher-trust-transition-proposal@v1',
|
||||
'actionRef', action_ref,
|
||||
'projectId', project_id,
|
||||
'actionType', action_type,
|
||||
'permission', permission,
|
||||
'actionDigest', action_digest,
|
||||
'previewDigest', preview_digest,
|
||||
'proposerAssurance', proposer_assurance,
|
||||
'createdAtMs', created_at_ms,
|
||||
'proposalDigest', proposal_digest
|
||||
) AND
|
||||
proposal_json -> 'actionInput' @> jsonb_build_object(
|
||||
'authorityProjectId', project_id,
|
||||
'trustAuthorityId', authority_id,
|
||||
'trustGeneration', trust_generation,
|
||||
'mode', mode,
|
||||
'publisher', publisher,
|
||||
'keyId', key_id,
|
||||
'previousTrustDigest', previous_trust_digest,
|
||||
'currentTrustDigest', current_trust_digest
|
||||
) AND
|
||||
proposal_json -> 'proposedBy' @> jsonb_build_object(
|
||||
'type', proposed_by_type, 'id', proposed_by_id
|
||||
) AND
|
||||
proposal_json -> 'proposalFence' @> jsonb_build_object(
|
||||
'projectVersion', fence_project_version,
|
||||
'bindingVersion', fence_binding_version
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE INDEX ql3_pp_trust_transition_proposal_project_idx ON "ql3"."plugin_package_publisher_trust_transition_proposals" (project_id, created_at_ms, action_ref)`,
|
||||
`CREATE INDEX ql3_pp_trust_transition_proposal_signer_idx ON "ql3"."plugin_package_publisher_trust_transition_proposals" (publisher, key_id, trust_generation)`,
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_publisher_trust_transition_receipts" (
|
||||
mutation_id varchar(128) PRIMARY KEY,
|
||||
proposal_digest char(64) NOT NULL,
|
||||
authority_id varchar(128) NOT NULL,
|
||||
previous_generation integer NOT NULL,
|
||||
current_generation integer NOT NULL,
|
||||
mode varchar(16) NOT NULL,
|
||||
publisher varchar(253) NOT NULL,
|
||||
key_id varchar(128) NOT NULL,
|
||||
previous_trust_digest char(64) NOT NULL,
|
||||
current_trust_digest char(64) NOT NULL,
|
||||
proposer_type varchar(32) NOT NULL,
|
||||
proposer_id varchar(255) NOT NULL,
|
||||
confirmer_type varchar(32) NOT NULL,
|
||||
confirmer_id varchar(255) NOT NULL,
|
||||
retirement_matching_installations integer,
|
||||
executed_at_ms bigint NOT NULL,
|
||||
receipt_json jsonb NOT NULL,
|
||||
receipt_digest char(64) NOT NULL,
|
||||
CONSTRAINT ql3_pp_trust_transition_receipt_proposal_key
|
||||
UNIQUE (proposal_digest),
|
||||
CONSTRAINT ql3_pp_trust_transition_receipt_digest_key
|
||||
UNIQUE (receipt_digest),
|
||||
CONSTRAINT ql3_pp_trust_transition_receipt_dispatch_fk
|
||||
FOREIGN KEY (mutation_id)
|
||||
REFERENCES "ql3"."approved_action_dispatches" (dispatch_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_pp_trust_transition_receipt_proposal_fk
|
||||
FOREIGN KEY (proposal_digest)
|
||||
REFERENCES "ql3"."plugin_package_publisher_trust_transition_proposals"
|
||||
(proposal_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_pp_trust_transition_receipt_head_fk
|
||||
FOREIGN KEY (authority_id)
|
||||
REFERENCES "ql3"."plugin_package_publisher_trust_heads" (authority_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_pp_trust_transition_receipt_previous_fk
|
||||
FOREIGN KEY (previous_trust_digest)
|
||||
REFERENCES "ql3"."plugin_package_publisher_trust_snapshots"
|
||||
(snapshot_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_pp_trust_transition_receipt_current_fk
|
||||
FOREIGN KEY (current_trust_digest)
|
||||
REFERENCES "ql3"."plugin_package_publisher_trust_snapshots"
|
||||
(snapshot_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_pp_trust_transition_receipt_identity_check
|
||||
CHECK (
|
||||
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
proposal_digest ~ '^[0-9a-f]{64}$' AND
|
||||
authority_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
mode IN ('overlap_add', 'safe_retire') AND
|
||||
publisher ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$' AND
|
||||
key_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
proposer_type = 'user' AND
|
||||
confirmer_type = 'user' AND
|
||||
octet_length(proposer_id) BETWEEN 1 AND 255 AND
|
||||
octet_length(confirmer_id) BETWEEN 1 AND 255 AND
|
||||
(proposer_type, proposer_id) <> (confirmer_type, confirmer_id)
|
||||
),
|
||||
CONSTRAINT ql3_pp_trust_transition_receipt_transition_check
|
||||
CHECK (
|
||||
previous_generation BETWEEN 1 AND 2147483646 AND
|
||||
current_generation = previous_generation + 1 AND
|
||||
previous_trust_digest ~ '^[0-9a-f]{64}$' AND
|
||||
current_trust_digest ~ '^[0-9a-f]{64}$' AND
|
||||
previous_trust_digest <> current_trust_digest AND
|
||||
(
|
||||
(mode = 'overlap_add' AND
|
||||
retirement_matching_installations IS NULL) OR
|
||||
(mode = 'safe_retire' AND
|
||||
retirement_matching_installations = 0)
|
||||
) AND
|
||||
executed_at_ms >= 0 AND
|
||||
receipt_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_pp_trust_transition_receipt_json_check
|
||||
CHECK (
|
||||
jsonb_typeof(receipt_json) = 'object' AND
|
||||
octet_length(receipt_json::text) BETWEEN 2 AND 262144 AND
|
||||
receipt_json @> jsonb_build_object(
|
||||
'schema',
|
||||
'qinglong/plugin-package-publisher-trust-transition-receipt@v1',
|
||||
'mutationId', mutation_id,
|
||||
'proposalDigest', proposal_digest,
|
||||
'trustAuthorityId', authority_id,
|
||||
'previousGeneration', previous_generation,
|
||||
'currentGeneration', current_generation,
|
||||
'mode', mode,
|
||||
'publisher', publisher,
|
||||
'keyId', key_id,
|
||||
'previousTrustDigest', previous_trust_digest,
|
||||
'currentTrustDigest', current_trust_digest,
|
||||
'retirementMatchingInstallations',
|
||||
retirement_matching_installations,
|
||||
'executedAtMs', executed_at_ms,
|
||||
'receiptDigest', receipt_digest
|
||||
) AND
|
||||
receipt_json -> 'proposer' @> jsonb_build_object(
|
||||
'type', proposer_type, 'id', proposer_id
|
||||
) AND
|
||||
receipt_json -> 'confirmer' @> jsonb_build_object(
|
||||
'type', confirmer_type, 'id', confirmer_id
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE INDEX ql3_pp_trust_transition_receipt_signer_idx ON "ql3"."plugin_package_publisher_trust_transition_receipts" (publisher, key_id, current_generation)`,
|
||||
`
|
||||
REVOKE ALL ON
|
||||
"ql3"."plugin_package_publisher_trust_transition_proposals",
|
||||
"ql3"."plugin_package_publisher_trust_transition_receipts"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT ON
|
||||
"ql3"."plugin_package_publisher_trust_transition_proposals"
|
||||
TO ql3_package_manager
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT ON
|
||||
"ql3"."plugin_package_publisher_trust_transition_receipts"
|
||||
TO ql3_package_manager
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT ON
|
||||
"ql3"."plugin_package_publisher_trust_transition_proposals"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT ON
|
||||
"ql3"."plugin_package_publisher_trust_transition_receipts"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 39,
|
||||
migration_id =
|
||||
'pg-0040-plugin-package-publisher-trust-transitions',
|
||||
capabilities = '${CAPABILITIES_V39}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 38
|
||||
AND migration_id =
|
||||
'pg-0039-plugin-package-publisher-trust-authority'
|
||||
AND capabilities = '${CAPABILITIES_V38}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 38'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
+108
@@ -0,0 +1,108 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V40 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V41 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0042PluginPackageLifecyclePlansMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0042-plugin-package-lifecycle-plans',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_lifecycle_plans" (
|
||||
action_ref varchar(255) PRIMARY KEY,
|
||||
plan_digest char(64) NOT NULL,
|
||||
action varchar(16) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
installation_id varchar(128) NOT NULL,
|
||||
lock_digest char(64) NOT NULL,
|
||||
impact_digest char(64) NOT NULL,
|
||||
requested_by_type varchar(16) NOT NULL,
|
||||
requested_by_id varchar(255) NOT NULL,
|
||||
planned_at_ms bigint NOT NULL,
|
||||
expires_at_ms bigint NOT NULL,
|
||||
plan_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_lifecycle_plan_install_fk
|
||||
FOREIGN KEY (installation_id)
|
||||
REFERENCES "ql3"."plugin_package_installs" (installation_id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_lifecycle_plan_identity_check CHECK (
|
||||
action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$'
|
||||
AND action IN ('disable', 'enable', 'uninstall')
|
||||
AND package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$'
|
||||
AND requested_by_type = 'user'
|
||||
AND char_length(requested_by_id) BETWEEN 1 AND 255
|
||||
AND requested_by_id !~ '[[:cntrl:]]'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_lifecycle_plan_digest_check CHECK (
|
||||
plan_digest ~ '^[0-9a-f]{64}$'
|
||||
AND lock_digest ~ '^[0-9a-f]{64}$'
|
||||
AND impact_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_lifecycle_plan_time_check CHECK (
|
||||
planned_at_ms >= 0
|
||||
AND expires_at_ms > planned_at_ms
|
||||
AND expires_at_ms - planned_at_ms <= 900000
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_lifecycle_plan_json_check CHECK (
|
||||
jsonb_typeof(plan_json) = 'object'
|
||||
AND octet_length(plan_json::text) BETWEEN 2 AND 98304
|
||||
AND plan_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-lifecycle-plan@v1',
|
||||
'actionRef', action_ref,
|
||||
'planDigest', plan_digest,
|
||||
'requestedBy', jsonb_build_object(
|
||||
'type', requested_by_type,
|
||||
'id', requested_by_id
|
||||
),
|
||||
'plannedAtMs', planned_at_ms,
|
||||
'expiresAtMs', expires_at_ms,
|
||||
'impact', jsonb_build_object(
|
||||
'action', action,
|
||||
'impactDigest', impact_digest,
|
||||
'target', jsonb_build_object(
|
||||
'projectId', project_id,
|
||||
'packageName', package_name,
|
||||
'installationId', installation_id,
|
||||
'lockDigest', lock_digest
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_plan_digest_key ON "ql3"."plugin_package_lifecycle_plans" (plan_digest)`,
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_plan_impact_key ON "ql3"."plugin_package_lifecycle_plans" (project_id, package_name, installation_id, lock_digest, impact_digest)`,
|
||||
`CREATE INDEX ql3_plugin_package_lifecycle_plan_expiry_idx ON "ql3"."plugin_package_lifecycle_plans" (expires_at_ms, action_ref)`,
|
||||
`
|
||||
REVOKE ALL ON "ql3"."plugin_package_lifecycle_plans"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`GRANT SELECT ON "ql3"."plugin_package_lifecycle_plans" TO ql3_package_manager, ql3_package_executor`,
|
||||
`GRANT INSERT ON "ql3"."plugin_package_lifecycle_plans" TO ql3_package_executor`,
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 41,
|
||||
migration_id = 'pg-0042-plugin-package-lifecycle-plans',
|
||||
capabilities = '${CAPABILITIES_V41}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 40
|
||||
AND migration_id = 'pg-0041-plugin-package-lifecycle'
|
||||
AND capabilities = '${CAPABILITIES_V40}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 40'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V41 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V42 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0043PluginPackageAutomationPublicationsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0043-plugin-package-automation-publications',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_automation_publications" (
|
||||
publication_digest char(64) PRIMARY KEY,
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
installation_id varchar(128) NOT NULL,
|
||||
lock_digest char(64) NOT NULL,
|
||||
generation integer NOT NULL,
|
||||
generation_digest char(64) NOT NULL,
|
||||
materialized_revision_digest char(64) NOT NULL,
|
||||
state varchar(16) NOT NULL,
|
||||
version integer NOT NULL,
|
||||
previous_publication_digest char(64),
|
||||
lifecycle_event_digest char(64),
|
||||
published_at_ms bigint NOT NULL,
|
||||
publication_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_automation_publication_revision_fk
|
||||
FOREIGN KEY (generation_digest)
|
||||
REFERENCES "ql3"."plugin_package_materialized_revisions" (
|
||||
generation_digest
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_automation_publication_previous_fk
|
||||
FOREIGN KEY (previous_publication_digest)
|
||||
REFERENCES "ql3"."plugin_package_automation_publications" (
|
||||
publication_digest
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_automation_publication_lifecycle_fk
|
||||
FOREIGN KEY (lifecycle_event_digest)
|
||||
REFERENCES "ql3"."plugin_package_lifecycle_events" (event_digest)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_automation_publication_identity_check CHECK (
|
||||
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' 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 (
|
||||
publication_digest ~ '^[0-9a-f]{64}$' AND
|
||||
lock_digest ~ '^[0-9a-f]{64}$' AND
|
||||
generation_digest ~ '^[0-9a-f]{64}$' AND
|
||||
materialized_revision_digest ~ '^[0-9a-f]{64}$' AND
|
||||
(
|
||||
previous_publication_digest IS NULL OR
|
||||
previous_publication_digest ~ '^[0-9a-f]{64}$'
|
||||
) AND
|
||||
(
|
||||
lifecycle_event_digest IS NULL OR
|
||||
lifecycle_event_digest ~ '^[0-9a-f]{64}$'
|
||||
)
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_automation_publication_json_check CHECK (
|
||||
jsonb_typeof(publication_json) = 'object' AND
|
||||
octet_length(publication_json::text) BETWEEN 2 AND 12582912 AND
|
||||
publication_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-automation-publication@v1',
|
||||
'state', state,
|
||||
'version', version,
|
||||
'previousPublicationDigest', previous_publication_digest,
|
||||
'lifecycleEventDigest', lifecycle_event_digest,
|
||||
'publishedAtMs', published_at_ms,
|
||||
'publicationDigest', publication_digest,
|
||||
'target', jsonb_build_object(
|
||||
'projectId', project_id,
|
||||
'packageName', package_name,
|
||||
'installationId', installation_id,
|
||||
'lockDigest', lock_digest,
|
||||
'generation', generation,
|
||||
'generationDigest', generation_digest,
|
||||
'materializedRevisionDigest', materialized_revision_digest
|
||||
)
|
||||
) AND
|
||||
jsonb_typeof(publication_json -> 'definitions' -> 'workflows') =
|
||||
'array' AND
|
||||
jsonb_typeof(publication_json -> 'definitions' -> 'prompts') =
|
||||
'array' AND
|
||||
(
|
||||
state = 'absent' AND
|
||||
jsonb_array_length(publication_json -> 'definitions' -> 'workflows') +
|
||||
jsonb_array_length(publication_json -> 'definitions' -> 'prompts') = 0
|
||||
OR
|
||||
state <> 'absent' AND
|
||||
jsonb_array_length(publication_json -> 'definitions' -> 'workflows') +
|
||||
jsonb_array_length(publication_json -> 'definitions' -> 'prompts') > 0
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_automation_publication_version_key ON "ql3"."plugin_package_automation_publications" (project_id, package_name, version)`,
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_automation_publication_previous_key ON "ql3"."plugin_package_automation_publications" (previous_publication_digest) WHERE previous_publication_digest IS NOT NULL`,
|
||||
`CREATE INDEX ql3_plugin_package_automation_publication_generation_idx ON "ql3"."plugin_package_automation_publications" (generation_digest, publication_digest)`,
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_automation_publication_heads" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
publication_digest char(64) NOT NULL,
|
||||
generation_digest char(64) NOT NULL,
|
||||
state varchar(16) NOT NULL,
|
||||
version integer NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
PRIMARY KEY (project_id, package_name),
|
||||
CONSTRAINT ql3_plugin_package_automation_publication_head_publication_fk
|
||||
FOREIGN KEY (publication_digest)
|
||||
REFERENCES "ql3"."plugin_package_automation_publications" (
|
||||
publication_digest
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_automation_publication_head_state_check CHECK (
|
||||
publication_digest ~ '^[0-9a-f]{64}$' AND
|
||||
generation_digest ~ '^[0-9a-f]{64}$' AND
|
||||
state IN ('active', 'withdrawn', 'absent') AND
|
||||
version BETWEEN 1 AND 2147483647 AND
|
||||
updated_at_ms >= 0
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_automation_publication_head_digest_key ON "ql3"."plugin_package_automation_publication_heads" (publication_digest)`,
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."plugin_package_automation_publications",
|
||||
"ql3"."plugin_package_automation_publication_heads"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT
|
||||
ON "ql3"."plugin_package_automation_publications",
|
||||
"ql3"."plugin_package_automation_publication_heads"
|
||||
TO ql3_runtime, ql3_package_manager, ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT INSERT
|
||||
ON "ql3"."plugin_package_automation_publications"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
GRANT INSERT, UPDATE
|
||||
ON "ql3"."plugin_package_automation_publication_heads"
|
||||
TO ql3_package_executor
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 42,
|
||||
migration_id = 'pg-0043-plugin-package-automation-publications',
|
||||
capabilities = '${CAPABILITIES_V42}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 41
|
||||
AND migration_id = 'pg-0042-plugin-package-lifecycle-plans'
|
||||
AND capabilities = '${CAPABILITIES_V41}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 41'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V42 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V43 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0044PluginPackageAutomationStartGuardMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0044-plugin-package-automation-start-guard',
|
||||
statements: [
|
||||
`
|
||||
CREATE FUNCTION "ql3"."plugin_package_automation_start_allowed"(
|
||||
p_project_id varchar,
|
||||
p_package_name varchar,
|
||||
p_publication_digest char(64)
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
DECLARE
|
||||
project_exists boolean;
|
||||
current_publication "ql3"."plugin_package_automation_publications"%ROWTYPE;
|
||||
current_lifecycle varchar(16);
|
||||
current_installation_id varchar(128);
|
||||
current_publisher varchar(253);
|
||||
current_key_id varchar(128);
|
||||
BEGIN
|
||||
IF NOT pg_has_role(session_user, 'ql3_runtime', 'member') THEN
|
||||
RAISE EXCEPTION 'Runtime authority is required'
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
|
||||
SELECT true INTO project_exists
|
||||
FROM "ql3"."projects"
|
||||
WHERE id = p_project_id
|
||||
FOR SHARE;
|
||||
IF NOT COALESCE(project_exists, false) THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
SELECT publication.* INTO current_publication
|
||||
FROM "ql3"."plugin_package_automation_publication_heads" AS head
|
||||
JOIN "ql3"."plugin_package_automation_publications" AS publication
|
||||
ON publication.publication_digest = head.publication_digest
|
||||
WHERE head.project_id = p_project_id
|
||||
AND head.package_name = p_package_name
|
||||
AND head.publication_digest = p_publication_digest
|
||||
FOR SHARE OF head, publication;
|
||||
IF NOT FOUND OR current_publication.state <> 'active' THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
SELECT install.installation_id INTO current_installation_id
|
||||
FROM "ql3"."plugin_package_install_heads" AS head
|
||||
JOIN "ql3"."plugin_package_installs" AS install
|
||||
ON install.installation_id = head.installation_id
|
||||
WHERE head.project_id = current_publication.project_id
|
||||
AND head.package_name = current_publication.package_name
|
||||
AND head.installation_id = current_publication.installation_id
|
||||
AND install.lock_digest = current_publication.lock_digest
|
||||
AND install.state = 'active'
|
||||
AND install.active_lock_digest = current_publication.lock_digest
|
||||
FOR SHARE OF head, install;
|
||||
IF NOT FOUND THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
SELECT disposition INTO current_lifecycle
|
||||
FROM "ql3"."plugin_package_lifecycle_heads"
|
||||
WHERE project_id = current_publication.project_id
|
||||
AND package_name = current_publication.package_name
|
||||
FOR SHARE;
|
||||
IF FOUND AND current_lifecycle <> 'active' THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_quarantine_events" AS quarantine
|
||||
WHERE quarantine.project_id = current_publication.project_id
|
||||
AND quarantine.package_name = current_publication.package_name
|
||||
AND quarantine.installation_id =
|
||||
current_publication.installation_id
|
||||
AND quarantine.lock_digest = current_publication.lock_digest
|
||||
) THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
SELECT provenance.publisher, provenance.key_id
|
||||
INTO current_publisher, current_key_id
|
||||
FROM "ql3"."plugin_package_publisher_provenance" AS provenance
|
||||
WHERE provenance.installation_id = current_publication.installation_id
|
||||
AND provenance.lock_digest = current_publication.lock_digest;
|
||||
IF NOT FOUND THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
PERFORM pg_advisory_xact_lock(
|
||||
hashtextextended(
|
||||
octet_length(current_publisher)::text || ':' ||
|
||||
current_publisher ||
|
||||
octet_length(current_key_id)::text || ':' || current_key_id,
|
||||
774635230
|
||||
)
|
||||
);
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_publisher_revocation_receipts" AS revoked
|
||||
WHERE revoked.publisher = current_publisher
|
||||
AND revoked.key_id = current_key_id
|
||||
) THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
RETURN true;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL ON FUNCTION
|
||||
"ql3"."plugin_package_automation_start_allowed"(
|
||||
varchar, varchar, char(64)
|
||||
)
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT EXECUTE ON FUNCTION
|
||||
"ql3"."plugin_package_automation_start_allowed"(
|
||||
varchar, varchar, char(64)
|
||||
)
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 43,
|
||||
migration_id = 'pg-0044-plugin-package-automation-start-guard',
|
||||
capabilities = '${CAPABILITIES_V43}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 42
|
||||
AND migration_id = 'pg-0043-plugin-package-automation-publications'
|
||||
AND capabilities = '${CAPABILITIES_V42}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 42'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V43 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V44 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"plugin_package_workflow_admission":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0045PluginPackageWorkflowAdmissionsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0045-plugin-package-workflow-admissions',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_workflow_admissions" (
|
||||
plan_digest char(64) PRIMARY KEY,
|
||||
plan_id varchar(128) NOT NULL,
|
||||
run_id varchar(36) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
package_name varchar(63) NOT NULL,
|
||||
installation_id varchar(128) NOT NULL,
|
||||
lock_digest char(64) NOT NULL,
|
||||
generation integer NOT NULL,
|
||||
generation_digest char(64) NOT NULL,
|
||||
materialized_revision_digest char(64) NOT NULL,
|
||||
publication_digest char(64) NOT NULL,
|
||||
workflow_id varchar(63) NOT NULL,
|
||||
workflow_definition_digest char(64) NOT NULL,
|
||||
step_count integer NOT NULL,
|
||||
admitted_at_ms bigint NOT NULL,
|
||||
final_run_version integer NOT NULL,
|
||||
final_run_event_sequence integer NOT NULL,
|
||||
receipt_digest char(64) NOT NULL,
|
||||
plan_json jsonb NOT NULL,
|
||||
receipt_json jsonb NOT NULL,
|
||||
CONSTRAINT plugin_package_workflow_admissions_plan_id_key
|
||||
UNIQUE (plan_id),
|
||||
CONSTRAINT plugin_package_workflow_admissions_run_id_key
|
||||
UNIQUE (run_id),
|
||||
CONSTRAINT plugin_package_workflow_admissions_receipt_digest_key
|
||||
UNIQUE (receipt_digest),
|
||||
CONSTRAINT ql3_plugin_package_workflow_admission_run_fk
|
||||
FOREIGN KEY (run_id) REFERENCES "ql3"."runs" (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_workflow_admission_publication_fk
|
||||
FOREIGN KEY (publication_digest)
|
||||
REFERENCES "ql3"."plugin_package_automation_publications" (
|
||||
publication_digest
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_workflow_admission_identity_check CHECK (
|
||||
plan_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$' AND
|
||||
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' AND
|
||||
workflow_id ~ '^[a-z][a-z0-9-]{0,62}$' AND
|
||||
generation BETWEEN 1 AND 2147483647 AND
|
||||
step_count BETWEEN 1 AND 128 AND
|
||||
admitted_at_ms >= 0 AND
|
||||
final_run_version = step_count + 1 AND
|
||||
final_run_event_sequence = step_count + 1
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_workflow_admission_digest_check CHECK (
|
||||
plan_digest ~ '^[0-9a-f]{64}$' AND
|
||||
lock_digest ~ '^[0-9a-f]{64}$' AND
|
||||
generation_digest ~ '^[0-9a-f]{64}$' AND
|
||||
materialized_revision_digest ~ '^[0-9a-f]{64}$' AND
|
||||
publication_digest ~ '^[0-9a-f]{64}$' AND
|
||||
workflow_definition_digest ~ '^[0-9a-f]{64}$' AND
|
||||
receipt_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_workflow_admission_json_check CHECK (
|
||||
jsonb_typeof(plan_json) = 'object' AND
|
||||
octet_length(plan_json::text) BETWEEN 2 AND 262144 AND
|
||||
plan_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-workflow-execution-plan@v1',
|
||||
'planId', plan_id, 'planDigest', plan_digest, 'runId', run_id,
|
||||
'plannedAtMs', admitted_at_ms
|
||||
) AND
|
||||
plan_json -> 'target' @> jsonb_build_object(
|
||||
'projectId', project_id, 'packageName', package_name,
|
||||
'installationId', installation_id, 'lockDigest', lock_digest,
|
||||
'generation', generation, 'generationDigest', generation_digest,
|
||||
'materializedRevisionDigest', materialized_revision_digest,
|
||||
'publicationDigest', publication_digest, 'workflowId', workflow_id,
|
||||
'workflowDefinitionDigest', workflow_definition_digest
|
||||
) AND
|
||||
jsonb_typeof(plan_json -> 'steps') = 'array' AND
|
||||
jsonb_array_length(plan_json -> 'steps') = step_count AND
|
||||
jsonb_typeof(receipt_json) = 'object' AND
|
||||
octet_length(receipt_json::text) BETWEEN 2 AND 262144 AND
|
||||
receipt_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-workflow-admission-receipt@v1',
|
||||
'planId', plan_id, 'planDigest', plan_digest, 'runId', run_id,
|
||||
'publicationDigest', publication_digest, 'workflowId', workflow_id,
|
||||
'finalRunVersion', final_run_version,
|
||||
'finalRunEventSequence', final_run_event_sequence,
|
||||
'admittedAtMs', admitted_at_ms, 'receiptDigest', receipt_digest
|
||||
) AND
|
||||
jsonb_typeof(receipt_json -> 'steps') = 'array' AND
|
||||
jsonb_array_length(receipt_json -> 'steps') = step_count
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_workflow_admission_plan_run_key ON "ql3"."plugin_package_workflow_admissions" (plan_digest, run_id)`,
|
||||
`CREATE INDEX ql3_plugin_package_workflow_admission_target_idx ON "ql3"."plugin_package_workflow_admissions" (project_id, package_name, admitted_at_ms, plan_digest)`,
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_workflow_admission_steps" (
|
||||
plan_digest char(64) NOT NULL,
|
||||
run_id varchar(36) NOT NULL,
|
||||
step_key varchar(63) NOT NULL,
|
||||
step_run_id varchar(128) NOT NULL,
|
||||
task_id varchar(63) NOT NULL,
|
||||
task_definition_ref varchar(512) NOT NULL,
|
||||
task_definition_digest char(64) NOT NULL,
|
||||
needs_json jsonb NOT NULL,
|
||||
initial_status varchar(16) NOT NULL,
|
||||
mutation_id varchar(128) NOT NULL,
|
||||
event_id varchar(36) NOT NULL,
|
||||
PRIMARY KEY (plan_digest, step_key),
|
||||
CONSTRAINT plugin_package_workflow_admission_steps_step_run_id_key
|
||||
UNIQUE (step_run_id),
|
||||
CONSTRAINT plugin_package_workflow_admission_steps_mutation_id_key
|
||||
UNIQUE (mutation_id),
|
||||
CONSTRAINT plugin_package_workflow_admission_steps_event_id_key
|
||||
UNIQUE (event_id),
|
||||
CONSTRAINT ql3_plugin_package_workflow_admission_step_admission_fk
|
||||
FOREIGN KEY (plan_digest, run_id)
|
||||
REFERENCES "ql3"."plugin_package_workflow_admissions" (
|
||||
plan_digest, run_id
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_workflow_admission_step_run_fk
|
||||
FOREIGN KEY (run_id, step_run_id)
|
||||
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_workflow_admission_step_mutation_fk
|
||||
FOREIGN KEY (mutation_id)
|
||||
REFERENCES "ql3"."step_run_mutations" (mutation_id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_workflow_admission_step_event_fk
|
||||
FOREIGN KEY (event_id)
|
||||
REFERENCES "ql3"."run_events" (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_workflow_admission_step_identity_check CHECK (
|
||||
step_key ~ '^[a-z][a-z0-9-]{0,62}$' AND
|
||||
task_id ~ '^[a-z][a-z0-9-]{0,62}$' AND
|
||||
initial_status IN ('pending', 'ready') AND
|
||||
task_definition_digest ~ '^[0-9a-f]{64}$' AND
|
||||
jsonb_typeof(needs_json) = 'array' AND
|
||||
jsonb_array_length(needs_json) BETWEEN 0 AND 127
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE INDEX ql3_plugin_package_workflow_admission_step_task_idx ON "ql3"."plugin_package_workflow_admission_steps" (task_id, task_definition_digest, plan_digest)`,
|
||||
`
|
||||
CREATE FUNCTION "ql3"."plugin_package_workflow_admission_snapshot"(
|
||||
p_project_id varchar,
|
||||
p_package_name varchar,
|
||||
p_publication_digest char(64)
|
||||
)
|
||||
RETURNS TABLE(publication_json jsonb, revision_json jsonb)
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
BEGIN
|
||||
IF NOT pg_has_role(session_user, 'ql3_runtime', 'member') THEN
|
||||
RAISE EXCEPTION 'Runtime authority is required'
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
IF NOT "ql3"."plugin_package_automation_start_allowed"(
|
||||
p_project_id, p_package_name, p_publication_digest
|
||||
) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
RETURN QUERY
|
||||
SELECT publication.publication_json, revision.revision_json
|
||||
FROM "ql3"."plugin_package_automation_publications" AS publication
|
||||
JOIN "ql3"."plugin_package_materialized_revisions" AS revision
|
||||
ON revision.generation_digest = publication.generation_digest
|
||||
AND revision.revision_digest =
|
||||
publication.materialized_revision_digest
|
||||
WHERE publication.project_id = p_project_id
|
||||
AND publication.package_name = p_package_name
|
||||
AND publication.publication_digest = p_publication_digest
|
||||
AND publication.state = 'active'
|
||||
FOR SHARE OF publication, revision;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."plugin_package_workflow_admissions",
|
||||
"ql3"."plugin_package_workflow_admission_steps"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."plugin_package_workflow_admissions",
|
||||
"ql3"."plugin_package_workflow_admission_steps"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL ON FUNCTION
|
||||
"ql3"."plugin_package_workflow_admission_snapshot"(
|
||||
varchar, varchar, char(64)
|
||||
)
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT EXECUTE ON FUNCTION
|
||||
"ql3"."plugin_package_workflow_admission_snapshot"(
|
||||
varchar, varchar, char(64)
|
||||
)
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 44,
|
||||
migration_id = 'pg-0045-plugin-package-workflow-admissions',
|
||||
capabilities = '${CAPABILITIES_V44}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 43
|
||||
AND migration_id = 'pg-0044-plugin-package-automation-start-guard'
|
||||
AND capabilities = '${CAPABILITIES_V43}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 43'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V44 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"plugin_package_workflow_admission":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V45 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"plugin_package_workflow_admission":1,"plugin_package_workflow_task_attempt_admission":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0046PluginPackageWorkflowTaskAttemptAdmissionsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0046-plugin-package-workflow-task-attempt-admissions',
|
||||
statements: [
|
||||
`
|
||||
CREATE UNIQUE INDEX ql3_plugin_package_task_reconciliation_receipt_uidx
|
||||
ON "ql3"."plugin_package_task_reconciliations"
|
||||
(generation_digest, receipt_digest)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_workflow_task_attempt_admissions" (
|
||||
receipt_digest char(64) PRIMARY KEY,
|
||||
attempt_id varchar(36) NOT NULL,
|
||||
plan_digest char(64) NOT NULL,
|
||||
run_id varchar(36) NOT NULL,
|
||||
step_run_id varchar(128) NOT NULL,
|
||||
step_run_version integer NOT NULL,
|
||||
step_run_digest char(64) NOT NULL,
|
||||
generation_digest char(64) NOT NULL,
|
||||
resource_task_id varchar(128) NOT NULL,
|
||||
task_reconciliation_receipt_digest char(64) NOT NULL,
|
||||
project_id varchar(128) NOT NULL,
|
||||
task_id varchar(128) NOT NULL,
|
||||
source_revision integer NOT NULL,
|
||||
task_revision varchar(96) NOT NULL,
|
||||
task_definition_digest char(64) NOT NULL,
|
||||
executor_type varchar(32) NOT NULL,
|
||||
execution_digest char(64) NOT NULL,
|
||||
attempt_number integer NOT NULL,
|
||||
event_id varchar(36) NOT NULL,
|
||||
run_version integer NOT NULL,
|
||||
run_event_sequence integer NOT NULL,
|
||||
admitted_at_ms bigint NOT NULL,
|
||||
receipt_json jsonb NOT NULL,
|
||||
CONSTRAINT plugin_package_workflow_task_attempt_admissions_attempt_id_key
|
||||
UNIQUE (attempt_id),
|
||||
CONSTRAINT plugin_package_workflow_task_attempt_admissions_event_id_key
|
||||
UNIQUE (event_id),
|
||||
CONSTRAINT plugin_package_workflow_task_attempt_admissions_epoch_key
|
||||
UNIQUE (run_id, step_run_id, step_run_version),
|
||||
CONSTRAINT plugin_package_workflow_task_attempt_admissions_number_key
|
||||
UNIQUE (run_id, attempt_number),
|
||||
CONSTRAINT ql3_plugin_package_workflow_task_attempt_admission_plan_fk
|
||||
FOREIGN KEY (plan_digest, run_id)
|
||||
REFERENCES "ql3"."plugin_package_workflow_admissions" (
|
||||
plan_digest, run_id
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_workflow_task_attempt_admission_step_fk
|
||||
FOREIGN KEY (run_id, step_run_id)
|
||||
REFERENCES "ql3"."step_runs" (run_id, id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_workflow_task_attempt_admission_attempt_fk
|
||||
FOREIGN KEY (attempt_id)
|
||||
REFERENCES "ql3"."run_attempts" (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_workflow_task_attempt_admission_event_fk
|
||||
FOREIGN KEY (event_id)
|
||||
REFERENCES "ql3"."run_events" (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_pp_workflow_task_attempt_reconciliation_fk
|
||||
FOREIGN KEY (
|
||||
generation_digest, task_reconciliation_receipt_digest
|
||||
) REFERENCES "ql3"."plugin_package_task_reconciliations" (
|
||||
generation_digest, receipt_digest
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_workflow_task_attempt_admission_execution_fk
|
||||
FOREIGN KEY (
|
||||
project_id, task_id, source_revision, executor_type
|
||||
) REFERENCES "ql3"."task_execution_revisions" (
|
||||
project_id, task_id, source_revision, executor_type
|
||||
) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_pp_workflow_task_attempt_identity_check
|
||||
CHECK (
|
||||
attempt_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$' AND
|
||||
run_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$' AND
|
||||
char_length(step_run_id) BETWEEN 1 AND 128 AND
|
||||
char_length(resource_task_id) BETWEEN 1 AND 128 AND
|
||||
char_length(project_id) BETWEEN 1 AND 128 AND
|
||||
char_length(task_id) BETWEEN 1 AND 128 AND
|
||||
event_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$' AND
|
||||
executor_type = 'remote_worker'
|
||||
),
|
||||
CONSTRAINT ql3_pp_workflow_task_attempt_counter_check
|
||||
CHECK (
|
||||
step_run_version BETWEEN 1 AND 2147483647 AND
|
||||
source_revision BETWEEN 1 AND 2147483647 AND
|
||||
attempt_number BETWEEN 1 AND 8192 AND
|
||||
run_version BETWEEN 1 AND 2147483647 AND
|
||||
run_event_sequence = run_version AND
|
||||
admitted_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT
|
||||
ql3_plugin_package_workflow_task_attempt_admission_digest_check
|
||||
CHECK (
|
||||
receipt_digest ~ '^[0-9a-f]{64}$' AND
|
||||
plan_digest ~ '^[0-9a-f]{64}$' AND
|
||||
step_run_digest ~ '^[0-9a-f]{64}$' AND
|
||||
generation_digest ~ '^[0-9a-f]{64}$' AND
|
||||
task_reconciliation_receipt_digest ~ '^[0-9a-f]{64}$' AND
|
||||
task_definition_digest ~ '^[0-9a-f]{64}$' AND
|
||||
execution_digest ~ '^[0-9a-f]{64}$' AND
|
||||
task_revision = concat(
|
||||
'qltd:v1:', source_revision::text, ':', task_definition_digest
|
||||
)
|
||||
),
|
||||
CONSTRAINT
|
||||
ql3_plugin_package_workflow_task_attempt_admission_json_check
|
||||
CHECK (
|
||||
jsonb_typeof(receipt_json) = 'object' AND
|
||||
octet_length(receipt_json::text) BETWEEN 2 AND 16384 AND
|
||||
receipt_json @> jsonb_build_object(
|
||||
'schema',
|
||||
'qinglong/plugin-package-workflow-task-attempt-admission@v1',
|
||||
'receiptDigest', receipt_digest,
|
||||
'attemptId', attempt_id,
|
||||
'planDigest', plan_digest,
|
||||
'runId', run_id,
|
||||
'stepRunId', step_run_id,
|
||||
'stepRunVersion', step_run_version,
|
||||
'stepRunDigest', step_run_digest,
|
||||
'resourceTaskId', resource_task_id,
|
||||
'taskReconciliationReceiptDigest',
|
||||
task_reconciliation_receipt_digest,
|
||||
'taskId', task_id,
|
||||
'taskRevision', task_revision,
|
||||
'taskDefinitionDigest', task_definition_digest,
|
||||
'executorType', executor_type,
|
||||
'executionDigest', execution_digest,
|
||||
'attemptNumber', attempt_number,
|
||||
'eventId', event_id,
|
||||
'runVersion', run_version,
|
||||
'runEventSequence', run_event_sequence,
|
||||
'admittedAtMs', admitted_at_ms
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE INDEX ql3_pp_workflow_task_attempt_candidate_idx
|
||||
ON "ql3"."plugin_package_workflow_task_attempt_admissions"
|
||||
(run_id, step_run_id, admitted_at_ms)
|
||||
`.trim(),
|
||||
`
|
||||
CREATE FUNCTION "ql3"."plugin_package_workflow_task_attempt_snapshot"(
|
||||
p_run_id varchar,
|
||||
p_step_run_id varchar
|
||||
)
|
||||
RETURNS TABLE(
|
||||
plan_json jsonb,
|
||||
reconciliation_json jsonb,
|
||||
execution_project_id varchar,
|
||||
execution_task_id varchar,
|
||||
execution_source_revision integer,
|
||||
execution_task_revision varchar,
|
||||
execution_source_content_digest char(64),
|
||||
execution_executor_type varchar,
|
||||
execution_plan_schema varchar,
|
||||
execution_plan_json jsonb,
|
||||
execution_content_digest char(64),
|
||||
execution_created_at_ms bigint
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, ql3
|
||||
AS $ql3$
|
||||
BEGIN
|
||||
IF NOT pg_has_role(session_user, 'ql3_runtime', 'member') THEN
|
||||
RAISE EXCEPTION 'Runtime authority is required'
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
workflow.plan_json,
|
||||
reconciliation.receipt_json,
|
||||
execution.project_id,
|
||||
execution.task_id,
|
||||
execution.source_revision,
|
||||
execution.task_revision,
|
||||
execution.source_content_digest,
|
||||
execution.executor_type,
|
||||
execution.plan_schema,
|
||||
execution.plan_json,
|
||||
execution.content_digest,
|
||||
execution.created_at_ms
|
||||
FROM "ql3"."plugin_package_workflow_admissions" AS workflow
|
||||
JOIN "ql3"."plugin_package_workflow_admission_steps" AS source
|
||||
ON source.plan_digest = workflow.plan_digest
|
||||
AND source.run_id = workflow.run_id
|
||||
AND source.step_run_id = p_step_run_id
|
||||
JOIN "ql3"."plugin_package_task_reconciliations" AS reconciliation
|
||||
ON reconciliation.generation_digest = workflow.generation_digest
|
||||
JOIN "ql3"."plugin_package_task_reconciliation_items" AS item
|
||||
ON item.generation_digest = reconciliation.generation_digest
|
||||
AND item.task_id =
|
||||
concat('pkg:', workflow.package_name, ':', source.task_id)
|
||||
AND item.disposition IN ('created', 'retained', 'updated')
|
||||
JOIN "ql3"."task_execution_revisions" AS execution
|
||||
ON execution.project_id = workflow.project_id
|
||||
AND execution.task_id = item.task_id
|
||||
AND execution.source_revision = item.revision
|
||||
AND execution.executor_type = 'remote_worker'
|
||||
AND execution.source_content_digest = item.content_digest
|
||||
WHERE workflow.run_id = p_run_id
|
||||
FOR KEY SHARE OF workflow, source, reconciliation, item, execution;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL
|
||||
ON "ql3"."plugin_package_workflow_task_attempt_admissions"
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT SELECT, INSERT
|
||||
ON "ql3"."plugin_package_workflow_task_attempt_admissions"
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
REVOKE ALL ON FUNCTION
|
||||
"ql3"."plugin_package_workflow_task_attempt_snapshot"(varchar, varchar)
|
||||
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
|
||||
ql3_package_executor, ql3_worker_ingress
|
||||
`.trim(),
|
||||
`
|
||||
GRANT EXECUTE ON FUNCTION
|
||||
"ql3"."plugin_package_workflow_task_attempt_snapshot"(varchar, varchar)
|
||||
TO ql3_runtime
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 45,
|
||||
migration_id =
|
||||
'pg-0046-plugin-package-workflow-task-attempt-admissions',
|
||||
capabilities = '${CAPABILITIES_V45}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 44
|
||||
AND migration_id = 'pg-0045-plugin-package-workflow-admissions'
|
||||
AND capabilities = '${CAPABILITIES_V44}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 44'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V45 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"plugin_package_workflow_admission":1,"plugin_package_workflow_task_attempt_admission":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V46 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"plugin_package_workflow_admission":1,"plugin_package_workflow_task_attempt_admission":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_management_plan":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0047WorkerCredentialManagementPlansMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0047-worker-credential-management-plans',
|
||||
statements: [
|
||||
`
|
||||
DO $ql3$
|
||||
DECLARE
|
||||
invalid_roles text[];
|
||||
BEGIN
|
||||
SELECT array_agg(expected.role_name ORDER BY expected.role_name)
|
||||
INTO invalid_roles
|
||||
FROM (
|
||||
VALUES
|
||||
('ql3_worker_credential_manager'),
|
||||
('ql3_worker_credential_executor')
|
||||
) AS expected(role_name)
|
||||
LEFT JOIN pg_catalog.pg_roles roles
|
||||
ON roles.rolname = expected.role_name
|
||||
WHERE roles.rolname IS NULL
|
||||
OR roles.rolcanlogin IS NOT TRUE
|
||||
OR roles.rolsuper IS NOT FALSE
|
||||
OR roles.rolcreatedb IS NOT FALSE
|
||||
OR roles.rolcreaterole IS NOT FALSE
|
||||
OR roles.rolreplication IS NOT FALSE
|
||||
OR roles.rolbypassrls IS NOT FALSE;
|
||||
|
||||
IF invalid_roles IS NOT NULL THEN
|
||||
RAISE EXCEPTION
|
||||
'required QingLong Worker credential roles are missing or privileged: %',
|
||||
invalid_roles
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
EXECUTE format(
|
||||
'GRANT CONNECT ON DATABASE %I TO ql3_worker_credential_manager, ql3_worker_credential_executor',
|
||||
current_database()
|
||||
);
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`GRANT USAGE ON SCHEMA "ql3" TO ql3_worker_credential_manager, ql3_worker_credential_executor`,
|
||||
`GRANT SELECT ON "ql3"."schema_migrations", "ql3"."schema_capabilities" TO ql3_worker_credential_manager, ql3_worker_credential_executor`,
|
||||
`
|
||||
CREATE TABLE "ql3"."worker_credential_management_plans" (
|
||||
action_ref varchar(255) PRIMARY KEY,
|
||||
authority_project_id varchar(128) NOT NULL,
|
||||
action varchar(16) NOT NULL,
|
||||
delivery_id varchar(36) NOT NULL,
|
||||
worker_id varchar(128) NOT NULL,
|
||||
credential_id varchar(64) NOT NULL,
|
||||
previous_credential_id varchar(64),
|
||||
credential_not_before_at_ms bigint NOT NULL,
|
||||
credential_expires_at_ms bigint NOT NULL,
|
||||
deployment_target_digest char(64) NOT NULL,
|
||||
deployment_generation varchar(128) NOT NULL,
|
||||
requested_by_type varchar(16) NOT NULL,
|
||||
requested_by_id varchar(255) NOT NULL,
|
||||
planned_at_ms bigint NOT NULL,
|
||||
expires_at_ms bigint NOT NULL,
|
||||
plan_digest char(64) NOT NULL,
|
||||
preview_digest char(64) NOT NULL,
|
||||
plan_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_worker_credential_management_plan_project_fk
|
||||
FOREIGN KEY (authority_project_id)
|
||||
REFERENCES "ql3"."projects" (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_worker_credential_management_plan_identity_check CHECK (
|
||||
action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$' AND
|
||||
action IN ('issue', 'rotate') AND
|
||||
delivery_id ~ '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' AND
|
||||
worker_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
credential_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$' AND
|
||||
(previous_credential_id IS NULL OR
|
||||
previous_credential_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$') AND
|
||||
deployment_generation ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
|
||||
requested_by_type = 'user' AND
|
||||
char_length(requested_by_id) BETWEEN 1 AND 255 AND
|
||||
requested_by_id !~ '[[:cntrl:]]'
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_management_plan_action_check CHECK (
|
||||
(action = 'issue' AND previous_credential_id IS NULL) OR
|
||||
(action = 'rotate' AND previous_credential_id IS NOT NULL AND
|
||||
previous_credential_id <> credential_id)
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_management_plan_digest_check CHECK (
|
||||
deployment_target_digest ~ '^[0-9a-f]{64}$' AND
|
||||
plan_digest ~ '^[0-9a-f]{64}$' AND
|
||||
preview_digest ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_management_plan_time_check CHECK (
|
||||
planned_at_ms >= 0 AND
|
||||
expires_at_ms > planned_at_ms AND
|
||||
expires_at_ms - planned_at_ms <= 900000 AND
|
||||
credential_not_before_at_ms >= planned_at_ms AND
|
||||
credential_expires_at_ms > credential_not_before_at_ms AND
|
||||
credential_expires_at_ms - credential_not_before_at_ms <= 63072000000
|
||||
),
|
||||
CONSTRAINT ql3_worker_credential_management_plan_json_check CHECK (
|
||||
jsonb_typeof(plan_json) = 'object' AND
|
||||
octet_length(plan_json::text) BETWEEN 2 AND 16384 AND
|
||||
plan_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/worker-credential-management-plan@v1',
|
||||
'actionRef', action_ref,
|
||||
'authorityProjectId', authority_project_id,
|
||||
'action', action,
|
||||
'planDigest', plan_digest,
|
||||
'previewDigest', preview_digest,
|
||||
'requestedBy', jsonb_build_object(
|
||||
'type', requested_by_type,
|
||||
'id', requested_by_id
|
||||
),
|
||||
'plannedAtMs', planned_at_ms,
|
||||
'expiresAtMs', expires_at_ms,
|
||||
'target', jsonb_build_object(
|
||||
'deliveryId', delivery_id,
|
||||
'workerId', worker_id,
|
||||
'credentialId', credential_id,
|
||||
'previousCredentialId', previous_credential_id,
|
||||
'credentialNotBeforeAtMs', credential_not_before_at_ms,
|
||||
'credentialExpiresAtMs', credential_expires_at_ms,
|
||||
'deploymentTargetDigest', deployment_target_digest,
|
||||
'deploymentGeneration', deployment_generation
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_worker_credential_management_plan_digest_key ON "ql3"."worker_credential_management_plans" (plan_digest)`,
|
||||
`CREATE UNIQUE INDEX ql3_worker_credential_management_plan_delivery_key ON "ql3"."worker_credential_management_plans" (delivery_id)`,
|
||||
`CREATE INDEX ql3_worker_credential_management_plan_expiry_idx ON "ql3"."worker_credential_management_plans" (expires_at_ms, action_ref)`,
|
||||
`REVOKE ALL ON "ql3"."worker_credential_management_plans" FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager, ql3_package_executor, ql3_worker_ingress, ql3_worker_credential_manager, ql3_worker_credential_executor`,
|
||||
`GRANT SELECT, INSERT ON "ql3"."worker_credential_management_plans" TO ql3_worker_credential_manager`,
|
||||
`GRANT SELECT ON "ql3"."worker_credential_management_plans" TO ql3_worker_credential_executor`,
|
||||
`GRANT SELECT ON "ql3"."projects", "ql3"."project_role_bindings" TO ql3_worker_credential_manager, ql3_worker_credential_executor`,
|
||||
`GRANT SELECT, INSERT ON "ql3"."security_audit_events" TO ql3_worker_credential_manager, ql3_worker_credential_executor`,
|
||||
`GRANT SELECT, INSERT, UPDATE ON "ql3"."approval_requests" TO ql3_worker_credential_manager`,
|
||||
`GRANT SELECT, UPDATE ON "ql3"."approval_requests" TO ql3_worker_credential_executor`,
|
||||
`GRANT SELECT, INSERT ON "ql3"."approved_action_dispatches" TO ql3_worker_credential_executor`,
|
||||
`GRANT SELECT, INSERT ON "ql3"."worker_credentials", "ql3"."worker_credential_mutations", "ql3"."worker_credential_deliveries", "ql3"."worker_credential_stage_discards" TO ql3_worker_credential_executor`,
|
||||
`GRANT EXECUTE ON FUNCTION "ql3"."lock_approval_policy_fence"(varchar, varchar, varchar, integer, integer) TO ql3_worker_credential_manager, ql3_worker_credential_executor`,
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 46,
|
||||
migration_id = 'pg-0047-worker-credential-management-plans',
|
||||
capabilities = '${CAPABILITIES_V46}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 45
|
||||
AND migration_id =
|
||||
'pg-0046-plugin-package-workflow-task-attempt-admissions'
|
||||
AND capabilities = '${CAPABILITIES_V45}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 45'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V46 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"plugin_package_workflow_admission":1,"plugin_package_workflow_task_attempt_admission":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_management_plan":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V47 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"plugin_package_workflow_admission":1,"plugin_package_workflow_task_attempt_admission":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_management_plan":1,"worker_credential_preapproved_activation":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0048WorkerCredentialPreapprovedActivationMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0048-worker-credential-preapproved-activation',
|
||||
statements: [
|
||||
`ALTER TABLE "ql3"."worker_credentials" DROP CONSTRAINT ql3_worker_credentials_lifetime_check`,
|
||||
`ALTER TABLE "ql3"."worker_credentials" ADD CONSTRAINT ql3_worker_credentials_lifetime_check CHECK (created_at_ms >= 0 AND not_before_at_ms >= 0 AND expires_at_ms > GREATEST(created_at_ms, not_before_at_ms))`,
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 47,
|
||||
migration_id = 'pg-0048-worker-credential-preapproved-activation',
|
||||
capabilities = '${CAPABILITIES_V47}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 46
|
||||
AND migration_id = 'pg-0047-worker-credential-management-plans'
|
||||
AND capabilities = '${CAPABILITIES_V46}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 46'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V47 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"plugin_package_workflow_admission":1,"plugin_package_workflow_task_attempt_admission":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_management_plan":1,"worker_credential_preapproved_activation":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
const CAPABILITIES_V48 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"plugin_package_workflow_admission":1,"plugin_package_workflow_task_attempt_admission":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_execution_receipt":1,"worker_credential_management_plan":1,"worker_credential_preapproved_activation":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
|
||||
export const pg0049WorkerCredentialExecutionReceiptsMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0049-worker-credential-execution-receipts',
|
||||
statements: [
|
||||
`REVOKE ALL ON "ql3"."approved_action_executions" FROM ql3_worker_credential_manager, ql3_worker_credential_executor`,
|
||||
`GRANT SELECT, INSERT, UPDATE ON "ql3"."approved_action_executions" TO ql3_worker_credential_executor`,
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 48,
|
||||
migration_id = 'pg-0049-worker-credential-execution-receipts',
|
||||
capabilities = '${CAPABILITIES_V48}'::jsonb,
|
||||
updated_at_ms = floor(
|
||||
extract(epoch FROM transaction_timestamp()) * 1000
|
||||
)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 47
|
||||
AND migration_id = 'pg-0048-worker-credential-preapproved-activation'
|
||||
AND capabilities = '${CAPABILITIES_V47}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 47'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
const CAPABILITIES_V48 =
|
||||
'{"api_credential":1,"api_credential_admin":1,"api_credential_pepper_binding":1,"approved_action":1,"approved_action_execution":1,"cluster_execution_revision":1,"cluster_recovery":1,"cluster_recovery_claim":1,"cluster_scheduler_admission":1,"database_role_grants":1,"identity_admin":1,"plugin_package_admission":1,"plugin_package_authority_split":1,"plugin_package_automation_publication":1,"plugin_package_automation_start_guard":1,"plugin_package_identity_keyset_ledger":1,"plugin_package_install":1,"plugin_package_lifecycle":1,"plugin_package_lifecycle_plan":1,"plugin_package_management_quota":1,"plugin_package_materialized_revision":1,"plugin_package_proposal":1,"plugin_package_publisher_provenance":1,"plugin_package_publisher_trust_authority":1,"plugin_package_publisher_trust_transition":1,"plugin_package_quarantine":1,"plugin_package_task_reconciliation":1,"plugin_package_workflow_admission":1,"plugin_package_workflow_task_attempt_admission":1,"project_policy":1,"project_tool_definition_snapshot":1,"run_core":1,"run_dispatch_lease":1,"run_retry_policy":1,"security_audit":1,"security_audit_query":1,"step_run":1,"task_definition":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_evidence":1,"tool_execution_failure_completion":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"trigger_definition":1,"worker_attestation":1,"worker_credential":1,"worker_credential_delivery":1,"worker_credential_execution_receipt":1,"worker_credential_management_plan":1,"worker_credential_preapproved_activation":1,"worker_credential_stage_discard":1,"worker_session":1}';
|
||||
export const CAPABILITIES_V49 = CAPABILITIES_V48.replace(
|
||||
'"worker_credential_management_plan":1,',
|
||||
'"worker_credential_management_identity_keyset_ledger":1,"worker_credential_management_plan":1,"worker_credential_management_quota":1,',
|
||||
);
|
||||
|
||||
export const pg0050WorkerCredentialManagementBoundaryMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0050-worker-credential-management-boundary',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."worker_credential_management_quota_buckets" (
|
||||
project_id varchar(128) NOT NULL,
|
||||
subject_type varchar(32) NOT NULL,
|
||||
subject_id varchar(255) NOT NULL,
|
||||
operation varchar(64) NOT NULL,
|
||||
window_started_at_ms bigint NOT NULL,
|
||||
consumed_count integer NOT NULL,
|
||||
receipt_ids jsonb NOT NULL,
|
||||
updated_at_ms bigint NOT NULL,
|
||||
CONSTRAINT worker_credential_management_quota_buckets_pkey
|
||||
PRIMARY KEY (project_id, subject_type, subject_id, operation),
|
||||
CONSTRAINT ql3_worker_credential_management_quota_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects"(id) ON DELETE RESTRICT,
|
||||
CONSTRAINT ql3_worker_credential_management_quota_identity_check
|
||||
CHECK (subject_type = 'user' AND char_length(subject_id) BETWEEN 1 AND 255
|
||||
AND subject_id !~ '[[:cntrl:]]'),
|
||||
CONSTRAINT ql3_worker_credential_management_quota_operation_check
|
||||
CHECK (operation IN ('worker-credential.plan', 'worker-credential.propose',
|
||||
'worker-credential.decide', 'worker-credential.inspect')),
|
||||
CONSTRAINT ql3_worker_credential_management_quota_window_check
|
||||
CHECK (window_started_at_ms >= 0 AND consumed_count BETWEEN 1 AND 1000
|
||||
AND updated_at_ms >= window_started_at_ms),
|
||||
CONSTRAINT ql3_worker_credential_management_quota_receipts_check
|
||||
CHECK (jsonb_typeof(receipt_ids) = 'array'
|
||||
AND jsonb_array_length(receipt_ids) = consumed_count
|
||||
AND octet_length(receipt_ids::text) BETWEEN 3 AND 262144)
|
||||
)
|
||||
`.trim(),
|
||||
`REVOKE ALL ON "ql3"."worker_credential_management_quota_buckets" FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager, ql3_package_executor, ql3_worker_ingress, ql3_worker_credential_executor`,
|
||||
`GRANT SELECT, INSERT, UPDATE ON "ql3"."worker_credential_management_quota_buckets" TO ql3_worker_credential_manager`,
|
||||
`ALTER TABLE "ql3"."plugin_package_identity_keyset_ledger" DROP CONSTRAINT ql3_plugin_package_identity_keyset_authority_check`,
|
||||
`ALTER TABLE "ql3"."plugin_package_identity_keyset_ledger" ADD CONSTRAINT ql3_plugin_package_identity_keyset_authority_check CHECK (authority IN ('plugin-package-management', 'worker-credential-management'))`,
|
||||
`REVOKE ALL ON "ql3"."plugin_package_identity_keyset_ledger" FROM ql3_worker_credential_manager, ql3_worker_credential_executor`,
|
||||
`GRANT SELECT, INSERT, UPDATE ON "ql3"."plugin_package_identity_keyset_ledger" TO ql3_worker_credential_manager`,
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 49,
|
||||
migration_id = 'pg-0050-worker-credential-management-boundary',
|
||||
capabilities = '${CAPABILITIES_V49}'::jsonb,
|
||||
updated_at_ms = floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 48
|
||||
AND migration_id = 'pg-0049-worker-credential-execution-receipts'
|
||||
AND capabilities = '${CAPABILITIES_V48}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 48'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import { CAPABILITIES_V49 } from './pg-0050-worker-credential-management-boundary';
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const CAPABILITIES_V50 = CAPABILITIES_V49.replace(
|
||||
'"approved_action":1,',
|
||||
'"approved_action":1,"automation_management_boundary":1,',
|
||||
);
|
||||
|
||||
export const pg0051AutomationManagementBoundaryMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0051-automation-management-boundary',
|
||||
statements: [
|
||||
`
|
||||
DO $ql3$
|
||||
DECLARE
|
||||
role_invalid boolean;
|
||||
BEGIN
|
||||
SELECT roles.rolname IS NULL
|
||||
OR roles.rolcanlogin IS NOT TRUE
|
||||
OR roles.rolsuper IS NOT FALSE
|
||||
OR roles.rolcreatedb IS NOT FALSE
|
||||
OR roles.rolcreaterole IS NOT FALSE
|
||||
OR roles.rolreplication IS NOT FALSE
|
||||
OR roles.rolbypassrls IS NOT FALSE
|
||||
INTO role_invalid
|
||||
FROM (SELECT 1) AS expected
|
||||
LEFT JOIN pg_catalog.pg_roles AS roles
|
||||
ON roles.rolname = 'ql3_automation_manager';
|
||||
|
||||
IF role_invalid IS NOT FALSE THEN
|
||||
RAISE EXCEPTION
|
||||
'required QingLong automation manager role is missing or privileged'
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
EXECUTE format(
|
||||
'GRANT CONNECT ON DATABASE %I TO ql3_automation_manager',
|
||||
current_database()
|
||||
);
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`GRANT USAGE ON SCHEMA "ql3" TO ql3_automation_manager`,
|
||||
`REVOKE ALL ON ALL TABLES IN SCHEMA "ql3" FROM ql3_automation_manager`,
|
||||
`REVOKE ALL ON ALL FUNCTIONS IN SCHEMA "ql3" FROM ql3_automation_manager`,
|
||||
`GRANT SELECT ON "ql3"."schema_migrations", "ql3"."schema_capabilities", "ql3"."projects", "ql3"."project_role_bindings", "ql3"."plugin_package_task_ownerships" TO ql3_automation_manager`,
|
||||
`GRANT SELECT, INSERT ON "ql3"."security_audit_events" TO ql3_automation_manager`,
|
||||
`GRANT SELECT, INSERT, UPDATE ON "ql3"."task_definitions", "ql3"."triggers", "ql3"."trigger_schedules" TO ql3_automation_manager`,
|
||||
`GRANT SELECT, INSERT ON "ql3"."task_definition_revisions", "ql3"."task_execution_revisions", "ql3"."trigger_revisions" TO ql3_automation_manager`,
|
||||
`REVOKE SELECT, INSERT, UPDATE ON "ql3"."task_definitions", "ql3"."triggers", "ql3"."trigger_schedules" FROM ql3_admin`,
|
||||
`REVOKE SELECT, INSERT ON "ql3"."task_definition_revisions", "ql3"."task_execution_revisions", "ql3"."trigger_revisions" FROM ql3_admin`,
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 50,
|
||||
migration_id = 'pg-0051-automation-management-boundary',
|
||||
capabilities = '${CAPABILITIES_V50}'::jsonb,
|
||||
updated_at_ms = floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 49
|
||||
AND migration_id = 'pg-0050-worker-credential-management-boundary'
|
||||
AND capabilities = '${CAPABILITIES_V49}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 49'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { CAPABILITIES_V50 } from './pg-0051-automation-management-boundary';
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const CAPABILITIES_V51 = CAPABILITIES_V50.replace(
|
||||
'"automation_management_boundary":1,',
|
||||
'"automation_management_boundary":1,"automation_management_identity_keyset_ledger":1,',
|
||||
);
|
||||
|
||||
export const pg0052AutomationManagementIdentityKeysetLedgerMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0052-automation-management-identity-keyset-ledger',
|
||||
statements: [
|
||||
`ALTER TABLE "ql3"."plugin_package_identity_keyset_ledger" DROP CONSTRAINT ql3_plugin_package_identity_keyset_authority_check`,
|
||||
`ALTER TABLE "ql3"."plugin_package_identity_keyset_ledger" ADD CONSTRAINT ql3_plugin_package_identity_keyset_authority_check CHECK (authority IN ('plugin-package-management', 'worker-credential-management', 'automation-management'))`,
|
||||
`REVOKE ALL ON "ql3"."plugin_package_identity_keyset_ledger" FROM ql3_automation_manager`,
|
||||
`GRANT SELECT, INSERT, UPDATE ON "ql3"."plugin_package_identity_keyset_ledger" TO ql3_automation_manager`,
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 51,
|
||||
migration_id = 'pg-0052-automation-management-identity-keyset-ledger',
|
||||
capabilities = '${CAPABILITIES_V51}'::jsonb,
|
||||
updated_at_ms = floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 50
|
||||
AND migration_id = 'pg-0051-automation-management-boundary'
|
||||
AND capabilities = '${CAPABILITIES_V50}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 50'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { CAPABILITIES_V51 } from './pg-0052-automation-management-identity-keyset-ledger';
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const CAPABILITIES_V52 = CAPABILITIES_V51.replace(
|
||||
'"plugin_package_workflow_admission":1,',
|
||||
'"plugin_package_workflow_admission":1,"plugin_package_workflow_run_list":1,',
|
||||
);
|
||||
|
||||
export const pg0053PluginPackageWorkflowRunListIndexMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0053-plugin-package-workflow-run-list-index',
|
||||
statements: [
|
||||
`CREATE INDEX ql3_plugin_package_workflow_admission_workflow_history_idx ON "ql3"."plugin_package_workflow_admissions" (project_id, package_name, workflow_id, admitted_at_ms, run_id)`,
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 52,
|
||||
migration_id = 'pg-0053-plugin-package-workflow-run-list-index',
|
||||
capabilities = '${CAPABILITIES_V52}'::jsonb,
|
||||
updated_at_ms = floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 51
|
||||
AND migration_id = 'pg-0052-automation-management-identity-keyset-ledger'
|
||||
AND capabilities = '${CAPABILITIES_V51}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 51'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { CAPABILITIES_V52 } from './pg-0053-plugin-package-workflow-run-list-index';
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const CAPABILITIES_V53 = CAPABILITIES_V52.replace(
|
||||
'"approved_action":1,',
|
||||
'"approval_management_boundary":1,"approved_action":1,',
|
||||
);
|
||||
|
||||
export const pg0054ApprovalManagementBoundaryMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0054-approval-management-boundary',
|
||||
statements: [
|
||||
`
|
||||
DO $ql3$
|
||||
DECLARE
|
||||
role_invalid boolean;
|
||||
BEGIN
|
||||
SELECT roles.rolname IS NULL
|
||||
OR roles.rolcanlogin IS NOT TRUE
|
||||
OR roles.rolsuper IS NOT FALSE
|
||||
OR roles.rolcreatedb IS NOT FALSE
|
||||
OR roles.rolcreaterole IS NOT FALSE
|
||||
OR roles.rolreplication IS NOT FALSE
|
||||
OR roles.rolbypassrls IS NOT FALSE
|
||||
INTO role_invalid
|
||||
FROM (SELECT 1) AS expected
|
||||
LEFT JOIN pg_catalog.pg_roles AS roles
|
||||
ON roles.rolname = 'ql3_approval_manager';
|
||||
|
||||
IF role_invalid IS NOT FALSE THEN
|
||||
RAISE EXCEPTION
|
||||
'required QingLong approval manager role is missing or privileged'
|
||||
USING ERRCODE = 'insufficient_privilege';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
EXECUTE format(
|
||||
'GRANT CONNECT ON DATABASE %I TO ql3_approval_manager',
|
||||
current_database()
|
||||
);
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`GRANT USAGE ON SCHEMA "ql3" TO ql3_approval_manager`,
|
||||
`REVOKE ALL ON ALL TABLES IN SCHEMA "ql3" FROM ql3_approval_manager`,
|
||||
`REVOKE ALL ON ALL FUNCTIONS IN SCHEMA "ql3" FROM ql3_approval_manager`,
|
||||
`GRANT SELECT ON "ql3"."schema_migrations", "ql3"."schema_capabilities", "ql3"."projects", "ql3"."project_role_bindings", "ql3"."tool_invocation_preview_artifacts" TO ql3_approval_manager`,
|
||||
`GRANT SELECT, INSERT ON "ql3"."security_audit_events" TO ql3_approval_manager`,
|
||||
`GRANT SELECT, UPDATE ON "ql3"."approval_requests" TO ql3_approval_manager`,
|
||||
`GRANT SELECT, INSERT, UPDATE ON "ql3"."plugin_package_identity_keyset_ledger" TO ql3_approval_manager`,
|
||||
`GRANT EXECUTE ON FUNCTION "ql3"."lock_approval_policy_fence"(varchar, varchar, varchar, integer, integer) TO ql3_approval_manager`,
|
||||
`ALTER TABLE "ql3"."plugin_package_identity_keyset_ledger" DROP CONSTRAINT ql3_plugin_package_identity_keyset_authority_check`,
|
||||
`ALTER TABLE "ql3"."plugin_package_identity_keyset_ledger" ADD CONSTRAINT ql3_plugin_package_identity_keyset_authority_check CHECK (authority IN ('plugin-package-management', 'worker-credential-management', 'automation-management', 'approval-management'))`,
|
||||
`
|
||||
DO $ql3$
|
||||
BEGIN
|
||||
UPDATE "ql3"."schema_capabilities"
|
||||
SET contract_version = 53,
|
||||
migration_id = 'pg-0054-approval-management-boundary',
|
||||
capabilities = '${CAPABILITIES_V53}'::jsonb,
|
||||
updated_at_ms = floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint
|
||||
WHERE contract_name = 'control-core'
|
||||
AND contract_version = 52
|
||||
AND migration_id = 'pg-0053-plugin-package-workflow-run-list-index'
|
||||
AND capabilities = '${CAPABILITIES_V52}'::jsonb;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'control-core capability is not at version 52'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,262 @@
|
||||
import {
|
||||
MigrationStreamHistoryCorruptionError,
|
||||
type MigrationStreamRecord,
|
||||
type MigrationStreamStore,
|
||||
type MigrationStreamTransaction,
|
||||
} from '@qinglong/runtime-core';
|
||||
import type {
|
||||
PostgresClient as PostgresMigrationClient,
|
||||
PostgresPool as PostgresMigrationPool,
|
||||
PostgresQueryable as PostgresMigrationQueryable,
|
||||
PostgresQueryResult as PostgresMigrationQueryResult,
|
||||
} from '@qinglong/runtime-core';
|
||||
|
||||
export type {
|
||||
PostgresClient as PostgresMigrationClient,
|
||||
PostgresPool as PostgresMigrationPool,
|
||||
PostgresQueryable as PostgresMigrationQueryable,
|
||||
PostgresQueryResult as PostgresMigrationQueryResult,
|
||||
} from '@qinglong/runtime-core';
|
||||
|
||||
export const POSTGRESQL_MAIN_MIGRATION_STREAM_ID = 'postgresql-main';
|
||||
export const POSTGRESQL_MIGRATION_SCHEMA = 'ql3';
|
||||
export const POSTGRESQL_MIGRATION_HISTORY_TABLE = 'schema_migrations';
|
||||
|
||||
const POSTGRESQL_MIGRATION_LOCK_KEY = [0x514c, 0x0300] as const;
|
||||
const POSTGRESQL_MIGRATION_STATEMENT_TIMEOUT_MS = 15_000;
|
||||
const POSTGRESQL_MIGRATION_LOCK_TIMEOUT_MS = 5_000;
|
||||
const POSTGRESQL_MIGRATION_IDLE_TRANSACTION_TIMEOUT_MS = 15_000;
|
||||
|
||||
export type PostgresMigrationContext = PostgresMigrationQueryable;
|
||||
|
||||
export class PostgresMigrationLeaderUnavailableError extends Error {
|
||||
constructor() {
|
||||
super('PostgreSQL migration leader lock is unavailable');
|
||||
this.name = 'PostgresMigrationLeaderUnavailableError';
|
||||
}
|
||||
}
|
||||
|
||||
interface HistoryRow extends Record<string, unknown> {
|
||||
streamId: unknown;
|
||||
dialect: unknown;
|
||||
migrationId: unknown;
|
||||
checksum: unknown;
|
||||
appliedAtMs: unknown;
|
||||
}
|
||||
|
||||
interface AdvisoryLockRow extends Record<string, unknown> {
|
||||
acquired: unknown;
|
||||
}
|
||||
|
||||
const CREATE_HISTORY_SQL = `
|
||||
CREATE TABLE IF NOT EXISTS "${POSTGRESQL_MIGRATION_SCHEMA}"."${POSTGRESQL_MIGRATION_HISTORY_TABLE}" (
|
||||
migration_id varchar(128) PRIMARY KEY,
|
||||
stream_id varchar(64) NOT NULL,
|
||||
dialect varchar(16) NOT NULL
|
||||
CONSTRAINT ql3_schema_migrations_dialect_check
|
||||
CHECK (dialect = 'postgresql'),
|
||||
checksum char(64) NOT NULL
|
||||
CONSTRAINT ql3_schema_migrations_checksum_check
|
||||
CHECK (checksum ~ '^[0-9a-f]{64}$'),
|
||||
applied_at_ms bigint NOT NULL
|
||||
CONSTRAINT ql3_schema_migrations_applied_at_check
|
||||
CHECK (applied_at_ms >= 0)
|
||||
)`.trim();
|
||||
|
||||
const SELECT_HISTORY_SQL = `
|
||||
SELECT
|
||||
stream_id AS "streamId",
|
||||
dialect,
|
||||
migration_id AS "migrationId",
|
||||
checksum,
|
||||
applied_at_ms AS "appliedAtMs"
|
||||
FROM "${POSTGRESQL_MIGRATION_SCHEMA}"."${POSTGRESQL_MIGRATION_HISTORY_TABLE}"
|
||||
WHERE migration_id = $1
|
||||
`.trim();
|
||||
|
||||
const SELECT_ALL_HISTORY_SQL = `
|
||||
SELECT
|
||||
stream_id AS "streamId",
|
||||
dialect,
|
||||
migration_id AS "migrationId",
|
||||
checksum,
|
||||
applied_at_ms AS "appliedAtMs"
|
||||
FROM "${POSTGRESQL_MIGRATION_SCHEMA}"."${POSTGRESQL_MIGRATION_HISTORY_TABLE}"
|
||||
ORDER BY applied_at_ms, migration_id
|
||||
`.trim();
|
||||
|
||||
const INSERT_HISTORY_SQL = `
|
||||
INSERT INTO "${POSTGRESQL_MIGRATION_SCHEMA}"."${POSTGRESQL_MIGRATION_HISTORY_TABLE}" (
|
||||
migration_id,
|
||||
stream_id,
|
||||
dialect,
|
||||
checksum,
|
||||
applied_at_ms
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
`.trim();
|
||||
|
||||
function parseAppliedAtMs(value: unknown, migrationId: string): number {
|
||||
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'string' && /^(0|[1-9]\d*)$/.test(value)) {
|
||||
const parsed = Number(value);
|
||||
if (Number.isSafeInteger(parsed)) return parsed;
|
||||
}
|
||||
throw new MigrationStreamHistoryCorruptionError(migrationId);
|
||||
}
|
||||
|
||||
async function findHistoryRecord(
|
||||
queryable: PostgresMigrationQueryable,
|
||||
migrationId: string,
|
||||
): Promise<MigrationStreamRecord | null> {
|
||||
const result = await queryable.query<HistoryRow>(SELECT_HISTORY_SQL, [
|
||||
migrationId,
|
||||
]);
|
||||
if (result.rows.length === 0) return null;
|
||||
if (result.rows.length !== 1) {
|
||||
throw new MigrationStreamHistoryCorruptionError(migrationId);
|
||||
}
|
||||
const row = result.rows[0];
|
||||
if (!row) {
|
||||
throw new MigrationStreamHistoryCorruptionError(migrationId);
|
||||
}
|
||||
return {
|
||||
streamId: row.streamId as string,
|
||||
dialect: row.dialect as 'postgresql',
|
||||
migrationId: row.migrationId as string,
|
||||
checksum: row.checksum as string,
|
||||
appliedAtMs: parseAppliedAtMs(row.appliedAtMs, migrationId),
|
||||
};
|
||||
}
|
||||
|
||||
export async function readPostgresMigrationHistory(
|
||||
queryable: PostgresMigrationQueryable,
|
||||
): Promise<readonly MigrationStreamRecord[]> {
|
||||
const result = await queryable.query<HistoryRow>(SELECT_ALL_HISTORY_SQL);
|
||||
return result.rows.map((row) => ({
|
||||
streamId: row.streamId as string,
|
||||
dialect: row.dialect as 'postgresql',
|
||||
migrationId: row.migrationId as string,
|
||||
checksum: row.checksum as string,
|
||||
appliedAtMs: parseAppliedAtMs(
|
||||
row.appliedAtMs,
|
||||
typeof row.migrationId === 'string' ? row.migrationId : 'unknown',
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
async function configureTransaction(
|
||||
client: PostgresMigrationClient,
|
||||
): Promise<void> {
|
||||
await client.query(`SELECT set_config('statement_timeout', $1, true)`, [
|
||||
`${POSTGRESQL_MIGRATION_STATEMENT_TIMEOUT_MS}ms`,
|
||||
]);
|
||||
await client.query(`SELECT set_config('lock_timeout', $1, true)`, [
|
||||
`${POSTGRESQL_MIGRATION_LOCK_TIMEOUT_MS}ms`,
|
||||
]);
|
||||
await client.query(
|
||||
`SELECT set_config('idle_in_transaction_session_timeout', $1, true)`,
|
||||
[`${POSTGRESQL_MIGRATION_IDLE_TRANSACTION_TIMEOUT_MS}ms`],
|
||||
);
|
||||
}
|
||||
|
||||
async function acquireLeaderLock(
|
||||
client: PostgresMigrationClient,
|
||||
): Promise<void> {
|
||||
const result = await client.query<AdvisoryLockRow>(
|
||||
'SELECT pg_try_advisory_xact_lock($1, $2) AS acquired',
|
||||
POSTGRESQL_MIGRATION_LOCK_KEY,
|
||||
);
|
||||
if (result.rows.length !== 1 || result.rows[0]?.acquired !== true) {
|
||||
throw new PostgresMigrationLeaderUnavailableError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL migration history adapter without a runtime dependency on `pg`.
|
||||
* The cluster-only package owns the concrete Pool binding; edge and standalone
|
||||
* builds can compile this contract without installing or importing the driver.
|
||||
*/
|
||||
export class PostgresMigrationStreamStore
|
||||
implements MigrationStreamStore<PostgresMigrationContext>
|
||||
{
|
||||
constructor(private readonly pool: PostgresMigrationPool) {}
|
||||
|
||||
async ensureHistory(): Promise<void> {
|
||||
await this.withLeaderTransaction(async (client) => {
|
||||
await client.query(
|
||||
`CREATE SCHEMA IF NOT EXISTS "${POSTGRESQL_MIGRATION_SCHEMA}"`,
|
||||
);
|
||||
await client.query(CREATE_HISTORY_SQL);
|
||||
});
|
||||
}
|
||||
|
||||
async findById(migrationId: string): Promise<MigrationStreamRecord | null> {
|
||||
return findHistoryRecord(this.pool, migrationId);
|
||||
}
|
||||
|
||||
async listAll(): Promise<readonly MigrationStreamRecord[]> {
|
||||
return readPostgresMigrationHistory(this.pool);
|
||||
}
|
||||
|
||||
async transaction<T>(
|
||||
work: (
|
||||
transaction: MigrationStreamTransaction<PostgresMigrationContext>,
|
||||
) => Promise<T>,
|
||||
): Promise<T> {
|
||||
return this.withLeaderTransaction(async (client) =>
|
||||
work({
|
||||
context: client,
|
||||
findById: (migrationId) => findHistoryRecord(client, migrationId),
|
||||
insert: async (record) => {
|
||||
if (
|
||||
record.streamId !== POSTGRESQL_MAIN_MIGRATION_STREAM_ID ||
|
||||
record.dialect !== 'postgresql'
|
||||
) {
|
||||
throw new TypeError(
|
||||
'PostgreSQL migration history record has the wrong stream or dialect',
|
||||
);
|
||||
}
|
||||
await client.query(INSERT_HISTORY_SQL, [
|
||||
record.migrationId,
|
||||
record.streamId,
|
||||
record.dialect,
|
||||
record.checksum,
|
||||
record.appliedAtMs,
|
||||
]);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async withLeaderTransaction<T>(
|
||||
work: (client: PostgresMigrationClient) => Promise<T>,
|
||||
): Promise<T> {
|
||||
const client = await this.pool.connect();
|
||||
let began = false;
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
began = true;
|
||||
await configureTransaction(client);
|
||||
await acquireLeaderLock(client);
|
||||
const result = await work(client);
|
||||
await client.query('COMMIT');
|
||||
began = false;
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (began) {
|
||||
try {
|
||||
await client.query('ROLLBACK');
|
||||
} catch {
|
||||
// Preserve the migration error. A broken connection is discarded by
|
||||
// the concrete driver when release() runs.
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { createHash } from 'crypto';
|
||||
import type { MigrationStreamStep } from '@qinglong/runtime-core';
|
||||
import type { PostgresMigrationContext } from './postgresMigrationStreamStore';
|
||||
|
||||
export interface PostgresSqlMigrationDefinition {
|
||||
readonly id: string;
|
||||
readonly statements: readonly string[];
|
||||
}
|
||||
|
||||
export function checksumPostgresStatements(
|
||||
statements: readonly string[],
|
||||
): string {
|
||||
return createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify({
|
||||
format: 1,
|
||||
statements,
|
||||
}),
|
||||
)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
export function definePostgresSqlMigration(
|
||||
definition: PostgresSqlMigrationDefinition,
|
||||
): MigrationStreamStep<PostgresMigrationContext> {
|
||||
const statements = Object.freeze([...definition.statements]);
|
||||
return Object.freeze({
|
||||
id: definition.id,
|
||||
checksum: checksumPostgresStatements(statements),
|
||||
async up(context: PostgresMigrationContext) {
|
||||
for (const statement of statements) await context.query(statement);
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user