mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 09:58:46 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// Cluster Plugin Package executor boundary; keep approved-action dispatch explicit.
|
||||
import type { PostgresPool } from '@qinglong/runtime-core';
|
||||
import {
|
||||
ApprovedActionDispatcher,
|
||||
type ApprovedActionDispatcherOptions,
|
||||
} from '@qinglong/runtime-core/approved-action-dispatcher';
|
||||
import { PluginPackageApprovedActionHandler } from '@qinglong/runtime-core/plugin-package-approved-action';
|
||||
import { PostgresApprovedActionExecutionRepository } from '@qinglong/cluster-postgres/approved-action-execution';
|
||||
import { PostgresPluginPackageInstallRepository } from '@qinglong/cluster-postgres/plugin-package-install';
|
||||
import { PostgresPluginPackageInstallProposalRepository } from '@qinglong/cluster-postgres/plugin-package-proposal';
|
||||
import {
|
||||
PostgresPluginPackagePublisherRevocationProposalRepository,
|
||||
PostgresPluginPackagePublisherTrustTransitionProposalRepository,
|
||||
PostgresPluginPackagePublisherTrustTransitionRepository,
|
||||
} from '@qinglong/cluster-postgres/package-executor';
|
||||
|
||||
import {
|
||||
ClusterPluginPackagePublisherRevocationApprovedActionHandler,
|
||||
type ClusterPluginPackagePublisherRevocationExecutionPort,
|
||||
} from '../publisher/pluginPackagePublisherRevocationApprovedAction';
|
||||
import {
|
||||
ClusterPluginPackagePublisherTrustTransitionApprovedActionHandler,
|
||||
type ClusterPluginPackagePublisherTrustTransitionExecutionPort,
|
||||
} from '../publisher/pluginPackagePublisherTrustTransitionApprovedAction';
|
||||
|
||||
export const CLUSTER_PLUGIN_PACKAGE_DISPATCH_BATCH_LIMIT = 16;
|
||||
|
||||
export interface ClusterPluginPackageApprovedActionDispatcherOptions
|
||||
extends Omit<ApprovedActionDispatcherOptions, 'defaultBatchSize'> {
|
||||
readonly pool: PostgresPool;
|
||||
readonly defaultBatchSize?: number;
|
||||
readonly publisherRevocations?: ClusterPluginPackagePublisherRevocationExecutionPort;
|
||||
readonly publisherTrustTransitions?: ClusterPluginPackagePublisherTrustTransitionExecutionPort;
|
||||
}
|
||||
|
||||
export function createClusterPluginPackageApprovedActionDispatcher(
|
||||
options: ClusterPluginPackageApprovedActionDispatcherOptions,
|
||||
): ApprovedActionDispatcher {
|
||||
if (!options || typeof options !== 'object') {
|
||||
throw new TypeError('cluster Package Approved Action options are invalid');
|
||||
}
|
||||
const {
|
||||
pool,
|
||||
defaultBatchSize,
|
||||
publisherRevocations,
|
||||
publisherTrustTransitions,
|
||||
...dispatcherOptions
|
||||
} = options;
|
||||
const executions = new PostgresApprovedActionExecutionRepository(pool);
|
||||
const handler = new PluginPackageApprovedActionHandler(
|
||||
new PostgresPluginPackageInstallProposalRepository(pool),
|
||||
new PostgresPluginPackageInstallRepository(pool),
|
||||
);
|
||||
const handlers = [
|
||||
handler,
|
||||
...(['overlap_add', 'safe_retire'] as const).map(
|
||||
(mode) =>
|
||||
new ClusterPluginPackagePublisherTrustTransitionApprovedActionHandler(
|
||||
mode,
|
||||
new PostgresPluginPackagePublisherTrustTransitionProposalRepository(
|
||||
pool,
|
||||
),
|
||||
publisherTrustTransitions ??
|
||||
new PostgresPluginPackagePublisherTrustTransitionRepository(pool),
|
||||
),
|
||||
),
|
||||
...(publisherRevocations
|
||||
? [
|
||||
new ClusterPluginPackagePublisherRevocationApprovedActionHandler(
|
||||
new PostgresPluginPackagePublisherRevocationProposalRepository(
|
||||
pool,
|
||||
),
|
||||
publisherRevocations,
|
||||
),
|
||||
]
|
||||
: []),
|
||||
];
|
||||
return new ApprovedActionDispatcher(executions, handlers, {
|
||||
...dispatcherOptions,
|
||||
defaultBatchSize:
|
||||
defaultBatchSize ?? CLUSTER_PLUGIN_PACKAGE_DISPATCH_BATCH_LIMIT,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env node
|
||||
// Cluster Plugin Package executor boundary; keep the operational CLI explicit.
|
||||
|
||||
import { runClusterPluginPackageExecutorProcess } from './pluginPackageExecutorProcess';
|
||||
|
||||
const USAGE = 'Usage: ql3-plugin-package-execute';
|
||||
|
||||
function emit(value: Readonly<Record<string, unknown>>): void {
|
||||
process.stdout.write(`${JSON.stringify(value)}\n`);
|
||||
}
|
||||
|
||||
async function run(argv: readonly string[]): Promise<void> {
|
||||
if (argv.length === 1 && (argv[0] === '--help' || argv[0] === '-h')) {
|
||||
process.stdout.write(`${USAGE}\n`);
|
||||
return;
|
||||
}
|
||||
if (argv.length !== 0) {
|
||||
process.stderr.write(
|
||||
`${JSON.stringify({
|
||||
code: 'QL3_PLUGIN_PACKAGE_EXECUTOR_CLI_USAGE_INVALID',
|
||||
message: USAGE,
|
||||
})}\n`,
|
||||
);
|
||||
process.exitCode = 64;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await runClusterPluginPackageExecutorProcess({
|
||||
environment: process.env,
|
||||
});
|
||||
if (result.status === 'disabled') {
|
||||
emit({
|
||||
schemaVersion: 1,
|
||||
component: 'qinglong3-plugin-package-executor',
|
||||
event: 'executor_disabled',
|
||||
});
|
||||
return;
|
||||
}
|
||||
emit({
|
||||
schemaVersion: 1,
|
||||
component: 'qinglong3-plugin-package-executor',
|
||||
event: 'executor_completed',
|
||||
databaseContractVersion: result.database.contractVersion,
|
||||
databaseMigrationCount: result.database.migrationIds.length,
|
||||
batches: result.batches,
|
||||
});
|
||||
} catch (error) {
|
||||
const candidate = error as {
|
||||
readonly name?: unknown;
|
||||
readonly code?: unknown;
|
||||
};
|
||||
process.stderr.write(
|
||||
`${JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
component: 'qinglong3-plugin-package-executor',
|
||||
event: 'executor_failed',
|
||||
name:
|
||||
typeof candidate?.name === 'string'
|
||||
? candidate.name.slice(0, 128)
|
||||
: 'Error',
|
||||
...(typeof candidate?.code === 'string'
|
||||
? { code: candidate.code.slice(0, 128) }
|
||||
: {}),
|
||||
})}\n`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void run(process.argv.slice(2));
|
||||
+464
@@ -0,0 +1,464 @@
|
||||
// Cluster Plugin Package executor boundary; keep process composition explicit.
|
||||
import type {
|
||||
OpenPostgresDatabase,
|
||||
PostgresDatabaseResource,
|
||||
PostgresPool,
|
||||
} from '@qinglong/runtime-core';
|
||||
import type {
|
||||
ApprovedActionDispatchBatchSummary,
|
||||
ApprovedActionDispatcher,
|
||||
} from '@qinglong/runtime-core/approved-action-dispatcher';
|
||||
import {
|
||||
assertPostgresPackageExecutorSchemaReady,
|
||||
createPostgresDatabaseOpener,
|
||||
isPostgresTlsDnsServername,
|
||||
loadPostgresCertificateAuthorityFile,
|
||||
loadPostgresConnectionEnvironment,
|
||||
type PostgresConnectionOptions,
|
||||
type PostgresPoolOptions,
|
||||
type PostgresSchemaReadinessReport,
|
||||
} from '@qinglong/cluster-postgres/package-executor';
|
||||
|
||||
import {
|
||||
createClusterPluginPackageApprovedActionDispatcher,
|
||||
type ClusterPluginPackageApprovedActionDispatcherOptions,
|
||||
} from './pluginPackageApprovedAction';
|
||||
import {
|
||||
consumeClusterPluginPackagePublisherRevocationApprovals,
|
||||
type ClusterPluginPackagePublisherRevocationApprovalSummary,
|
||||
type ConsumeClusterPluginPackagePublisherRevocationApprovalsOptions,
|
||||
} from '../publisher/pluginPackagePublisherRevocationApprovalConsumer';
|
||||
import {
|
||||
consumeClusterPluginPackagePublisherTrustTransitionApprovals,
|
||||
type ClusterPluginPackagePublisherTrustTransitionApprovalSummary,
|
||||
type ConsumeClusterPluginPackagePublisherTrustTransitionApprovalsOptions,
|
||||
} from '../publisher/pluginPackagePublisherTrustTransitionApprovalConsumer';
|
||||
import {
|
||||
runClusterPluginPackagePublisherRevocation,
|
||||
} from '../publisher/pluginPackagePublisherRevocation';
|
||||
|
||||
export type ClusterPluginPackageExecutorProcessEnvironment = Readonly<
|
||||
Record<string, string | undefined>
|
||||
>;
|
||||
|
||||
export type ClusterPluginPackageExecutorProcessConfig =
|
||||
| Readonly<{ enabled: false }>
|
||||
| Readonly<{
|
||||
enabled: true;
|
||||
owner: string;
|
||||
approvalBatchSize: number;
|
||||
dispatchBatchSize: number;
|
||||
maxBatches: number;
|
||||
leaseDurationMs: number;
|
||||
revocationPageSize: number;
|
||||
revocationMaxPages: number;
|
||||
database: Readonly<{
|
||||
connection: PostgresConnectionOptions;
|
||||
pool: PostgresPoolOptions;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
export interface ClusterPluginPackageExecutorBatchResult {
|
||||
readonly approvals: Readonly<ClusterPluginPackagePublisherRevocationApprovalSummary>;
|
||||
readonly trustTransitionApprovals: Readonly<ClusterPluginPackagePublisherTrustTransitionApprovalSummary>;
|
||||
readonly dispatch: Readonly<ApprovedActionDispatchBatchSummary>;
|
||||
}
|
||||
|
||||
export type ClusterPluginPackageExecutorProcessResult =
|
||||
| Readonly<{ status: 'disabled' }>
|
||||
| Readonly<{
|
||||
status: 'completed';
|
||||
database: PostgresSchemaReadinessReport;
|
||||
batches: readonly Readonly<ClusterPluginPackageExecutorBatchResult>[];
|
||||
}>;
|
||||
|
||||
export interface RunClusterPluginPackageExecutorProcessOptions {
|
||||
readonly environment: ClusterPluginPackageExecutorProcessEnvironment;
|
||||
readonly openDatabase?: OpenPostgresDatabase;
|
||||
readonly consumeApprovals?: (
|
||||
options: ConsumeClusterPluginPackagePublisherRevocationApprovalsOptions,
|
||||
) => Promise<
|
||||
Readonly<ClusterPluginPackagePublisherRevocationApprovalSummary>
|
||||
>;
|
||||
readonly consumeTrustTransitionApprovals?: (
|
||||
options: ConsumeClusterPluginPackagePublisherTrustTransitionApprovalsOptions,
|
||||
) => Promise<
|
||||
Readonly<ClusterPluginPackagePublisherTrustTransitionApprovalSummary>
|
||||
>;
|
||||
readonly createDispatcher?: (
|
||||
options: ClusterPluginPackageApprovedActionDispatcherOptions,
|
||||
) => ApprovedActionDispatcher;
|
||||
readonly now?: () => number;
|
||||
}
|
||||
|
||||
export class ClusterPluginPackageExecutorProcessConfigError extends TypeError {
|
||||
readonly code = 'QL3_PLUGIN_PACKAGE_EXECUTOR_PROCESS_CONFIG_INVALID';
|
||||
|
||||
constructor(message: string) {
|
||||
super(
|
||||
`Plugin Package executor process configuration is invalid: ${message}`,
|
||||
);
|
||||
this.name = 'ClusterPluginPackageExecutorProcessConfigError';
|
||||
}
|
||||
}
|
||||
|
||||
const SAFE_OWNER = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
||||
|
||||
function enabledValue(
|
||||
environment: ClusterPluginPackageExecutorProcessEnvironment,
|
||||
): boolean {
|
||||
const value = environment.QL3_PLUGIN_PACKAGE_EXECUTOR_ENABLED;
|
||||
if (value === undefined || value === '' || value === 'false') return false;
|
||||
if (value === 'true') return true;
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
'QL3_PLUGIN_PACKAGE_EXECUTOR_ENABLED must be true or false',
|
||||
);
|
||||
}
|
||||
|
||||
function boundedValue(
|
||||
environment: ClusterPluginPackageExecutorProcessEnvironment,
|
||||
name: string,
|
||||
maximumLength: number,
|
||||
): string | undefined {
|
||||
const value = environment[name];
|
||||
if (value === undefined || value === '') return undefined;
|
||||
if (value.length > maximumLength || /[\0\r\n]/.test(value)) {
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
`${name} is invalid`,
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function integerValue(
|
||||
environment: ClusterPluginPackageExecutorProcessEnvironment,
|
||||
name: string,
|
||||
defaultValue: number,
|
||||
minimum: number,
|
||||
maximum: number,
|
||||
): number {
|
||||
const value = environment[name];
|
||||
if (value === undefined || value === '') return defaultValue;
|
||||
if (!/^\d+$/.test(value)) {
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
`${name} must be an integer`,
|
||||
);
|
||||
}
|
||||
const parsed = Number(value);
|
||||
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
`${name} must be between ${minimum} and ${maximum}`,
|
||||
);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function databaseConfig(
|
||||
environment: ClusterPluginPackageExecutorProcessEnvironment,
|
||||
): Readonly<{
|
||||
connection: PostgresConnectionOptions;
|
||||
pool: PostgresPoolOptions;
|
||||
}> {
|
||||
let connection: PostgresConnectionOptions;
|
||||
try {
|
||||
connection = loadPostgresConnectionEnvironment(environment, {
|
||||
connectionString: 'QL3_POSTGRES_PACKAGE_EXECUTOR_URL',
|
||||
host: 'QL3_POSTGRES_PACKAGE_EXECUTOR_HOST',
|
||||
port: 'QL3_POSTGRES_PACKAGE_EXECUTOR_PORT',
|
||||
database: 'QL3_POSTGRES_PACKAGE_EXECUTOR_DATABASE',
|
||||
user: 'QL3_POSTGRES_PACKAGE_EXECUTOR_USER',
|
||||
password: 'QL3_POSTGRES_PACKAGE_EXECUTOR_PASSWORD',
|
||||
});
|
||||
} catch (error) {
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'PostgreSQL Package executor connection is invalid',
|
||||
);
|
||||
}
|
||||
const tlsMode = environment.QL3_POSTGRES_TLS_MODE ?? 'verify-full';
|
||||
let tls: PostgresConnectionOptions['tls'];
|
||||
if (tlsMode === 'disable') {
|
||||
if (environment.QL3_POSTGRES_ALLOW_INSECURE !== 'true') {
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
'disabling PostgreSQL TLS requires QL3_POSTGRES_ALLOW_INSECURE=true',
|
||||
);
|
||||
}
|
||||
tls = Object.freeze({ mode: 'disable' });
|
||||
} else if (tlsMode === 'verify-full') {
|
||||
const servername = boundedValue(
|
||||
environment,
|
||||
'QL3_POSTGRES_TLS_SERVERNAME',
|
||||
253,
|
||||
);
|
||||
if (!isPostgresTlsDnsServername(servername)) {
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
'QL3_POSTGRES_TLS_SERVERNAME must be an explicit DNS name for verify-full',
|
||||
);
|
||||
}
|
||||
const caFile = boundedValue(
|
||||
environment,
|
||||
'QL3_POSTGRES_TLS_CA_FILE',
|
||||
4096,
|
||||
);
|
||||
let ca: string | undefined;
|
||||
if (caFile !== undefined) {
|
||||
try {
|
||||
ca = loadPostgresCertificateAuthorityFile(caFile);
|
||||
} catch {
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
'QL3_POSTGRES_TLS_CA_FILE must contain a bounded trusted CA bundle',
|
||||
);
|
||||
}
|
||||
}
|
||||
tls = Object.freeze({
|
||||
mode: 'verify-full',
|
||||
servername,
|
||||
...(ca === undefined ? {} : { ca }),
|
||||
});
|
||||
} else {
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
'QL3_POSTGRES_TLS_MODE must be verify-full or disable',
|
||||
);
|
||||
}
|
||||
const applicationName =
|
||||
boundedValue(environment, 'QL3_POSTGRES_APPLICATION_NAME', 63) ??
|
||||
'qinglong3-plugin-package-executor';
|
||||
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,62}$/.test(applicationName)) {
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
'QL3_POSTGRES_APPLICATION_NAME is invalid',
|
||||
);
|
||||
}
|
||||
return Object.freeze({
|
||||
connection: Object.freeze({ ...connection, tls }),
|
||||
pool: Object.freeze({
|
||||
applicationName,
|
||||
maxConnections: integerValue(
|
||||
environment,
|
||||
'QL3_POSTGRES_MAX_CONNECTIONS',
|
||||
2,
|
||||
1,
|
||||
4,
|
||||
),
|
||||
connectionTimeoutMs: integerValue(
|
||||
environment,
|
||||
'QL3_POSTGRES_CONNECTION_TIMEOUT_MS',
|
||||
5_000,
|
||||
100,
|
||||
60_000,
|
||||
),
|
||||
idleTimeoutMs: integerValue(
|
||||
environment,
|
||||
'QL3_POSTGRES_IDLE_TIMEOUT_MS',
|
||||
10_000,
|
||||
1_000,
|
||||
300_000,
|
||||
),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export function loadClusterPluginPackageExecutorProcessConfig(
|
||||
environment: ClusterPluginPackageExecutorProcessEnvironment,
|
||||
): ClusterPluginPackageExecutorProcessConfig {
|
||||
if (!environment || typeof environment !== 'object') {
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
'environment is required',
|
||||
);
|
||||
}
|
||||
if (!enabledValue(environment)) return Object.freeze({ enabled: false });
|
||||
const owner =
|
||||
boundedValue(environment, 'QL3_PLUGIN_PACKAGE_EXECUTOR_OWNER', 128) ??
|
||||
'cluster_package_executor_1';
|
||||
if (!SAFE_OWNER.test(owner)) {
|
||||
throw new ClusterPluginPackageExecutorProcessConfigError(
|
||||
'QL3_PLUGIN_PACKAGE_EXECUTOR_OWNER is invalid',
|
||||
);
|
||||
}
|
||||
return Object.freeze({
|
||||
enabled: true,
|
||||
owner,
|
||||
approvalBatchSize: integerValue(
|
||||
environment,
|
||||
'QL3_PLUGIN_PACKAGE_EXECUTOR_APPROVAL_BATCH_SIZE',
|
||||
8,
|
||||
1,
|
||||
64,
|
||||
),
|
||||
dispatchBatchSize: integerValue(
|
||||
environment,
|
||||
'QL3_PLUGIN_PACKAGE_EXECUTOR_DISPATCH_BATCH_SIZE',
|
||||
8,
|
||||
1,
|
||||
64,
|
||||
),
|
||||
maxBatches: integerValue(
|
||||
environment,
|
||||
'QL3_PLUGIN_PACKAGE_EXECUTOR_MAX_BATCHES',
|
||||
4,
|
||||
1,
|
||||
64,
|
||||
),
|
||||
leaseDurationMs: integerValue(
|
||||
environment,
|
||||
'QL3_PLUGIN_PACKAGE_EXECUTOR_LEASE_DURATION_MS',
|
||||
600_000,
|
||||
1,
|
||||
600_000,
|
||||
),
|
||||
revocationPageSize: integerValue(
|
||||
environment,
|
||||
'QL3_PLUGIN_PACKAGE_EXECUTOR_REVOCATION_PAGE_SIZE',
|
||||
16,
|
||||
1,
|
||||
128,
|
||||
),
|
||||
revocationMaxPages: integerValue(
|
||||
environment,
|
||||
'QL3_PLUGIN_PACKAGE_EXECUTOR_REVOCATION_MAX_PAGES',
|
||||
16,
|
||||
1,
|
||||
64,
|
||||
),
|
||||
database: databaseConfig(environment),
|
||||
});
|
||||
}
|
||||
|
||||
function borrowedDatabase(
|
||||
database: PostgresDatabaseResource,
|
||||
): OpenPostgresDatabase {
|
||||
return async () =>
|
||||
Object.freeze({
|
||||
pool: database.pool,
|
||||
close: async () => undefined,
|
||||
});
|
||||
}
|
||||
|
||||
function isIdleBatch(
|
||||
batch: Readonly<ClusterPluginPackageExecutorBatchResult>,
|
||||
): boolean {
|
||||
return (
|
||||
batch.approvals.scanned === 0 &&
|
||||
batch.trustTransitionApprovals.scanned === 0 &&
|
||||
batch.dispatch.scanned === 0
|
||||
);
|
||||
}
|
||||
|
||||
export async function runClusterPluginPackageExecutorProcess(
|
||||
options: RunClusterPluginPackageExecutorProcessOptions,
|
||||
): Promise<ClusterPluginPackageExecutorProcessResult> {
|
||||
if (
|
||||
!options ||
|
||||
typeof options !== 'object' ||
|
||||
Array.isArray(options) ||
|
||||
!options.environment ||
|
||||
(options.openDatabase !== undefined &&
|
||||
typeof options.openDatabase !== 'function') ||
|
||||
(options.consumeApprovals !== undefined &&
|
||||
typeof options.consumeApprovals !== 'function') ||
|
||||
(options.consumeTrustTransitionApprovals !== undefined &&
|
||||
typeof options.consumeTrustTransitionApprovals !== 'function') ||
|
||||
(options.createDispatcher !== undefined &&
|
||||
typeof options.createDispatcher !== 'function') ||
|
||||
(options.now !== undefined && typeof options.now !== 'function')
|
||||
) {
|
||||
throw new TypeError('Plugin Package executor process options are invalid');
|
||||
}
|
||||
const config = loadClusterPluginPackageExecutorProcessConfig(
|
||||
options.environment,
|
||||
);
|
||||
if (!config.enabled) return Object.freeze({ status: 'disabled' });
|
||||
const openDatabase =
|
||||
options.openDatabase ??
|
||||
createPostgresDatabaseOpener({
|
||||
role: 'package-executor',
|
||||
connection: config.database.connection,
|
||||
pool: config.database.pool,
|
||||
onPoolError: () => undefined,
|
||||
});
|
||||
const database = await openDatabase();
|
||||
let failure: unknown;
|
||||
try {
|
||||
const evidence = await assertPostgresPackageExecutorSchemaReady(
|
||||
database.pool,
|
||||
);
|
||||
const dispatcherFactory =
|
||||
options.createDispatcher ??
|
||||
createClusterPluginPackageApprovedActionDispatcher;
|
||||
const consumeApprovals =
|
||||
options.consumeApprovals ??
|
||||
consumeClusterPluginPackagePublisherRevocationApprovals;
|
||||
const consumeTrustTransitionApprovals =
|
||||
options.consumeTrustTransitionApprovals ??
|
||||
consumeClusterPluginPackagePublisherTrustTransitionApprovals;
|
||||
const dispatcher = dispatcherFactory({
|
||||
pool: database.pool,
|
||||
owner: config.owner,
|
||||
leaseDurationMs: config.leaseDurationMs,
|
||||
defaultBatchSize: config.dispatchBatchSize,
|
||||
...(options.now ? { clock: options.now } : {}),
|
||||
publisherRevocations: {
|
||||
async run(receipt) {
|
||||
const result = await runClusterPluginPackagePublisherRevocation({
|
||||
openDatabase: borrowedDatabase(database),
|
||||
receipt,
|
||||
// Durable proposal, dispatch, Project Policy fence and trust-head
|
||||
// generation are revalidated in the same SERIALIZABLE mutation.
|
||||
confirmAuthorization: () => undefined,
|
||||
pageSize: config.revocationPageSize,
|
||||
maxPages: config.revocationMaxPages,
|
||||
});
|
||||
return Object.freeze({
|
||||
safeToAdmit: result.safeToAdmit,
|
||||
receiptDigest: result.receiptDigest,
|
||||
impactDigest: result.impactDigest,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
const batches: Readonly<ClusterPluginPackageExecutorBatchResult>[] = [];
|
||||
for (let index = 0; index < config.maxBatches; index += 1) {
|
||||
const approvals = await consumeApprovals({
|
||||
pool: database.pool,
|
||||
limit: config.approvalBatchSize,
|
||||
...(options.now ? { now: options.now } : {}),
|
||||
});
|
||||
const trustTransitionApprovals =
|
||||
await consumeTrustTransitionApprovals({
|
||||
pool: database.pool,
|
||||
limit: config.approvalBatchSize,
|
||||
...(options.now ? { now: options.now } : {}),
|
||||
});
|
||||
const dispatch = await dispatcher.dispatchBatch({
|
||||
limit: config.dispatchBatchSize,
|
||||
});
|
||||
const batch = Object.freeze({
|
||||
approvals,
|
||||
trustTransitionApprovals,
|
||||
dispatch,
|
||||
});
|
||||
batches.push(batch);
|
||||
if (isIdleBatch(batch)) break;
|
||||
}
|
||||
return Object.freeze({
|
||||
status: 'completed',
|
||||
database: evidence,
|
||||
batches: Object.freeze([...batches]),
|
||||
});
|
||||
} catch (error) {
|
||||
failure = error;
|
||||
throw error;
|
||||
} finally {
|
||||
try {
|
||||
await database.close();
|
||||
} catch (closeError) {
|
||||
if (failure !== undefined) {
|
||||
throw new AggregateError(
|
||||
[failure, closeError],
|
||||
'Plugin Package executor process failed and PostgreSQL did not close',
|
||||
);
|
||||
}
|
||||
throw closeError;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user