feat(ql3): add strong cluster run stop

This commit is contained in:
whyour
2026-08-12 08:21:28 +08:00
parent c0ab62e64a
commit dd370b2842
22 changed files with 1602 additions and 321 deletions
@@ -15,14 +15,15 @@ export interface PostgresSchemaContractFunction {
export interface PostgresSchemaContract {
readonly schema: 'ql3';
readonly contractName: 'control-core';
readonly contractVersion: 55;
readonly migrationId: 'pg-0056-run-management-boundary';
readonly contractVersion: 56;
readonly migrationId: 'pg-0057-run-management-stop-boundary';
readonly minimumServerMajor: 16;
readonly maximumServerMajor: 18;
readonly capabilities: Readonly<{
run_core: 1;
run_attempt_log_retention: 1;
run_management_boundary: 1;
run_management_stop: 1;
run_dispatch_lease: 1;
run_retry_policy: 1;
project_policy: 1;
@@ -102,8 +103,8 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
Object.freeze({
schema: 'ql3',
contractName: 'control-core',
contractVersion: 55,
migrationId: 'pg-0056-run-management-boundary',
contractVersion: 56,
migrationId: 'pg-0057-run-management-stop-boundary',
minimumServerMajor: 16,
maximumServerMajor: 18,
capabilities: Object.freeze({
@@ -145,6 +146,7 @@ export const postgresqlControlSchemaContract: PostgresSchemaContract =
run_core: 1,
run_attempt_log_retention: 1,
run_management_boundary: 1,
run_management_stop: 1,
run_dispatch_lease: 1,
run_retry_policy: 1,
security_audit: 1,
@@ -119,6 +119,11 @@ interface FunctionPrivilegeRow extends Record<string, unknown> {
isOwner: unknown;
}
interface ColumnPrivilegeRow extends Record<string, unknown> {
columnName: unknown;
updateAllowed: unknown;
}
const REQUIRED_RUNTIME_PRIVILEGES = Object.freeze({
schema_migrations: Object.freeze({
select: true,
@@ -1315,13 +1320,13 @@ const REQUIRED_AUTOMATION_MANAGER_PRIVILEGES: RequiredPrivileges =
update: true,
}
: name === 'security_audit_events' ||
name === 'task_definition_revisions' ||
name === 'task_execution_revisions' ||
name === 'trigger_revisions'
name === 'task_definition_revisions' ||
name === 'task_execution_revisions' ||
name === 'trigger_revisions'
? { ...NO_TABLE_PRIVILEGES, select: true, insert: true }
: name === 'task_definitions' ||
name === 'triggers' ||
name === 'trigger_schedules'
name === 'triggers' ||
name === 'trigger_schedules'
? {
...NO_TABLE_PRIVILEGES,
select: true,
@@ -1376,9 +1381,9 @@ const REQUIRED_RUN_MANAGER_PRIVILEGES: RequiredPrivileges = Object.freeze(
name === 'task_execution_revisions'
? { ...NO_TABLE_PRIVILEGES, select: true }
: name === 'runs' ||
name === 'run_attempts' ||
name === 'run_events' ||
name === 'security_audit_events'
name === 'run_attempts' ||
name === 'run_events' ||
name === 'security_audit_events'
? { ...NO_TABLE_PRIVILEGES, select: true, insert: true }
: name === 'plugin_package_identity_keyset_ledger'
? {
@@ -1415,7 +1420,7 @@ const REQUIRED_WORKER_CREDENTIAL_MANAGER_PRIVILEGES: RequiredPrivileges =
update: true,
}
: name === 'worker_credential_management_quota_buckets' ||
name === 'plugin_package_identity_keyset_ledger'
name === 'plugin_package_identity_keyset_ledger'
? {
...NO_TABLE_PRIVILEGES,
select: true,
@@ -2042,6 +2047,56 @@ ORDER BY requested.function_name
}
}
async function assertRunManagerColumnPrivileges(
queryable: PostgresMigrationQueryable,
contract: PostgresSchemaContract,
): Promise<void> {
const run = contract.tables.find(({ name }) => name === 'runs');
if (!run) {
throw new PostgresSchemaReadinessError('run_manager_role_invalid', [
'missing-runs-contract',
]);
}
const result = await queryable.query<ColumnPrivilegeRow>(
`
SELECT
requested.column_name AS "columnName",
has_column_privilege(
current_user,
format('%I.%I', $1::text, 'runs'),
requested.column_name,
'UPDATE'
) AS "updateAllowed"
FROM unnest($2::text[]) AS requested(column_name)
ORDER BY requested.column_name
`.trim(),
[contract.schema, run.columns],
);
const allowed = new Set([
'cancel_requested_at_ms',
'cancel_reason',
'version',
'event_sequence',
]);
const actual = new Map(result.rows.map((row) => [row.columnName, row]));
const findings: string[] = [];
for (const columnName of run.columns) {
const row = actual.get(columnName);
if (!row || row.updateAllowed !== allowed.has(columnName)) {
findings.push(`column-update-privilege:runs.${columnName}`);
}
}
if (actual.size !== run.columns.length) {
findings.push('column-privilege-row-count:runs');
}
if (findings.length > 0) {
throw new PostgresSchemaReadinessError(
'run_manager_role_invalid',
sorted(findings),
);
}
}
export async function assertPostgresSchemaReady(
queryable: PostgresMigrationQueryable,
contract: PostgresSchemaContract = postgresqlControlSchemaContract,
@@ -2177,6 +2232,7 @@ export async function assertPostgresRunManagerSchemaReady(
REQUIRED_RUN_MANAGER_FUNCTION_PRIVILEGES,
'run_manager_role_invalid',
);
await assertRunManagerColumnPrivileges(queryable, contract);
return Object.freeze({
ready: true,
...server,