mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 10:32:40 +08:00
feat(ql3): persist package secret approval plans
This commit is contained in:
@@ -41,6 +41,7 @@ export {
|
||||
|
||||
export { PostgresPluginPackageMaterializedRevisionRepository } from '../plugin-package/installation/pluginPackageMaterializedRevisionRepository';
|
||||
export { PostgresPluginPackageSecretBindingRepository } from '../plugin-package/installation/pluginPackageSecretBindingRepository';
|
||||
export { PostgresPluginPackageSecretBindingApprovalPlanReader } from '../plugin-package/secret-binding/pluginPackageSecretBindingApprovalPlanRepository';
|
||||
export { PostgresPluginPackageAutomationPublicationRepository } from '../plugin-package/publication/pluginPackageAutomationPublicationRepository';
|
||||
export {
|
||||
CLUSTER_PLUGIN_PACKAGE_QUARANTINE_TARGET_LIMIT,
|
||||
|
||||
@@ -56,6 +56,10 @@ export {
|
||||
|
||||
export { PostgresPluginPackagePublisherTrustAuthorityRepository } from '../plugin-package/publisher/pluginPackagePublisherTrustAuthorityRepository';
|
||||
export { PostgresPluginPackageLifecyclePlanReader } from '../plugin-package/lifecycle/pluginPackageLifecyclePlanRepository';
|
||||
export {
|
||||
PostgresPluginPackageSecretBindingApprovalPlanReader,
|
||||
PostgresPluginPackageSecretBindingApprovalPlanRepository,
|
||||
} from '../plugin-package/secret-binding/pluginPackageSecretBindingApprovalPlanRepository';
|
||||
export { PostgresPluginPackageInstallInventoryReader } from '../plugin-package/installation/pluginPackageInstallRepository';
|
||||
export { PostgresPluginPackagePublisherRevocationProposalRepository } from '../plugin-package/publisher/pluginPackagePublisherRevocationProposalRepository';
|
||||
export { PostgresPluginPackagePublisherTrustTransitionProposalRepository } from '../plugin-package/publisher/pluginPackagePublisherTrustTransitionProposalRepository';
|
||||
|
||||
@@ -308,5 +308,10 @@ export const postgresqlMainMigrationManifest: MigrationStreamManifest =
|
||||
checksum:
|
||||
'28284ca860b39ff9de5b2aa1a2a60ef2c463fd6a72798d237040174272b64b1e',
|
||||
}),
|
||||
Object.freeze({
|
||||
id: 'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
checksum:
|
||||
'c995b7846ae8a57d3abb4b5523961e81aeba890e7405a030bcb505dfc6be3d25',
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
@@ -63,6 +63,7 @@ import { pg0057RunManagementStopBoundaryMigration } from '../run-management/pg-0
|
||||
import { pg0058PluginPackageAutomationDispositionEventsMigration } from './pg-0058-plugin-package-automation-disposition-events';
|
||||
import { pg0059PluginPackageSecretBindingsMigration } from './pg-0059-plugin-package-secret-bindings';
|
||||
import { pg0060PluginPackageSecretMaterializationGuardMigration } from './pg-0060-plugin-package-secret-materialization-guard';
|
||||
import { pg0061PluginPackageSecretBindingApprovalPlansMigration } from './pg-0061-plugin-package-secret-binding-approval-plans';
|
||||
|
||||
export const postgresqlMainMigrationStream: MigrationStreamDefinition<PostgresMigrationContext> =
|
||||
Object.freeze({
|
||||
@@ -131,5 +132,6 @@ export const postgresqlMainMigrationStream: MigrationStreamDefinition<PostgresMi
|
||||
pg0058PluginPackageAutomationDispositionEventsMigration,
|
||||
pg0059PluginPackageSecretBindingsMigration,
|
||||
pg0060PluginPackageSecretMaterializationGuardMigration,
|
||||
pg0061PluginPackageSecretBindingApprovalPlansMigration,
|
||||
]),
|
||||
});
|
||||
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
import { CAPABILITIES_V59 } from './pg-0060-plugin-package-secret-materialization-guard';
|
||||
import { definePostgresSqlMigration } from './sqlMigration';
|
||||
|
||||
export const CAPABILITIES_V60 = CAPABILITIES_V59.replace(
|
||||
'"plugin_package_secret_binding":1,',
|
||||
'"plugin_package_secret_binding":1,"plugin_package_secret_binding_approval_plan":1,',
|
||||
);
|
||||
|
||||
export const pg0061PluginPackageSecretBindingApprovalPlansMigration =
|
||||
definePostgresSqlMigration({
|
||||
id: 'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "ql3"."plugin_package_secret_binding_approval_plans" (
|
||||
action_ref varchar(255) PRIMARY KEY,
|
||||
approval_plan_digest char(64) NOT NULL,
|
||||
binding_plan_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,
|
||||
requested_by_type varchar(16) NOT NULL,
|
||||
requested_by_id varchar(255) NOT NULL,
|
||||
planned_at_ms bigint NOT NULL,
|
||||
expires_at_ms bigint NOT NULL,
|
||||
plan_json jsonb NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_secret_binding_approval_plan_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "ql3"."projects" (id)
|
||||
ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_secret_binding_approval_plan_install_fk
|
||||
FOREIGN KEY (installation_id)
|
||||
REFERENCES "ql3"."plugin_package_installs" (installation_id)
|
||||
ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_secret_binding_approval_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 1 AND 2147483647 AND
|
||||
requested_by_type = 'user' AND
|
||||
octet_length(requested_by_id) BETWEEN 1 AND 255 AND
|
||||
requested_by_id !~ '[[:cntrl:]]'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_secret_binding_approval_plan_digest_check CHECK (
|
||||
approval_plan_digest ~ '^[0-9a-f]{64}$' AND
|
||||
binding_plan_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}$'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_secret_binding_approval_plan_time_check CHECK (
|
||||
planned_at_ms >= 0 AND
|
||||
expires_at_ms > planned_at_ms AND
|
||||
expires_at_ms - planned_at_ms <= 900000
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_secret_binding_approval_plan_json_check CHECK (
|
||||
jsonb_typeof(plan_json) = 'object' AND
|
||||
octet_length(plan_json::text) BETWEEN 2 AND 98304 AND
|
||||
plan_json @> jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-secret-binding-approval-plan@v1',
|
||||
'actionRef', action_ref,
|
||||
'approvalPlanDigest', approval_plan_digest,
|
||||
'requestedBy', jsonb_build_object(
|
||||
'type', requested_by_type,
|
||||
'id', requested_by_id
|
||||
),
|
||||
'expiresAtMs', expires_at_ms,
|
||||
'bindingPlan', jsonb_build_object(
|
||||
'schema', 'qinglong/plugin-package-secret-binding-plan@v1',
|
||||
'planDigest', binding_plan_digest,
|
||||
'plannedAtMs', planned_at_ms,
|
||||
'target', 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 #> '{bindingPlan,entries}') = 'array' AND
|
||||
jsonb_array_length(plan_json #> '{bindingPlan,entries}') BETWEEN 1 AND 64
|
||||
)
|
||||
)
|
||||
`.trim(),
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_secret_binding_approval_plan_digest_uidx ON "ql3"."plugin_package_secret_binding_approval_plans" (approval_plan_digest)`,
|
||||
`CREATE INDEX ql3_plugin_package_secret_binding_approval_plan_target_idx ON "ql3"."plugin_package_secret_binding_approval_plans" (project_id, package_name, generation, action_ref)`,
|
||||
`CREATE INDEX ql3_plugin_package_secret_binding_approval_plan_expiry_idx ON "ql3"."plugin_package_secret_binding_approval_plans" (expires_at_ms, action_ref)`,
|
||||
`
|
||||
CREATE FUNCTION "ql3"."plugin_package_secret_binding_planning_snapshot"(
|
||||
p_project_id varchar,
|
||||
p_package_name varchar
|
||||
)
|
||||
RETURNS TABLE (
|
||||
record_json jsonb,
|
||||
lock_json jsonb,
|
||||
proposal_json jsonb,
|
||||
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;
|
||||
IF p_project_id !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' OR
|
||||
p_package_name !~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$' THEN
|
||||
RAISE EXCEPTION 'Package planning identity is invalid'
|
||||
USING ERRCODE = 'invalid_parameter_value';
|
||||
END IF;
|
||||
|
||||
RETURN QUERY
|
||||
SELECT install.record_json,
|
||||
install.lock_json,
|
||||
proposal.proposal_json,
|
||||
floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint
|
||||
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
|
||||
LEFT JOIN "ql3"."plugin_package_quarantine_events" AS quarantine
|
||||
ON quarantine.project_id = install.project_id
|
||||
AND quarantine.package_name = install.package_name
|
||||
AND quarantine.installation_id = install.installation_id
|
||||
AND quarantine.lock_digest = install.lock_digest
|
||||
LEFT JOIN "ql3"."plugin_package_lifecycle_heads" AS lifecycle
|
||||
ON lifecycle.project_id = install.project_id
|
||||
AND lifecycle.package_name = install.package_name
|
||||
AND lifecycle.installation_id = install.installation_id
|
||||
AND lifecycle.lock_digest = install.lock_digest
|
||||
WHERE head.project_id = p_project_id
|
||||
AND head.package_name = p_package_name
|
||||
AND install.state = 'active'
|
||||
AND install.active_lock_digest = install.lock_digest
|
||||
AND quarantine.event_digest IS NULL
|
||||
AND COALESCE(lifecycle.disposition, 'active') = 'active'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_secret_bindings" AS binding
|
||||
WHERE binding.project_id = install.project_id
|
||||
AND binding.package_name = install.package_name
|
||||
AND binding.generation = install.target_generation
|
||||
)
|
||||
FOR SHARE OF head, install;
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`
|
||||
CREATE FUNCTION "ql3"."create_plugin_package_secret_binding_approval_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;
|
||||
IF jsonb_typeof(p_plan_json) IS DISTINCT FROM 'object' THEN
|
||||
RAISE EXCEPTION 'Secret binding approval plan is invalid'
|
||||
USING ERRCODE = 'invalid_parameter_value';
|
||||
END IF;
|
||||
IF COALESCE(p_plan_json ->> 'actionRef', '') !~
|
||||
'^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$' THEN
|
||||
RAISE EXCEPTION 'Secret binding approval actionRef is invalid'
|
||||
USING ERRCODE = 'invalid_parameter_value';
|
||||
END IF;
|
||||
|
||||
PERFORM pg_advisory_xact_lock(
|
||||
hashtextextended(p_plan_json ->> 'actionRef', 70513061)
|
||||
);
|
||||
|
||||
SELECT plan_json INTO existing_plan
|
||||
FROM "ql3"."plugin_package_secret_binding_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 binding approval actionRef is already bound'
|
||||
USING ERRCODE = 'unique_violation';
|
||||
END IF;
|
||||
|
||||
INSERT INTO "ql3"."plugin_package_secret_binding_approval_plans" (
|
||||
action_ref, approval_plan_digest, binding_plan_digest,
|
||||
generation_digest, project_id, package_name, installation_id,
|
||||
lock_digest, generation, manifest_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 #>> '{bindingPlan,planDigest}',
|
||||
p_plan_json #>> '{bindingPlan,target,generationDigest}',
|
||||
p_plan_json #>> '{bindingPlan,target,projectId}',
|
||||
p_plan_json #>> '{bindingPlan,target,packageName}',
|
||||
p_plan_json #>> '{bindingPlan,target,installationId}',
|
||||
p_plan_json #>> '{bindingPlan,target,lockDigest}',
|
||||
(p_plan_json #>> '{bindingPlan,target,generation}')::integer,
|
||||
p_plan_json #>> '{bindingPlan,target,manifestDigest}',
|
||||
p_plan_json #>> '{requestedBy,type}',
|
||||
p_plan_json #>> '{requestedBy,id}',
|
||||
(p_plan_json #>> '{bindingPlan,plannedAtMs}')::bigint,
|
||||
(p_plan_json ->> 'expiresAtMs')::bigint,
|
||||
p_plan_json
|
||||
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
|
||||
LEFT JOIN "ql3"."plugin_package_quarantine_events" AS quarantine
|
||||
ON quarantine.project_id = install.project_id
|
||||
AND quarantine.package_name = install.package_name
|
||||
AND quarantine.installation_id = install.installation_id
|
||||
AND quarantine.lock_digest = install.lock_digest
|
||||
LEFT JOIN "ql3"."plugin_package_lifecycle_heads" AS lifecycle
|
||||
ON lifecycle.project_id = install.project_id
|
||||
AND lifecycle.package_name = install.package_name
|
||||
AND lifecycle.installation_id = install.installation_id
|
||||
AND lifecycle.lock_digest = install.lock_digest
|
||||
WHERE head.project_id = p_plan_json #>> '{bindingPlan,target,projectId}'
|
||||
AND head.package_name = p_plan_json #>> '{bindingPlan,target,packageName}'
|
||||
AND install.installation_id =
|
||||
p_plan_json #>> '{bindingPlan,target,installationId}'
|
||||
AND install.lock_digest =
|
||||
p_plan_json #>> '{bindingPlan,target,lockDigest}'
|
||||
AND install.target_generation =
|
||||
(p_plan_json #>> '{bindingPlan,target,generation}')::integer
|
||||
AND install.lock_json ->> 'manifestDigest' =
|
||||
p_plan_json #>> '{bindingPlan,target,manifestDigest}'
|
||||
AND install.state = 'active'
|
||||
AND install.active_lock_digest = install.lock_digest
|
||||
AND quarantine.event_digest IS NULL
|
||||
AND COALESCE(lifecycle.disposition, 'active') = 'active'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "ql3"."plugin_package_secret_bindings" AS binding
|
||||
WHERE binding.generation_digest =
|
||||
p_plan_json #>> '{bindingPlan,target,generationDigest}'
|
||||
OR (
|
||||
binding.project_id = install.project_id AND
|
||||
binding.package_name = install.package_name AND
|
||||
binding.generation = install.target_generation
|
||||
)
|
||||
)
|
||||
RETURNING action_ref INTO inserted_action_ref;
|
||||
|
||||
IF inserted_action_ref IS NULL THEN
|
||||
RAISE EXCEPTION 'Secret binding target is not the current unbound generation'
|
||||
USING ERRCODE = 'check_violation';
|
||||
END IF;
|
||||
RETURN 'created';
|
||||
END
|
||||
$ql3$
|
||||
`.trim(),
|
||||
`REVOKE ALL ON "ql3"."plugin_package_secret_binding_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_approval_plans" TO ql3_package_manager`,
|
||||
`GRANT SELECT ON "ql3"."plugin_package_secret_binding_approval_plans" TO ql3_package_executor`,
|
||||
`REVOKE ALL ON FUNCTION "ql3"."plugin_package_secret_binding_planning_snapshot"(varchar, varchar) FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager, ql3_package_executor, ql3_worker_ingress`,
|
||||
`REVOKE ALL ON FUNCTION "ql3"."create_plugin_package_secret_binding_approval_plan"(jsonb) FROM PUBLIC, ql3_runtime, ql3_admin, ql3_package_manager, ql3_package_executor, ql3_worker_ingress`,
|
||||
`GRANT EXECUTE ON FUNCTION "ql3"."plugin_package_secret_binding_planning_snapshot"(varchar, varchar) TO ql3_package_manager`,
|
||||
`GRANT EXECUTE ON FUNCTION "ql3"."create_plugin_package_secret_binding_approval_plan"(jsonb) TO ql3_package_manager`,
|
||||
`DO $ql3$ BEGIN UPDATE "ql3"."schema_capabilities" SET contract_version = 60, migration_id = 'pg-0061-plugin-package-secret-binding-approval-plans', capabilities = '${CAPABILITIES_V60}'::jsonb, updated_at_ms = floor(extract(epoch FROM transaction_timestamp()) * 1000)::bigint WHERE contract_name = 'control-core' AND contract_version = 59 AND migration_id = 'pg-0060-plugin-package-secret-materialization-guard' AND capabilities = '${CAPABILITIES_V59}'::jsonb; IF NOT FOUND THEN RAISE EXCEPTION 'control-core capability is not at version 59' USING ERRCODE = 'check_violation'; END IF; END $ql3$`,
|
||||
],
|
||||
});
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
import type { PostgresPool } from '@qinglong/runtime-core';
|
||||
import {
|
||||
normalizePluginPackageInstallProposal,
|
||||
type PluginPackageInstallProposal,
|
||||
} from '@qinglong/runtime-core/plugin-package-proposal';
|
||||
import {
|
||||
normalizePluginPackageInstallRecord,
|
||||
normalizePluginPackageLock,
|
||||
type PluginPackageInstallRecord,
|
||||
type PluginPackageLock,
|
||||
} from '@qinglong/runtime-core/plugin-package-install';
|
||||
import {
|
||||
InvalidPluginPackageSecretBindingApprovalPlanError,
|
||||
PluginPackageSecretBindingApprovalPlanConflictError,
|
||||
PluginPackageSecretBindingApprovalPlanUnavailableError,
|
||||
normalizePluginPackageSecretBindingApprovalPlan,
|
||||
type CreatePluginPackageSecretBindingApprovalPlanResult,
|
||||
type PluginPackageSecretBindingApprovalPlan,
|
||||
type PluginPackageSecretBindingApprovalPlanRepository,
|
||||
} from '@qinglong/runtime-core/plugin-package-secret-binding-approval-plan';
|
||||
|
||||
import {
|
||||
postgresRequiredInteger,
|
||||
postgresRequiredJsonObject,
|
||||
postgresRequiredString,
|
||||
postgresSqlState,
|
||||
} from '../../repository/definitionRepositorySupport';
|
||||
|
||||
type Row = Record<string, unknown>;
|
||||
|
||||
export interface PostgresPluginPackageSecretBindingPlanningSnapshot {
|
||||
readonly record: Readonly<PluginPackageInstallRecord>;
|
||||
readonly lock: Readonly<PluginPackageLock>;
|
||||
readonly proposal: Readonly<PluginPackageInstallProposal>;
|
||||
readonly observedAtMs: number;
|
||||
}
|
||||
|
||||
const ACTION_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
|
||||
|
||||
function unavailable(
|
||||
cause?: unknown,
|
||||
): PluginPackageSecretBindingApprovalPlanUnavailableError {
|
||||
return new PluginPackageSecretBindingApprovalPlanUnavailableError({
|
||||
cause: cause instanceof Error ? cause : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
function mapStorageError(error: unknown): Error {
|
||||
if (
|
||||
error instanceof InvalidPluginPackageSecretBindingApprovalPlanError ||
|
||||
error instanceof PluginPackageSecretBindingApprovalPlanConflictError ||
|
||||
error instanceof PluginPackageSecretBindingApprovalPlanUnavailableError
|
||||
) {
|
||||
return error;
|
||||
}
|
||||
const state = postgresSqlState(error);
|
||||
if (state === '23503' || state === '23505' || state === '23514') {
|
||||
return new PluginPackageSecretBindingApprovalPlanConflictError(
|
||||
'plan identity or Package generation is already bound',
|
||||
);
|
||||
}
|
||||
return unavailable(error);
|
||||
}
|
||||
|
||||
function validateActionRef(value: string): string {
|
||||
if (typeof value !== 'string' || !ACTION_REF_PATTERN.test(value)) {
|
||||
throw new InvalidPluginPackageSecretBindingApprovalPlanError(
|
||||
'actionRef is invalid',
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function normalizeRow(
|
||||
row: Row,
|
||||
): Readonly<PluginPackageSecretBindingApprovalPlan> {
|
||||
try {
|
||||
return normalizePluginPackageSecretBindingApprovalPlan(
|
||||
postgresRequiredJsonObject(
|
||||
row.planJson,
|
||||
unavailable,
|
||||
) as unknown as PluginPackageSecretBindingApprovalPlan,
|
||||
);
|
||||
} catch (error) {
|
||||
throw unavailable(error);
|
||||
}
|
||||
}
|
||||
|
||||
function same(left: unknown, right: unknown): boolean {
|
||||
return JSON.stringify(left) === JSON.stringify(right);
|
||||
}
|
||||
|
||||
export class PostgresPluginPackageSecretBindingApprovalPlanReader {
|
||||
constructor(protected readonly pool: Pick<PostgresPool, 'query'>) {
|
||||
if (!pool || typeof pool.query !== 'function') {
|
||||
throw new TypeError(
|
||||
'PostgreSQL Plugin Package Secret binding approval plan reader is invalid',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async findByActionRef(
|
||||
actionRefValue: string,
|
||||
): Promise<Readonly<PluginPackageSecretBindingApprovalPlan> | null> {
|
||||
const actionRef = validateActionRef(actionRefValue);
|
||||
try {
|
||||
const result = await this.pool.query<Row>(
|
||||
`SELECT plan_json AS "planJson"
|
||||
FROM "ql3"."plugin_package_secret_binding_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(
|
||||
projectIdValue: string,
|
||||
packageNameValue: string,
|
||||
): Promise<Readonly<PostgresPluginPackageSecretBindingPlanningSnapshot> | null> {
|
||||
if (
|
||||
typeof projectIdValue !== 'string' ||
|
||||
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(projectIdValue) ||
|
||||
typeof packageNameValue !== 'string' ||
|
||||
!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(packageNameValue)
|
||||
) {
|
||||
throw new InvalidPluginPackageSecretBindingApprovalPlanError(
|
||||
'planning target is invalid',
|
||||
);
|
||||
}
|
||||
try {
|
||||
const result = await this.pool.query<Row>(
|
||||
`SELECT record_json AS "recordJson",
|
||||
lock_json AS "lockJson",
|
||||
proposal_json AS "proposalJson",
|
||||
observed_at_ms AS "observedAtMs"
|
||||
FROM "ql3"."plugin_package_secret_binding_planning_snapshot"($1, $2)`,
|
||||
[projectIdValue, packageNameValue],
|
||||
);
|
||||
if (result.rows.length === 0) return null;
|
||||
if (result.rows.length !== 1) throw unavailable();
|
||||
const row = result.rows[0]!;
|
||||
const record = normalizePluginPackageInstallRecord(
|
||||
postgresRequiredJsonObject(
|
||||
row.recordJson,
|
||||
unavailable,
|
||||
) as unknown as PluginPackageInstallRecord,
|
||||
);
|
||||
const lock = normalizePluginPackageLock(
|
||||
postgresRequiredJsonObject(
|
||||
row.lockJson,
|
||||
unavailable,
|
||||
) as unknown as PluginPackageLock,
|
||||
);
|
||||
const proposal = normalizePluginPackageInstallProposal(
|
||||
postgresRequiredJsonObject(
|
||||
row.proposalJson,
|
||||
unavailable,
|
||||
) as unknown as PluginPackageInstallProposal,
|
||||
);
|
||||
const observedAtMs = postgresRequiredInteger(
|
||||
row.observedAtMs,
|
||||
unavailable,
|
||||
);
|
||||
if (
|
||||
record.projectId !== projectIdValue ||
|
||||
record.packageName !== packageNameValue ||
|
||||
record.lockDigest !== lock.lockDigest ||
|
||||
proposal.actionDigest !== lock.approval.actionDigest ||
|
||||
proposal.previewDigest !== lock.approval.previewDigest ||
|
||||
proposal.actionInput.projectId !== projectIdValue ||
|
||||
proposal.actionInput.manifest.metadata.name !== packageNameValue ||
|
||||
proposal.actionInput.targetGeneration !== record.targetGeneration ||
|
||||
proposal.actionInput.source.contentDigest !==
|
||||
lock.source.contentDigest ||
|
||||
proposal.actionInput.manifest.metadata.version !==
|
||||
record.packageVersion ||
|
||||
proposal.actionInput.manifest.spec.permissions.secrets.length === 0 ||
|
||||
!proposal.actionInput.manifest.spec.permissions.tools.includes(
|
||||
'secret.use',
|
||||
)
|
||||
) {
|
||||
throw unavailable();
|
||||
}
|
||||
return Object.freeze({ record, lock, proposal, observedAtMs });
|
||||
} catch (error) {
|
||||
throw mapStorageError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PostgresPluginPackageSecretBindingApprovalPlanRepository
|
||||
extends PostgresPluginPackageSecretBindingApprovalPlanReader
|
||||
implements PluginPackageSecretBindingApprovalPlanRepository
|
||||
{
|
||||
async create(
|
||||
planValue: Readonly<PluginPackageSecretBindingApprovalPlan>,
|
||||
): Promise<Readonly<CreatePluginPackageSecretBindingApprovalPlanResult>> {
|
||||
const plan = normalizePluginPackageSecretBindingApprovalPlan(planValue);
|
||||
try {
|
||||
const inserted = await this.pool.query<Row>(
|
||||
`SELECT "ql3"."create_plugin_package_secret_binding_approval_plan"(
|
||||
$1::jsonb
|
||||
) AS status`,
|
||||
[JSON.stringify(plan)],
|
||||
);
|
||||
if (inserted.rows.length !== 1) throw unavailable();
|
||||
const status = postgresRequiredString(
|
||||
inserted.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 PluginPackageSecretBindingApprovalPlanConflictError(
|
||||
'actionRef is already bound to another Secret binding plan',
|
||||
);
|
||||
}
|
||||
return Object.freeze({
|
||||
status,
|
||||
plan: stored,
|
||||
});
|
||||
} catch (error) {
|
||||
throw mapStorageError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -386,6 +386,72 @@ export const pluginPackageSecretBindings = ql3Schema.table(
|
||||
],
|
||||
);
|
||||
|
||||
export const pluginPackageSecretBindingApprovalPlans = ql3Schema.table(
|
||||
'plugin_package_secret_binding_approval_plans',
|
||||
{
|
||||
actionRef: varchar('action_ref', { length: 255 }).primaryKey(),
|
||||
approvalPlanDigest: char('approval_plan_digest', { length: 64 }).notNull(),
|
||||
bindingPlanDigest: char('binding_plan_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(),
|
||||
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_plugin_package_secret_binding_approval_plan_project_fk',
|
||||
columns: [table.projectId],
|
||||
foreignColumns: [projects.id],
|
||||
})
|
||||
.onDelete('restrict')
|
||||
.onUpdate('restrict'),
|
||||
foreignKey({
|
||||
name: 'ql3_plugin_package_secret_binding_approval_plan_install_fk',
|
||||
columns: [table.installationId],
|
||||
foreignColumns: [pluginPackageInstalls.installationId],
|
||||
})
|
||||
.onDelete('restrict')
|
||||
.onUpdate('restrict'),
|
||||
check(
|
||||
'ql3_plugin_package_secret_binding_approval_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 1 and 2147483647 and ${table.requestedByType} = 'user' and octet_length(${table.requestedById}) between 1 and 255 and ${table.requestedById} !~ '[[:cntrl:]]'`,
|
||||
),
|
||||
check(
|
||||
'ql3_plugin_package_secret_binding_approval_plan_digest_check',
|
||||
sql`${table.approvalPlanDigest} ~ '^[0-9a-f]{64}$' and ${table.bindingPlanDigest} ~ '^[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}$'`,
|
||||
),
|
||||
check(
|
||||
'ql3_plugin_package_secret_binding_approval_plan_time_check',
|
||||
sql`${table.plannedAtMs} >= 0 and ${table.expiresAtMs} > ${table.plannedAtMs} and ${table.expiresAtMs} - ${table.plannedAtMs} <= 900000`,
|
||||
),
|
||||
check(
|
||||
'ql3_plugin_package_secret_binding_approval_plan_json_check',
|
||||
sql`jsonb_typeof(${table.planJson}) = 'object' and octet_length(${table.planJson}::text) between 2 and 98304 and ${table.planJson} @> jsonb_build_object('schema', 'qinglong/plugin-package-secret-binding-approval-plan@v1', 'actionRef', ${table.actionRef}, 'approvalPlanDigest', ${table.approvalPlanDigest}, 'requestedBy', jsonb_build_object('type', ${table.requestedByType}, 'id', ${table.requestedById}), 'expiresAtMs', ${table.expiresAtMs}, 'bindingPlan', jsonb_build_object('schema', 'qinglong/plugin-package-secret-binding-plan@v1', 'planDigest', ${table.bindingPlanDigest}, 'plannedAtMs', ${table.plannedAtMs}, 'target', 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} #> '{bindingPlan,entries}') = 'array' and jsonb_array_length(${table.planJson} #> '{bindingPlan,entries}') between 1 and 64`,
|
||||
),
|
||||
uniqueIndex(
|
||||
'ql3_plugin_package_secret_binding_approval_plan_digest_uidx',
|
||||
).on(table.approvalPlanDigest),
|
||||
index('ql3_plugin_package_secret_binding_approval_plan_target_idx').on(
|
||||
table.projectId,
|
||||
table.packageName,
|
||||
table.generation,
|
||||
table.actionRef,
|
||||
),
|
||||
index('ql3_plugin_package_secret_binding_approval_plan_expiry_idx').on(
|
||||
table.expiresAtMs,
|
||||
table.actionRef,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
export const projectToolDefinitionSnapshots = ql3Schema.table(
|
||||
'project_tool_definition_snapshots',
|
||||
{
|
||||
@@ -5873,6 +5939,7 @@ export const ql3PostgresTables = [
|
||||
pluginPackageInstallMutations,
|
||||
pluginPackageMaterializedRevisions,
|
||||
pluginPackageSecretBindings,
|
||||
pluginPackageSecretBindingApprovalPlans,
|
||||
projectToolDefinitionSnapshots,
|
||||
projectToolDefinitionSnapshotSources,
|
||||
pluginPackageQuarantineEvents,
|
||||
|
||||
@@ -15,8 +15,8 @@ export interface PostgresSchemaContractFunction {
|
||||
export interface PostgresSchemaContract {
|
||||
readonly schema: 'ql3';
|
||||
readonly contractName: 'control-core';
|
||||
readonly contractVersion: 59;
|
||||
readonly migrationId: 'pg-0060-plugin-package-secret-materialization-guard';
|
||||
readonly contractVersion: 60;
|
||||
readonly migrationId: 'pg-0061-plugin-package-secret-binding-approval-plans';
|
||||
readonly minimumServerMajor: 16;
|
||||
readonly maximumServerMajor: 18;
|
||||
readonly capabilities: Readonly<{
|
||||
@@ -56,6 +56,7 @@ export interface PostgresSchemaContract {
|
||||
plugin_package_management_quota: 1;
|
||||
plugin_package_materialized_revision: 1;
|
||||
plugin_package_secret_binding: 1;
|
||||
plugin_package_secret_binding_approval_plan: 1;
|
||||
plugin_package_secret_materialization: 1;
|
||||
plugin_package_proposal: 1;
|
||||
plugin_package_publisher_provenance: 1;
|
||||
@@ -106,8 +107,8 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
Object.freeze({
|
||||
schema: 'ql3',
|
||||
contractName: 'control-core',
|
||||
contractVersion: 59,
|
||||
migrationId: 'pg-0060-plugin-package-secret-materialization-guard',
|
||||
contractVersion: 60,
|
||||
migrationId: 'pg-0061-plugin-package-secret-binding-approval-plans',
|
||||
minimumServerMajor: 16,
|
||||
maximumServerMajor: 18,
|
||||
capabilities: Object.freeze({
|
||||
@@ -140,6 +141,7 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
plugin_package_management_quota: 1,
|
||||
plugin_package_materialized_revision: 1,
|
||||
plugin_package_secret_binding: 1,
|
||||
plugin_package_secret_binding_approval_plan: 1,
|
||||
plugin_package_secret_materialization: 1,
|
||||
plugin_package_proposal: 1,
|
||||
plugin_package_publisher_provenance: 1,
|
||||
@@ -260,6 +262,23 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
'binding_digest',
|
||||
'binding_json',
|
||||
]),
|
||||
table('plugin_package_secret_binding_approval_plans', [
|
||||
'action_ref',
|
||||
'approval_plan_digest',
|
||||
'binding_plan_digest',
|
||||
'generation_digest',
|
||||
'project_id',
|
||||
'package_name',
|
||||
'installation_id',
|
||||
'lock_digest',
|
||||
'generation',
|
||||
'manifest_digest',
|
||||
'requested_by_type',
|
||||
'requested_by_id',
|
||||
'planned_at_ms',
|
||||
'expires_at_ms',
|
||||
'plan_json',
|
||||
]),
|
||||
table('project_tool_definition_snapshots', [
|
||||
'project_id',
|
||||
'active_vector_digest',
|
||||
@@ -1439,6 +1458,10 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
'ql3_plugin_package_secret_binding_generation_uidx',
|
||||
'ql3_plugin_package_secret_binding_digest_uidx',
|
||||
'ql3_plugin_package_secret_binding_install_idx',
|
||||
'plugin_package_secret_binding_approval_plans_pkey',
|
||||
'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',
|
||||
'project_tool_definition_snapshots_pkey',
|
||||
'ql3_project_tool_snapshot_withdrawal_key',
|
||||
'ql3_project_tool_definition_snapshot_digest_uidx',
|
||||
@@ -1733,6 +1756,10 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
'ql3_plugin_package_secret_binding_identity_check',
|
||||
'ql3_plugin_package_secret_binding_digest_check',
|
||||
'ql3_plugin_package_secret_binding_json_check',
|
||||
'ql3_plugin_package_secret_binding_approval_plan_identity_check',
|
||||
'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_plugin_package_quarantine_identity_check',
|
||||
'ql3_plugin_package_quarantine_state_check',
|
||||
'ql3_plugin_package_quarantine_subject_check',
|
||||
@@ -2188,6 +2215,8 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
'ql3_plugin_package_materialized_revision_project_fk',
|
||||
'ql3_plugin_package_secret_binding_project_fk',
|
||||
'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_project_tool_definition_snapshot_project_fk',
|
||||
'ql3_project_tool_definition_snapshot_source_snapshot_fk',
|
||||
'ql3_project_tool_definition_snapshot_source_install_fk',
|
||||
@@ -2338,6 +2367,23 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
|
||||
'ql3_run_retry_policies_run_fk',
|
||||
]),
|
||||
functions: Object.freeze([
|
||||
Object.freeze({
|
||||
name: 'plugin_package_secret_binding_planning_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_binding_approval_plan',
|
||||
identityArguments: 'p_plan_json jsonb',
|
||||
owner: 'ql3_migration',
|
||||
securityDefiner: true,
|
||||
volatility: 'volatile',
|
||||
configuration: Object.freeze(['search_path=pg_catalog, ql3']),
|
||||
}),
|
||||
Object.freeze({
|
||||
name: 'enforce_plugin_package_secret_materialization',
|
||||
identityArguments: '',
|
||||
|
||||
@@ -173,6 +173,12 @@ const REQUIRED_RUNTIME_PRIVILEGES = Object.freeze({
|
||||
update: false,
|
||||
delete: false,
|
||||
}),
|
||||
plugin_package_secret_binding_approval_plans: Object.freeze({
|
||||
select: false,
|
||||
insert: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
}),
|
||||
project_tool_definition_snapshots: Object.freeze({
|
||||
select: false,
|
||||
insert: false,
|
||||
@@ -692,6 +698,12 @@ const REQUIRED_ADMIN_PRIVILEGES = Object.freeze({
|
||||
update: false,
|
||||
delete: false,
|
||||
}),
|
||||
plugin_package_secret_binding_approval_plans: Object.freeze({
|
||||
select: false,
|
||||
insert: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
}),
|
||||
project_tool_definition_snapshots: Object.freeze({
|
||||
select: false,
|
||||
insert: false,
|
||||
@@ -1193,6 +1205,7 @@ const REQUIRED_PACKAGE_MANAGER_PRIVILEGES: RequiredPrivileges = Object.freeze(
|
||||
name === 'projects' ||
|
||||
name === 'project_role_bindings' ||
|
||||
name === 'plugin_package_lifecycle_plans' ||
|
||||
name === 'plugin_package_secret_binding_approval_plans' ||
|
||||
name === 'plugin_package_automation_publications' ||
|
||||
name === 'plugin_package_automation_publication_heads' ||
|
||||
name === 'plugin_package_publisher_trust_transition_receipts'
|
||||
@@ -1235,6 +1248,7 @@ const REQUIRED_PACKAGE_EXECUTOR_PRIVILEGES: RequiredPrivileges = Object.freeze(
|
||||
name === 'projects' ||
|
||||
name === 'project_role_bindings' ||
|
||||
name === 'plugin_package_install_proposals' ||
|
||||
name === 'plugin_package_secret_binding_approval_plans' ||
|
||||
name === 'plugin_package_publisher_revocation_proposals' ||
|
||||
name === 'plugin_package_publisher_trust_transition_proposals'
|
||||
? { ...NO_TABLE_PRIVILEGES, select: true }
|
||||
@@ -1504,6 +1518,7 @@ const REQUIRED_ADMIN_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
|
||||
|
||||
const REQUIRED_RUNTIME_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
|
||||
Object.freeze({
|
||||
create_plugin_package_secret_binding_approval_plan: false,
|
||||
commit_plugin_package_lifecycle: false,
|
||||
commit_plugin_package_quarantine: false,
|
||||
commit_plugin_package_task_reconciliation: false,
|
||||
@@ -1517,12 +1532,14 @@ const REQUIRED_RUNTIME_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
|
||||
plugin_package_workflow_task_attempt_snapshot: true,
|
||||
plugin_package_lifecycle_blocking_runs: false,
|
||||
plugin_package_run_start_allowed: true,
|
||||
plugin_package_secret_binding_planning_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_binding_approval_plan: true,
|
||||
commit_plugin_package_lifecycle: false,
|
||||
commit_plugin_package_quarantine: false,
|
||||
commit_plugin_package_task_reconciliation: false,
|
||||
@@ -1536,12 +1553,14 @@ const REQUIRED_PACKAGE_MANAGER_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges =
|
||||
plugin_package_workflow_task_attempt_snapshot: false,
|
||||
plugin_package_lifecycle_blocking_runs: false,
|
||||
plugin_package_run_start_allowed: false,
|
||||
plugin_package_secret_binding_planning_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_binding_approval_plan: false,
|
||||
commit_plugin_package_lifecycle: true,
|
||||
commit_plugin_package_quarantine: true,
|
||||
commit_plugin_package_task_reconciliation: true,
|
||||
@@ -1555,6 +1574,7 @@ const REQUIRED_PACKAGE_EXECUTOR_FUNCTION_PRIVILEGES: RequiredFunctionPrivileges
|
||||
plugin_package_workflow_task_attempt_snapshot: false,
|
||||
plugin_package_lifecycle_blocking_runs: true,
|
||||
plugin_package_run_start_allowed: false,
|
||||
plugin_package_secret_binding_planning_snapshot: false,
|
||||
plugin_package_tool_start_allowed: false,
|
||||
register_plugin_package_automation_disposition_event: false,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user