Files
qinglong/packages/ql3-cluster-postgres/src/migrations/pg-0041-plugin-package-lifecycle.ts
T

1320 lines
56 KiB
TypeScript

import { definePostgresSqlMigration } from './sqlMigration';
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}';
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}';
export const pg0041PluginPackageLifecycleMigration =
definePostgresSqlMigration({
id: 'pg-0041-plugin-package-lifecycle',
statements: [
`
ALTER TABLE "ql3"."approved_action_dispatches"
ADD CONSTRAINT ql3_approved_action_dispatch_lifecycle_key
UNIQUE (
dispatch_id, project_id, action_type, action_digest, preview_digest
)
`.trim(),
`
CREATE TABLE "ql3"."plugin_package_lifecycle_events" (
event_digest char(64) PRIMARY KEY,
mutation_id varchar(128) NOT NULL,
dispatch_id varchar(128) NOT NULL,
approved_action_type varchar(128) 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,
install_version integer NOT NULL,
install_record_digest char(64) NOT NULL,
expected_version integer NOT NULL,
expected_disposition varchar(16) NOT NULL,
expected_event_digest char(64),
generation_digest char(64) NOT NULL,
materialized_revision_digest char(64) NOT NULL,
current_tool_snapshot_digest char(64) NOT NULL,
reference_graph_digest char(64) NOT NULL,
impact_digest char(64) NOT NULL,
action_digest char(64) NOT NULL,
requested_by_type varchar(16) NOT NULL,
requested_by_id varchar(255) NOT NULL,
approved_by_type varchar(16) NOT NULL,
approved_by_id varchar(255) NOT NULL,
authorization_mode varchar(32) NOT NULL,
occurred_at_ms bigint NOT NULL,
event_json jsonb NOT NULL,
CONSTRAINT ql3_plugin_package_lifecycle_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_lifecycle_dispatch_fk FOREIGN KEY (
dispatch_id, project_id, approved_action_type, action_digest,
impact_digest
) REFERENCES "ql3"."approved_action_dispatches" (
dispatch_id, project_id, action_type, action_digest, preview_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_previous_event_fk FOREIGN KEY (
expected_event_digest
) REFERENCES "ql3"."plugin_package_lifecycle_events" (event_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_identity_check CHECK (
mutation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
dispatch_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_version BETWEEN 1 AND 2147483647 AND
action IN ('disable', 'enable', 'uninstall') AND
approved_action_type = 'plugin_package.lifecycle.' || action
),
CONSTRAINT ql3_plugin_package_lifecycle_expectation_check CHECK (
(action = 'disable' AND expected_disposition = 'active') OR
(action IN ('enable', 'uninstall') AND
expected_disposition = 'disabled')
),
CONSTRAINT ql3_plugin_package_lifecycle_origin_check CHECK (
(expected_version = 0 AND expected_disposition = 'active' AND
expected_event_digest IS NULL) OR
(expected_version BETWEEN 1 AND 2147483646 AND
expected_event_digest IS NOT NULL)
),
CONSTRAINT ql3_plugin_package_lifecycle_subject_check CHECK (
requested_by_type = 'user' AND approved_by_type = 'user' AND
octet_length(requested_by_id) BETWEEN 1 AND 255 AND
octet_length(approved_by_id) BETWEEN 1 AND 255 AND
authorization_mode IN (
'human_confirmation', 'separation_of_duty'
) AND (
(authorization_mode = 'human_confirmation' AND
requested_by_id = approved_by_id) OR
(authorization_mode = 'separation_of_duty' AND
requested_by_id <> approved_by_id)
)
),
CONSTRAINT ql3_plugin_package_lifecycle_digest_check CHECK (
event_digest ~ '^[0-9a-f]{64}$' AND
lock_digest ~ '^[0-9a-f]{64}$' AND
install_record_digest ~ '^[0-9a-f]{64}$' AND
generation_digest ~ '^[0-9a-f]{64}$' AND
materialized_revision_digest ~ '^[0-9a-f]{64}$' AND
current_tool_snapshot_digest ~ '^[0-9a-f]{64}$' AND
reference_graph_digest ~ '^[0-9a-f]{64}$' AND
impact_digest ~ '^[0-9a-f]{64}$' AND
action_digest ~ '^[0-9a-f]{64}$' AND
(expected_event_digest IS NULL OR
expected_event_digest ~ '^[0-9a-f]{64}$')
),
CONSTRAINT ql3_plugin_package_lifecycle_json_check CHECK (
jsonb_typeof(event_json) = 'object' AND
octet_length(event_json::text) BETWEEN 2 AND 524288 AND
event_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-lifecycle-event@v1',
'mutationId', mutation_id,
'dispatchId', dispatch_id,
'actionDigest', action_digest,
'authorizationMode', authorization_mode,
'occurredAtMs', occurred_at_ms,
'eventDigest', event_digest
) AND
event_json -> 'impact' @> jsonb_build_object(
'schema', 'qinglong/plugin-package-lifecycle-impact@v1',
'action', action,
'generationDigest', generation_digest,
'materializedRevisionDigest', materialized_revision_digest,
'currentToolSnapshotDigest', current_tool_snapshot_digest,
'referenceGraphDigest', reference_graph_digest,
'impactDigest', impact_digest
) AND
event_json -> 'impact' -> 'target' @> jsonb_build_object(
'projectId', project_id,
'packageName', package_name,
'installationId', installation_id,
'lockDigest', lock_digest,
'installVersion', install_version,
'installRecordDigest', install_record_digest
) AND
event_json -> 'impact' -> 'expected' @> jsonb_build_object(
'version', expected_version,
'disposition', expected_disposition,
'eventDigest', expected_event_digest
) AND
event_json -> 'requestedBy' @> jsonb_build_object(
'type', requested_by_type, 'id', requested_by_id
) AND
event_json -> 'approvedBy' @> jsonb_build_object(
'type', approved_by_type, 'id', approved_by_id
)
),
CONSTRAINT ql3_plugin_package_lifecycle_time_check CHECK (
occurred_at_ms >= 0
)
)
`.trim(),
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_mutation_key ON "ql3"."plugin_package_lifecycle_events" (mutation_id)`,
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_dispatch_key ON "ql3"."plugin_package_lifecycle_events" (dispatch_id)`,
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_target_version_key ON "ql3"."plugin_package_lifecycle_events" (project_id, package_name, installation_id, lock_digest, expected_version)`,
`CREATE INDEX ql3_plugin_package_lifecycle_project_idx ON "ql3"."plugin_package_lifecycle_events" (project_id, package_name, occurred_at_ms, event_digest)`,
`
CREATE TABLE "ql3"."plugin_package_lifecycle_heads" (
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_record_digest char(64) NOT NULL,
version integer NOT NULL,
disposition varchar(16) NOT NULL,
event_digest char(64) NOT NULL,
updated_at_ms bigint NOT NULL,
PRIMARY KEY (project_id, package_name),
CONSTRAINT ql3_plugin_package_lifecycle_head_install_fk FOREIGN KEY (
project_id, package_name, installation_id, lock_digest,
install_record_digest
) REFERENCES "ql3"."plugin_package_installs" (
project_id, package_name, installation_id, lock_digest, record_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_head_event_fk FOREIGN KEY (
event_digest
) REFERENCES "ql3"."plugin_package_lifecycle_events" (event_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_head_state_check CHECK (
version BETWEEN 1 AND 2147483647 AND
disposition IN ('active', 'disabled', 'uninstalled') AND
updated_at_ms >= 0
),
CONSTRAINT ql3_plugin_package_lifecycle_head_digest_check CHECK (
lock_digest ~ '^[0-9a-f]{64}$' AND
install_record_digest ~ '^[0-9a-f]{64}$' AND
event_digest ~ '^[0-9a-f]{64}$'
)
)
`.trim(),
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_head_event_key ON "ql3"."plugin_package_lifecycle_heads" (event_digest)`,
`
CREATE TABLE "ql3"."plugin_package_lifecycle_receipts" (
event_digest char(64) PRIMARY KEY,
receipt_digest char(64) NOT NULL,
project_id varchar(128) NOT NULL,
action varchar(16) NOT NULL,
capability_status varchar(16) NOT NULL,
task_count integer NOT NULL,
previous_active_vector_digest char(64) NOT NULL,
current_active_vector_digest char(64) NOT NULL,
current_tool_snapshot_digest char(64) NOT NULL,
retained_source_count integer NOT NULL,
committed_at_ms bigint NOT NULL,
receipt_json jsonb NOT NULL,
CONSTRAINT ql3_plugin_package_lifecycle_receipt_event_fk FOREIGN KEY (
event_digest
) REFERENCES "ql3"."plugin_package_lifecycle_events" (event_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_receipt_snapshot_fk FOREIGN KEY (
project_id, current_active_vector_digest, current_tool_snapshot_digest
) REFERENCES "ql3"."project_tool_definition_snapshots" (
project_id, active_vector_digest, snapshot_digest
) ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_receipt_state_check CHECK (
(action = 'disable' AND capability_status = 'withdrawn' AND
previous_active_vector_digest <> current_active_vector_digest) OR
(action = 'enable' AND capability_status = 'restored' AND
previous_active_vector_digest <> current_active_vector_digest) OR
(action = 'uninstall' AND capability_status = 'retired' AND
task_count = 0 AND
previous_active_vector_digest = current_active_vector_digest)
),
CONSTRAINT ql3_plugin_package_lifecycle_receipt_bounds_check CHECK (
task_count BETWEEN 0 AND 128 AND
retained_source_count BETWEEN 0 AND 128 AND committed_at_ms >= 0
),
CONSTRAINT ql3_plugin_package_lifecycle_receipt_digest_check CHECK (
event_digest ~ '^[0-9a-f]{64}$' AND
receipt_digest ~ '^[0-9a-f]{64}$' AND
previous_active_vector_digest ~ '^[0-9a-f]{64}$' AND
current_active_vector_digest ~ '^[0-9a-f]{64}$' AND
current_tool_snapshot_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_plugin_package_lifecycle_receipt_json_check CHECK (
jsonb_typeof(receipt_json) = 'object' AND
octet_length(receipt_json::text) BETWEEN 2 AND 524288 AND
receipt_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-lifecycle-receipt@v1',
'eventDigest', event_digest,
'action', action,
'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' -> 'taskTransitions'
) = task_count
)
)
`.trim(),
`CREATE UNIQUE INDEX ql3_plugin_package_lifecycle_receipt_key ON "ql3"."plugin_package_lifecycle_receipts" (receipt_digest)`,
`CREATE INDEX ql3_plugin_package_lifecycle_receipt_snapshot_idx ON "ql3"."plugin_package_lifecycle_receipts" (project_id, current_active_vector_digest, event_digest)`,
`
CREATE TABLE "ql3"."plugin_package_lifecycle_tasks" (
event_digest char(64) NOT NULL,
project_id varchar(128) NOT NULL,
task_id varchar(128) NOT NULL,
previous_revision integer NOT NULL,
current_revision integer NOT NULL,
previous_content_digest char(64) NOT NULL,
current_content_digest char(64) NOT NULL,
previous_enabled boolean NOT NULL,
current_enabled boolean NOT NULL,
PRIMARY KEY (event_digest, task_id),
CONSTRAINT ql3_plugin_package_lifecycle_task_receipt_fk FOREIGN KEY (
event_digest
) REFERENCES "ql3"."plugin_package_lifecycle_receipts" (event_digest)
ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_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_lifecycle_task_current_fk FOREIGN KEY (
project_id, task_id, current_revision
) REFERENCES "ql3"."task_definition_revisions" (
project_id, task_id, revision
) ON DELETE RESTRICT,
CONSTRAINT ql3_plugin_package_lifecycle_task_transition_check CHECK (
task_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
current_revision = previous_revision + 1 AND
previous_enabled <> current_enabled
),
CONSTRAINT ql3_plugin_package_lifecycle_task_digest_check CHECK (
event_digest ~ '^[0-9a-f]{64}$' AND
previous_content_digest ~ '^[0-9a-f]{64}$' AND
current_content_digest ~ '^[0-9a-f]{64}$'
)
)
`.trim(),
`CREATE INDEX ql3_plugin_package_lifecycle_task_idx ON "ql3"."plugin_package_lifecycle_tasks" (project_id, task_id, event_digest)`,
`
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_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
) AND NOT EXISTS (
SELECT 1
FROM "ql3"."plugin_package_lifecycle_heads" AS lifecycle
JOIN "ql3"."plugin_package_task_reconciliations" AS reconciliation
ON reconciliation.project_id = lifecycle.project_id
AND reconciliation.package_name = lifecycle.package_name
AND reconciliation.lock_digest = lifecycle.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 lifecycle.project_id = p_project_id
AND lifecycle.disposition <> 'active'
);
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"."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
) AND NOT EXISTS (
SELECT 1
FROM "ql3"."plugin_package_lifecycle_heads" AS lifecycle
JOIN "ql3"."project_tool_definition_snapshot_sources" AS source
ON source.project_id = lifecycle.project_id
AND source.package_name = lifecycle.package_name
AND source.installation_id = lifecycle.installation_id
AND source.lock_digest = lifecycle.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 lifecycle.project_id = p_project_id
AND lifecycle.disposition <> 'active'
AND definition.item ->> 'packageName' = lifecycle.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"."plugin_package_lifecycle_blocking_runs"(
p_project_id varchar,
p_package_name varchar,
p_limit integer
)
RETURNS TABLE (
run_id varchar(36),
status varchar(32),
version integer,
event_sequence integer,
task_id varchar(255),
task_revision varchar(128)
)
LANGUAGE plpgsql
VOLATILE
SECURITY DEFINER
SET search_path = pg_catalog, ql3
AS $ql3$
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 p_project_id IS NULL
OR octet_length(p_project_id) NOT BETWEEN 1 AND 128
OR p_package_name IS NULL
OR p_package_name !~
'^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$'
OR p_limit NOT BETWEEN 1 AND 129 THEN
RAISE EXCEPTION 'invalid Package lifecycle blocking Run query'
USING ERRCODE = 'check_violation';
END IF;
RETURN QUERY
SELECT
run.id,
run.status,
run.version,
run.event_sequence,
run.task_id,
run.task_revision
FROM "ql3"."runs" AS run
JOIN "ql3"."plugin_package_task_ownerships" AS ownership
ON ownership.project_id = run.project_id
AND ownership.task_id = run.task_id
WHERE ownership.project_id = p_project_id
AND ownership.package_name = p_package_name
AND run.status NOT IN (
'succeeded', 'failed', 'cancelled', 'timed_out'
)
ORDER BY run.id
LIMIT p_limit;
END
$ql3$
`.trim(),
`
CREATE FUNCTION "ql3"."commit_plugin_package_lifecycle"(
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);
action_value varchar(16);
project_id_value varchar(128);
package_name_value varchar(63);
installation_id_value varchar(128);
lock_digest_value char(64);
expected_version_value integer;
expected_event_digest_value char(64);
committed_at_value bigint;
install_record "ql3"."plugin_package_installs"%ROWTYPE;
lifecycle_head "ql3"."plugin_package_lifecycle_heads"%ROWTYPE;
dispatch_record "ql3"."approved_action_dispatches"%ROWTYPE;
existing_event jsonb;
existing_receipt jsonb;
materialized_record "ql3"."plugin_package_materialized_revisions"%ROWTYPE;
task_write jsonb;
next_task jsonb;
transition jsonb;
previous_task "ql3"."task_definition_revisions"%ROWTYPE;
current_source_count integer;
expected_source_count integer;
expected_task_count integer;
inserted_snapshot integer;
updated_head 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 lifecycle input'
USING ERRCODE = 'check_violation';
END IF;
event_digest_value := p_event ->> 'eventDigest';
action_value := p_event -> 'impact' ->> 'action';
project_id_value := p_event -> 'impact' -> 'target' ->> 'projectId';
package_name_value :=
p_event -> 'impact' -> 'target' ->> 'packageName';
installation_id_value :=
p_event -> 'impact' -> 'target' ->> 'installationId';
lock_digest_value :=
p_event -> 'impact' -> 'target' ->> 'lockDigest';
expected_version_value :=
(p_event -> 'impact' -> 'expected' ->> 'version')::integer;
expected_event_digest_value :=
p_event -> 'impact' -> 'expected' ->> 'eventDigest';
committed_at_value := (p_receipt ->> 'committedAtMs')::bigint;
IF action_value NOT IN ('disable', 'enable', 'uninstall')
OR p_receipt ->> 'eventDigest' IS DISTINCT FROM event_digest_value
OR p_receipt ->> 'action' IS DISTINCT FROM action_value
OR p_receipt -> 'target' IS DISTINCT FROM
p_event -> 'impact' -> 'target'
OR p_receipt -> 'lifecycle' ->> 'eventDigest' IS DISTINCT FROM
event_digest_value
OR (p_receipt -> 'lifecycle' ->> 'version')::integer IS DISTINCT FROM
expected_version_value + 1
OR committed_at_value <
(p_event ->> 'occurredAtMs')::bigint THEN
RAISE EXCEPTION 'Package lifecycle 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 lifecycle Project is absent'
USING ERRCODE = 'check_violation';
END IF;
SELECT * INTO dispatch_record
FROM "ql3"."approved_action_dispatches"
WHERE dispatch_id = p_event ->> 'dispatchId'
FOR SHARE;
IF NOT FOUND
OR dispatch_record.project_id <> project_id_value
OR dispatch_record.action_type <>
'plugin_package.lifecycle.' || action_value
OR dispatch_record.action_digest <> p_event ->> 'actionDigest'
OR dispatch_record.preview_digest <>
p_event -> 'impact' ->> 'impactDigest'
OR dispatch_record.dispatch_json -> 'action' ->> 'permission' <>
'package.manage'
OR dispatch_record.dispatch_json -> 'requestedBy' IS DISTINCT FROM
p_event -> 'requestedBy'
OR dispatch_record.dispatch_json -> 'approvedBy' IS DISTINCT FROM
p_event -> 'approvedBy'
OR (p_event ->> 'occurredAtMs')::bigint <
(dispatch_record.dispatch_json ->> 'approvedAtMs')::bigint
OR (p_event ->> 'occurredAtMs')::bigint >
(dispatch_record.dispatch_json ->> 'expiresAtMs')::bigint THEN
RAISE EXCEPTION 'Package lifecycle dispatch is invalid'
USING ERRCODE = 'check_violation';
END IF;
SELECT event_json INTO existing_event
FROM "ql3"."plugin_package_lifecycle_events"
WHERE event_digest = event_digest_value;
IF FOUND THEN
SELECT receipt_json INTO existing_receipt
FROM "ql3"."plugin_package_lifecycle_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 lifecycle 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 <> 'active'
OR install_record.active_lock_digest <> lock_digest_value
OR install_record.version <>
(p_event -> 'impact' -> 'target' ->> 'installVersion')::integer
OR install_record.record_digest <>
p_event -> 'impact' -> 'target' ->> 'installRecordDigest' THEN
RAISE EXCEPTION 'Package lifecycle target drifted'
USING ERRCODE = 'check_violation';
END IF;
SELECT * INTO materialized_record
FROM "ql3"."plugin_package_materialized_revisions"
WHERE project_id = project_id_value
AND package_name = package_name_value
AND generation = install_record.target_generation
AND lock_digest = lock_digest_value
FOR SHARE;
IF NOT FOUND
OR materialized_record.generation_digest <>
p_event -> 'impact' ->> 'generationDigest'
OR materialized_record.revision_digest <>
p_event -> 'impact' ->> 'materializedRevisionDigest' THEN
RAISE EXCEPTION 'Package lifecycle materialization drifted'
USING ERRCODE = 'check_violation';
END IF;
SELECT * INTO lifecycle_head
FROM "ql3"."plugin_package_lifecycle_heads"
WHERE project_id = project_id_value AND package_name = package_name_value
FOR UPDATE;
IF FOUND AND lifecycle_head.installation_id = installation_id_value
AND lifecycle_head.lock_digest = lock_digest_value
AND lifecycle_head.install_record_digest = install_record.record_digest
THEN
IF lifecycle_head.version <> expected_version_value
OR lifecycle_head.disposition <>
p_event -> 'impact' -> 'expected' ->> 'disposition'
OR lifecycle_head.event_digest IS DISTINCT FROM
expected_event_digest_value THEN
RAISE EXCEPTION 'Package lifecycle head drifted'
USING ERRCODE = 'serialization_failure';
END IF;
ELSIF expected_version_value <> 0
OR p_event -> 'impact' -> 'expected' ->> 'disposition' <> 'active'
OR expected_event_digest_value IS NOT NULL THEN
RAISE EXCEPTION 'Package lifecycle origin drifted'
USING ERRCODE = 'serialization_failure';
END IF;
IF (action_value = 'disable' AND
p_event -> 'impact' -> 'expected' ->> 'disposition' <> 'active')
OR (action_value IN ('enable', 'uninstall') AND
p_event -> 'impact' -> 'expected' ->> 'disposition' <> 'disabled')
THEN
RAISE EXCEPTION 'Package lifecycle transition is invalid'
USING ERRCODE = 'check_violation';
END IF;
IF NOT EXISTS (
SELECT 1 FROM "ql3"."project_tool_definition_snapshots"
WHERE project_id = project_id_value
AND snapshot_digest =
p_event -> 'impact' ->> 'currentToolSnapshotDigest'
) THEN
RAISE EXCEPTION 'Package lifecycle prior Tool snapshot is absent'
USING ERRCODE = 'check_violation';
END IF;
IF action_value = 'enable' AND EXISTS (
SELECT 1 FROM "ql3"."plugin_package_quarantine_events"
WHERE project_id = project_id_value
AND package_name = package_name_value
AND installation_id = installation_id_value
AND lock_digest = lock_digest_value
) THEN
RAISE EXCEPTION 'quarantined Package cannot be enabled'
USING ERRCODE = 'check_violation';
END IF;
IF action_value = 'uninstall' AND (
jsonb_array_length(
p_event -> 'impact' -> 'blockingReferences'
) <> 0 OR EXISTS (
SELECT 1
FROM "ql3"."runs" AS run
JOIN "ql3"."plugin_package_task_ownerships" AS ownership
ON ownership.project_id = run.project_id
AND ownership.task_id = run.task_id
WHERE ownership.project_id = project_id_value
AND ownership.package_name = package_name_value
AND run.status NOT IN (
'succeeded', 'failed', 'cancelled', 'timed_out'
)
)
) THEN
RAISE EXCEPTION 'Package lifecycle uninstall has live references'
USING ERRCODE = 'check_violation';
END IF;
SELECT count(*)::integer INTO current_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
LEFT JOIN "ql3"."plugin_package_lifecycle_heads" AS lifecycle
ON lifecycle.project_id = active_install.project_id
AND lifecycle.package_name = active_install.package_name
AND lifecycle.installation_id = active_install.installation_id
AND lifecycle.lock_digest = active_install.lock_digest
AND lifecycle.install_record_digest = active_install.record_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 (lifecycle.event_digest IS NULL OR lifecycle.disposition = 'active')
AND NOT 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 = active_install.installation_id
AND provenance.lock_digest = active_install.lock_digest
);
IF current_source_count <> (
SELECT count(*)::integer
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
WHERE snapshot.project_id = project_id_value
AND snapshot.snapshot_digest =
p_event -> 'impact' ->> 'currentToolSnapshotDigest'
) 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
LEFT JOIN "ql3"."plugin_package_lifecycle_heads" AS lifecycle
ON lifecycle.project_id = active_install.project_id
AND lifecycle.package_name = active_install.package_name
AND lifecycle.installation_id = active_install.installation_id
AND lifecycle.lock_digest = active_install.lock_digest
AND lifecycle.install_record_digest = active_install.record_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 (
lifecycle.event_digest IS NULL OR lifecycle.disposition = 'active'
)
AND NOT 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 = active_install.installation_id
AND provenance.lock_digest = active_install.lock_digest
)
AND 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
WHERE snapshot.project_id = project_id_value
AND snapshot.snapshot_digest =
p_event -> 'impact' ->> 'currentToolSnapshotDigest'
AND source.package_name = active_install.package_name
AND source.installation_id = active_install.installation_id
AND source.generation = active_install.target_generation
AND source.generation_digest = revision.generation_digest
AND source.lock_digest = active_install.lock_digest
AND source.revision_digest = revision.revision_digest
)
) THEN
RAISE EXCEPTION 'Package lifecycle prior Tool vector drifted'
USING ERRCODE = 'check_violation';
END IF;
IF (action_value = 'disable' AND NOT EXISTS (
SELECT 1
FROM "ql3"."project_tool_definition_snapshot_sources"
WHERE project_id = project_id_value
AND active_vector_digest = (
SELECT active_vector_digest
FROM "ql3"."project_tool_definition_snapshots"
WHERE project_id = project_id_value
AND snapshot_digest =
p_event -> 'impact' ->> 'currentToolSnapshotDigest'
)
AND package_name = package_name_value
AND installation_id = installation_id_value
AND lock_digest = lock_digest_value
)) OR (action_value IN ('enable', 'uninstall') AND EXISTS (
SELECT 1
FROM "ql3"."project_tool_definition_snapshot_sources"
WHERE project_id = project_id_value
AND active_vector_digest = (
SELECT active_vector_digest
FROM "ql3"."project_tool_definition_snapshots"
WHERE project_id = project_id_value
AND snapshot_digest =
p_event -> 'impact' ->> 'currentToolSnapshotDigest'
)
AND package_name = package_name_value
AND installation_id = installation_id_value
AND lock_digest = lock_digest_value
)) THEN
RAISE EXCEPTION 'Package lifecycle prior Tool source drifted'
USING ERRCODE = 'check_violation';
END IF;
expected_task_count :=
jsonb_array_length(p_event -> 'impact' -> 'taskIds');
IF expected_task_count <> jsonb_array_length(p_task_writes)
OR expected_task_count <>
jsonb_array_length(
p_receipt -> 'capability' -> 'taskTransitions'
)
OR (
action_value = 'disable' AND expected_task_count <> (
SELECT count(*)::integer
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
)
)
OR (
action_value = 'enable' AND (
NOT EXISTS (
SELECT 1
FROM "ql3"."plugin_package_lifecycle_receipts" AS previous
WHERE previous.event_digest = expected_event_digest_value
AND previous.action = 'disable'
AND jsonb_array_length(
previous.receipt_json -> 'capability' -> 'taskTransitions'
) = expected_task_count
) OR EXISTS (
SELECT 1
FROM jsonb_array_elements_text(
p_event -> 'impact' -> 'taskIds'
) AS task_id(value)
WHERE NOT EXISTS (
SELECT 1
FROM "ql3"."plugin_package_lifecycle_tasks"
AS prior_lifecycle_task
WHERE prior_lifecycle_task.event_digest =
expected_event_digest_value
AND prior_lifecycle_task.task_id = task_id.value
)
)
)
)
OR (action_value = 'uninstall' AND expected_task_count <> 0)
THEN
RAISE EXCEPTION 'Package lifecycle Task set drifted'
USING ERRCODE = 'check_violation';
END IF;
FOR task_write IN SELECT value FROM jsonb_array_elements(p_task_writes)
LOOP
next_task := task_write -> 'next';
transition := task_write -> 'transition';
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 = transition ->> 'taskId'
FOR UPDATE OF head;
IF NOT FOUND
OR NOT (p_event -> 'impact' -> 'taskIds' ? (transition ->> 'taskId'))
OR previous_task.revision <>
(transition ->> 'previousRevision')::integer
OR previous_task.content_digest <>
transition ->> 'previousContentDigest'
OR previous_task.enabled <>
(transition ->> 'previousEnabled')::boolean
OR next_task ->> 'projectId' <> project_id_value
OR next_task ->> 'taskId' <> transition ->> 'taskId'
OR (next_task ->> 'revision')::integer <>
previous_task.revision + 1
OR next_task ->> 'contentDigest' <>
transition ->> 'currentContentDigest'
OR (next_task ->> 'enabled')::boolean <>
(transition ->> 'currentEnabled')::boolean
OR (action_value = 'disable' AND (
previous_task.enabled <> true OR
(next_task ->> 'enabled')::boolean <> false
))
OR (action_value = 'enable' AND (
previous_task.enabled <> false OR
(next_task ->> 'enabled')::boolean <> true
)) THEN
RAISE EXCEPTION 'Package lifecycle 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,
next_task ->> 'taskId',
(next_task ->> 'revision')::integer,
(next_task ->> 'mutationId')::uuid,
next_task ->> 'name',
next_task ->> 'description',
next_task ->> 'kind',
next_task -> 'spec',
next_task -> 'labels',
(next_task ->> 'enabled')::boolean,
next_task ->> 'contentDigest',
(next_task ->> 'updatedAtMs')::bigint
);
UPDATE "ql3"."task_definitions"
SET current_revision = (next_task ->> 'revision')::integer,
updated_at_ms = (next_task ->> 'updatedAtMs')::bigint
WHERE project_id = project_id_value
AND task_id = next_task ->> 'taskId'
AND current_revision = previous_task.revision;
IF NOT FOUND THEN
RAISE EXCEPTION 'Package lifecycle Task head drifted'
USING ERRCODE = 'serialization_failure';
END IF;
END LOOP;
IF action_value = 'uninstall' THEN
IF p_snapshot <> 'null'::jsonb
OR p_receipt -> 'capability' ->> 'previousActiveVectorDigest' <>
p_receipt -> 'capability' ->> 'currentActiveVectorDigest' THEN
RAISE EXCEPTION 'Package lifecycle uninstall snapshot is invalid'
USING ERRCODE = 'check_violation';
END IF;
ELSE
IF p_snapshot = 'null'::jsonb THEN
RAISE EXCEPTION 'Package lifecycle snapshot is absent'
USING ERRCODE = 'check_violation';
END IF;
expected_source_count := current_source_count +
CASE action_value WHEN 'enable' THEN 1 ELSE -1 END;
IF expected_source_count < 0 OR expected_source_count > 128
OR jsonb_array_length(p_snapshot -> 'sources') <>
expected_source_count
OR (p_receipt -> 'capability' ->> 'retainedSourceCount')::integer <>
expected_source_count
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
LEFT JOIN "ql3"."plugin_package_lifecycle_heads" AS lifecycle
ON lifecycle.project_id = active_install.project_id
AND lifecycle.package_name = active_install.package_name
AND lifecycle.installation_id = active_install.installation_id
AND lifecycle.lock_digest = active_install.lock_digest
AND lifecycle.install_record_digest = active_install.record_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 (
lifecycle.event_digest IS NULL OR
lifecycle.disposition = 'active'
)
AND NOT 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 =
active_install.installation_id
AND provenance.lock_digest = active_install.lock_digest
)
AND NOT (
action_value = 'disable'
AND active_install.installation_id = installation_id_value
AND active_install.lock_digest = lock_digest_value
)
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 lifecycle Tool source count drifted'
USING ERRCODE = 'check_violation';
END IF;
IF action_value = 'disable' AND EXISTS (
SELECT 1 FROM jsonb_array_elements(p_snapshot -> 'sources') AS source
WHERE source ->> 'packageName' = package_name_value
AND source ->> 'installationId' = installation_id_value
AND source ->> 'lockDigest' = lock_digest_value
) THEN
RAISE EXCEPTION 'disabled Package remains in Tool snapshot'
USING ERRCODE = 'check_violation';
END IF;
IF action_value = 'enable' AND NOT EXISTS (
SELECT 1 FROM jsonb_array_elements(p_snapshot -> 'sources') AS source
WHERE source ->> 'packageName' = package_name_value
AND source ->> 'installationId' = installation_id_value
AND source ->> 'generationDigest' =
materialized_record.generation_digest
AND source ->> 'lockDigest' = lock_digest_value
AND source ->> 'revisionDigest' =
materialized_record.revision_digest
) THEN
RAISE EXCEPTION 'enabled Package is absent from Tool snapshot'
USING ERRCODE = 'check_violation';
END IF;
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,
committed_at_value
)
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 lifecycle snapshot conflicts'
USING ERRCODE = 'unique_violation';
END IF;
END IF;
INSERT INTO "ql3"."plugin_package_lifecycle_events" (
event_digest, mutation_id, dispatch_id, approved_action_type, action,
project_id, package_name, installation_id, lock_digest,
install_version, install_record_digest, expected_version,
expected_disposition, expected_event_digest, generation_digest,
materialized_revision_digest, current_tool_snapshot_digest,
reference_graph_digest, impact_digest, action_digest,
requested_by_type, requested_by_id, approved_by_type, approved_by_id,
authorization_mode, occurred_at_ms, event_json
) VALUES (
event_digest_value,
p_event ->> 'mutationId',
p_event ->> 'dispatchId',
'plugin_package.lifecycle.' || action_value,
action_value,
project_id_value,
package_name_value,
installation_id_value,
lock_digest_value,
(p_event -> 'impact' -> 'target' ->> 'installVersion')::integer,
p_event -> 'impact' -> 'target' ->> 'installRecordDigest',
expected_version_value,
p_event -> 'impact' -> 'expected' ->> 'disposition',
expected_event_digest_value,
p_event -> 'impact' ->> 'generationDigest',
p_event -> 'impact' ->> 'materializedRevisionDigest',
p_event -> 'impact' ->> 'currentToolSnapshotDigest',
p_event -> 'impact' ->> 'referenceGraphDigest',
p_event -> 'impact' ->> 'impactDigest',
p_event ->> 'actionDigest',
p_event -> 'requestedBy' ->> 'type',
p_event -> 'requestedBy' ->> 'id',
p_event -> 'approvedBy' ->> 'type',
p_event -> 'approvedBy' ->> 'id',
p_event ->> 'authorizationMode',
(p_event ->> 'occurredAtMs')::bigint,
p_event
);
INSERT INTO "ql3"."plugin_package_lifecycle_receipts" (
event_digest, receipt_digest, project_id, action, 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,
action_value,
p_receipt -> 'capability' ->> 'status',
expected_task_count,
p_receipt -> 'capability' ->> 'previousActiveVectorDigest',
p_receipt -> 'capability' ->> 'currentActiveVectorDigest',
p_receipt -> 'capability' ->> 'currentToolSnapshotDigest',
(p_receipt -> 'capability' ->> 'retainedSourceCount')::integer,
committed_at_value,
p_receipt
);
INSERT INTO "ql3"."plugin_package_lifecycle_tasks" (
event_digest, project_id, task_id, previous_revision,
current_revision, previous_content_digest, current_content_digest,
previous_enabled, current_enabled
)
SELECT
event_digest_value,
project_id_value,
item ->> 'taskId',
(item ->> 'previousRevision')::integer,
(item ->> 'currentRevision')::integer,
item ->> 'previousContentDigest',
item ->> 'currentContentDigest',
(item ->> 'previousEnabled')::boolean,
(item ->> 'currentEnabled')::boolean
FROM jsonb_array_elements(
p_receipt -> 'capability' -> 'taskTransitions'
) AS item;
INSERT INTO "ql3"."plugin_package_lifecycle_heads" (
project_id, package_name, installation_id, lock_digest,
install_record_digest, version, disposition, event_digest,
updated_at_ms
) VALUES (
project_id_value,
package_name_value,
installation_id_value,
lock_digest_value,
install_record.record_digest,
expected_version_value + 1,
CASE action_value
WHEN 'enable' THEN 'active'
WHEN 'disable' THEN 'disabled'
ELSE 'uninstalled'
END,
event_digest_value,
committed_at_value
)
ON CONFLICT (project_id, package_name) DO UPDATE SET
installation_id = EXCLUDED.installation_id,
lock_digest = EXCLUDED.lock_digest,
install_record_digest = EXCLUDED.install_record_digest,
version = EXCLUDED.version,
disposition = EXCLUDED.disposition,
event_digest = EXCLUDED.event_digest,
updated_at_ms = EXCLUDED.updated_at_ms
WHERE (
plugin_package_lifecycle_heads.installation_id =
EXCLUDED.installation_id
AND plugin_package_lifecycle_heads.lock_digest = EXCLUDED.lock_digest
AND plugin_package_lifecycle_heads.version = EXCLUDED.version - 1
AND plugin_package_lifecycle_heads.event_digest IS NOT DISTINCT FROM
expected_event_digest_value
) OR (
EXCLUDED.version = 1 AND (
plugin_package_lifecycle_heads.installation_id <>
EXCLUDED.installation_id OR
plugin_package_lifecycle_heads.lock_digest <> EXCLUDED.lock_digest
)
);
GET DIAGNOSTICS updated_head = ROW_COUNT;
IF updated_head <> 1 THEN
RAISE EXCEPTION 'Package lifecycle head update lost its fence'
USING ERRCODE = 'serialization_failure';
END IF;
RETURN true;
END
$ql3$
`.trim(),
`
REVOKE ALL ON
"ql3"."plugin_package_lifecycle_events",
"ql3"."plugin_package_lifecycle_heads",
"ql3"."plugin_package_lifecycle_receipts",
"ql3"."plugin_package_lifecycle_tasks"
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
ql3_package_executor, ql3_worker_ingress
`.trim(),
`
REVOKE ALL ON FUNCTION
"ql3"."plugin_package_lifecycle_blocking_runs"(varchar, varchar, integer)
FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager,
ql3_package_executor, ql3_worker_ingress
`.trim(),
`
REVOKE ALL ON FUNCTION
"ql3"."commit_plugin_package_lifecycle"(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_lifecycle_events", "ql3"."plugin_package_lifecycle_heads", "ql3"."plugin_package_lifecycle_receipts", "ql3"."plugin_package_lifecycle_tasks" TO ql3_package_executor`,
`GRANT EXECUTE ON FUNCTION "ql3"."plugin_package_lifecycle_blocking_runs"(varchar, varchar, integer) TO ql3_package_executor`,
`GRANT EXECUTE ON FUNCTION "ql3"."commit_plugin_package_lifecycle"(jsonb, jsonb, jsonb, jsonb) TO ql3_package_executor`,
`
DO $ql3$
BEGIN
UPDATE "ql3"."schema_capabilities"
SET contract_version = 40,
migration_id = 'pg-0041-plugin-package-lifecycle',
capabilities = '${CAPABILITIES_V40}'::jsonb,
updated_at_ms = floor(
extract(epoch FROM transaction_timestamp()) * 1000
)::bigint
WHERE contract_name = 'control-core'
AND contract_version = 39
AND migration_id =
'pg-0040-plugin-package-publisher-trust-transitions'
AND capabilities = '${CAPABILITIES_V39}'::jsonb;
IF NOT FOUND THEN
RAISE EXCEPTION 'control-core capability is not at version 39'
USING ERRCODE = 'check_violation';
END IF;
END
$ql3$
`.trim(),
],
});