mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-23 03:18:09 +08:00
feat(ql3): add cluster legacy env migration plan ledger
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
ClusterLegacyEnvMigrationPlanConflictError,
|
||||
ClusterLegacyEnvMigrationPlanUnavailableError,
|
||||
} = require('@qinglong/runtime-core/cluster-legacy-env-migration-plan');
|
||||
const { createSecretRef } = require('@qinglong/runtime-core/secret-reference');
|
||||
const {
|
||||
PostgresClusterLegacyEnvMigrationPlanRepository,
|
||||
} = require('@qinglong/cluster-postgres/cluster-legacy-env-migration-plan');
|
||||
|
||||
function intent(overrides = {}) {
|
||||
const projectId = overrides.projectId ?? 'project-a';
|
||||
return {
|
||||
planId: 'legacy-env-plan-a',
|
||||
mutationId: 'legacy-env-mutation-a',
|
||||
projectId,
|
||||
source: {
|
||||
reconciliationBundleDigest: '1'.repeat(64),
|
||||
decisionDigest: '2'.repeat(64),
|
||||
candidateSetDigest: '3'.repeat(64),
|
||||
sourceRowCount: 3,
|
||||
activeRowCount: 2,
|
||||
disabledRowCount: 1,
|
||||
effectiveBindingCount: 2,
|
||||
},
|
||||
target: {
|
||||
secretRef: createSecretRef({
|
||||
projectId,
|
||||
name: 'legacy-env-bundle',
|
||||
version: 7,
|
||||
}),
|
||||
taskRevisionSetDigest: '4'.repeat(64),
|
||||
triggerRevisionSetDigest: '5'.repeat(64),
|
||||
taskCount: 2,
|
||||
triggerCount: 3,
|
||||
totalEffectiveBytes: 1024,
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function fixture(options = {}) {
|
||||
const plansById = new Map();
|
||||
const plansByMutation = new Map();
|
||||
const queries = [];
|
||||
let serializationFailures = options.serializationFailures ?? 0;
|
||||
let connections = 0;
|
||||
|
||||
const pool = {
|
||||
async query(text, values) {
|
||||
queries.push({ scope: 'pool', text, values });
|
||||
if (text.includes('WHERE plan_id')) {
|
||||
const plan = plansById.get(values[0]);
|
||||
return {
|
||||
rows: plan ? [{ planJson: plan }] : [],
|
||||
rowCount: plan ? 1 : 0,
|
||||
};
|
||||
}
|
||||
throw new Error('unexpected pool query');
|
||||
},
|
||||
async connect() {
|
||||
connections += 1;
|
||||
return {
|
||||
async query(text, values) {
|
||||
queries.push({ scope: 'client', text, values });
|
||||
if (
|
||||
text === 'BEGIN ISOLATION LEVEL SERIALIZABLE' ||
|
||||
text === 'COMMIT' ||
|
||||
text === 'ROLLBACK' ||
|
||||
text.includes("set_config('")
|
||||
) {
|
||||
return { rows: [], rowCount: 0 };
|
||||
}
|
||||
if (text.includes('WHERE mutation_id')) {
|
||||
if (serializationFailures > 0) {
|
||||
serializationFailures -= 1;
|
||||
throw Object.assign(new Error('serialization retry'), {
|
||||
code: '40001',
|
||||
});
|
||||
}
|
||||
const plan = plansByMutation.get(values[0]);
|
||||
return {
|
||||
rows: plan ? [{ planJson: plan }] : [],
|
||||
rowCount: plan ? 1 : 0,
|
||||
};
|
||||
}
|
||||
if (text.includes('FROM "ql3"."projects"')) {
|
||||
return options.projectStatus === 'archived'
|
||||
? { rows: [{ status: 'archived' }], rowCount: 1 }
|
||||
: { rows: [{ status: 'active' }], rowCount: 1 };
|
||||
}
|
||||
if (text.includes('WHERE plan_id')) {
|
||||
const plan = plansById.get(values[0]);
|
||||
return {
|
||||
rows: plan ? [{ planJson: plan }] : [],
|
||||
rowCount: plan ? 1 : 0,
|
||||
};
|
||||
}
|
||||
if (text.includes('transaction_timestamp')) {
|
||||
return { rows: [{ plannedAtMs: '12345' }], rowCount: 1 };
|
||||
}
|
||||
if (text.includes('INSERT INTO')) {
|
||||
const plan = JSON.parse(values[18]);
|
||||
plansById.set(plan.planId, plan);
|
||||
plansByMutation.set(plan.mutationId, plan);
|
||||
return { rows: [], rowCount: 1 };
|
||||
}
|
||||
if (text === 'SELECT hook_boundary') {
|
||||
return { rows: [], rowCount: 0 };
|
||||
}
|
||||
throw new Error(`unexpected client query: ${text}`);
|
||||
},
|
||||
release() {
|
||||
queries.push({ scope: 'client', text: 'RELEASE' });
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
return {
|
||||
repository: new PostgresClusterLegacyEnvMigrationPlanRepository(pool),
|
||||
plansById,
|
||||
plansByMutation,
|
||||
queries,
|
||||
connectionCount: () => connections,
|
||||
};
|
||||
}
|
||||
|
||||
test('publishes and exactly replays one content-free plan in serializable transactions', async () => {
|
||||
const state = fixture();
|
||||
const hookContexts = [];
|
||||
const hook = async (client, context) => {
|
||||
hookContexts.push(context);
|
||||
await client.query('SELECT hook_boundary');
|
||||
};
|
||||
const created = await state.repository.publish(intent(), hook);
|
||||
const replay = await state.repository.publish(intent(), hook);
|
||||
|
||||
assert.equal(created.status, 'created');
|
||||
assert.equal(replay.status, 'existing');
|
||||
assert.deepEqual(replay.plan, created.plan);
|
||||
assert.equal(state.plansById.size, 1);
|
||||
assert.equal(hookContexts[0].replay, null);
|
||||
assert.deepEqual(hookContexts[1].replay, created.plan);
|
||||
assert.equal(
|
||||
state.queries.filter(
|
||||
({ text }) => text === 'BEGIN ISOLATION LEVEL SERIALIZABLE',
|
||||
).length,
|
||||
2,
|
||||
);
|
||||
assert.equal(state.queries.filter(({ text }) => text === 'COMMIT').length, 2);
|
||||
|
||||
const insert = state.queries.find(({ text }) => text.includes('INSERT INTO'));
|
||||
const encodedPlan = insert.values[18];
|
||||
assert.doesNotMatch(encodedPlan, /TOKEN|secretValue|ciphertext|keyId/i);
|
||||
assert.equal(
|
||||
JSON.parse(encodedPlan).target.secretRef,
|
||||
intent().target.secretRef,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects mutation replay drift and inactive Projects without writing', async () => {
|
||||
const replayState = fixture();
|
||||
await replayState.repository.publish(intent());
|
||||
await assert.rejects(
|
||||
replayState.repository.publish(
|
||||
intent({
|
||||
target: { ...intent().target, taskCount: 3 },
|
||||
}),
|
||||
),
|
||||
ClusterLegacyEnvMigrationPlanConflictError,
|
||||
);
|
||||
assert.equal(
|
||||
replayState.queries.filter(({ text }) => text.includes('INSERT INTO'))
|
||||
.length,
|
||||
1,
|
||||
);
|
||||
assert.equal(
|
||||
replayState.queries.some(({ text }) => text === 'ROLLBACK'),
|
||||
true,
|
||||
);
|
||||
|
||||
const archivedState = fixture({ projectStatus: 'archived' });
|
||||
await assert.rejects(
|
||||
archivedState.repository.publish(intent()),
|
||||
ClusterLegacyEnvMigrationPlanConflictError,
|
||||
);
|
||||
assert.equal(
|
||||
archivedState.queries.some(({ text }) => text.includes('INSERT INTO')),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test('retries bounded serializable failures and preserves the transaction hook error', async () => {
|
||||
const state = fixture({ serializationFailures: 1 });
|
||||
const created = await state.repository.publish(intent());
|
||||
assert.equal(created.status, 'created');
|
||||
assert.equal(state.connectionCount(), 2);
|
||||
assert.equal(
|
||||
state.queries.filter(({ text }) => text === 'ROLLBACK').length,
|
||||
1,
|
||||
);
|
||||
|
||||
const hookError = new Error('caller hook failed');
|
||||
await assert.rejects(
|
||||
fixture().repository.publish(intent(), async () => {
|
||||
throw hookError;
|
||||
}),
|
||||
(error) => error === hookError,
|
||||
);
|
||||
});
|
||||
|
||||
test('fails closed on malformed durable JSON and hides raw storage errors', async () => {
|
||||
const malformedPool = {
|
||||
async query() {
|
||||
return { rows: [{ planJson: { schema: 'wrong' } }], rowCount: 1 };
|
||||
},
|
||||
async connect() {
|
||||
throw new Error('unused');
|
||||
},
|
||||
};
|
||||
await assert.rejects(
|
||||
new PostgresClusterLegacyEnvMigrationPlanRepository(
|
||||
malformedPool,
|
||||
).findByPlanId('legacy-env-plan-a'),
|
||||
ClusterLegacyEnvMigrationPlanUnavailableError,
|
||||
);
|
||||
|
||||
const identityDrift = fixture();
|
||||
const created = await identityDrift.repository.publish(intent());
|
||||
identityDrift.plansById.set('legacy-env-plan-b', created.plan);
|
||||
await assert.rejects(
|
||||
identityDrift.repository.findByPlanId('legacy-env-plan-b'),
|
||||
ClusterLegacyEnvMigrationPlanUnavailableError,
|
||||
);
|
||||
identityDrift.plansByMutation.set('legacy-env-mutation-b', created.plan);
|
||||
await assert.rejects(
|
||||
identityDrift.repository.publish(
|
||||
intent({
|
||||
planId: 'legacy-env-plan-b',
|
||||
mutationId: 'legacy-env-mutation-b',
|
||||
}),
|
||||
),
|
||||
ClusterLegacyEnvMigrationPlanUnavailableError,
|
||||
);
|
||||
|
||||
const failedPool = {
|
||||
async query() {
|
||||
throw new Error('password=do-not-leak');
|
||||
},
|
||||
async connect() {
|
||||
throw new Error('unused');
|
||||
},
|
||||
};
|
||||
await assert.rejects(
|
||||
new PostgresClusterLegacyEnvMigrationPlanRepository(
|
||||
failedPool,
|
||||
).findByPlanId('legacy-env-plan-a'),
|
||||
(error) => {
|
||||
assert.ok(error instanceof ClusterLegacyEnvMigrationPlanUnavailableError);
|
||||
assert.doesNotMatch(error.message, /password|do-not-leak/i);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps the append authority behind its explicit package subpath', () => {
|
||||
const root = require('@qinglong/cluster-postgres');
|
||||
const runtime = require('@qinglong/cluster-postgres/runtime');
|
||||
const admin = require('@qinglong/cluster-postgres/admin');
|
||||
const authority = require('@qinglong/cluster-postgres/cluster-legacy-env-migration-plan');
|
||||
assert.equal(root.PostgresClusterLegacyEnvMigrationPlanRepository, undefined);
|
||||
assert.equal(
|
||||
runtime.PostgresClusterLegacyEnvMigrationPlanRepository,
|
||||
undefined,
|
||||
);
|
||||
assert.equal(
|
||||
admin.PostgresClusterLegacyEnvMigrationPlanRepository,
|
||||
undefined,
|
||||
);
|
||||
assert.equal(
|
||||
typeof authority.PostgresClusterLegacyEnvMigrationPlanRepository,
|
||||
'function',
|
||||
);
|
||||
});
|
||||
@@ -71,6 +71,9 @@ const {
|
||||
const {
|
||||
resolveClusterScheduleDecision,
|
||||
} = require('@qinglong/runtime-core/cluster-scheduler');
|
||||
const {
|
||||
PostgresClusterLegacyEnvMigrationPlanRepository,
|
||||
} = require('../dist/reconciliation/clusterLegacyEnvMigrationPlanRepository');
|
||||
|
||||
function nextMinute(schedule, afterMs) {
|
||||
if (schedule.expression !== '* * * * *' || schedule.timezone !== 'UTC') {
|
||||
@@ -877,7 +880,10 @@ if (!migrationConnectionString) {
|
||||
assert.equal(competing?.status, 'leased');
|
||||
assert.equal(claimed.dispatch.version, 1);
|
||||
assert.equal(claimed.dispatch.dispatchCount, 1);
|
||||
assert.equal(claimed.dispatch.createdAtMs >= Number(before.rows[0].nowMs), true);
|
||||
assert.equal(
|
||||
claimed.dispatch.createdAtMs >= Number(before.rows[0].nowMs),
|
||||
true,
|
||||
);
|
||||
const rawLeaseToken = claimed.leaseToken;
|
||||
const stored = await migrationDatabase.pool.query(
|
||||
`SELECT lease_token_digest AS "leaseTokenDigest",
|
||||
@@ -963,11 +969,13 @@ if (!migrationConnectionString) {
|
||||
assert.equal(retry.dispatch.status, 'retry_wait');
|
||||
assert.equal(retry.event.type, 'run.cancel_dispatch_failed');
|
||||
assert.equal(
|
||||
(await firstRepository.claim({
|
||||
...candidate,
|
||||
owner: 'primary-a',
|
||||
leaseToken: 'lease-a-retry',
|
||||
})).status,
|
||||
(
|
||||
await firstRepository.claim({
|
||||
...candidate,
|
||||
owner: 'primary-a',
|
||||
leaseToken: 'lease-a-retry',
|
||||
})
|
||||
).status,
|
||||
'not_due',
|
||||
);
|
||||
await migrationDatabase.pool.query(
|
||||
@@ -2199,9 +2207,7 @@ if (!migrationConnectionString) {
|
||||
const migrationDatabase = await open('migration');
|
||||
try {
|
||||
await runPostgresMigrations({ pool: migrationDatabase.pool });
|
||||
await migrationDatabase.pool.query(
|
||||
'TRUNCATE TABLE "ql3"."runs" CASCADE',
|
||||
);
|
||||
await migrationDatabase.pool.query('TRUNCATE TABLE "ql3"."runs" CASCADE');
|
||||
await observeContractPublisherTrust(migrationDatabase.pool);
|
||||
await migrationDatabase.pool.query(
|
||||
`INSERT INTO "ql3"."projects" (
|
||||
@@ -3847,12 +3853,13 @@ if (!migrationConnectionString) {
|
||||
const executions = new PostgresApprovedActionExecutionRepository(
|
||||
executorDatabase.pool,
|
||||
);
|
||||
const pendingSecretActions =
|
||||
await executions.listReconciliableExecutions({
|
||||
const pendingSecretActions = await executions.listReconciliableExecutions(
|
||||
{
|
||||
nowMs: claimedAtMs,
|
||||
limit: 1,
|
||||
actionTypes: [consumed.dispatch.action.actionType],
|
||||
});
|
||||
},
|
||||
);
|
||||
assert.equal(pendingSecretActions.truncated, false);
|
||||
assert.equal(pendingSecretActions.executions.length, 1);
|
||||
assert.equal(
|
||||
@@ -6532,4 +6539,136 @@ if (!migrationConnectionString) {
|
||||
await database.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('persists one content-free Legacy Env migration plan with isolated authority', async () => {
|
||||
const projectId = `legacy-env-project-${process.pid}`;
|
||||
const planId = `legacy-env-plan-${process.pid}`;
|
||||
const mutationId = `legacy-env-mutation-${process.pid}`;
|
||||
const migrationDatabase = await open('migration');
|
||||
const automationDatabase = await open('automation-manager');
|
||||
const runtimeDatabase = await open('runtime');
|
||||
const adminDatabase = await open('admin');
|
||||
try {
|
||||
await runPostgresMigrations({ pool: migrationDatabase.pool });
|
||||
await migrationDatabase.pool.query(
|
||||
`INSERT INTO "ql3"."projects"
|
||||
(id, name, slug, status, version, created_at_ms, updated_at_ms)
|
||||
VALUES ($1, $1, $1, 'active', 1, 1, 1)
|
||||
ON CONFLICT (id) DO NOTHING`,
|
||||
[projectId],
|
||||
);
|
||||
const intent = {
|
||||
planId,
|
||||
mutationId,
|
||||
projectId,
|
||||
source: {
|
||||
reconciliationBundleDigest: '1'.repeat(64),
|
||||
decisionDigest: '2'.repeat(64),
|
||||
candidateSetDigest: '3'.repeat(64),
|
||||
sourceRowCount: 3,
|
||||
activeRowCount: 2,
|
||||
disabledRowCount: 1,
|
||||
effectiveBindingCount: 2,
|
||||
},
|
||||
target: {
|
||||
secretRef: createSecretRef({
|
||||
projectId,
|
||||
name: 'legacy-env-bundle',
|
||||
version: 1,
|
||||
}),
|
||||
taskRevisionSetDigest: '4'.repeat(64),
|
||||
triggerRevisionSetDigest: '5'.repeat(64),
|
||||
taskCount: 2,
|
||||
triggerCount: 1,
|
||||
totalEffectiveBytes: 1024,
|
||||
},
|
||||
};
|
||||
const repository = new PostgresClusterLegacyEnvMigrationPlanRepository(
|
||||
automationDatabase.pool,
|
||||
);
|
||||
const created = await repository.publish(intent);
|
||||
const replay = await repository.publish(intent);
|
||||
assert.equal(created.status, 'created');
|
||||
assert.equal(replay.status, 'existing');
|
||||
assert.deepEqual(replay.plan, created.plan);
|
||||
assert.deepEqual(await repository.findByPlanId(planId), created.plan);
|
||||
|
||||
const stored = await automationDatabase.pool.query(
|
||||
`SELECT plan_json AS "planJson"
|
||||
FROM "ql3"."cluster_legacy_env_migration_plans"
|
||||
WHERE plan_id = $1`,
|
||||
[planId],
|
||||
);
|
||||
assert.equal(stored.rowCount, 1);
|
||||
assert.deepEqual(stored.rows[0].planJson, created.plan);
|
||||
assert.doesNotMatch(
|
||||
JSON.stringify(stored.rows[0].planJson),
|
||||
/TOKEN|secretValue|ciphertext|keyId/i,
|
||||
);
|
||||
|
||||
const invalidPlanId = `${planId}-widened`;
|
||||
const invalidMutationId = `${mutationId}-widened`;
|
||||
const invalidDigest = 'f'.repeat(64);
|
||||
await assert.rejects(
|
||||
migrationDatabase.pool.query(
|
||||
`INSERT INTO "ql3"."cluster_legacy_env_migration_plans" (
|
||||
plan_id, mutation_id, project_id, plan_digest,
|
||||
reconciliation_bundle_digest, decision_digest,
|
||||
candidate_set_digest, source_row_count, active_row_count,
|
||||
disabled_row_count, effective_binding_count, secret_ref,
|
||||
task_revision_set_digest, trigger_revision_set_digest,
|
||||
task_count, trigger_count, total_effective_bytes,
|
||||
planned_at_ms, plan_json
|
||||
)
|
||||
SELECT $2::varchar, $3::varchar, project_id, $4::varchar,
|
||||
reconciliation_bundle_digest, decision_digest,
|
||||
candidate_set_digest, source_row_count, active_row_count,
|
||||
disabled_row_count, effective_binding_count, secret_ref,
|
||||
task_revision_set_digest, trigger_revision_set_digest,
|
||||
task_count, trigger_count, total_effective_bytes,
|
||||
planned_at_ms,
|
||||
plan_json || jsonb_build_object(
|
||||
'planId', $2::varchar,
|
||||
'mutationId', $3::varchar,
|
||||
'planDigest', $4::varchar,
|
||||
'envName', 'TOKEN'
|
||||
)
|
||||
FROM "ql3"."cluster_legacy_env_migration_plans"
|
||||
WHERE plan_id = $1`,
|
||||
[planId, invalidPlanId, invalidMutationId, invalidDigest],
|
||||
),
|
||||
(error) =>
|
||||
error?.code === '23514' &&
|
||||
error?.constraint === 'ql3_cluster_legacy_env_plan_json_check',
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
automationDatabase.pool.query(
|
||||
`UPDATE "ql3"."cluster_legacy_env_migration_plans"
|
||||
SET planned_at_ms = planned_at_ms
|
||||
WHERE plan_id = $1`,
|
||||
[planId],
|
||||
),
|
||||
(error) => error?.code === '42501',
|
||||
);
|
||||
for (const database of [runtimeDatabase, adminDatabase]) {
|
||||
await assert.rejects(
|
||||
database.pool.query(
|
||||
`SELECT plan_id
|
||||
FROM "ql3"."cluster_legacy_env_migration_plans"
|
||||
WHERE plan_id = $1`,
|
||||
[planId],
|
||||
),
|
||||
(error) => error?.code === '42501',
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await Promise.all([
|
||||
adminDatabase.close(),
|
||||
runtimeDatabase.close(),
|
||||
automationDatabase.close(),
|
||||
migrationDatabase.close(),
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ test('defines the immutable PostgreSQL capability and Run core stream', async ()
|
||||
'pg-0067-cancellation-dispatch-management',
|
||||
'pg-0068-cancellation-dispatch-project-keyset',
|
||||
'pg-0069-worker-session-management-observation',
|
||||
'pg-0070-cluster-legacy-env-migration-plans',
|
||||
],
|
||||
);
|
||||
for (const migration of postgresqlMainMigrationStream.migrations) {
|
||||
@@ -603,6 +604,11 @@ test('freezes every published PostgreSQL migration checksum', () => {
|
||||
checksum:
|
||||
'1191255575589abc2686b391827607abddb4edb78007245dbaaf45dc1c4e5e8b',
|
||||
},
|
||||
{
|
||||
id: 'pg-0070-cluster-legacy-env-migration-plans',
|
||||
checksum:
|
||||
'7cd6d993f48e7bcebcd62c93571a738d5117c9bcde33b974c5ac8962e2a03fe4',
|
||||
},
|
||||
];
|
||||
assert.deepEqual(
|
||||
postgresqlMainMigrationStream.migrations.map(({ id, checksum }) => ({
|
||||
@@ -2241,11 +2247,11 @@ test('advances capability v63 with manager-only immutable Secret transition plan
|
||||
sql,
|
||||
/GRANT SELECT ON "ql3"\."plugin_package_secret_binding_transition_approval_plans" TO ql3_package_manager, ql3_package_executor/,
|
||||
);
|
||||
assert.match(
|
||||
assert.match(sql, /GRANT EXECUTE ON FUNCTION [^;]+ TO ql3_package_manager/);
|
||||
assert.doesNotMatch(
|
||||
sql,
|
||||
/GRANT EXECUTE ON FUNCTION [^;]+ TO ql3_package_manager/,
|
||||
/GRANT EXECUTE ON FUNCTION [^;]+ TO ql3_package_executor/,
|
||||
);
|
||||
assert.doesNotMatch(sql, /GRANT EXECUTE ON FUNCTION [^;]+ TO ql3_package_executor/);
|
||||
assert.match(sql, /contract_version = 63/);
|
||||
assert.match(
|
||||
sql,
|
||||
@@ -2321,10 +2327,7 @@ test('advances capability v65 with database-timed fenced cancellation dispatch',
|
||||
sql,
|
||||
/CREATE UNIQUE INDEX ql3_run_attempts_run_id_uidx ON "ql3"\."run_attempts" \(run_id, id\)/,
|
||||
);
|
||||
assert.match(
|
||||
sql,
|
||||
/CREATE TABLE "ql3"\."run_cancellation_dispatches"/,
|
||||
);
|
||||
assert.match(sql, /CREATE TABLE "ql3"\."run_cancellation_dispatches"/);
|
||||
assert.match(
|
||||
sql,
|
||||
/FOREIGN KEY \(run_id, attempt_id\)[\s\S]+REFERENCES "ql3"\."run_attempts" \(run_id, id\)/,
|
||||
@@ -2342,16 +2345,11 @@ test('advances capability v65 with database-timed fenced cancellation dispatch',
|
||||
assert.match(sql, /contract_version = 65/);
|
||||
assert.match(sql, /"run_cancellation_dispatch":1/);
|
||||
assert.match(sql, /contract_version = 64/);
|
||||
assert.match(
|
||||
sql,
|
||||
/migration_id = 'pg-0065-approved-action-manual-recovery'/,
|
||||
);
|
||||
assert.match(sql, /migration_id = 'pg-0065-approved-action-manual-recovery'/);
|
||||
});
|
||||
|
||||
test('advances capability v66 with least-privilege cancellation diagnostics and rearm', async () => {
|
||||
const migration = migrationById(
|
||||
'pg-0067-cancellation-dispatch-management',
|
||||
);
|
||||
const migration = migrationById('pg-0067-cancellation-dispatch-management');
|
||||
const statements = [];
|
||||
await migration.up({
|
||||
async query(statement) {
|
||||
@@ -2398,10 +2396,7 @@ test('advances capability v67 with a Project-scoped blocked keyset', async () =>
|
||||
},
|
||||
});
|
||||
const sql = statements.join('\n');
|
||||
assert.match(
|
||||
sql,
|
||||
/ADD COLUMN project_id varchar\(128\)/,
|
||||
);
|
||||
assert.match(sql, /ADD COLUMN project_id varchar\(128\)/);
|
||||
assert.match(
|
||||
sql,
|
||||
/SET project_id = run\.project_id FROM "ql3"\."runs" AS run/,
|
||||
@@ -2460,3 +2455,37 @@ test('advances capability v68 with read-only Worker session observation', async
|
||||
/migration_id = 'pg-0068-cancellation-dispatch-project-keyset'/,
|
||||
);
|
||||
});
|
||||
|
||||
test('advances capability v69 with a content-free Legacy Env plan ledger', async () => {
|
||||
const migration = migrationById('pg-0070-cluster-legacy-env-migration-plans');
|
||||
const statements = [];
|
||||
await migration.up({
|
||||
async query(statement) {
|
||||
statements.push(statement);
|
||||
return { rows: [] };
|
||||
},
|
||||
});
|
||||
const sql = statements.join('\n');
|
||||
assert.match(sql, /CREATE TABLE "ql3"\."cluster_legacy_env_migration_plans"/);
|
||||
assert.match(sql, /source_row_count BETWEEN 1 AND 100000/);
|
||||
assert.match(sql, /total_effective_bytes BETWEEN 1 AND 65536/);
|
||||
assert.match(
|
||||
sql,
|
||||
/plan_json = jsonb_build_object\([\s\S]+qinglong\/cluster-legacy-env-migration-plan@v1/,
|
||||
);
|
||||
assert.match(
|
||||
sql,
|
||||
/GRANT SELECT, INSERT ON "ql3"\."cluster_legacy_env_migration_plans" TO ql3_automation_manager/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
sql,
|
||||
/GRANT (?:UPDATE|DELETE|TRUNCATE)[^;]+cluster_legacy_env_migration_plans/,
|
||||
);
|
||||
assert.match(sql, /contract_version = 69/);
|
||||
assert.match(sql, /"cluster_legacy_env_migration_plan":1/);
|
||||
assert.match(sql, /contract_version = 68/);
|
||||
assert.match(
|
||||
sql,
|
||||
/migration_id = 'pg-0069-worker-session-management-observation'/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@ function validPrivileges() {
|
||||
schema_migrations: [true, false, false, false],
|
||||
schema_capabilities: [true, false, false, false],
|
||||
projects: [true, true, true, false],
|
||||
cluster_legacy_env_migration_plans: [false, false, false, false],
|
||||
task_definitions: [true, false, false, false],
|
||||
task_definition_revisions: [true, false, false, false],
|
||||
task_execution_revisions: [true, false, false, false],
|
||||
@@ -167,6 +168,7 @@ function validAdminPrivileges() {
|
||||
schema_migrations: [true, false, false, false],
|
||||
schema_capabilities: [true, false, false, false],
|
||||
projects: [true, false, false, false],
|
||||
cluster_legacy_env_migration_plans: [false, false, false, false],
|
||||
task_definitions: [false, false, false, false],
|
||||
task_definition_revisions: [false, false, false, false],
|
||||
task_execution_revisions: [false, false, false, false],
|
||||
@@ -454,6 +456,7 @@ function automationManagerPrivileges() {
|
||||
'project_role_bindings',
|
||||
'plugin_package_task_ownerships',
|
||||
'plugin_package_identity_keyset_ledger',
|
||||
'cluster_legacy_env_migration_plans',
|
||||
'security_audit_events',
|
||||
'task_definitions',
|
||||
'task_definition_revisions',
|
||||
@@ -471,6 +474,7 @@ function automationManagerPrivileges() {
|
||||
'trigger_revisions',
|
||||
'trigger_schedules',
|
||||
'plugin_package_identity_keyset_ledger',
|
||||
'cluster_legacy_env_migration_plans',
|
||||
]);
|
||||
return postgresqlControlSchemaContract.tables.map(({ name: tableName }) => ({
|
||||
tableName,
|
||||
@@ -791,9 +795,7 @@ function queryable(overrides = {}) {
|
||||
: 'runs';
|
||||
assert.match(
|
||||
text,
|
||||
new RegExp(
|
||||
`format\\('%I\\.%I', \\$1::text, '${tableName}'\\)`,
|
||||
),
|
||||
new RegExp(`format\\('%I\\.%I', \\$1::text, '${tableName}'\\)`),
|
||||
);
|
||||
const columns = contract.tables.find(
|
||||
({ name }) => name === tableName,
|
||||
@@ -836,7 +838,7 @@ test('accepts the exact PostgreSQL control schema and least-privilege runtime ro
|
||||
serverMajor: 16,
|
||||
currentUser: 'ql3_runtime',
|
||||
contractName: 'control-core',
|
||||
contractVersion: 68,
|
||||
contractVersion: 69,
|
||||
migrationIds: [
|
||||
'pg-0001-schema-capability',
|
||||
'pg-0002-run-core',
|
||||
@@ -907,6 +909,7 @@ test('accepts the exact PostgreSQL control schema and least-privilege runtime ro
|
||||
'pg-0067-cancellation-dispatch-management',
|
||||
'pg-0068-cancellation-dispatch-project-keyset',
|
||||
'pg-0069-worker-session-management-observation',
|
||||
'pg-0070-cluster-legacy-env-migration-plans',
|
||||
],
|
||||
});
|
||||
});
|
||||
@@ -937,10 +940,10 @@ test('accepts the exact schema and isolated least-privilege admin role', async (
|
||||
}),
|
||||
);
|
||||
assert.equal(report.currentUser, 'ql3_admin');
|
||||
assert.equal(report.contractVersion, 68);
|
||||
assert.equal(report.contractVersion, 69);
|
||||
assert.equal(
|
||||
report.migrationIds.at(-1),
|
||||
'pg-0069-worker-session-management-observation',
|
||||
'pg-0070-cluster-legacy-env-migration-plans',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -953,10 +956,10 @@ test('accepts the isolated least-privilege automation manager role', async () =>
|
||||
}),
|
||||
);
|
||||
assert.equal(report.currentUser, 'ql3_automation_manager');
|
||||
assert.equal(report.contractVersion, 68);
|
||||
assert.equal(report.contractVersion, 69);
|
||||
assert.equal(
|
||||
report.migrationIds.at(-1),
|
||||
'pg-0069-worker-session-management-observation',
|
||||
'pg-0070-cluster-legacy-env-migration-plans',
|
||||
);
|
||||
|
||||
const widened = automationManagerPrivileges();
|
||||
@@ -985,10 +988,10 @@ test('accepts the isolated least-privilege human Approval manager role', async (
|
||||
}),
|
||||
);
|
||||
assert.equal(report.currentUser, 'ql3_approval_manager');
|
||||
assert.equal(report.contractVersion, 68);
|
||||
assert.equal(report.contractVersion, 69);
|
||||
assert.equal(
|
||||
report.migrationIds.at(-1),
|
||||
'pg-0069-worker-session-management-observation',
|
||||
'pg-0070-cluster-legacy-env-migration-plans',
|
||||
);
|
||||
|
||||
const widened = approvalManagerPrivileges();
|
||||
@@ -1019,10 +1022,10 @@ test('accepts the isolated least-privilege Run manager role', async () => {
|
||||
}),
|
||||
);
|
||||
assert.equal(report.currentUser, 'ql3_run_manager');
|
||||
assert.equal(report.contractVersion, 68);
|
||||
assert.equal(report.contractVersion, 69);
|
||||
assert.equal(
|
||||
report.migrationIds.at(-1),
|
||||
'pg-0069-worker-session-management-observation',
|
||||
'pg-0070-cluster-legacy-env-migration-plans',
|
||||
);
|
||||
|
||||
const widened = runManagerPrivileges();
|
||||
@@ -1183,10 +1186,10 @@ test('accepts the exact schema and isolated Worker ingress role', async () => {
|
||||
}),
|
||||
);
|
||||
assert.equal(report.currentUser, 'ql3_worker_ingress');
|
||||
assert.equal(report.contractVersion, 68);
|
||||
assert.equal(report.contractVersion, 69);
|
||||
assert.equal(
|
||||
report.migrationIds.at(-1),
|
||||
'pg-0069-worker-session-management-observation',
|
||||
'pg-0070-cluster-legacy-env-migration-plans',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user