feat(ql3): separate cluster secret transition authority

This commit is contained in:
whyour
2026-08-13 22:56:02 +08:00
parent 5f93e0b5d2
commit d41c821ce0
34 changed files with 4157 additions and 27 deletions
@@ -150,6 +150,11 @@
"require": "./dist/plugin-package/secret-binding/pluginPackageSecretBindingApprovalPlanRepository.js",
"default": "./dist/plugin-package/secret-binding/pluginPackageSecretBindingApprovalPlanRepository.js"
},
"./plugin-package-secret-binding-transition-approval-plan": {
"types": "./dist/plugin-package/secret-binding/pluginPackageSecretBindingTransitionApprovalPlanRepository.d.ts",
"require": "./dist/plugin-package/secret-binding/pluginPackageSecretBindingTransitionApprovalPlanRepository.js",
"default": "./dist/plugin-package/secret-binding/pluginPackageSecretBindingTransitionApprovalPlanRepository.js"
},
"./plugin-package-automation-publication": {
"types": "./dist/plugin-package/publication/pluginPackageAutomationPublicationRepository.d.ts",
"require": "./dist/plugin-package/publication/pluginPackageAutomationPublicationRepository.js",
@@ -50,6 +50,7 @@ export {
type ApplyPostgresPluginPackageSecretBindingTransitionResult,
} from '../plugin-package/secret-binding/pluginPackageSecretBindingTransitionRepository';
export { PostgresPluginPackageSecretBindingApprovalPlanReader } from '../plugin-package/secret-binding/pluginPackageSecretBindingApprovalPlanRepository';
export { PostgresPluginPackageSecretBindingTransitionApprovalPlanReader } from '../plugin-package/secret-binding/pluginPackageSecretBindingTransitionApprovalPlanRepository';
export { PostgresPluginPackageAutomationPublicationRepository } from '../plugin-package/publication/pluginPackageAutomationPublicationRepository';
export {
CLUSTER_PLUGIN_PACKAGE_QUARANTINE_TARGET_LIMIT,
@@ -62,6 +62,11 @@ export {
PostgresPluginPackageSecretBindingApprovalPlanReader,
PostgresPluginPackageSecretBindingApprovalPlanRepository,
} from '../plugin-package/secret-binding/pluginPackageSecretBindingApprovalPlanRepository';
export {
PostgresPluginPackageSecretBindingTransitionApprovalPlanReader,
PostgresPluginPackageSecretBindingTransitionApprovalPlanRepository,
type PostgresPluginPackageSecretBindingTransitionPlanningSnapshot,
} from '../plugin-package/secret-binding/pluginPackageSecretBindingTransitionApprovalPlanRepository';
export { PostgresPluginPackageInstallInventoryReader } from '../plugin-package/installation/pluginPackageInstallRepository';
export { PostgresPluginPackagePublisherRevocationProposalRepository } from '../plugin-package/publisher/pluginPackagePublisherRevocationProposalRepository';
export { PostgresPluginPackagePublisherTrustTransitionProposalRepository } from '../plugin-package/publisher/pluginPackagePublisherTrustTransitionProposalRepository';
@@ -323,5 +323,10 @@ export const postgresqlMainMigrationManifest: MigrationStreamManifest =
checksum:
'20c4d6e640b7fb05fc2f44dd9bec6d2d081697b83acf4e7dd5deb9a07afe54df',
}),
Object.freeze({
id: 'pg-0064-plugin-package-secret-binding-transition-approval-plans',
checksum:
'1951b77a0265f8826169e4724424b2fbbd30061b27e27d3ba95de03430c1bac9',
}),
]),
});
@@ -66,6 +66,7 @@ import { pg0060PluginPackageSecretMaterializationGuardMigration } from './pg-006
import { pg0061PluginPackageSecretBindingApprovalPlansMigration } from './pg-0061-plugin-package-secret-binding-approval-plans';
import { pg0062PluginPackageSecretBindingTargetGuardMigration } from './pg-0062-plugin-package-secret-binding-target-guard';
import { pg0063PluginPackageSecretBindingTransitionReceiptsMigration } from './pg-0063-plugin-package-secret-binding-transition-receipts';
import { pg0064PluginPackageSecretBindingTransitionApprovalPlansMigration } from './pg-0064-plugin-package-secret-binding-transition-approval-plans';
export const postgresqlMainMigrationStream: MigrationStreamDefinition<PostgresMigrationContext> =
Object.freeze({
@@ -137,5 +138,6 @@ export const postgresqlMainMigrationStream: MigrationStreamDefinition<PostgresMi
pg0061PluginPackageSecretBindingApprovalPlansMigration,
pg0062PluginPackageSecretBindingTargetGuardMigration,
pg0063PluginPackageSecretBindingTransitionReceiptsMigration,
pg0064PluginPackageSecretBindingTransitionApprovalPlansMigration,
]),
});
@@ -0,0 +1,307 @@
import { CAPABILITIES_V62 } from './pg-0063-plugin-package-secret-binding-transition-receipts';
import { definePostgresSqlMigration } from './sqlMigration';
export const CAPABILITIES_V63 = CAPABILITIES_V62.replace(
'"plugin_package_secret_binding_transition_receipt":1,',
'"plugin_package_secret_binding_transition_approval_plan":1,"plugin_package_secret_binding_transition_receipt":1,',
);
export const pg0064PluginPackageSecretBindingTransitionApprovalPlansMigration =
definePostgresSqlMigration({
id: 'pg-0064-plugin-package-secret-binding-transition-approval-plans',
statements: [
`
CREATE TABLE "ql3"."plugin_package_secret_binding_transition_approval_plans" (
action_ref varchar(255) PRIMARY KEY,
approval_plan_digest char(64) NOT NULL,
transition_digest char(64) NOT NULL,
generation_digest char(64) NOT NULL,
project_id varchar(128) NOT NULL,
package_name varchar(63) NOT NULL,
installation_id varchar(128) NOT NULL,
lock_digest char(64) NOT NULL,
generation integer NOT NULL,
manifest_digest char(64) NOT NULL,
previous_active_lock_digest char(64) NOT NULL,
requested_by_type varchar(16) NOT NULL,
requested_by_id varchar(255) NOT NULL,
planned_at_ms bigint NOT NULL,
expires_at_ms bigint NOT NULL,
plan_json jsonb NOT NULL,
CONSTRAINT ql3_pp_secret_transition_plan_project_fk
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_pp_secret_transition_plan_install_fk
FOREIGN KEY (installation_id)
REFERENCES "ql3"."plugin_package_installs" (installation_id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_pp_secret_transition_plan_identity_check CHECK (
action_ref ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$' AND
project_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
package_name ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' AND
installation_id ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' AND
generation BETWEEN 2 AND 2147483647 AND
requested_by_type = 'user' AND
octet_length(requested_by_id) BETWEEN 1 AND 255 AND
requested_by_id !~ '[[:cntrl:]]'
),
CONSTRAINT ql3_pp_secret_transition_plan_digest_check CHECK (
approval_plan_digest ~ '^[0-9a-f]{64}$' AND
transition_digest ~ '^[0-9a-f]{64}$' AND
generation_digest ~ '^[0-9a-f]{64}$' AND
lock_digest ~ '^[0-9a-f]{64}$' AND
manifest_digest ~ '^[0-9a-f]{64}$' AND
previous_active_lock_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_pp_secret_transition_plan_time_check CHECK (
planned_at_ms >= 0 AND expires_at_ms > planned_at_ms AND
expires_at_ms - planned_at_ms <= 900000
),
CONSTRAINT ql3_pp_secret_transition_plan_json_check CHECK (
jsonb_typeof(plan_json) = 'object' AND
octet_length(plan_json::text) BETWEEN 2 AND 229376 AND
plan_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-secret-binding-transition-approval-plan@v1',
'actionRef', action_ref,
'approvalPlanDigest', approval_plan_digest,
'requestedBy', jsonb_build_object(
'type', requested_by_type,
'id', requested_by_id
),
'plannedAtMs', planned_at_ms,
'expiresAtMs', expires_at_ms,
'transitionPlan', jsonb_build_object(
'schema', 'qinglong/plugin-package-secret-binding-transition-plan@v1',
'transitionDigest', transition_digest,
'previousActiveLockDigest', previous_active_lock_digest,
'nextTarget', jsonb_build_object(
'generationDigest', generation_digest,
'projectId', project_id,
'packageName', package_name,
'installationId', installation_id,
'lockDigest', lock_digest,
'generation', generation,
'manifestDigest', manifest_digest
)
)
) AND
jsonb_typeof(plan_json #> '{transitionPlan,changes}') = 'array' AND
jsonb_array_length(plan_json #> '{transitionPlan,changes}') BETWEEN 1 AND 64
)
)
`.trim(),
`CREATE UNIQUE INDEX ql3_pp_secret_transition_plan_digest_uidx ON "ql3"."plugin_package_secret_binding_transition_approval_plans" (approval_plan_digest)`,
`CREATE UNIQUE INDEX ql3_pp_secret_transition_plan_target_uidx ON "ql3"."plugin_package_secret_binding_transition_approval_plans" (generation_digest)`,
`CREATE INDEX ql3_pp_secret_transition_plan_expiry_idx ON "ql3"."plugin_package_secret_binding_transition_approval_plans" (expires_at_ms, action_ref)`,
`
CREATE FUNCTION "ql3"."plugin_package_secret_binding_transition_snapshot"(
p_project_id varchar,
p_package_name varchar
)
RETURNS TABLE (
next_record_json jsonb,
next_lock_json jsonb,
next_proposal_json jsonb,
previous_record_json jsonb,
previous_lock_json jsonb,
previous_proposal_json jsonb,
previous_binding_json jsonb,
previous_attempt_generation integer,
observed_at_ms bigint
)
LANGUAGE plpgsql
VOLATILE
SECURITY DEFINER
SET search_path = pg_catalog, ql3
AS $ql3$
BEGIN
IF NOT pg_has_role(session_user, 'ql3_package_manager', 'member') THEN
RAISE EXCEPTION 'Package manager authority is required'
USING ERRCODE = 'insufficient_privilege';
END IF;
RETURN QUERY
SELECT current.record_json,
current.lock_json,
current_proposal.proposal_json,
previous.record_json,
previous.lock_json,
previous_proposal.proposal_json,
previous_binding.binding_json,
(
SELECT MAX(history.target_generation)
FROM "ql3"."plugin_package_installs" AS history
WHERE history.project_id = current.project_id
AND history.package_name = current.package_name
AND history.target_generation < current.target_generation
),
floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint
FROM "ql3"."plugin_package_install_heads" AS head
JOIN "ql3"."plugin_package_installs" AS current
ON current.installation_id = head.installation_id
AND current.project_id = head.project_id
AND current.package_name = head.package_name
JOIN "ql3"."plugin_package_admission_receipts" AS current_admission
ON current_admission.installation_id = current.installation_id
JOIN "ql3"."plugin_package_install_proposals" AS current_proposal
ON current_proposal.action_ref = current_admission.action_ref
JOIN "ql3"."plugin_package_installs" AS previous
ON previous.project_id = current.project_id
AND previous.package_name = current.package_name
AND previous.lock_digest = current.previous_active_lock_digest
JOIN "ql3"."plugin_package_admission_receipts" AS previous_admission
ON previous_admission.installation_id = previous.installation_id
JOIN "ql3"."plugin_package_install_proposals" AS previous_proposal
ON previous_proposal.action_ref = previous_admission.action_ref
LEFT JOIN "ql3"."plugin_package_secret_bindings" AS previous_binding
ON previous_binding.installation_id = previous.installation_id
AND previous_binding.project_id = previous.project_id
AND previous_binding.package_name = previous.package_name
AND previous_binding.lock_digest = previous.lock_digest
AND previous_binding.generation = previous.target_generation
WHERE head.project_id = p_project_id
AND head.package_name = p_package_name
AND current.state = 'staged'
AND current.previous_active_lock_digest IS NOT NULL
AND current.active_lock_digest = current.previous_active_lock_digest
AND current.target_generation = (
SELECT MAX(latest.target_generation)
FROM "ql3"."plugin_package_installs" AS latest
WHERE latest.project_id = current.project_id
AND latest.package_name = current.package_name
)
AND previous.state = 'active'
AND previous.active_lock_digest = previous.lock_digest
AND NOT EXISTS (
SELECT 1
FROM "ql3"."plugin_package_secret_binding_transition_receipts" receipt
WHERE receipt.project_id = current.project_id
AND receipt.package_name = current.package_name
AND receipt.generation = current.target_generation
)
FOR SHARE OF head, current, previous;
END
$ql3$
`.trim(),
`
CREATE FUNCTION "ql3"."create_plugin_package_secret_transition_plan"(
p_plan_json jsonb
)
RETURNS varchar
LANGUAGE plpgsql
VOLATILE
SECURITY DEFINER
SET search_path = pg_catalog, ql3
AS $ql3$
DECLARE
existing_plan jsonb;
inserted_action_ref varchar(255);
BEGIN
IF NOT pg_has_role(session_user, 'ql3_package_manager', 'member') THEN
RAISE EXCEPTION 'Package manager authority is required'
USING ERRCODE = 'insufficient_privilege';
END IF;
PERFORM pg_advisory_xact_lock(
hashtextextended(p_plan_json ->> 'actionRef', 70513064)
);
SELECT plan_json INTO existing_plan
FROM "ql3"."plugin_package_secret_binding_transition_approval_plans"
WHERE action_ref = p_plan_json ->> 'actionRef'
FOR SHARE;
IF existing_plan IS NOT NULL THEN
IF existing_plan = p_plan_json THEN RETURN 'existing'; END IF;
RAISE EXCEPTION 'Secret transition actionRef is already bound'
USING ERRCODE = 'unique_violation';
END IF;
INSERT INTO "ql3"."plugin_package_secret_binding_transition_approval_plans" (
action_ref, approval_plan_digest, transition_digest, generation_digest,
project_id, package_name, installation_id, lock_digest, generation,
manifest_digest, previous_active_lock_digest, requested_by_type,
requested_by_id, planned_at_ms, expires_at_ms, plan_json
)
SELECT p_plan_json ->> 'actionRef',
p_plan_json ->> 'approvalPlanDigest',
p_plan_json #>> '{transitionPlan,transitionDigest}',
p_plan_json #>> '{transitionPlan,nextTarget,generationDigest}',
p_plan_json #>> '{transitionPlan,nextTarget,projectId}',
p_plan_json #>> '{transitionPlan,nextTarget,packageName}',
p_plan_json #>> '{transitionPlan,nextTarget,installationId}',
p_plan_json #>> '{transitionPlan,nextTarget,lockDigest}',
(p_plan_json #>> '{transitionPlan,nextTarget,generation}')::integer,
p_plan_json #>> '{transitionPlan,nextTarget,manifestDigest}',
p_plan_json #>> '{transitionPlan,previousActiveLockDigest}',
p_plan_json #>> '{requestedBy,type}',
p_plan_json #>> '{requestedBy,id}',
(p_plan_json ->> 'plannedAtMs')::bigint,
(p_plan_json ->> 'expiresAtMs')::bigint,
p_plan_json
FROM "ql3"."plugin_package_install_heads" head
JOIN "ql3"."plugin_package_installs" install
ON install.installation_id = head.installation_id
AND install.project_id = head.project_id
AND install.package_name = head.package_name
JOIN "ql3"."plugin_package_installs" previous
ON previous.project_id = install.project_id
AND previous.package_name = install.package_name
AND previous.lock_digest = install.previous_active_lock_digest
LEFT JOIN "ql3"."plugin_package_secret_bindings" previous_binding
ON previous_binding.installation_id = previous.installation_id
AND previous_binding.project_id = previous.project_id
AND previous_binding.package_name = previous.package_name
AND previous_binding.lock_digest = previous.lock_digest
AND previous_binding.generation = previous.target_generation
WHERE head.project_id = p_plan_json #>> '{transitionPlan,nextTarget,projectId}'
AND head.package_name = p_plan_json #>> '{transitionPlan,nextTarget,packageName}'
AND install.installation_id = p_plan_json #>> '{transitionPlan,nextTarget,installationId}'
AND install.lock_digest = p_plan_json #>> '{transitionPlan,nextTarget,lockDigest}'
AND install.target_generation = (p_plan_json #>> '{transitionPlan,nextTarget,generation}')::integer
AND install.lock_json ->> 'manifestDigest' = p_plan_json #>> '{transitionPlan,nextTarget,manifestDigest}'
AND install.state = 'staged'
AND install.previous_active_lock_digest = p_plan_json #>> '{transitionPlan,previousActiveLockDigest}'
AND install.active_lock_digest = install.previous_active_lock_digest
AND previous.installation_id = p_plan_json #>> '{transitionPlan,previousTarget,installationId}'
AND previous.lock_digest = p_plan_json #>> '{transitionPlan,previousTarget,lockDigest}'
AND previous.target_generation = (p_plan_json #>> '{transitionPlan,previousTarget,generation}')::integer
AND previous.lock_json ->> 'manifestDigest' = p_plan_json #>> '{transitionPlan,previousTarget,manifestDigest}'
AND previous.state = 'active'
AND previous.active_lock_digest = previous.lock_digest
AND (
SELECT MAX(attempt.target_generation)
FROM "ql3"."plugin_package_installs" attempt
WHERE attempt.project_id = install.project_id
AND attempt.package_name = install.package_name
AND attempt.target_generation < install.target_generation
) = (p_plan_json #>> '{transitionPlan,previousAttemptGeneration}')::integer
AND (
(previous_binding.binding_json IS NULL AND
p_plan_json #> '{transitionPlan,previousBinding}' = 'null'::jsonb) OR
previous_binding.binding_json = p_plan_json #> '{transitionPlan,previousBinding}'
)
AND install.target_generation = (
SELECT MAX(history.target_generation)
FROM "ql3"."plugin_package_installs" history
WHERE history.project_id = install.project_id
AND history.package_name = install.package_name
)
AND NOT EXISTS (
SELECT 1 FROM "ql3"."plugin_package_secret_binding_transition_receipts" receipt
WHERE receipt.generation_digest = p_plan_json #>> '{transitionPlan,nextTarget,generationDigest}'
)
RETURNING action_ref INTO inserted_action_ref;
IF inserted_action_ref IS NULL THEN
RAISE EXCEPTION 'Secret transition target is not current staged generation'
USING ERRCODE = 'check_violation';
END IF;
RETURN 'created';
END
$ql3$
`.trim(),
`REVOKE ALL ON "ql3"."plugin_package_secret_binding_transition_approval_plans" FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager, ql3_package_executor, ql3_worker_ingress`,
`GRANT SELECT ON "ql3"."plugin_package_secret_binding_transition_approval_plans" TO ql3_package_manager, ql3_package_executor`,
`REVOKE ALL ON FUNCTION "ql3"."plugin_package_secret_binding_transition_snapshot"(varchar, varchar) FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager, ql3_package_executor, ql3_worker_ingress`,
`GRANT EXECUTE ON FUNCTION "ql3"."plugin_package_secret_binding_transition_snapshot"(varchar, varchar) TO ql3_package_manager`,
`REVOKE ALL ON FUNCTION "ql3"."create_plugin_package_secret_transition_plan"(jsonb) FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager, ql3_package_executor, ql3_worker_ingress`,
`GRANT EXECUTE ON FUNCTION "ql3"."create_plugin_package_secret_transition_plan"(jsonb) TO ql3_package_manager`,
`DO $ql3$ BEGIN UPDATE "ql3"."schema_capabilities" SET contract_version = 63, migration_id = 'pg-0064-plugin-package-secret-binding-transition-approval-plans', capabilities = '${CAPABILITIES_V63}'::jsonb, updated_at_ms = floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint WHERE contract_name = 'control-core' AND contract_version = 62 AND migration_id = 'pg-0063-plugin-package-secret-binding-transition-receipts' AND capabilities = '${CAPABILITIES_V62}'::jsonb; IF NOT FOUND THEN RAISE EXCEPTION 'control-core capability is not at version 62' USING ERRCODE = 'check_violation'; END IF; END $ql3$`,
],
});
@@ -0,0 +1,367 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import {
approvalRequestDigest,
normalizeApprovalRequestRecord,
type ApprovalRequestRecord,
} from '@qinglong/runtime-core/approved-action';
import {
normalizePluginPackageInstallRecord,
normalizePluginPackageLock,
type PluginPackageInstallRecord,
type PluginPackageLock,
} from '@qinglong/runtime-core/plugin-package-install';
import {
normalizePluginPackageInstallProposal,
type PluginPackageInstallProposal,
} from '@qinglong/runtime-core/plugin-package-proposal';
import {
normalizePluginPackageSecretBinding,
type PluginPackageSecretBinding,
} from '@qinglong/runtime-core/plugin-package-secret-binding';
import {
InvalidPluginPackageSecretBindingTransitionApprovalPlanError,
PluginPackageSecretBindingTransitionApprovalPlanConflictError,
PluginPackageSecretBindingTransitionApprovalPlanUnavailableError,
normalizePluginPackageSecretBindingTransitionApprovalPlan,
type CreatePluginPackageSecretBindingTransitionApprovalPlanResult,
type PluginPackageSecretBindingTransitionApprovalPlan,
type PluginPackageSecretBindingTransitionApprovalPlanRepository,
} from '@qinglong/runtime-core/plugin-package-secret-binding-transition-approval-plan';
import {
postgresRequiredInteger,
postgresRequiredJsonObject,
postgresRequiredString,
postgresSqlState,
} from '../../repository/definitionRepositorySupport';
type Row = Record<string, unknown>;
export interface PostgresPluginPackageSecretBindingTransitionPlanningSnapshot {
readonly next: Readonly<{
record: Readonly<PluginPackageInstallRecord>;
lock: Readonly<PluginPackageLock>;
proposal: Readonly<PluginPackageInstallProposal>;
}>;
readonly previous: Readonly<{
record: Readonly<PluginPackageInstallRecord>;
lock: Readonly<PluginPackageLock>;
proposal: Readonly<PluginPackageInstallProposal>;
binding: Readonly<PluginPackageSecretBinding> | null;
}>;
readonly previousAttemptGeneration: number;
readonly observedAtMs: number;
}
const ACTION_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
const PROJECT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const PACKAGE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
function unavailable(
cause?: unknown,
): PluginPackageSecretBindingTransitionApprovalPlanUnavailableError {
return new PluginPackageSecretBindingTransitionApprovalPlanUnavailableError({
cause: cause instanceof Error ? cause : undefined,
});
}
function mapStorageError(error: unknown): Error {
if (
error instanceof
InvalidPluginPackageSecretBindingTransitionApprovalPlanError ||
error instanceof
PluginPackageSecretBindingTransitionApprovalPlanConflictError ||
error instanceof
PluginPackageSecretBindingTransitionApprovalPlanUnavailableError
) {
return error;
}
const state = postgresSqlState(error);
if (state === '23503' || state === '23505' || state === '23514') {
return new PluginPackageSecretBindingTransitionApprovalPlanConflictError(
'plan identity or staged generation is already bound',
);
}
return unavailable(error);
}
function validateActionRef(value: string): string {
if (typeof value !== 'string' || !ACTION_REF_PATTERN.test(value)) {
throw new InvalidPluginPackageSecretBindingTransitionApprovalPlanError(
'actionRef is invalid',
);
}
return value;
}
function normalizeRow(
row: Row,
): Readonly<PluginPackageSecretBindingTransitionApprovalPlan> {
try {
return normalizePluginPackageSecretBindingTransitionApprovalPlan(
postgresRequiredJsonObject(
row.planJson,
unavailable,
) as unknown as PluginPackageSecretBindingTransitionApprovalPlan,
);
} catch (error) {
throw unavailable(error);
}
}
function normalizeApprovalRow(row: Row): Readonly<ApprovalRequestRecord> {
try {
const request = normalizeApprovalRequestRecord(
postgresRequiredJsonObject(
row.requestJson,
unavailable,
) as unknown as ApprovalRequestRecord,
);
if (
approvalRequestDigest(request) !==
postgresRequiredString(row.requestDigest, unavailable)
) {
throw unavailable();
}
return request;
} catch (error) {
throw unavailable(error);
}
}
function same(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
export class PostgresPluginPackageSecretBindingTransitionApprovalPlanReader {
constructor(protected readonly pool: Pick<PostgresPool, 'query'>) {
if (!pool || typeof pool.query !== 'function') {
throw new TypeError(
'PostgreSQL Secret binding transition approval reader is invalid',
);
}
}
async findByActionRef(
actionRefValue: string,
): Promise<
Readonly<PluginPackageSecretBindingTransitionApprovalPlan> | null
> {
const actionRef = validateActionRef(actionRefValue);
try {
const result = await this.pool.query<Row>(
`SELECT plan_json AS "planJson"
FROM "ql3"."plugin_package_secret_binding_transition_approval_plans"
WHERE action_ref = $1
LIMIT 2`,
[actionRef],
);
if (result.rows.length === 0) return null;
if (result.rows.length !== 1) throw unavailable();
const plan = normalizeRow(result.rows[0]!);
if (plan.actionRef !== actionRef) throw unavailable();
return plan;
} catch (error) {
throw mapStorageError(error);
}
}
async loadPlanningSnapshot(
projectId: string,
packageName: string,
): Promise<
Readonly<PostgresPluginPackageSecretBindingTransitionPlanningSnapshot> | null
> {
if (
typeof projectId !== 'string' ||
!PROJECT_PATTERN.test(projectId) ||
typeof packageName !== 'string' ||
!PACKAGE_PATTERN.test(packageName)
) {
throw new InvalidPluginPackageSecretBindingTransitionApprovalPlanError(
'planning target is invalid',
);
}
try {
const result = await this.pool.query<Row>(
`SELECT next_record_json AS "nextRecordJson",
next_lock_json AS "nextLockJson",
next_proposal_json AS "nextProposalJson",
previous_record_json AS "previousRecordJson",
previous_lock_json AS "previousLockJson",
previous_proposal_json AS "previousProposalJson",
previous_binding_json AS "previousBindingJson",
previous_attempt_generation AS "previousAttemptGeneration",
observed_at_ms AS "observedAtMs"
FROM "ql3"."plugin_package_secret_binding_transition_snapshot"($1, $2)`,
[projectId, packageName],
);
if (result.rows.length === 0) return null;
if (result.rows.length !== 1) throw unavailable();
const row = result.rows[0]!;
const next = Object.freeze({
record: normalizePluginPackageInstallRecord(
postgresRequiredJsonObject(
row.nextRecordJson,
unavailable,
) as unknown as PluginPackageInstallRecord,
),
lock: normalizePluginPackageLock(
postgresRequiredJsonObject(
row.nextLockJson,
unavailable,
) as unknown as PluginPackageLock,
),
proposal: normalizePluginPackageInstallProposal(
postgresRequiredJsonObject(
row.nextProposalJson,
unavailable,
) as unknown as PluginPackageInstallProposal,
),
});
const previous = Object.freeze({
record: normalizePluginPackageInstallRecord(
postgresRequiredJsonObject(
row.previousRecordJson,
unavailable,
) as unknown as PluginPackageInstallRecord,
),
lock: normalizePluginPackageLock(
postgresRequiredJsonObject(
row.previousLockJson,
unavailable,
) as unknown as PluginPackageLock,
),
proposal: normalizePluginPackageInstallProposal(
postgresRequiredJsonObject(
row.previousProposalJson,
unavailable,
) as unknown as PluginPackageInstallProposal,
),
binding:
row.previousBindingJson === null
? null
: normalizePluginPackageSecretBinding(
postgresRequiredJsonObject(row.previousBindingJson, unavailable),
),
});
const previousAttemptGeneration = postgresRequiredInteger(
row.previousAttemptGeneration,
unavailable,
);
const observedAtMs = postgresRequiredInteger(
row.observedAtMs,
unavailable,
);
const previousBindingTarget = previous.binding?.target;
if (
next.record.projectId !== projectId ||
next.record.packageName !== packageName ||
next.record.state !== 'staged' ||
next.record.targetGeneration !== previousAttemptGeneration + 1 ||
next.record.lockDigest !== next.lock.lockDigest ||
next.record.previousActiveLockDigest !== previous.lock.lockDigest ||
next.proposal.actionDigest !== next.lock.approval.actionDigest ||
next.proposal.previewDigest !== next.lock.approval.previewDigest ||
next.proposal.actionInput.targetGeneration !==
next.record.targetGeneration ||
next.proposal.actionInput.source.contentDigest !==
next.lock.source.contentDigest ||
previous.record.state !== 'active' ||
previous.record.lockDigest !== previous.lock.lockDigest ||
previous.proposal.actionDigest !==
previous.lock.approval.actionDigest ||
previous.proposal.previewDigest !== previous.lock.approval.previewDigest ||
previous.proposal.actionInput.targetGeneration !==
previous.record.targetGeneration ||
previous.proposal.actionInput.source.contentDigest !==
previous.lock.source.contentDigest ||
(previousBindingTarget !== undefined &&
(previousBindingTarget.projectId !== previous.record.projectId ||
previousBindingTarget.packageName !== previous.record.packageName ||
previousBindingTarget.installationId !==
previous.record.installationId ||
previousBindingTarget.lockDigest !== previous.record.lockDigest ||
previousBindingTarget.generation !==
previous.record.targetGeneration ||
previousBindingTarget.manifestDigest !==
previous.lock.manifestDigest))
) {
throw unavailable();
}
return Object.freeze({
next,
previous,
previousAttemptGeneration,
observedAtMs,
});
} catch (error) {
throw mapStorageError(error);
}
}
async listApprovedRequests(
limit: number,
): Promise<readonly Readonly<ApprovalRequestRecord>[]> {
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 64) {
throw new TypeError('Secret transition approval page limit is invalid');
}
try {
const result = await this.pool.query<Row>(
`SELECT request.request_json AS "requestJson",
request.request_digest AS "requestDigest"
FROM "ql3"."approval_requests" AS request
JOIN "ql3"."plugin_package_secret_binding_transition_approval_plans" AS plan
ON plan.action_ref = request.action_ref
AND plan.approval_plan_digest = request.action_digest
AND plan.transition_digest = request.preview_digest
WHERE request.state = 'approved'
AND request.action_type = 'plugin_package.secret_binding.transition'
ORDER BY request.updated_at_ms, request.request_id
LIMIT $1`,
[limit],
);
if (result.rows.length > limit) throw unavailable();
return Object.freeze(result.rows.map(normalizeApprovalRow));
} catch (error) {
throw mapStorageError(error);
}
}
}
export class PostgresPluginPackageSecretBindingTransitionApprovalPlanRepository
extends PostgresPluginPackageSecretBindingTransitionApprovalPlanReader
implements PluginPackageSecretBindingTransitionApprovalPlanRepository
{
async create(
value: Readonly<PluginPackageSecretBindingTransitionApprovalPlan>,
): Promise<
Readonly<CreatePluginPackageSecretBindingTransitionApprovalPlanResult>
> {
const plan =
normalizePluginPackageSecretBindingTransitionApprovalPlan(value);
try {
const result = await this.pool.query<Row>(
`SELECT "ql3"."create_plugin_package_secret_transition_plan"(
$1::jsonb
) AS status`,
[JSON.stringify(plan)],
);
if (result.rows.length !== 1) throw unavailable();
const status = postgresRequiredString(
result.rows[0]?.status,
unavailable,
);
if (status !== 'created' && status !== 'existing') throw unavailable();
const stored = await this.findByActionRef(plan.actionRef);
if (!stored || !same(stored, plan)) {
throw new PluginPackageSecretBindingTransitionApprovalPlanConflictError(
'actionRef is already bound to another transition plan',
);
}
return Object.freeze({ status, plan: stored });
} catch (error) {
throw mapStorageError(error);
}
}
}
@@ -515,6 +515,73 @@ export const pluginPackageSecretBindingTransitionReceipts = ql3Schema.table(
],
);
export const pluginPackageSecretBindingTransitionApprovalPlans =
ql3Schema.table(
'plugin_package_secret_binding_transition_approval_plans',
{
actionRef: varchar('action_ref', { length: 255 }).primaryKey(),
approvalPlanDigest: char('approval_plan_digest', { length: 64 }).notNull(),
transitionDigest: char('transition_digest', { length: 64 }).notNull(),
generationDigest: char('generation_digest', { length: 64 }).notNull(),
projectId: varchar('project_id', { length: 128 }).notNull(),
packageName: varchar('package_name', { length: 63 }).notNull(),
installationId: varchar('installation_id', { length: 128 }).notNull(),
lockDigest: char('lock_digest', { length: 64 }).notNull(),
generation: integer('generation').notNull(),
manifestDigest: char('manifest_digest', { length: 64 }).notNull(),
previousActiveLockDigest: char('previous_active_lock_digest', {
length: 64,
}).notNull(),
requestedByType: varchar('requested_by_type', { length: 16 }).notNull(),
requestedById: varchar('requested_by_id', { length: 255 }).notNull(),
plannedAtMs: bigint('planned_at_ms', { mode: 'number' }).notNull(),
expiresAtMs: bigint('expires_at_ms', { mode: 'number' }).notNull(),
planJson: jsonb('plan_json').$type<Record<string, unknown>>().notNull(),
},
(table) => [
foreignKey({
name: 'ql3_pp_secret_transition_plan_project_fk',
columns: [table.projectId],
foreignColumns: [projects.id],
})
.onDelete('restrict')
.onUpdate('restrict'),
foreignKey({
name: 'ql3_pp_secret_transition_plan_install_fk',
columns: [table.installationId],
foreignColumns: [pluginPackageInstalls.installationId],
})
.onDelete('restrict')
.onUpdate('restrict'),
check(
'ql3_pp_secret_transition_plan_identity_check',
sql`${table.actionRef} ~ '^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$' and ${table.projectId} ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' and ${table.packageName} ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' and ${table.installationId} ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' and ${table.generation} between 2 and 2147483647 and ${table.requestedByType} = 'user' and octet_length(${table.requestedById}) between 1 and 255 and ${table.requestedById} !~ '[[:cntrl:]]'`,
),
check(
'ql3_pp_secret_transition_plan_digest_check',
sql`${table.approvalPlanDigest} ~ '^[0-9a-f]{64}$' and ${table.transitionDigest} ~ '^[0-9a-f]{64}$' and ${table.generationDigest} ~ '^[0-9a-f]{64}$' and ${table.lockDigest} ~ '^[0-9a-f]{64}$' and ${table.manifestDigest} ~ '^[0-9a-f]{64}$' and ${table.previousActiveLockDigest} ~ '^[0-9a-f]{64}$'`,
),
check(
'ql3_pp_secret_transition_plan_time_check',
sql`${table.plannedAtMs} >= 0 and ${table.expiresAtMs} > ${table.plannedAtMs} and ${table.expiresAtMs} - ${table.plannedAtMs} <= 900000`,
),
check(
'ql3_pp_secret_transition_plan_json_check',
sql`jsonb_typeof(${table.planJson}) = 'object' and octet_length(${table.planJson}::text) between 2 and 229376 and ${table.planJson} @> jsonb_build_object('schema', 'qinglong/plugin-package-secret-binding-transition-approval-plan@v1', 'actionRef', ${table.actionRef}, 'approvalPlanDigest', ${table.approvalPlanDigest}, 'requestedBy', jsonb_build_object('type', ${table.requestedByType}, 'id', ${table.requestedById}), 'plannedAtMs', ${table.plannedAtMs}, 'expiresAtMs', ${table.expiresAtMs}, 'transitionPlan', jsonb_build_object('schema', 'qinglong/plugin-package-secret-binding-transition-plan@v1', 'transitionDigest', ${table.transitionDigest}, 'previousActiveLockDigest', ${table.previousActiveLockDigest}, 'nextTarget', jsonb_build_object('generationDigest', ${table.generationDigest}, 'projectId', ${table.projectId}, 'packageName', ${table.packageName}, 'installationId', ${table.installationId}, 'lockDigest', ${table.lockDigest}, 'generation', ${table.generation}, 'manifestDigest', ${table.manifestDigest}))) and jsonb_typeof(${table.planJson} #> '{transitionPlan,changes}') = 'array' and jsonb_array_length(${table.planJson} #> '{transitionPlan,changes}') between 1 and 64`,
),
uniqueIndex('ql3_pp_secret_transition_plan_digest_uidx').on(
table.approvalPlanDigest,
),
uniqueIndex('ql3_pp_secret_transition_plan_target_uidx').on(
table.generationDigest,
),
index('ql3_pp_secret_transition_plan_expiry_idx').on(
table.expiresAtMs,
table.actionRef,
),
],
);
export const projectToolDefinitionSnapshots = ql3Schema.table(
'project_tool_definition_snapshots',
{
@@ -6003,6 +6070,7 @@ export const ql3PostgresTables = [
pluginPackageMaterializedRevisions,
pluginPackageSecretBindings,
pluginPackageSecretBindingApprovalPlans,
pluginPackageSecretBindingTransitionApprovalPlans,
pluginPackageSecretBindingTransitionReceipts,
projectToolDefinitionSnapshots,
projectToolDefinitionSnapshotSources,
@@ -21,8 +21,8 @@ export interface PostgresSchemaContractTrigger {
export interface PostgresSchemaContract {
readonly schema: 'ql3';
readonly contractName: 'control-core';
readonly contractVersion: 62;
readonly migrationId: 'pg-0063-plugin-package-secret-binding-transition-receipts';
readonly contractVersion: 63;
readonly migrationId: 'pg-0064-plugin-package-secret-binding-transition-approval-plans';
readonly minimumServerMajor: 16;
readonly maximumServerMajor: 18;
readonly capabilities: Readonly<{
@@ -64,6 +64,7 @@ export interface PostgresSchemaContract {
plugin_package_secret_binding: 1;
plugin_package_secret_binding_approval_plan: 1;
plugin_package_secret_binding_transition: 1;
plugin_package_secret_binding_transition_approval_plan: 1;
plugin_package_secret_binding_transition_receipt: 1;
plugin_package_secret_materialization: 1;
plugin_package_proposal: 1;
@@ -116,8 +117,8 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
Object.freeze({
schema: 'ql3',
contractName: 'control-core',
contractVersion: 62,
migrationId: 'pg-0063-plugin-package-secret-binding-transition-receipts',
contractVersion: 63,
migrationId: 'pg-0064-plugin-package-secret-binding-transition-approval-plans',
minimumServerMajor: 16,
maximumServerMajor: 18,
capabilities: Object.freeze({
@@ -152,6 +153,7 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
plugin_package_secret_binding: 1,
plugin_package_secret_binding_approval_plan: 1,
plugin_package_secret_binding_transition: 1,
plugin_package_secret_binding_transition_approval_plan: 1,
plugin_package_secret_binding_transition_receipt: 1,
plugin_package_secret_materialization: 1,
plugin_package_proposal: 1,
@@ -290,6 +292,24 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
'expires_at_ms',
'plan_json',
]),
table('plugin_package_secret_binding_transition_approval_plans', [
'action_ref',
'approval_plan_digest',
'transition_digest',
'generation_digest',
'project_id',
'package_name',
'installation_id',
'lock_digest',
'generation',
'manifest_digest',
'previous_active_lock_digest',
'requested_by_type',
'requested_by_id',
'planned_at_ms',
'expires_at_ms',
'plan_json',
]),
table('plugin_package_secret_binding_transition_receipts', [
'generation_digest',
'transition_digest',
@@ -1494,6 +1514,10 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
'ql3_package_secret_transition_receipt_transition_uidx',
'ql3_package_secret_transition_receipt_digest_uidx',
'ql3_package_secret_transition_receipt_install_idx',
'plugin_package_secret_binding_transition_approval_plans_pkey',
'ql3_pp_secret_transition_plan_digest_uidx',
'ql3_pp_secret_transition_plan_target_uidx',
'ql3_pp_secret_transition_plan_expiry_idx',
'project_tool_definition_snapshots_pkey',
'ql3_project_tool_snapshot_withdrawal_key',
'ql3_project_tool_definition_snapshot_digest_uidx',
@@ -1795,6 +1819,10 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
'ql3_package_secret_transition_receipt_identity_check',
'ql3_package_secret_transition_receipt_digest_check',
'ql3_package_secret_transition_receipt_json_check',
'ql3_pp_secret_transition_plan_identity_check',
'ql3_pp_secret_transition_plan_digest_check',
'ql3_pp_secret_transition_plan_time_check',
'ql3_pp_secret_transition_plan_json_check',
'ql3_plugin_package_quarantine_identity_check',
'ql3_plugin_package_quarantine_state_check',
'ql3_plugin_package_quarantine_subject_check',
@@ -2254,6 +2282,8 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
'ql3_plugin_package_secret_binding_approval_plan_install_fk',
'ql3_plugin_package_secret_binding_transition_receipt_project_fk',
'ql3_plugin_package_secret_binding_transition_receipt_install_fk',
'ql3_pp_secret_transition_plan_project_fk',
'ql3_pp_secret_transition_plan_install_fk',
'ql3_project_tool_definition_snapshot_project_fk',
'ql3_project_tool_definition_snapshot_source_snapshot_fk',
'ql3_project_tool_definition_snapshot_source_install_fk',
@@ -2404,6 +2434,23 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
'ql3_run_retry_policies_run_fk',
]),
functions: Object.freeze([
Object.freeze({
name: 'plugin_package_secret_binding_transition_snapshot',
identityArguments:
'p_project_id character varying, p_package_name character varying',
owner: 'ql3_migration',
securityDefiner: true,
volatility: 'volatile',
configuration: Object.freeze(['search_path=pg_catalog, ql3']),
}),
Object.freeze({
name: 'create_plugin_package_secret_transition_plan',
identityArguments: 'p_plan_json jsonb',
owner: 'ql3_migration',
securityDefiner: true,
volatility: 'volatile',
configuration: Object.freeze(['search_path=pg_catalog, ql3']),
}),
Object.freeze({
name: 'plugin_package_secret_binding_planning_snapshot',
identityArguments:
@@ -186,6 +186,12 @@ const REQUIRED_RUNTIME_PRIVILEGES = Object.freeze({
update: false,
delete: false,
}),
plugin_package_secret_binding_transition_approval_plans: Object.freeze({
select: false,
insert: false,
update: false,
delete: false,
}),
plugin_package_secret_binding_transition_receipts: Object.freeze({
select: false,
insert: false,
@@ -717,6 +723,12 @@ const REQUIRED_ADMIN_PRIVILEGES = Object.freeze({
update: false,
delete: false,
}),
plugin_package_secret_binding_transition_approval_plans: Object.freeze({
select: false,
insert: false,
update: false,
delete: false,
}),
plugin_package_secret_binding_transition_receipts: Object.freeze({
select: false,
insert: false,
@@ -1225,6 +1237,7 @@ const REQUIRED_PACKAGE_MANAGER_PRIVILEGES: RequiredPrivileges = Object.freeze(
name === 'project_role_bindings' ||
name === 'plugin_package_lifecycle_plans' ||
name === 'plugin_package_secret_binding_approval_plans' ||
name === 'plugin_package_secret_binding_transition_approval_plans' ||
name === 'plugin_package_automation_publications' ||
name === 'plugin_package_automation_publication_heads' ||
name === 'plugin_package_publisher_trust_transition_receipts'
@@ -1268,6 +1281,7 @@ const REQUIRED_PACKAGE_EXECUTOR_PRIVILEGES: RequiredPrivileges = Object.freeze(
name === 'project_role_bindings' ||
name === 'plugin_package_install_proposals' ||
name === 'plugin_package_secret_binding_approval_plans' ||
name === 'plugin_package_secret_binding_transition_approval_plans' ||
name === 'plugin_package_publisher_revocation_proposals' ||
name === 'plugin_package_publisher_trust_transition_proposals'
? { ...NO_TABLE_PRIVILEGES, select: true }
@@ -1538,6 +1552,7 @@ const REQUIRED_ADMIN_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
const REQUIRED_RUNTIME_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
Object.freeze({
create_plugin_package_secret_transition_plan: false,
create_plugin_package_secret_binding_approval_plan: false,
commit_plugin_package_lifecycle: false,
commit_plugin_package_quarantine: false,
@@ -1555,12 +1570,14 @@ const REQUIRED_RUNTIME_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
plugin_package_lifecycle_blocking_runs: false,
plugin_package_run_start_allowed: true,
plugin_package_secret_binding_planning_snapshot: false,
plugin_package_secret_binding_transition_snapshot: false,
plugin_package_tool_start_allowed: true,
register_plugin_package_automation_disposition_event: false,
});
const REQUIRED_PACKAGE_MANAGER_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
Object.freeze({
create_plugin_package_secret_transition_plan: true,
create_plugin_package_secret_binding_approval_plan: true,
commit_plugin_package_lifecycle: false,
commit_plugin_package_quarantine: false,
@@ -1578,12 +1595,14 @@ const REQUIRED_PACKAGE_MANAGER_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
plugin_package_lifecycle_blocking_runs: false,
plugin_package_run_start_allowed: false,
plugin_package_secret_binding_planning_snapshot: true,
plugin_package_secret_binding_transition_snapshot: true,
plugin_package_tool_start_allowed: false,
register_plugin_package_automation_disposition_event: false,
});
const REQUIRED_PACKAGE_EXECUTOR_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
Object.freeze({
create_plugin_package_secret_transition_plan: false,
create_plugin_package_secret_binding_approval_plan: false,
commit_plugin_package_lifecycle: true,
commit_plugin_package_quarantine: true,
@@ -1601,6 +1620,7 @@ const REQUIRED_PACKAGE_EXECUTOR_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges
plugin_package_lifecycle_blocking_runs: true,
plugin_package_run_start_allowed: false,
plugin_package_secret_binding_planning_snapshot: false,
plugin_package_secret_binding_transition_snapshot: false,
plugin_package_tool_start_allowed: false,
register_plugin_package_automation_disposition_event: false,
});
@@ -114,6 +114,7 @@ test('defines the immutable PostgreSQL capability and Run core stream', async ()
'pg-0061-plugin-package-secret-binding-approval-plans',
'pg-0062-plugin-package-secret-binding-target-guard',
'pg-0063-plugin-package-secret-binding-transition-receipts',
'pg-0064-plugin-package-secret-binding-transition-approval-plans',
],
);
for (const migration of postgresqlMainMigrationStream.migrations) {
@@ -567,6 +568,11 @@ test('freezes every published PostgreSQL migration checksum', () => {
checksum:
'20c4d6e640b7fb05fc2f44dd9bec6d2d081697b83acf4e7dd5deb9a07afe54df',
},
{
id: 'pg-0064-plugin-package-secret-binding-transition-approval-plans',
checksum:
'1951b77a0265f8826169e4724424b2fbbd30061b27e27d3ba95de03430c1bac9',
},
];
assert.deepEqual(
postgresqlMainMigrationStream.migrations.map(({ id, checksum }) => ({
@@ -2172,3 +2178,52 @@ test('advances capability v62 with immutable Secret binding transition receipts'
/migration_id = 'pg-0062-plugin-package-secret-binding-target-guard'/,
);
});
test('advances capability v63 with manager-only immutable Secret transition plans', async () => {
const migration = migrationById(
'pg-0064-plugin-package-secret-binding-transition-approval-plans',
);
const statements = [];
await migration.up({
async query(statement) {
statements.push(statement);
return { rows: [] };
},
});
const sql = statements.join('\n');
assert.match(
sql,
/CREATE TABLE "ql3"\."plugin_package_secret_binding_transition_approval_plans"/,
);
assert.match(
sql,
/CREATE FUNCTION "ql3"\."plugin_package_secret_binding_transition_snapshot"\(/,
);
assert.match(
sql,
/CREATE FUNCTION "ql3"\."create_plugin_package_secret_transition_plan"\(/,
);
assert.match(sql, /SECURITY DEFINER/);
assert.match(sql, /previous\.state = 'active'/);
assert.match(sql, /install\.state = 'staged'/);
assert.match(sql, /previous_binding\.binding_json = p_plan_json/);
assert.match(
sql,
/GRANT SELECT ON "ql3"\."plugin_package_secret_binding_transition_approval_plans" TO ql3_package_manager, ql3_package_executor/,
);
assert.match(
sql,
/GRANT EXECUTE ON FUNCTION [^;]+ TO ql3_package_manager/,
);
assert.doesNotMatch(sql, /GRANT EXECUTE ON FUNCTION [^;]+ TO ql3_package_executor/);
assert.match(sql, /contract_version = 63/);
assert.match(
sql,
/"plugin_package_secret_binding_transition_approval_plan":1/,
);
assert.match(sql, /contract_version = 62/);
assert.match(
sql,
/migration_id = 'pg-0063-plugin-package-secret-binding-transition-receipts'/,
);
});
@@ -76,6 +76,12 @@ function validPrivileges() {
plugin_package_materialized_revisions: [false, false, false, false],
plugin_package_secret_bindings: [false, false, false, false],
plugin_package_secret_binding_approval_plans: [false, false, false, false],
plugin_package_secret_binding_transition_approval_plans: [
false,
false,
false,
false,
],
plugin_package_secret_binding_transition_receipts: [
false,
false,
@@ -202,6 +208,12 @@ function validAdminPrivileges() {
plugin_package_materialized_revisions: [false, false, false, false],
plugin_package_secret_bindings: [false, false, false, false],
plugin_package_secret_binding_approval_plans: [false, false, false, false],
plugin_package_secret_binding_transition_approval_plans: [
false,
false,
false,
false,
],
plugin_package_secret_binding_transition_receipts: [
false,
false,
@@ -302,6 +314,7 @@ function packagePrivileges(kind) {
'plugin_package_publisher_trust_transition_receipts',
'plugin_package_lifecycle_plans',
'plugin_package_secret_binding_approval_plans',
'plugin_package_secret_binding_transition_approval_plans',
'plugin_package_automation_publications',
'plugin_package_automation_publication_heads',
...(manager
@@ -686,9 +699,11 @@ function queryable(overrides = {}) {
executeAllowed:
overrides.functionMode === 'package-manager'
? [
'create_plugin_package_secret_transition_plan',
'create_plugin_package_secret_binding_approval_plan',
'lock_approval_policy_fence',
'plugin_package_secret_binding_planning_snapshot',
'plugin_package_secret_binding_transition_snapshot',
].includes(functionName)
: overrides.functionMode === 'manager'
? functionName === 'lock_approval_policy_fence'
@@ -790,7 +805,7 @@ test('accepts the exact PostgreSQL control schema and least-privilege runtime ro
serverMajor: 16,
currentUser: 'ql3_runtime',
contractName: 'control-core',
contractVersion: 62,
contractVersion: 63,
migrationIds: [
'pg-0001-schema-capability',
'pg-0002-run-core',
@@ -855,6 +870,7 @@ test('accepts the exact PostgreSQL control schema and least-privilege runtime ro
'pg-0061-plugin-package-secret-binding-approval-plans',
'pg-0062-plugin-package-secret-binding-target-guard',
'pg-0063-plugin-package-secret-binding-transition-receipts',
'pg-0064-plugin-package-secret-binding-transition-approval-plans',
],
});
});
@@ -885,10 +901,10 @@ test('accepts the exact schema and isolated least-privilege admin role', async (
}),
);
assert.equal(report.currentUser, 'ql3_admin');
assert.equal(report.contractVersion, 62);
assert.equal(report.contractVersion, 63);
assert.equal(
report.migrationIds.at(-1),
'pg-0063-plugin-package-secret-binding-transition-receipts',
'pg-0064-plugin-package-secret-binding-transition-approval-plans',
);
});
@@ -901,10 +917,10 @@ test('accepts the isolated least-privilege automation manager role', async () =>
}),
);
assert.equal(report.currentUser, 'ql3_automation_manager');
assert.equal(report.contractVersion, 62);
assert.equal(report.contractVersion, 63);
assert.equal(
report.migrationIds.at(-1),
'pg-0063-plugin-package-secret-binding-transition-receipts',
'pg-0064-plugin-package-secret-binding-transition-approval-plans',
);
const widened = automationManagerPrivileges();
@@ -933,10 +949,10 @@ test('accepts the isolated least-privilege human Approval manager role', async (
}),
);
assert.equal(report.currentUser, 'ql3_approval_manager');
assert.equal(report.contractVersion, 62);
assert.equal(report.contractVersion, 63);
assert.equal(
report.migrationIds.at(-1),
'pg-0063-plugin-package-secret-binding-transition-receipts',
'pg-0064-plugin-package-secret-binding-transition-approval-plans',
);
const widened = approvalManagerPrivileges();
@@ -967,10 +983,10 @@ test('accepts the isolated least-privilege Run manager role', async () => {
}),
);
assert.equal(report.currentUser, 'ql3_run_manager');
assert.equal(report.contractVersion, 62);
assert.equal(report.contractVersion, 63);
assert.equal(
report.migrationIds.at(-1),
'pg-0063-plugin-package-secret-binding-transition-receipts',
'pg-0064-plugin-package-secret-binding-transition-approval-plans',
);
const widened = runManagerPrivileges();
@@ -1102,10 +1118,10 @@ test('accepts the exact schema and isolated Worker ingress role', async () => {
}),
);
assert.equal(report.currentUser, 'ql3_worker_ingress');
assert.equal(report.contractVersion, 62);
assert.equal(report.contractVersion, 63);
assert.equal(
report.migrationIds.at(-1),
'pg-0063-plugin-package-secret-binding-transition-receipts',
'pg-0064-plugin-package-secret-binding-transition-approval-plans',
);
});