feat(ql3): persist cluster secret transition receipts

This commit is contained in:
whyour
2026-08-13 21:48:57 +08:00
parent c877147d0f
commit 5f93e0b5d2
26 changed files with 1127 additions and 66 deletions
@@ -43,6 +43,12 @@ export { PostgresPluginPackageMaterializedRevisionRepository } from '../plugin-p
export { PostgresApprovalRequestRepository } from '../approved-action/approvalRequestRepository';
export { PostgresProjectPolicyRepository } from '../security/projectPolicyRepository';
export { PostgresPluginPackageSecretBindingRepository } from '../plugin-package/installation/pluginPackageSecretBindingRepository';
export { PostgresPluginPackageSecretBindingActivationPrerequisite } from '../plugin-package/secret-binding/pluginPackageSecretBindingActivationPrerequisite';
export {
PostgresPluginPackageSecretBindingTransitionRepository,
type ApplyPostgresPluginPackageSecretBindingTransitionInput,
type ApplyPostgresPluginPackageSecretBindingTransitionResult,
} from '../plugin-package/secret-binding/pluginPackageSecretBindingTransitionRepository';
export { PostgresPluginPackageSecretBindingApprovalPlanReader } from '../plugin-package/secret-binding/pluginPackageSecretBindingApprovalPlanRepository';
export { PostgresPluginPackageAutomationPublicationRepository } from '../plugin-package/publication/pluginPackageAutomationPublicationRepository';
export {
@@ -318,5 +318,10 @@ export const postgresqlMainMigrationManifest: MigrationStreamManifest =
checksum:
'cd4f92d8702da6b92dd9ae5153b5180400b94442f56393692b6ec038f998596b',
}),
Object.freeze({
id: 'pg-0063-plugin-package-secret-binding-transition-receipts',
checksum:
'20c4d6e640b7fb05fc2f44dd9bec6d2d081697b83acf4e7dd5deb9a07afe54df',
}),
]),
});
@@ -65,6 +65,7 @@ import { pg0059PluginPackageSecretBindingsMigration } from './pg-0059-plugin-pac
import { pg0060PluginPackageSecretMaterializationGuardMigration } from './pg-0060-plugin-package-secret-materialization-guard';
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';
export const postgresqlMainMigrationStream: MigrationStreamDefinition<PostgresMigrationContext> =
Object.freeze({
@@ -135,5 +136,6 @@ export const postgresqlMainMigrationStream: MigrationStreamDefinition<PostgresMi
pg0060PluginPackageSecretMaterializationGuardMigration,
pg0061PluginPackageSecretBindingApprovalPlansMigration,
pg0062PluginPackageSecretBindingTargetGuardMigration,
pg0063PluginPackageSecretBindingTransitionReceiptsMigration,
]),
});
@@ -0,0 +1,161 @@
import { CAPABILITIES_V61 } from './pg-0062-plugin-package-secret-binding-target-guard';
import { definePostgresSqlMigration } from './sqlMigration';
export const CAPABILITIES_V62 = CAPABILITIES_V61.replace(
'"plugin_package_secret_binding_transition":1,',
'"plugin_package_secret_binding_transition":1,"plugin_package_secret_binding_transition_receipt":1,',
);
export const pg0063PluginPackageSecretBindingTransitionReceiptsMigration =
definePostgresSqlMigration({
id: 'pg-0063-plugin-package-secret-binding-transition-receipts',
statements: [
`
CREATE TABLE "ql3"."plugin_package_secret_binding_transition_receipts" (
generation_digest char(64) PRIMARY KEY,
transition_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,
authority_kind varchar(32) NOT NULL,
evidence_digest char(64) NOT NULL,
binding_digest char(64),
committed_at_ms bigint NOT NULL,
receipt_digest char(64) NOT NULL,
receipt_json jsonb NOT NULL,
CONSTRAINT ql3_plugin_package_secret_binding_transition_receipt_project_fk
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_plugin_package_secret_binding_transition_receipt_install_fk
FOREIGN KEY (installation_id)
REFERENCES "ql3"."plugin_package_installs" (installation_id)
ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT ql3_package_secret_transition_receipt_identity_check CHECK (
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
authority_kind = 'approved-action-execution' AND
committed_at_ms >= 0
),
CONSTRAINT ql3_package_secret_transition_receipt_digest_check CHECK (
generation_digest ~ '^[0-9a-f]{64}$' AND
transition_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}$' AND
evidence_digest ~ '^[0-9a-f]{64}$' AND
(binding_digest IS NULL OR binding_digest ~ '^[0-9a-f]{64}$') AND
receipt_digest ~ '^[0-9a-f]{64}$'
),
CONSTRAINT ql3_package_secret_transition_receipt_json_check CHECK (
jsonb_typeof(receipt_json) = 'object' AND
octet_length(receipt_json::text) BETWEEN 2 AND 196608 AND
receipt_json @> jsonb_build_object(
'schema', 'qinglong/plugin-package-secret-binding-transition-receipt@v1',
'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
)
),
'authority', jsonb_build_object(
'kind', authority_kind,
'evidenceDigest', evidence_digest
),
'bindingDigest', binding_digest,
'committedAtMs', committed_at_ms,
'receiptDigest', receipt_digest
)
)
)
`.trim(),
`CREATE UNIQUE INDEX ql3_package_secret_transition_receipt_transition_uidx ON "ql3"."plugin_package_secret_binding_transition_receipts" (transition_digest)`,
`CREATE UNIQUE INDEX ql3_package_secret_transition_receipt_digest_uidx ON "ql3"."plugin_package_secret_binding_transition_receipts" (receipt_digest)`,
`CREATE INDEX ql3_package_secret_transition_receipt_install_idx ON "ql3"."plugin_package_secret_binding_transition_receipts" (installation_id, generation_digest)`,
`
CREATE FUNCTION "ql3"."enforce_plugin_package_secret_binding_transition_receipt_target"()
RETURNS trigger
LANGUAGE plpgsql
VOLATILE
SET search_path = pg_catalog, ql3
AS $ql3$
BEGIN
PERFORM 1
FROM "ql3"."plugin_package_install_heads" AS head
JOIN "ql3"."plugin_package_installs" AS 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" AS previous
ON previous.project_id = install.project_id
AND previous.package_name = install.package_name
AND previous.lock_digest = install.previous_active_lock_digest
WHERE head.project_id = NEW.project_id
AND head.package_name = NEW.package_name
AND install.installation_id = NEW.installation_id
AND install.lock_digest = NEW.lock_digest
AND install.target_generation = NEW.generation
AND install.lock_json ->> 'manifestDigest' = NEW.manifest_digest
AND install.state = 'staged'
AND install.previous_active_lock_digest = NEW.previous_active_lock_digest
AND install.active_lock_digest = install.previous_active_lock_digest
AND install.target_generation = (
SELECT MAX(history.target_generation)
FROM "ql3"."plugin_package_installs" AS history
WHERE history.project_id = install.project_id
AND history.package_name = install.package_name
)
AND previous.state = 'active'
AND previous.active_lock_digest = previous.lock_digest
AND previous.target_generation < install.target_generation
AND (
(NEW.binding_digest IS NULL AND NOT EXISTS (
SELECT 1 FROM "ql3"."plugin_package_secret_bindings" AS binding
WHERE binding.generation_digest = NEW.generation_digest
)) OR
EXISTS (
SELECT 1 FROM "ql3"."plugin_package_secret_bindings" AS binding
WHERE binding.generation_digest = NEW.generation_digest
AND binding.binding_digest = NEW.binding_digest
AND binding.project_id = NEW.project_id
AND binding.package_name = NEW.package_name
AND binding.installation_id = NEW.installation_id
AND binding.lock_digest = NEW.lock_digest
AND binding.generation = NEW.generation
AND binding.manifest_digest = NEW.manifest_digest
AND binding.authority_kind = NEW.authority_kind
AND binding.evidence_digest = NEW.evidence_digest
AND binding.bound_at_ms = NEW.committed_at_ms
)
)
FOR SHARE OF head, install, previous;
IF NOT FOUND THEN
RAISE EXCEPTION
'Plugin Package Secret binding transition receipt target is not reviewed staged generation'
USING ERRCODE = 'check_violation';
END IF;
RETURN NEW;
END
$ql3$
`.trim(),
`REVOKE ALL ON "ql3"."plugin_package_secret_binding_transition_receipts" FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager, ql3_package_executor, ql3_worker_ingress`,
`GRANT SELECT, INSERT ON "ql3"."plugin_package_secret_binding_transition_receipts" TO ql3_package_executor`,
`REVOKE ALL ON FUNCTION "ql3"."enforce_plugin_package_secret_binding_transition_receipt_target"() FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager, ql3_package_executor, ql3_worker_ingress`,
`CREATE TRIGGER ql3_package_secret_transition_receipt_target_guard BEFORE INSERT ON "ql3"."plugin_package_secret_binding_transition_receipts" FOR EACH ROW EXECUTE FUNCTION "ql3"."enforce_plugin_package_secret_binding_transition_receipt_target"()`,
`DO $ql3$ BEGIN UPDATE "ql3"."schema_capabilities" SET contract_version = 62, migration_id = 'pg-0063-plugin-package-secret-binding-transition-receipts', capabilities = '${CAPABILITIES_V62}'::jsonb, updated_at_ms = floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint WHERE contract_name = 'control-core' AND contract_version = 61 AND migration_id = 'pg-0062-plugin-package-secret-binding-target-guard' AND capabilities = '${CAPABILITIES_V61}'::jsonb; IF NOT FOUND THEN RAISE EXCEPTION 'control-core capability is not at version 61' USING ERRCODE = 'check_violation'; END IF; END $ql3$`,
],
});
@@ -1345,6 +1345,44 @@ export class PostgresPluginPackageInstallRepository
JOIN "ql3"."plugin_package_installs" AS install
ON install.installation_id = head.installation_id
WHERE install.state IN ('queued', 'staged', 'activating')
AND (
install.state <> 'staged' OR
install.previous_active_lock_digest IS NULL OR
NOT (
EXISTS (
SELECT 1
FROM "ql3"."plugin_package_admission_receipts" AS admission
JOIN "ql3"."plugin_package_install_proposals" AS proposal
ON proposal.action_ref = admission.action_ref
WHERE admission.installation_id = install.installation_id
AND jsonb_array_length(
proposal.proposal_json #> '{actionInput,manifest,spec,permissions,secrets}'
) > 0
) OR
EXISTS (
SELECT 1
FROM "ql3"."plugin_package_installs" AS previous
JOIN "ql3"."plugin_package_secret_bindings" AS binding
ON binding.installation_id = previous.installation_id
WHERE previous.project_id = install.project_id
AND previous.package_name = install.package_name
AND previous.lock_digest = install.previous_active_lock_digest
)
) OR
EXISTS (
SELECT 1
FROM "ql3"."plugin_package_secret_binding_transition_receipts"
AS receipt
WHERE receipt.project_id = install.project_id
AND receipt.package_name = install.package_name
AND receipt.installation_id = install.installation_id
AND receipt.lock_digest = install.lock_digest
AND receipt.generation = install.target_generation
AND receipt.manifest_digest = install.lock_json ->> 'manifestDigest'
AND receipt.previous_active_lock_digest =
install.previous_active_lock_digest
)
)
AND NOT EXISTS (
SELECT 1
FROM "ql3"."plugin_package_quarantine_events" AS quarantine
@@ -0,0 +1,154 @@
import type { PostgresPool } from '@qinglong/runtime-core';
import {
normalizePluginPackageInstallProposal,
type PluginPackageInstallProposal,
} from '@qinglong/runtime-core/plugin-package-proposal';
import {
assertPluginPackageInstallMatchesLock,
normalizePluginPackageInstallRecord,
normalizePluginPackageLock,
type PluginPackageInstallRecord,
type PluginPackageLock,
} from '@qinglong/runtime-core/plugin-package-install';
import type {
PluginPackageActivationPrerequisite,
PluginPackageActivationPrerequisiteObservation,
} from '@qinglong/runtime-core/plugin-package-installation';
import { createPluginPackageResourceGenerationFromReferences } from '@qinglong/runtime-core/plugin-package-resource-generation';
import { createPluginPackageSecretBindingTarget } from '@qinglong/runtime-core/plugin-package-secret-binding';
import {
postgresRequiredJsonObject,
postgresRequiredString,
} from '../../repository/definitionRepositorySupport';
import { PostgresPluginPackageSecretBindingTransitionRepository } from './pluginPackageSecretBindingTransitionRepository';
type Row = Record<string, unknown>;
function unavailable(cause?: unknown): Error {
return new Error('Secret binding activation prerequisite is unavailable', {
cause: cause instanceof Error ? cause : undefined,
});
}
export class PostgresPluginPackageSecretBindingActivationPrerequisite
implements PluginPackageActivationPrerequisite
{
readonly #receipts: PostgresPluginPackageSecretBindingTransitionRepository;
constructor(private readonly pool: PostgresPool) {
if (
!pool ||
typeof pool.query !== 'function' ||
typeof pool.connect !== 'function'
) {
throw new TypeError(
'PostgreSQL Secret binding activation prerequisite pool is invalid',
);
}
this.#receipts = new PostgresPluginPackageSecretBindingTransitionRepository(
pool,
);
}
async inspect(
recordValue: Readonly<PluginPackageInstallRecord>,
lockValue: Readonly<PluginPackageLock>,
): Promise<Readonly<PluginPackageActivationPrerequisiteObservation>> {
const record = normalizePluginPackageInstallRecord(recordValue);
const lock = normalizePluginPackageLock(lockValue);
assertPluginPackageInstallMatchesLock(lock, record);
if (record.state !== 'staged') {
throw new Error(
'Secret binding activation prerequisite requires staged install',
);
}
if (record.previousActiveLockDigest === null) {
return Object.freeze({ status: 'ready' as const });
}
try {
const result = await this.pool.query<Row>(
`SELECT proposal.proposal_json AS "proposalJson",
EXISTS (
SELECT 1
FROM "ql3"."plugin_package_installs" AS previous
JOIN "ql3"."plugin_package_secret_bindings" AS binding
ON binding.installation_id = previous.installation_id
WHERE previous.project_id = install.project_id
AND previous.package_name = install.package_name
AND previous.lock_digest = install.previous_active_lock_digest
) AS "previousBindingPresent"
FROM "ql3"."plugin_package_install_heads" AS head
JOIN "ql3"."plugin_package_installs" AS install
ON install.installation_id = head.installation_id
JOIN "ql3"."plugin_package_admission_receipts" AS admission
ON admission.installation_id = install.installation_id
JOIN "ql3"."plugin_package_install_proposals" AS proposal
ON proposal.action_ref = admission.action_ref
WHERE head.project_id = $1 AND head.package_name = $2
AND install.installation_id = $3 AND install.lock_digest = $4
AND install.state = 'staged'
AND install.previous_active_lock_digest = $5
LIMIT 2`,
[
record.projectId,
record.packageName,
record.installationId,
record.lockDigest,
record.previousActiveLockDigest,
],
);
if (result.rows.length !== 1) throw unavailable();
const proposal = normalizePluginPackageInstallProposal(
postgresRequiredJsonObject(
result.rows[0]!.proposalJson,
unavailable,
) as unknown as PluginPackageInstallProposal,
);
if (
proposal.actionDigest !== lock.approval.actionDigest ||
proposal.previewDigest !== lock.approval.previewDigest ||
proposal.actionInput.targetGeneration !== record.targetGeneration ||
proposal.actionInput.source.contentDigest !== lock.source.contentDigest
) {
throw unavailable(
new Error('Secret binding activation prerequisite provenance drift'),
);
}
const required =
proposal.actionInput.manifest.spec.permissions.secrets.length > 0 ||
result.rows[0]!.previousBindingPresent === true;
if (!required) return Object.freeze({ status: 'ready' as const });
const generation = createPluginPackageResourceGenerationFromReferences({
installationId: record.installationId,
projectId: record.projectId,
packageName: record.packageName,
lockDigest: record.lockDigest,
generation: record.targetGeneration,
previousActiveLockDigest: record.previousActiveLockDigest,
contentDigest: lock.source.contentDigest,
resources: lock.resources,
});
const target = createPluginPackageSecretBindingTarget(
generation,
proposal.actionInput.manifest,
);
const receipt = await this.#receipts.find(target.generationDigest);
if (
!receipt ||
JSON.stringify(receipt.transitionPlan.nextTarget) !==
JSON.stringify(target) ||
receipt.transitionPlan.previousActiveLockDigest !==
record.previousActiveLockDigest
) {
return Object.freeze({
status: 'deferred' as const,
reason: 'secret_binding_transition_required' as const,
});
}
return Object.freeze({ status: 'ready' as const });
} catch (error) {
throw unavailable(error);
}
}
}
@@ -0,0 +1,391 @@
import type { PostgresClient, PostgresPool } from '@qinglong/runtime-core';
import {
createPluginPackageSecretBindingFromTransitionPlan,
createPluginPackageSecretBindingTransitionReceipt,
normalizePluginPackageSecretBindingTransitionReceipt,
type PluginPackageSecretBindingTransitionReceipt,
type PluginPackageSecretBindingTransitionReceiptRepository,
} from '@qinglong/runtime-core/plugin-package-secret-binding-transition-receipt';
import {
normalizePluginPackageSecretBindingTransitionPlan,
type PluginPackageSecretBindingTransitionPlan,
} from '@qinglong/runtime-core/plugin-package-secret-binding-transition-plan';
import {
PluginPackageSecretBindingConflictError,
PluginPackageSecretBindingUnavailableError,
normalizePluginPackageSecretBinding,
type PluginPackageSecretBinding,
} from '@qinglong/runtime-core/plugin-package-secret-binding';
import {
POSTGRES_DEFINITION_RETRYABLE_SQL_STATES,
POSTGRES_DEFINITION_TRANSACTION_ATTEMPTS,
configurePostgresDefinitionTransaction,
postgresRequiredJsonObject,
postgresRequiredString,
postgresSqlState,
rollbackPostgresDefinitionTransaction,
} from '../../repository/definitionRepositorySupport';
import { PostgresPluginPackageSecretBindingRepository } from '../installation/pluginPackageSecretBindingRepository';
type Row = Record<string, unknown>;
const DIGEST = /^[0-9a-f]{64}$/;
export interface ApplyPostgresPluginPackageSecretBindingTransitionInput {
readonly transitionPlan: Readonly<PluginPackageSecretBindingTransitionPlan>;
readonly evidenceDigest: string;
readonly committedAtMs: number;
}
export interface ApplyPostgresPluginPackageSecretBindingTransitionResult {
readonly status: 'created' | 'existing';
readonly binding: Readonly<PluginPackageSecretBinding> | null;
readonly receipt: Readonly<PluginPackageSecretBindingTransitionReceipt>;
}
function unavailable(
cause?: unknown,
): PluginPackageSecretBindingUnavailableError {
return new PluginPackageSecretBindingUnavailableError({
cause: cause instanceof Error ? cause : undefined,
});
}
function mappedError(error: unknown): Error {
if (
error instanceof PluginPackageSecretBindingConflictError ||
error instanceof PluginPackageSecretBindingUnavailableError ||
error instanceof TypeError
) {
return error;
}
const state = postgresSqlState(error);
if (state === '23503' || state === '23505' || state === '23514') {
return new PluginPackageSecretBindingConflictError(
'durable transition identity or staged generation conflicts',
);
}
return unavailable(error);
}
function digest(value: unknown, label: string): string {
if (typeof value !== 'string' || !DIGEST.test(value)) {
throw new TypeError(`${label} is invalid`);
}
return value;
}
function timestamp(value: unknown): number {
if (!Number.isSafeInteger(value) || (value as number) < 0) {
throw new TypeError('Secret binding transition commit time is invalid');
}
return value as number;
}
function same(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
function parseReceipt(
row: Row,
): Readonly<PluginPackageSecretBindingTransitionReceipt> {
try {
const receipt = normalizePluginPackageSecretBindingTransitionReceipt(
postgresRequiredJsonObject(
row.receiptJson,
unavailable,
) as unknown as PluginPackageSecretBindingTransitionReceipt,
);
if (
receipt.receiptDigest !==
postgresRequiredString(row.receiptDigest, unavailable)
) {
throw unavailable();
}
return receipt;
} catch (error) {
if (error instanceof PluginPackageSecretBindingUnavailableError)
throw error;
throw unavailable(error);
}
}
export class PostgresPluginPackageSecretBindingTransitionRepository
implements PluginPackageSecretBindingTransitionReceiptRepository
{
constructor(private readonly pool: PostgresPool) {
if (
!pool ||
typeof pool.query !== 'function' ||
typeof pool.connect !== 'function'
) {
throw new TypeError(
'PostgreSQL Secret binding transition pool is invalid',
);
}
}
private async findStored(
queryable: Pick<PostgresPool, 'query'> | Pick<PostgresClient, 'query'>,
generationDigest: string,
): Promise<Readonly<PluginPackageSecretBindingTransitionReceipt> | null> {
const result = await queryable.query<Row>(
`SELECT receipt_json AS "receiptJson", receipt_digest AS "receiptDigest"
FROM "ql3"."plugin_package_secret_binding_transition_receipts"
WHERE generation_digest = $1
LIMIT 2`,
[generationDigest],
);
if (result.rows.length === 0) return null;
if (result.rows.length !== 1) throw unavailable();
return parseReceipt(result.rows[0]!);
}
async find(
generationDigestValue: string,
): Promise<Readonly<PluginPackageSecretBindingTransitionReceipt> | null> {
try {
return await this.findStored(
this.pool,
digest(generationDigestValue, 'generation digest'),
);
} catch (error) {
throw mappedError(error);
}
}
async #transaction<T>(
work: (client: PostgresClient) => Promise<T>,
): Promise<T> {
for (
let attempt = 0;
attempt < POSTGRES_DEFINITION_TRANSACTION_ATTEMPTS;
attempt += 1
) {
let client: PostgresClient;
try {
client = await this.pool.connect();
} catch (error) {
throw mappedError(error);
}
let began = false;
try {
await configurePostgresDefinitionTransaction(client);
began = true;
const result = await work(client);
await client.query('COMMIT');
began = false;
return result;
} catch (error) {
if (began) await rollbackPostgresDefinitionTransaction(client);
const state = postgresSqlState(error);
if (
state &&
POSTGRES_DEFINITION_RETRYABLE_SQL_STATES.has(state) &&
attempt + 1 < POSTGRES_DEFINITION_TRANSACTION_ATTEMPTS
) {
continue;
}
throw mappedError(error);
} finally {
client.release();
}
}
throw unavailable();
}
apply(
input: Readonly<ApplyPostgresPluginPackageSecretBindingTransitionInput>,
): Promise<
Readonly<ApplyPostgresPluginPackageSecretBindingTransitionResult>
> {
if (
!input ||
typeof input !== 'object' ||
Array.isArray(input) ||
Object.keys(input).sort().join('\0') !==
'committedAtMs\0evidenceDigest\0transitionPlan'
) {
throw new TypeError('Secret binding transition execution is invalid');
}
const plan = normalizePluginPackageSecretBindingTransitionPlan(
input.transitionPlan,
);
const evidenceDigest = digest(input.evidenceDigest, 'evidence digest');
const committedAtMs = timestamp(input.committedAtMs);
const proposedBinding = createPluginPackageSecretBindingFromTransitionPlan(
plan,
'approved-action-execution',
evidenceDigest,
committedAtMs,
);
const proposedReceipt = createPluginPackageSecretBindingTransitionReceipt({
transitionPlan: plan,
authority: {
kind: 'approved-action-execution',
evidenceDigest,
},
binding: proposedBinding,
committedAtMs,
});
return this.#transaction(async (client) => {
const existing = await this.findStored(
client,
plan.nextTarget.generationDigest,
);
if (existing) {
if (!same(existing, proposedReceipt)) {
throw new PluginPackageSecretBindingConflictError(
'generation is committed by another transition authority',
);
}
const binding = existing.bindingDigest
? await new PostgresPluginPackageSecretBindingRepository(client).find(
plan.nextTarget.generationDigest,
)
: null;
if (
(existing.bindingDigest === null) !== (binding === null) ||
(binding && binding.bindingDigest !== existing.bindingDigest)
) {
throw unavailable();
}
return Object.freeze({
status: 'existing' as const,
binding,
receipt: existing,
});
}
const authority = await client.query<Row>(
`SELECT previous_binding.binding_json AS "previousBindingJson"
FROM "ql3"."plugin_package_install_heads" AS head
JOIN "ql3"."plugin_package_installs" AS 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" AS 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" 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 = $1 AND head.package_name = $2
AND install.installation_id = $3 AND install.lock_digest = $4
AND install.target_generation = $5
AND install.lock_json ->> 'manifestDigest' = $6
AND install.state = 'staged'
AND install.previous_active_lock_digest = $7
AND install.active_lock_digest = install.previous_active_lock_digest
AND install.target_generation = (
SELECT MAX(history.target_generation)
FROM "ql3"."plugin_package_installs" AS history
WHERE history.project_id = install.project_id
AND history.package_name = install.package_name
)
AND previous.state = 'active'
AND previous.active_lock_digest = previous.lock_digest
AND previous.installation_id = $8
AND previous.lock_digest = $7
AND previous.target_generation = $9
AND previous.lock_json ->> 'manifestDigest' = $10
FOR SHARE OF head, install, previous`,
[
plan.nextTarget.projectId,
plan.nextTarget.packageName,
plan.nextTarget.installationId,
plan.nextTarget.lockDigest,
plan.nextTarget.generation,
plan.nextTarget.manifestDigest,
plan.previousActiveLockDigest,
plan.previousTarget.installationId,
plan.previousTarget.generation,
plan.previousTarget.manifestDigest,
],
);
if (authority.rows.length !== 1) {
throw new PluginPackageSecretBindingConflictError(
'transition is not the current reviewed staged generation',
);
}
const previousBindingJson = authority.rows[0]!.previousBindingJson;
const durablePreviousBinding =
previousBindingJson === null
? null
: normalizePluginPackageSecretBinding(
postgresRequiredJsonObject(previousBindingJson, unavailable),
);
if (!same(durablePreviousBinding, plan.previousBinding)) {
throw new PluginPackageSecretBindingConflictError(
'previous active binding changed after transition planning',
);
}
const bindingResult = proposedBinding
? await new PostgresPluginPackageSecretBindingRepository(
client,
).publish(proposedBinding)
: null;
const receipt = createPluginPackageSecretBindingTransitionReceipt({
transitionPlan: plan,
authority: {
kind: 'approved-action-execution',
evidenceDigest,
},
binding: bindingResult?.binding ?? null,
committedAtMs,
});
const target = plan.nextTarget;
const inserted = await client.query(
`INSERT INTO "ql3"."plugin_package_secret_binding_transition_receipts" (
generation_digest, transition_digest, project_id, package_name,
installation_id, lock_digest, generation, manifest_digest,
previous_active_lock_digest, authority_kind, evidence_digest,
binding_digest, committed_at_ms, receipt_digest, receipt_json
) VALUES (
$1::char(64), $2::char(64), $3::varchar(128), $4::varchar(63),
$5::varchar(128), $6::char(64), $7::integer, $8::char(64),
$9::char(64), $10::varchar(32), $11::char(64), $12::char(64),
$13::bigint, $14::char(64), $15::jsonb
) ON CONFLICT (generation_digest) DO NOTHING
RETURNING generation_digest`,
[
target.generationDigest,
plan.transitionDigest,
target.projectId,
target.packageName,
target.installationId,
target.lockDigest,
target.generation,
target.manifestDigest,
plan.previousActiveLockDigest,
receipt.authority.kind,
receipt.authority.evidenceDigest,
receipt.bindingDigest,
receipt.committedAtMs,
receipt.receiptDigest,
JSON.stringify(receipt),
],
);
const stored = await this.findStored(client, target.generationDigest);
if (!stored || !same(stored, receipt)) {
throw new PluginPackageSecretBindingConflictError(
'generation is bound to another transition receipt',
);
}
return Object.freeze({
status:
inserted.rows.length === 1
? ('created' as const)
: ('existing' as const),
binding: bindingResult?.binding ?? null,
receipt: stored,
});
});
}
}
@@ -452,6 +452,69 @@ export const pluginPackageSecretBindingApprovalPlans = ql3Schema.table(
],
);
export const pluginPackageSecretBindingTransitionReceipts = ql3Schema.table(
'plugin_package_secret_binding_transition_receipts',
{
generationDigest: char('generation_digest', { length: 64 }).primaryKey(),
transitionDigest: char('transition_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(),
authorityKind: varchar('authority_kind', { length: 32 }).notNull(),
evidenceDigest: char('evidence_digest', { length: 64 }).notNull(),
bindingDigest: char('binding_digest', { length: 64 }),
committedAtMs: bigint('committed_at_ms', { mode: 'number' }).notNull(),
receiptDigest: char('receipt_digest', { length: 64 }).notNull(),
receiptJson: jsonb('receipt_json')
.$type<Record<string, unknown>>()
.notNull(),
},
(table) => [
foreignKey({
name: 'ql3_plugin_package_secret_binding_transition_receipt_project_fk',
columns: [table.projectId],
foreignColumns: [projects.id],
})
.onDelete('restrict')
.onUpdate('restrict'),
foreignKey({
name: 'ql3_plugin_package_secret_binding_transition_receipt_install_fk',
columns: [table.installationId],
foreignColumns: [pluginPackageInstalls.installationId],
})
.onDelete('restrict')
.onUpdate('restrict'),
check(
'ql3_package_secret_transition_receipt_identity_check',
sql`${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.authorityKind} = 'approved-action-execution' and ${table.committedAtMs} >= 0`,
),
check(
'ql3_package_secret_transition_receipt_digest_check',
sql`${table.generationDigest} ~ '^[0-9a-f]{64}$' and ${table.transitionDigest} ~ '^[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}$' and ${table.evidenceDigest} ~ '^[0-9a-f]{64}$' and (${table.bindingDigest} is null or ${table.bindingDigest} ~ '^[0-9a-f]{64}$') and ${table.receiptDigest} ~ '^[0-9a-f]{64}$'`,
),
check(
'ql3_package_secret_transition_receipt_json_check',
sql`jsonb_typeof(${table.receiptJson}) = 'object' and octet_length(${table.receiptJson}::text) between 2 and 196608 and ${table.receiptJson} @> jsonb_build_object('schema', 'qinglong/plugin-package-secret-binding-transition-receipt@v1', '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})), 'authority', jsonb_build_object('kind', ${table.authorityKind}, 'evidenceDigest', ${table.evidenceDigest}), 'bindingDigest', ${table.bindingDigest}, 'committedAtMs', ${table.committedAtMs}, 'receiptDigest', ${table.receiptDigest})`,
),
uniqueIndex('ql3_package_secret_transition_receipt_transition_uidx').on(
table.transitionDigest,
),
uniqueIndex('ql3_package_secret_transition_receipt_digest_uidx').on(
table.receiptDigest,
),
index('ql3_package_secret_transition_receipt_install_idx').on(
table.installationId,
table.generationDigest,
),
],
);
export const projectToolDefinitionSnapshots = ql3Schema.table(
'project_tool_definition_snapshots',
{
@@ -5940,6 +6003,7 @@ export const ql3PostgresTables = [
pluginPackageMaterializedRevisions,
pluginPackageSecretBindings,
pluginPackageSecretBindingApprovalPlans,
pluginPackageSecretBindingTransitionReceipts,
projectToolDefinitionSnapshots,
projectToolDefinitionSnapshotSources,
pluginPackageQuarantineEvents,
@@ -21,8 +21,8 @@ export interface PostgresSchemaContractTrigger {
export interface PostgresSchemaContract {
readonly schema: 'ql3';
readonly contractName: 'control-core';
readonly contractVersion: 61;
readonly migrationId: 'pg-0062-plugin-package-secret-binding-target-guard';
readonly contractVersion: 62;
readonly migrationId: 'pg-0063-plugin-package-secret-binding-transition-receipts';
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_receipt: 1;
plugin_package_secret_materialization: 1;
plugin_package_proposal: 1;
plugin_package_publisher_provenance: 1;
@@ -115,8 +116,8 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
Object.freeze({
schema: 'ql3',
contractName: 'control-core',
contractVersion: 61,
migrationId: 'pg-0062-plugin-package-secret-binding-target-guard',
contractVersion: 62,
migrationId: 'pg-0063-plugin-package-secret-binding-transition-receipts',
minimumServerMajor: 16,
maximumServerMajor: 18,
capabilities: Object.freeze({
@@ -151,6 +152,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_receipt: 1,
plugin_package_secret_materialization: 1,
plugin_package_proposal: 1,
plugin_package_publisher_provenance: 1,
@@ -288,6 +290,23 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
'expires_at_ms',
'plan_json',
]),
table('plugin_package_secret_binding_transition_receipts', [
'generation_digest',
'transition_digest',
'project_id',
'package_name',
'installation_id',
'lock_digest',
'generation',
'manifest_digest',
'previous_active_lock_digest',
'authority_kind',
'evidence_digest',
'binding_digest',
'committed_at_ms',
'receipt_digest',
'receipt_json',
]),
table('project_tool_definition_snapshots', [
'project_id',
'active_vector_digest',
@@ -1471,6 +1490,10 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
'ql3_plugin_package_secret_binding_approval_plan_digest_uidx',
'ql3_plugin_package_secret_binding_approval_plan_target_idx',
'ql3_plugin_package_secret_binding_approval_plan_expiry_idx',
'plugin_package_secret_binding_transition_receipts_pkey',
'ql3_package_secret_transition_receipt_transition_uidx',
'ql3_package_secret_transition_receipt_digest_uidx',
'ql3_package_secret_transition_receipt_install_idx',
'project_tool_definition_snapshots_pkey',
'ql3_project_tool_snapshot_withdrawal_key',
'ql3_project_tool_definition_snapshot_digest_uidx',
@@ -1769,6 +1792,9 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
'ql3_plugin_package_secret_binding_approval_plan_digest_check',
'ql3_plugin_package_secret_binding_approval_plan_time_check',
'ql3_plugin_package_secret_binding_approval_plan_json_check',
'ql3_package_secret_transition_receipt_identity_check',
'ql3_package_secret_transition_receipt_digest_check',
'ql3_package_secret_transition_receipt_json_check',
'ql3_plugin_package_quarantine_identity_check',
'ql3_plugin_package_quarantine_state_check',
'ql3_plugin_package_quarantine_subject_check',
@@ -2226,6 +2252,8 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
'ql3_plugin_package_secret_binding_install_fk',
'ql3_plugin_package_secret_binding_approval_plan_project_fk',
'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_project_tool_definition_snapshot_project_fk',
'ql3_project_tool_definition_snapshot_source_snapshot_fk',
'ql3_project_tool_definition_snapshot_source_install_fk',
@@ -2401,6 +2429,14 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
volatility: 'volatile',
configuration: Object.freeze(['search_path=pg_catalog, ql3']),
}),
Object.freeze({
name: 'enforce_plugin_package_secret_binding_transition_receipt_target',
identityArguments: '',
owner: 'ql3_migration',
securityDefiner: false,
volatility: 'volatile',
configuration: Object.freeze(['search_path=pg_catalog, ql3']),
}),
Object.freeze({
name: 'enforce_plugin_package_secret_materialization',
identityArguments: '',
@@ -2539,6 +2575,12 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
tableName: 'plugin_package_secret_bindings',
functionName: 'enforce_plugin_package_secret_binding_target',
}),
Object.freeze({
name: 'ql3_package_secret_transition_receipt_target_guard',
tableName: 'plugin_package_secret_binding_transition_receipts',
functionName:
'enforce_plugin_package_secret_binding_transition_receipt_target',
}),
Object.freeze({
name: 'ql3_plugin_package_secret_materialization_guard',
tableName: 'plugin_package_materialized_revisions',
@@ -186,6 +186,12 @@ const REQUIRED_RUNTIME_PRIVILEGES = Object.freeze({
update: false,
delete: false,
}),
plugin_package_secret_binding_transition_receipts: Object.freeze({
select: false,
insert: false,
update: false,
delete: false,
}),
project_tool_definition_snapshots: Object.freeze({
select: false,
insert: false,
@@ -711,6 +717,12 @@ const REQUIRED_ADMIN_PRIVILEGES = Object.freeze({
update: false,
delete: false,
}),
plugin_package_secret_binding_transition_receipts: Object.freeze({
select: false,
insert: false,
update: false,
delete: false,
}),
project_tool_definition_snapshots: Object.freeze({
select: false,
insert: false,
@@ -1264,6 +1276,7 @@ const REQUIRED_PACKAGE_EXECUTOR_PRIVILEGES: RequiredPrivileges = Object.freeze(
name === 'plugin_package_install_mutations' ||
name === 'plugin_package_materialized_revisions' ||
name === 'plugin_package_secret_bindings' ||
name === 'plugin_package_secret_binding_transition_receipts' ||
name === 'project_tool_definition_snapshots' ||
name === 'project_tool_definition_snapshot_sources' ||
name === 'plugin_package_lifecycle_plans' ||
@@ -1531,6 +1544,7 @@ const REQUIRED_RUNTIME_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
commit_plugin_package_task_reconciliation: false,
enforce_plugin_package_secret_materialization: false,
enforce_plugin_package_secret_binding_target: false,
enforce_plugin_package_secret_binding_transition_receipt_target: false,
enforce_plugin_package_stage_provenance: false,
lock_active_plugin_package_project: false,
lock_approval_policy_fence: false,
@@ -1553,6 +1567,7 @@ const REQUIRED_PACKAGE_MANAGER_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
commit_plugin_package_task_reconciliation: false,
enforce_plugin_package_secret_materialization: false,
enforce_plugin_package_secret_binding_target: false,
enforce_plugin_package_secret_binding_transition_receipt_target: false,
enforce_plugin_package_stage_provenance: false,
lock_active_plugin_package_project: false,
lock_approval_policy_fence: true,
@@ -1575,6 +1590,7 @@ const REQUIRED_PACKAGE_EXECUTOR_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges
commit_plugin_package_task_reconciliation: true,
enforce_plugin_package_secret_materialization: false,
enforce_plugin_package_secret_binding_target: false,
enforce_plugin_package_secret_binding_transition_receipt_target: false,
enforce_plugin_package_stage_provenance: false,
lock_active_plugin_package_project: true,
lock_approval_policy_fence: true,
@@ -1744,10 +1760,9 @@ async function assertSchemaContract(
constraintsResult,
functionsResult,
triggersResult,
] =
await Promise.all([
queryable.query<ColumnRow>(
`
] = await Promise.all([
queryable.query<ColumnRow>(
`
SELECT
tables.relname AS "tableName",
columns.attname AS "columnName"
@@ -1760,19 +1775,19 @@ WHERE schemas.nspname = $1
AND NOT columns.attisdropped
ORDER BY tables.relname, columns.attnum
`.trim(),
[contract.schema],
),
queryable.query<IndexRow>(
`
[contract.schema],
),
queryable.query<IndexRow>(
`
SELECT indexname AS "indexName"
FROM pg_indexes
WHERE schemaname = $1
ORDER BY indexname
`.trim(),
[contract.schema],
),
queryable.query<ConstraintRow>(
`
[contract.schema],
),
queryable.query<ConstraintRow>(
`
SELECT
constraints.conname AS "constraintName",
CASE constraints.contype
@@ -1786,10 +1801,10 @@ WHERE schemas.nspname = $1
AND constraints.contype IN ('c', 'f')
ORDER BY constraints.contype, constraints.conname
`.trim(),
[contract.schema],
),
queryable.query<FunctionRow>(
`
[contract.schema],
),
queryable.query<FunctionRow>(
`
SELECT
routines.proname AS "functionName",
pg_get_function_identity_arguments(routines.oid) AS "identityArguments",
@@ -1817,10 +1832,10 @@ JOIN pg_namespace schemas ON schemas.oid = routines.pronamespace
WHERE schemas.nspname = $1
ORDER BY routines.proname, pg_get_function_identity_arguments(routines.oid)
`.trim(),
[contract.schema],
),
queryable.query<TriggerRow>(
`
[contract.schema],
),
queryable.query<TriggerRow>(
`
SELECT
triggers.tgname AS "triggerName",
tables.relname AS "tableName",
@@ -1835,12 +1850,9 @@ WHERE schemas.nspname = $1
AND triggers.tgname = ANY($2::text[])
ORDER BY triggers.tgname
`.trim(),
[
contract.schema,
contract.triggers.map(({ name }) => name),
],
),
]);
[contract.schema, contract.triggers.map(({ name }) => name)],
),
]);
const actualTables = new Map<string, Set<string>>();
for (const row of columnsResult.rows) {
if (
@@ -8,18 +8,22 @@ const {
const {
createPluginPackageResourceGeneration,
} = require('@qinglong/runtime-core/plugin-package-resource-generation');
const {
createPluginPackageSecretBindingTarget,
} = require('@qinglong/runtime-core/plugin-package-secret-binding');
const {
createPluginPackageSecretBindingTransitionPlan,
} = require('@qinglong/runtime-core/plugin-package-secret-binding-transition-plan');
const { createSecretRef } = require('@qinglong/runtime-core/secret-reference');
const {
assertPostgresPackageExecutorSchemaReady,
createPostgresDatabaseOpener,
PostgresPluginPackageSecretBindingRepository,
PostgresPluginPackageSecretBindingTransitionRepository,
} = require('../dist/entrypoints/packageExecutor');
const {
runPostgresMigrations,
} = require('../dist/migration/migration');
const { runPostgresMigrations } = require('../dist/migration/migration');
const migrationConnectionString =
process.env.QL3_TEST_POSTGRES_MIGRATION_URL;
const migrationConnectionString = process.env.QL3_TEST_POSTGRES_MIGRATION_URL;
const executorConnectionString =
process.env.QL3_TEST_POSTGRES_PACKAGE_EXECUTOR_URL;
@@ -221,9 +225,12 @@ async function open(role, connectionString) {
}
if (!migrationConnectionString || !executorConnectionString) {
test('PostgreSQL Secret binding transition gate requires migration and executor URLs', {
skip: true,
});
test(
'PostgreSQL Secret binding transition gate requires migration and executor URLs',
{
skip: true,
},
);
} else {
test('PostgreSQL enforces active and reviewed staged Secret binding targets', async () => {
const migrationDatabase = await open(
@@ -241,7 +248,7 @@ if (!migrationConnectionString || !executorConnectionString) {
executorDatabase.pool,
);
assert.equal(readiness.ready, true);
assert.equal(readiness.contractVersion, 61);
assert.equal(readiness.contractVersion, 62);
const repository = new PostgresPluginPackageSecretBindingRepository(
executorDatabase.pool,
@@ -326,8 +333,75 @@ if (!migrationConnectionString || !executorConnectionString) {
packageName: stagedPackageName,
installationId: stagedInstallationId,
});
assert.equal((await repository.publish(stagedBinding)).status, 'created');
assert.equal((await repository.publish(stagedBinding)).status, 'existing');
const previousGeneration = createPluginPackageResourceGeneration({
installationId: previousInstallationId,
projectId: stagedProjectId,
packageName: stagedPackageName,
lockDigest: previousLockDigest,
generation: 1,
previousActiveLockDigest: null,
contentDigest: '7'.repeat(64),
contents: manifest(stagedPackageName).spec.contents,
});
const nextGeneration = createPluginPackageResourceGeneration({
installationId: stagedInstallationId,
projectId: stagedProjectId,
packageName: stagedPackageName,
lockDigest: stagedLockDigest,
generation: 2,
previousActiveLockDigest: previousLockDigest,
contentDigest: 'e'.repeat(64),
contents: manifest(stagedPackageName).spec.contents,
});
const transitionPlan = createPluginPackageSecretBindingTransitionPlan({
previousTarget: createPluginPackageSecretBindingTarget(
previousGeneration,
manifest(stagedPackageName),
),
previousBinding: null,
previousAttemptGeneration: 1,
nextGeneration,
nextManifest: manifest(stagedPackageName),
assignments: [
{
name: 'TOKEN',
secretRef: createSecretRef({
projectId: stagedProjectId,
name: 'runtime-token',
version: 2,
}),
},
],
plannedAtMs: 100,
});
const transitions =
new PostgresPluginPackageSecretBindingTransitionRepository(
executorDatabase.pool,
);
const committed = await transitions.apply({
transitionPlan,
evidenceDigest: transitionPlan.transitionDigest,
committedAtMs: 102,
});
assert.equal(committed.status, 'created');
assert.equal(
committed.receipt.bindingDigest,
committed.binding.bindingDigest,
);
assert.equal(
(
await transitions.apply({
transitionPlan,
evidenceDigest: transitionPlan.transitionDigest,
committedAtMs: 102,
})
).status,
'existing',
);
assert.deepEqual(
await transitions.find(transitionPlan.nextTarget.generationDigest),
committed.receipt,
);
const activatingProjectId = `${namespace}-activating`;
const activatingPackageName = 'activating-binding';
@@ -113,6 +113,7 @@ test('defines the immutable PostgreSQL capability and Run core stream', async ()
'pg-0060-plugin-package-secret-materialization-guard',
'pg-0061-plugin-package-secret-binding-approval-plans',
'pg-0062-plugin-package-secret-binding-target-guard',
'pg-0063-plugin-package-secret-binding-transition-receipts',
],
);
for (const migration of postgresqlMainMigrationStream.migrations) {
@@ -166,6 +167,7 @@ test('keeps local-only and legacy tables out of the cluster baseline', async ()
'plugin_package_materialized_revisions',
'plugin_package_secret_bindings',
'plugin_package_secret_binding_approval_plans',
'plugin_package_secret_binding_transition_receipts',
'plugin_package_lifecycle_events',
'plugin_package_lifecycle_heads',
'plugin_package_lifecycle_receipts',
@@ -560,6 +562,11 @@ test('freezes every published PostgreSQL migration checksum', () => {
checksum:
'cd4f92d8702da6b92dd9ae5153b5180400b94442f56393692b6ec038f998596b',
},
{
id: 'pg-0063-plugin-package-secret-binding-transition-receipts',
checksum:
'20c4d6e640b7fb05fc2f44dd9bec6d2d081697b83acf4e7dd5deb9a07afe54df',
},
];
assert.deepEqual(
postgresqlMainMigrationStream.migrations.map(({ id, checksum }) => ({
@@ -2127,3 +2134,41 @@ test('advances capability v61 with a staged-target Secret binding database guard
/migration_id = 'pg-0061-plugin-package-secret-binding-approval-plans'/,
);
});
test('advances capability v62 with immutable Secret binding transition receipts', async () => {
const migration = migrationById(
'pg-0063-plugin-package-secret-binding-transition-receipts',
);
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_receipts"/,
);
assert.match(
sql,
/CREATE FUNCTION "ql3"\."enforce_plugin_package_secret_binding_transition_receipt_target"\(\)/,
);
assert.match(sql, /install\.state = 'staged'/);
assert.match(sql, /previous\.state = 'active'/);
assert.match(sql, /MAX\(history\.target_generation\)/);
assert.match(sql, /binding\.binding_digest = NEW\.binding_digest/);
assert.doesNotMatch(sql, /SECURITY DEFINER/);
assert.match(
sql,
/GRANT SELECT, INSERT ON "ql3"\."plugin_package_secret_binding_transition_receipts" TO ql3_package_executor/,
);
assert.match(sql, /contract_version = 62/);
assert.match(sql, /"plugin_package_secret_binding_transition_receipt":1/);
assert.match(sql, /contract_version = 61/);
assert.match(
sql,
/migration_id = 'pg-0062-plugin-package-secret-binding-target-guard'/,
);
});
@@ -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_receipts: [
false,
false,
false,
false,
],
project_tool_definition_snapshots: [false, false, false, false],
project_tool_definition_snapshot_sources: [false, false, false, false],
plugin_package_quarantine_events: [false, false, false, false],
@@ -196,6 +202,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_receipts: [
false,
false,
false,
false,
],
project_tool_definition_snapshots: [false, false, false, false],
project_tool_definition_snapshot_sources: [false, false, false, false],
plugin_package_quarantine_events: [false, false, false, false],
@@ -308,6 +320,7 @@ function packagePrivileges(kind) {
'plugin_package_install_mutations',
'plugin_package_materialized_revisions',
'plugin_package_secret_bindings',
'plugin_package_secret_binding_transition_receipts',
'project_tool_definition_snapshots',
'project_tool_definition_snapshot_sources',
'plugin_package_admission_receipts',
@@ -351,6 +364,7 @@ function packagePrivileges(kind) {
'plugin_package_install_mutations',
'plugin_package_materialized_revisions',
'plugin_package_secret_bindings',
'plugin_package_secret_binding_transition_receipts',
'plugin_package_automation_publications',
'plugin_package_automation_publication_heads',
'project_tool_definition_snapshots',
@@ -776,7 +790,7 @@ test('accepts the exact PostgreSQL control schema and least-privilege runtime ro
serverMajor: 16,
currentUser: 'ql3_runtime',
contractName: 'control-core',
contractVersion: 61,
contractVersion: 62,
migrationIds: [
'pg-0001-schema-capability',
'pg-0002-run-core',
@@ -840,6 +854,7 @@ test('accepts the exact PostgreSQL control schema and least-privilege runtime ro
'pg-0060-plugin-package-secret-materialization-guard',
'pg-0061-plugin-package-secret-binding-approval-plans',
'pg-0062-plugin-package-secret-binding-target-guard',
'pg-0063-plugin-package-secret-binding-transition-receipts',
],
});
});
@@ -870,10 +885,10 @@ test('accepts the exact schema and isolated least-privilege admin role', async (
}),
);
assert.equal(report.currentUser, 'ql3_admin');
assert.equal(report.contractVersion, 61);
assert.equal(report.contractVersion, 62);
assert.equal(
report.migrationIds.at(-1),
'pg-0062-plugin-package-secret-binding-target-guard',
'pg-0063-plugin-package-secret-binding-transition-receipts',
);
});
@@ -886,10 +901,10 @@ test('accepts the isolated least-privilege automation manager role', async () =>
}),
);
assert.equal(report.currentUser, 'ql3_automation_manager');
assert.equal(report.contractVersion, 61);
assert.equal(report.contractVersion, 62);
assert.equal(
report.migrationIds.at(-1),
'pg-0062-plugin-package-secret-binding-target-guard',
'pg-0063-plugin-package-secret-binding-transition-receipts',
);
const widened = automationManagerPrivileges();
@@ -918,10 +933,10 @@ test('accepts the isolated least-privilege human Approval manager role', async (
}),
);
assert.equal(report.currentUser, 'ql3_approval_manager');
assert.equal(report.contractVersion, 61);
assert.equal(report.contractVersion, 62);
assert.equal(
report.migrationIds.at(-1),
'pg-0062-plugin-package-secret-binding-target-guard',
'pg-0063-plugin-package-secret-binding-transition-receipts',
);
const widened = approvalManagerPrivileges();
@@ -952,10 +967,10 @@ test('accepts the isolated least-privilege Run manager role', async () => {
}),
);
assert.equal(report.currentUser, 'ql3_run_manager');
assert.equal(report.contractVersion, 61);
assert.equal(report.contractVersion, 62);
assert.equal(
report.migrationIds.at(-1),
'pg-0062-plugin-package-secret-binding-target-guard',
'pg-0063-plugin-package-secret-binding-transition-receipts',
);
const widened = runManagerPrivileges();
@@ -1087,10 +1102,10 @@ test('accepts the exact schema and isolated Worker ingress role', async () => {
}),
);
assert.equal(report.currentUser, 'ql3_worker_ingress');
assert.equal(report.contractVersion, 61);
assert.equal(report.contractVersion, 62);
assert.equal(
report.migrationIds.at(-1),
'pg-0062-plugin-package-secret-binding-target-guard',
'pg-0063-plugin-package-secret-binding-transition-receipts',
);
});