mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
1320 lines
56 KiB
TypeScript
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(),
|
|
],
|
|
});
|