feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env node
import type { ModelGatewayProfileAudit } from '@qinglong/ai/profile';
import {
loadProductionClusterAiConfig,
startProductionClusterAiControlApplication,
} from './application-runtime/aiProductionApplication';
import {
runProductionClusterControlProcess,
type ClusterControlProcessEvent,
type ClusterControlProcessSignal,
type ClusterControlProcessSignalSource,
} from './production-process/processApplication';
const USAGE = 'Usage: ql3-cluster-control-ai';
const nodeSignals: ClusterControlProcessSignalSource = Object.freeze({
subscribe(listener: (signal: ClusterControlProcessSignal) => void) {
const handlers = Object.freeze({
SIGINT: () => listener('SIGINT' as const),
SIGTERM: () => listener('SIGTERM' as const),
});
process.once('SIGINT', handlers.SIGINT);
process.once('SIGTERM', handlers.SIGTERM);
return () => {
process.off('SIGINT', handlers.SIGINT);
process.off('SIGTERM', handlers.SIGTERM);
};
},
});
function write(record: object): void {
process.stdout.write(`${JSON.stringify(record)}\n`);
}
function emit(record: ClusterControlProcessEvent): void {
write(record);
}
function audit(record: Readonly<ModelGatewayProfileAudit>): void {
write(
Object.freeze({
schemaVersion: 1,
component: 'qinglong3-cluster-ai',
level: record.state === 'failed' ? 'error' : 'info',
event: 'activation',
profile: record.profile,
state: record.state,
...(record.maxConcurrent === undefined
? {}
: { maxConcurrent: record.maxConcurrent }),
...(record.recoveryLimit === undefined
? {}
: { recoveryLimit: record.recoveryLimit }),
...(record.recovered === undefined
? {}
: { recovered: record.recovered }),
...(record.alreadyCompleted === undefined
? {}
: { alreadyCompleted: record.alreadyCompleted }),
}),
);
}
async function main(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_CLUSTER_AI_CLI_USAGE_INVALID',
message: USAGE,
})}\n`,
);
process.exitCode = 64;
return;
}
try {
const ai = loadProductionClusterAiConfig(process.env);
const stopResult = await runProductionClusterControlProcess({
environment: process.env,
signals: nodeSignals,
emit,
start: (control) =>
startProductionClusterAiControlApplication({ control, ai, audit }),
});
if (stopResult !== 'stopped') process.exitCode = 1;
} catch (error) {
const candidate = error as { readonly name?: unknown; readonly code?: unknown };
process.stderr.write(
`${JSON.stringify({
schemaVersion: 1,
component: 'qinglong3-cluster-ai',
level: 'error',
event: 'process_failed',
name:
typeof candidate?.name === 'string' ? candidate.name : 'Error',
...(typeof candidate?.code === 'string'
? { code: candidate.code }
: {}),
})}\n`,
);
process.exitCode = 1;
}
}
void main(process.argv.slice(2));
@@ -0,0 +1,365 @@
import type { ModelGatewayProfileAudit } from '@qinglong/ai/profile';
import { BoundModelProviderCredentialProvider } from '@qinglong/ai/provider-credential';
import { PostgresModelProviderCredentialReader } from '@qinglong/ai/postgres-model-provider-credential-storage';
import { loadProjectedModelGatewayProviderAuthority } from '@qinglong/ai/projected-model-gateway-authority';
import { createProjectedModelProviderSecretMaterialProvider } from '@qinglong/ai/projected-model-provider-secret-material';
import { createPluginPackagePromptOutputProjectedKeyring } from '@qinglong/ai/plugin-package-prompt-output-projected-keyring';
import {
bootstrapPostgresPluginPackagePromptApplication,
type BootstrapPostgresPluginPackagePromptApplicationResult,
} from '@qinglong/ai/postgres-plugin-package-prompt-application';
import { createPostgresDatabaseOpener } from '@qinglong/cluster-postgres/runtime';
import { PostgresProjectPolicyRepository } from '@qinglong/cluster-postgres/project-policy';
import type { ClusterControlStopResult } from '@qinglong/runtime-core';
import { ProjectPolicyEngine } from '@qinglong/runtime-core/project-policy';
import type { ClusterControlApplicationResult } from './application';
import type {
ClusterControlEnvironment,
EnabledClusterControlConfig,
} from '../production-process/config';
import {
startProductionClusterControlApplication,
type ProductionClusterControlApplicationOptions,
} from './productionApplication';
export interface EnabledProductionClusterAiConfig {
readonly enabled: true;
readonly providerAuthorityFile: string;
readonly secretRootDirectory: string;
readonly promptOutputKeyringRootDirectory?: string;
readonly maxConcurrent: number;
readonly recoveryLimit: number;
readonly databaseMaxConnections: number;
}
export interface ProductionClusterAiControlApplicationOptions {
readonly control: ProductionClusterControlApplicationOptions;
readonly ai: EnabledProductionClusterAiConfig;
readonly audit: (
record: Readonly<ModelGatewayProfileAudit>,
) => void | Promise<void>;
readonly startControl?: typeof startProductionClusterControlApplication;
readonly bootstrapPrompt?: typeof bootstrapPostgresPluginPackagePromptApplication;
}
export class ProductionClusterAiConfigError extends TypeError {
readonly code = 'QL3_CLUSTER_AI_CONFIG_INVALID';
constructor(message: string) {
super(`Cluster AI configuration is invalid: ${message}`);
this.name = 'ProductionClusterAiConfigError';
}
}
function booleanValue(
environment: ClusterControlEnvironment,
name: string,
defaultValue: boolean,
): boolean {
const value = environment[name];
if (value === undefined || value === '') return defaultValue;
if (value === 'true') return true;
if (value === 'false') return false;
throw new ProductionClusterAiConfigError(`${name} must be true or false`);
}
function boundedInteger(
environment: ClusterControlEnvironment,
name: string,
fallback: number,
minimum: number,
maximum: number,
): number {
const value = environment[name];
if (value === undefined || value === '') return fallback;
if (!/^\d+$/.test(value)) {
throw new ProductionClusterAiConfigError(`${name} must be an integer`);
}
const parsed = Number(value);
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
throw new ProductionClusterAiConfigError(
`${name} must be between ${minimum} and ${maximum}`,
);
}
return parsed;
}
function requiredPath(
environment: ClusterControlEnvironment,
name: string,
): string {
const value = environment[name];
if (
typeof value !== 'string' ||
value.length < 2 ||
value.length > 4096 ||
!value.startsWith('/') ||
/[\0\r\n]/.test(value)
) {
throw new ProductionClusterAiConfigError(`${name} is invalid`);
}
return value;
}
/** Parsed only by the explicit AI process entrypoint; the default CLI ignores it. */
export function loadProductionClusterAiConfig(
environment: ClusterControlEnvironment,
): EnabledProductionClusterAiConfig {
if (
!environment ||
typeof environment !== 'object' ||
Array.isArray(environment) ||
!booleanValue(environment, 'QL3_CLUSTER_AI_ENABLED', false)
) {
throw new ProductionClusterAiConfigError(
'QL3_CLUSTER_AI_ENABLED must be true',
);
}
const promptOutputEnabled = booleanValue(
environment,
'QL3_CLUSTER_AI_PROMPT_OUTPUT_ENABLED',
false,
);
return Object.freeze({
enabled: true,
providerAuthorityFile: requiredPath(
environment,
'QL3_CLUSTER_AI_PROVIDER_AUTHORITY_FILE',
),
secretRootDirectory: requiredPath(
environment,
'QL3_CLUSTER_AI_SECRET_ROOT',
),
...(promptOutputEnabled
? {
promptOutputKeyringRootDirectory: requiredPath(
environment,
'QL3_CLUSTER_AI_PROMPT_OUTPUT_KEYRING_ROOT',
),
}
: {}),
maxConcurrent: boundedInteger(
environment,
'QL3_CLUSTER_AI_MAX_CONCURRENT',
4,
1,
64,
),
recoveryLimit: boundedInteger(
environment,
'QL3_CLUSTER_AI_RECOVERY_LIMIT',
32,
1,
128,
),
databaseMaxConnections: boundedInteger(
environment,
'QL3_CLUSTER_AI_DATABASE_MAX_CONNECTIONS',
4,
1,
16,
),
});
}
function aiDatabaseOpener(
control: EnabledClusterControlConfig,
ai: EnabledProductionClusterAiConfig,
onUnavailable: (error: Error) => void,
) {
return createPostgresDatabaseOpener({
role: 'runtime',
connection: control.database.connection,
pool: {
...control.database.pool,
applicationName: 'qinglong-cluster-ai',
maxConnections: ai.databaseMaxConnections,
},
onPoolError: onUnavailable,
});
}
/**
* Explicit AI-enabled composition root. It keeps the normal control image and
* process AI-free while sharing the reviewed authentication/Policy pipeline
* and route registry when the separate AI image entrypoint is selected.
*/
export async function startProductionClusterAiControlApplication(
options: ProductionClusterAiControlApplicationOptions,
): Promise<ClusterControlApplicationResult> {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
typeof options.audit !== 'function'
) {
throw new TypeError('Production Cluster AI application options are invalid');
}
const startControl =
options.startControl ?? startProductionClusterControlApplication;
const bootstrapPrompt =
options.bootstrapPrompt ?? bootstrapPostgresPluginPackagePromptApplication;
if (typeof startControl !== 'function' || typeof bootstrapPrompt !== 'function') {
throw new TypeError('Production Cluster AI application factories are invalid');
}
const secretMaterial =
await createProjectedModelProviderSecretMaterialProvider({
rootDirectory: options.ai.secretRootDirectory,
});
const promptOutputKeys =
options.ai.promptOutputKeyringRootDirectory === undefined
? undefined
: await createPluginPackagePromptOutputProjectedKeyring({
rootDirectory: options.ai.promptOutputKeyringRootDirectory,
});
let aiDatabase:
| Awaited<ReturnType<ReturnType<typeof createPostgresDatabaseOpener>>>
| undefined;
let resolveAiUnavailable: ((error: Error) => void) | undefined;
let aiUnavailableError: Error | undefined;
const aiUnavailable = new Promise<Error>((resolve) => {
resolveAiUnavailable = resolve;
});
const onAiUnavailable = (error: Error): void => {
aiUnavailableError ??= error;
resolveAiUnavailable?.(aiUnavailableError);
resolveAiUnavailable = undefined;
};
let promptApplication:
| BootstrapPostgresPluginPackagePromptApplicationResult
| undefined;
let controlApplication: ClusterControlApplicationResult | undefined;
let stopPromise: Promise<ClusterControlStopResult> | undefined;
let promptOutputPolicy: ProjectPolicyEngine | undefined;
const promptOutputReadAuthorizer = Object.freeze({
async authorize(request: Readonly<{
principal: Parameters<ProjectPolicyEngine['authorize']>[0];
projectId: string;
}>) {
if (!aiDatabase) {
throw new Error('Cluster AI database is unavailable during output read');
}
promptOutputPolicy ??= new ProjectPolicyEngine(
new PostgresProjectPolicyRepository(aiDatabase.pool),
);
const decision = await promptOutputPolicy.authorize(
request.principal,
request.projectId,
'artifact.read',
);
return decision.effect === 'allow'
? Object.freeze({ effect: 'allow' as const })
: Object.freeze({
effect: decision.effect,
reasonCode: 'artifact_read_denied',
});
},
});
const stop = async (): Promise<ClusterControlStopResult> => {
stopPromise ??= (async () => {
const controlResult =
controlApplication?.status === 'active'
? await controlApplication.stop()
: 'stopped';
const promptResult = await promptApplication?.stop();
return controlResult === 'stopped' &&
(promptResult === undefined || promptResult === 'stopped')
? 'stopped'
: 'timed_out';
})();
return stopPromise;
};
try {
const openDatabase = aiDatabaseOpener(
options.control.config,
options.ai,
onAiUnavailable,
);
promptApplication = await bootstrapPrompt({
enabled: true,
async openDatabase() {
if (aiDatabase) {
throw new Error('Cluster AI database was opened more than once');
}
aiDatabase = await openDatabase();
return aiDatabase;
},
async loadProviders() {
if (!aiDatabase) {
throw new Error('Cluster AI database is unavailable during provider load');
}
const credentialStorage = new PostgresModelProviderCredentialReader(
aiDatabase.pool,
);
const credentials = new BoundModelProviderCredentialProvider({
bindings: credentialStorage,
secrets: secretMaterial,
audit: credentialStorage,
});
return loadProjectedModelGatewayProviderAuthority({
configFile: options.ai.providerAuthorityFile,
credentials,
});
},
audit: options.audit,
maxConcurrent: options.ai.maxConcurrent,
recoveryLimit: options.ai.recoveryLimit,
...(promptOutputKeys === undefined
? {}
: {
promptOutputKeys,
promptOutputRead: { authorizer: promptOutputReadAuthorizer },
}),
});
if (promptApplication.status !== 'active') {
throw new Error('Cluster AI Prompt application did not activate');
}
controlApplication = await startControl({
...options.control,
promptCatalog: {
capability: promptApplication.promptCatalog,
},
promptExecution: {
capability: promptApplication.promptExecutions,
},
promptExecutionInspection: {
capability: promptApplication.promptExecutionInspections,
},
...(promptApplication.promptOutputs === undefined
? {}
: {
promptOutputRead: {
capability: promptApplication.promptOutputs,
},
}),
...(promptApplication.promptExecutionOutputs === undefined
? {}
: {
promptExecutionOutputRead: {
capability: promptApplication.promptExecutionOutputs,
},
}),
});
if (controlApplication.status !== 'active') {
throw new Error('AI-enabled cluster-control did not activate');
}
const activeControl = controlApplication;
return Object.freeze({
status: 'active' as const,
address: activeControl.address,
evidence: activeControl.evidence,
recovery: activeControl.recovery,
unavailable: Promise.race([activeControl.unavailable, aiUnavailable]),
availabilityStatus() {
return aiUnavailableError
? 'unavailable'
: activeControl.availabilityStatus();
},
stop,
});
} catch (error) {
await stop().catch(() => undefined);
throw error;
}
}
@@ -0,0 +1,244 @@
import type {
ClusterControlActivationAudit,
ClusterControlActivationStack,
ClusterControlReadinessEvidence,
ClusterControlRuntimeActivationResult,
ClusterControlStartupRecoverySummary,
ClusterControlStopResult,
DeploymentProfile,
OpenPostgresDatabase,
} from '@qinglong/runtime-core';
import {
bootstrapClusterControlRuntime,
type ClusterControlAssemblyInput,
type ClusterControlRecoveryRuntimeOptions,
type ClusterRunCancellationConvergenceRuntimeOptions,
type ClusterSchedulerRuntimeOptions,
type ClusterWorkerRuntimeDependencies,
} from './clusterControlRuntime';
import { assertClusterControlApiCredentialPepper } from '../authentication/apiCredentialAuthenticator';
import {
startClusterControlHttpSurface,
type ClusterControlAdmissionPipeline,
type ClusterControlHttpAddress,
type ClusterControlHttpSurfaceOptions,
} from '../transport/httpSurface';
import type { ClusterControlAvailabilitySource } from '../database/availability';
export interface ClusterControlApplicationStack {
reconcile(): Promise<ClusterControlStartupRecoverySummary>;
startLifecycles(): Promise<boolean>;
admission: ClusterControlAdmissionPipeline;
stop(): Promise<ClusterControlStopResult>;
}
export interface ClusterControlApplicationOptions {
readonly enabled?: boolean;
readonly profile: DeploymentProfile;
readonly apiCredentialPepper?: string;
readonly recovery?: ClusterControlRecoveryRuntimeOptions;
readonly scheduler?: ClusterSchedulerRuntimeOptions;
readonly cancellationConvergence?: ClusterRunCancellationConvergenceRuntimeOptions;
readonly workerRuntime?: ClusterWorkerRuntimeDependencies;
readonly openDatabase: OpenPostgresDatabase;
readonly availability: ClusterControlAvailabilitySource;
readonly http: ClusterControlHttpSurfaceOptions;
readonly create: (
input: ClusterControlAssemblyInput,
) => ClusterControlApplicationStack;
readonly audit: (
record: ClusterControlActivationAudit,
) => void | Promise<void>;
}
export type ClusterControlApplicationResult =
| { readonly status: 'disabled'; stop(): Promise<'stopped'> }
| {
readonly status: 'active';
readonly address: ClusterControlHttpAddress;
readonly evidence: ClusterControlReadinessEvidence;
readonly recovery: ClusterControlStartupRecoverySummary;
readonly unavailable: Promise<Error>;
availabilityStatus(): 'ready' | 'unavailable' | 'stopped';
stop(): Promise<ClusterControlStopResult>;
};
export class ClusterControlDatabaseUnavailableError extends Error {
readonly code = 'CLUSTER_CONTROL_DATABASE_UNAVAILABLE';
constructor() {
super('Cluster-control database became unavailable');
this.name = 'ClusterControlDatabaseUnavailableError';
}
}
function inactiveBootstrap(
options: ClusterControlApplicationOptions,
): Promise<ClusterControlRuntimeActivationResult> {
return bootstrapClusterControlRuntime({
...(options.enabled === undefined ? {} : { enabled: options.enabled }),
profile: options.profile,
...(options.apiCredentialPepper === undefined
? {}
: { apiCredentialPepper: options.apiCredentialPepper }),
...(options.recovery === undefined ? {} : { recovery: options.recovery }),
...(options.scheduler === undefined
? {}
: { scheduler: options.scheduler }),
...(options.cancellationConvergence === undefined
? {}
: { cancellationConvergence: options.cancellationConvergence }),
...(options.workerRuntime === undefined
? {}
: { workerRuntime: options.workerRuntime }),
openDatabase: options.openDatabase,
create() {
throw new Error('Inactive cluster-control unexpectedly created a stack');
},
audit: options.audit,
});
}
/**
* Starts the cluster probe surface before database readiness, then installs the
* /api/v3 admission handler only after recovery and lifecycles are safe. Stop
* withdraws and drains admission before stack, Pool and listener shutdown.
*/
export async function startClusterControlApplication(
options: ClusterControlApplicationOptions,
): Promise<ClusterControlApplicationResult> {
const enabled = options.enabled ?? false;
if (!enabled || options.profile !== 'cluster-control') {
const inactive = await inactiveBootstrap(options);
if (inactive.status !== 'disabled') {
throw new Error('Inactive cluster-control unexpectedly became active');
}
return inactive;
}
assertClusterControlApiCredentialPepper(options.apiCredentialPepper ?? '');
const apiCredentialPepper = options.apiCredentialPepper!;
if (
!options.availability ||
typeof options.availability.subscribe !== 'function'
) {
throw new TypeError('Cluster-control availability source is invalid');
}
const http = await startClusterControlHttpSurface(options.http);
let activation: ClusterControlRuntimeActivationResult | undefined;
let unavailableError: Error | undefined;
let unavailableStopPromise: Promise<ClusterControlStopResult> | undefined;
let availabilityStatus: 'ready' | 'unavailable' | 'stopped' = 'ready';
let unsubscribeAvailability: (() => void) | undefined;
let resolveUnavailable: ((error: Error) => void) | undefined;
const unavailable = new Promise<Error>((resolve) => {
resolveUnavailable = resolve;
});
const withdrawForUnavailable = (error: Error): Promise<void> => {
unavailableError ??= error;
availabilityStatus = 'unavailable';
resolveUnavailable?.(unavailableError);
resolveUnavailable = undefined;
if (!activation || activation.status === 'disabled')
return Promise.resolve();
unavailableStopPromise ??= activation.stop();
return unavailableStopPromise.then(
() => undefined,
() => undefined,
);
};
try {
unsubscribeAvailability = options.availability.subscribe(
withdrawForUnavailable,
);
activation = await bootstrapClusterControlRuntime({
enabled: true,
profile: options.profile,
apiCredentialPepper,
...(options.recovery === undefined ? {} : { recovery: options.recovery }),
...(options.scheduler === undefined
? {}
: { scheduler: options.scheduler }),
...(options.cancellationConvergence === undefined
? {}
: { cancellationConvergence: options.cancellationConvergence }),
...(options.workerRuntime === undefined
? {}
: { workerRuntime: options.workerRuntime }),
openDatabase: options.openDatabase,
create(input): ClusterControlActivationStack {
const application = options.create(input);
if (
!application ||
typeof application !== 'object' ||
!application.admission ||
typeof application.admission.prepare !== 'function'
) {
throw new TypeError(
'Cluster-control application stack has no admission pipeline',
);
}
return {
reconcile: () => application.reconcile(),
startLifecycles: () => application.startLifecycles(),
installAdmission: () =>
http.installAdmission(input.evidence, application.admission),
stop: () => application.stop(),
};
},
audit: options.audit,
});
if (activation.status === 'disabled') {
unsubscribeAvailability();
await http.close();
return activation;
}
if (unavailableError) {
await withdrawForUnavailable(unavailableError);
throw new ClusterControlDatabaseUnavailableError();
}
const activeActivation = activation;
let stopPromise: Promise<ClusterControlStopResult> | undefined;
return {
status: 'active',
address: http.address,
evidence: activeActivation.evidence,
recovery: activeActivation.recovery,
unavailable,
availabilityStatus: () => availabilityStatus,
stop() {
if (stopPromise) return stopPromise;
availabilityStatus = 'stopped';
unsubscribeAvailability?.();
unsubscribeAvailability = undefined;
stopPromise = (async () => {
let result: ClusterControlStopResult | undefined;
let primaryError: unknown;
try {
result = await activeActivation.stop();
} catch (error) {
primaryError = error;
}
try {
await http.close();
} catch (error) {
primaryError ??= error;
}
if (primaryError) throw primaryError;
return result!;
})();
return stopPromise;
},
};
} catch (error) {
unsubscribeAvailability?.();
try {
await http.close();
} catch {
// Preserve the readiness/assembly/activation failure.
}
throw error;
}
}
@@ -0,0 +1,842 @@
import { randomUUID } from 'node:crypto';
import {
activateClusterControlRuntime,
ClusterRunLostRetryCoordinator,
ClusterRunCancellationConvergenceCoordinator,
ClusterControlRecoveryEvidenceRegistry,
ClusterControlRecoveryConvergenceVerifier,
ClusterControlRecoverySupervisor,
ClusterControlStartupRecoveryCoordinator,
EvidenceBasedClusterControlRecoveryProcessor,
MAX_CLUSTER_CONTROL_RECOVERY_CLAIMS_PER_PASS,
MAX_CLUSTER_CONTROL_RECOVERY_CLAIM_LEASE_MS,
MAX_CLUSTER_CONTROL_RECOVERY_EVIDENCE_TIMEOUT_MS,
MAX_CLUSTER_CONTROL_RECOVERY_RETRY_DELAY_MS,
MAX_CLUSTER_CONTROL_STARTUP_RECOVERY_PASSES,
MAX_CLUSTER_RUN_CANCELLATION_CONVERGENCE_PAGE_SIZE,
MAX_CLUSTER_RUN_CANCELLATION_CONVERGENCE_PAGES_PER_CYCLE,
type ClusterControlActivationAudit,
type ClusterControlActivationStack,
type ClusterControlReadinessEvidence,
type ClusterControlRecoveryExecutorEvidenceProvider,
type ClusterControlRuntimeActivationResult,
type ClusterControlStopResult,
type DeploymentProfile,
type OpenPostgresDatabase,
type PostgresDatabaseResource,
type PostgresPool,
type ProjectPolicyRepository,
type RunRepository,
type ClusterRunCancellationConvergenceCycleResult,
} from '@qinglong/runtime-core';
import type { ClusterRunCancellationRepository } from '@qinglong/runtime-core/cluster-run-cancellation';
import type { ProjectRunListReader } from '@qinglong/runtime-core/project-run-list';
import type { ClusterScheduleStore } from '@qinglong/runtime-core/cluster-scheduler';
import type { TaskDefinitionSource } from '@qinglong/runtime-core/task-definition';
import type { TriggerSource } from '@qinglong/runtime-core/trigger';
import type { ClusterTaskExecutionRevisionSource } from '@qinglong/runtime-core/cluster-execution-revision';
import type {
ProjectToolDefinitionSnapshotRepository,
ProjectToolDefinitionSnapshotSourceRepository,
} from '@qinglong/runtime-core/project-tool-definition-snapshot';
import type { StepRunRepository } from '@qinglong/runtime-core/step-run';
import type { ToolExecutionCompletionRepository } from '@qinglong/runtime-core/tool-execution-completion';
import type { ToolExecutionFailureCompletionRepository } from '@qinglong/runtime-core/tool-execution-failure-completion';
import type { ToolExecutionStartBarrierRepository } from '@qinglong/runtime-core/tool-execution-start-barrier';
import type { ToolInvocationArtifactRepository } from '@qinglong/runtime-core/tool-invocation-artifact';
import type { ToolResultKeyCatalogReader } from '@qinglong/runtime-core/tool-result-key-catalog';
import type { ToolExecutionResultRekeyReader } from '@qinglong/runtime-core/tool-result-rekey';
import {
assertPostgresSchemaReady,
PostgresClusterControlRecoverySource,
PostgresClusterControlRecoveryClaimRepository,
PostgresClusterControlRecoveryResolutionRepository,
PostgresClusterRuntimeRecoverySource,
PostgresClusterRunLostRetryRepository,
PostgresClusterRunCancellationRepository,
PostgresClusterRunCancellationConvergenceRepository,
PostgresProjectPolicyRepository,
PostgresApiCredentialRepository,
PostgresSecurityAuditRepository,
PostgresRunRepository,
PostgresWorkerExecutionAttestationRepository,
PostgresTaskDefinitionSource,
PostgresTaskExecutionRevisionSource,
PostgresTriggerSource,
PostgresClusterScheduleRepository,
PostgresRemoteWorkerAttestationEvidenceProvider,
PostgresProjectToolDefinitionSnapshotRepository,
PostgresStepRunRepository,
PostgresToolExecutionCompletionRepository,
PostgresToolExecutionFailureCompletionRepository,
PostgresToolExecutionStartBarrierRepository,
PostgresToolInvocationArtifactRepository,
PostgresToolResultKeyCatalogReader,
PostgresToolResultRekeyReader,
type PostgresSchemaReadinessReport,
} from '@qinglong/cluster-postgres/runtime';
import { PostgresPluginPackageWorkflowFrontierRepository } from '@qinglong/cluster-postgres/plugin-package-workflow-frontier';
import { PostgresPluginPackageWorkflowTaskAttemptAdmissionRepository } from '@qinglong/cluster-postgres/plugin-package-workflow-task-attempt-admission';
import { PostgresTaskStartRepository } from '@qinglong/cluster-postgres/task-start';
import { PostgresPluginPackageAutomationPublicationRepository } from '@qinglong/cluster-postgres/plugin-package-automation-publication';
import { PostgresPluginPackageMaterializedRevisionRepository } from '@qinglong/cluster-postgres/plugin-package-materialized-revision';
import {
PostgresAuthorizedPluginPackageWorkflowAdmissionRepository,
PostgresAuthorizedPluginPackageWorkflowRunEventListRepository,
PostgresAuthorizedPluginPackageWorkflowRunInspectionRepository,
PostgresAuthorizedPluginPackageWorkflowRunListRepository,
PostgresAuthorizedPluginPackageWorkflowStepRunListRepository,
} from '@qinglong/cluster-postgres/plugin-package-workflow-administration';
import {
assertClusterControlApiCredentialPepper,
createClusterControlApiCredentialAuthenticator,
} from '../authentication/apiCredentialAuthenticator';
import type {
ClusterControlRequestAuthenticator,
ClusterControlSecurityAuditSink,
} from '../transport/admissionPipeline';
import {
MAX_CLUSTER_SCHEDULE_CLAIMS_PER_CYCLE,
ClusterSchedulerCoordinator,
ClusterSchedulerLifecycle,
type ClusterSchedulerCycleSummary,
} from '../scheduling/scheduler';
import { ClusterWorkflowSchedulerCoordinator } from '../scheduling/workflowScheduler';
import { ClusterRuntimeSchedulerCoordinator } from '../scheduling/runtimeScheduler';
import { ClusterRunCancellationConvergenceLifecycle } from '../run/runCancellationLifecycle';
import type { TaskStartRepository } from '@qinglong/runtime-core/task-start';
import {
createClusterWorkerRuntimePort,
type ClusterWorkerRuntimeDependencies,
type ClusterWorkerRuntimePort,
} from '../remote-execution/workerRuntimePort';
import {
createClusterPluginPackageWorkflowAdministrationCapability,
type ClusterPluginPackageWorkflowAdministrationCapability,
} from '../plugin-package/workflow/pluginPackageWorkflowAdministration';
export interface ClusterTrustedToolStorage {
readonly invocationArtifacts: ToolInvocationArtifactRepository;
readonly stepRuns: StepRunRepository;
readonly startBarriers: ToolExecutionStartBarrierRepository;
readonly completions: ToolExecutionCompletionRepository;
readonly failureCompletions: ToolExecutionFailureCompletionRepository;
readonly resultKeyCatalog: ToolResultKeyCatalogReader;
readonly resultRekeys: ToolExecutionResultRekeyReader;
readonly toolDefinitionSnapshots: ProjectToolDefinitionSnapshotRepository &
ProjectToolDefinitionSnapshotSourceRepository;
}
export interface ClusterControlAssemblyInput {
readonly evidence: ClusterControlReadinessEvidence;
readonly policies: ProjectPolicyRepository;
readonly runs: RunRepository & ProjectRunListReader;
readonly runCancellation: ClusterRunCancellationRepository;
readonly taskStart: TaskStartRepository;
readonly taskDefinitions: TaskDefinitionSource;
readonly taskExecutionRevisions: ClusterTaskExecutionRevisionSource;
readonly triggers: TriggerSource;
readonly schedules: ClusterScheduleStore;
readonly trustedToolStorage: ClusterTrustedToolStorage;
readonly authenticator: ClusterControlRequestAuthenticator;
readonly securityAudit: ClusterControlSecurityAuditSink;
readonly workflowAdministration: ClusterPluginPackageWorkflowAdministrationCapability;
readonly workerRuntime?: ClusterWorkerRuntimePort;
}
export interface ClusterControlRecoveryRuntimeOptions {
readonly ownerId: string;
readonly providers?: readonly ClusterControlRecoveryExecutorEvidenceProvider[];
readonly claimLimit?: number;
readonly claimLeaseMs?: number;
readonly retryDelayMs?: number;
readonly providerTimeoutMs?: number;
readonly maxStartupPasses?: number;
}
export interface ClusterSchedulerRuntimeOptions {
readonly ownerId?: string;
readonly claimLeaseMs?: number;
readonly maxClaimsPerCycle?: number;
readonly misfireGraceMs?: number;
readonly intervalMs?: number;
readonly stopTimeoutMs?: number;
readonly onDiagnostic?: (
error: unknown,
summary?: ClusterSchedulerCycleSummary,
) => void | Promise<void>;
}
export interface ClusterRunCancellationConvergenceRuntimeOptions {
readonly pageSize?: number;
readonly maxPages?: number;
readonly intervalMs?: number;
readonly stopTimeoutMs?: number;
readonly onDiagnostic?: (
error: unknown,
summary?: Readonly<ClusterRunCancellationConvergenceCycleResult>,
) => void | Promise<void>;
}
export interface ClusterControlBootstrapOptions {
readonly enabled?: boolean;
readonly profile: DeploymentProfile;
readonly apiCredentialPepper?: string;
readonly recovery?: ClusterControlRecoveryRuntimeOptions;
readonly scheduler?: ClusterSchedulerRuntimeOptions;
readonly cancellationConvergence?: ClusterRunCancellationConvergenceRuntimeOptions;
readonly workerRuntime?: ClusterWorkerRuntimeDependencies;
readonly openDatabase: OpenPostgresDatabase;
readonly create: (
input: ClusterControlAssemblyInput,
) => ClusterControlActivationStack;
readonly audit: (
record: ClusterControlActivationAudit,
) => void | Promise<void>;
}
interface PreparedRecoveryRuntime {
readonly providers: readonly ClusterControlRecoveryExecutorEvidenceProvider[];
readonly providerTimeoutMs: number;
readonly ownerId: string;
readonly claimLimit: number;
readonly claimLeaseMs: number;
readonly retryDelayMs: number;
readonly maxStartupPasses: number;
}
interface PreparedSchedulerRuntime {
readonly ownerId: string;
readonly claimLeaseMs: number;
readonly maxClaimsPerCycle: number;
readonly misfireGraceMs: number;
readonly intervalMs: number;
readonly stopTimeoutMs: number;
readonly onDiagnostic?: ClusterSchedulerRuntimeOptions['onDiagnostic'];
}
interface PreparedCancellationConvergenceRuntime {
readonly pageSize: number;
readonly maxPages: number;
readonly intervalMs: number;
readonly stopTimeoutMs: number;
readonly onDiagnostic?: ClusterRunCancellationConvergenceRuntimeOptions['onDiagnostic'];
}
function boundedInteger(
name: string,
value: number | undefined,
fallback: number,
minimum: number,
maximum: number,
): number {
const normalized = value ?? fallback;
if (
!Number.isSafeInteger(normalized) ||
normalized < minimum ||
normalized > maximum
) {
throw new RangeError(`${name} must be between ${minimum} and ${maximum}`);
}
return normalized;
}
function prepareRecoveryRuntime(
options: ClusterControlRecoveryRuntimeOptions | undefined,
): PreparedRecoveryRuntime {
if (!options) {
throw new TypeError(
'Enabled cluster-control requires bounded recovery configuration',
);
}
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(options.ownerId)) {
throw new TypeError('Cluster-control recovery ownerId is invalid');
}
const claimLeaseMs = boundedInteger(
'Cluster-control recovery claim lease',
options.claimLeaseMs,
30_000,
1_000,
MAX_CLUSTER_CONTROL_RECOVERY_CLAIM_LEASE_MS,
);
const providerTimeoutMs = boundedInteger(
'Cluster-control recovery evidence timeout',
options.providerTimeoutMs,
5_000,
1,
MAX_CLUSTER_CONTROL_RECOVERY_EVIDENCE_TIMEOUT_MS,
);
if (providerTimeoutMs + 250 > claimLeaseMs) {
throw new RangeError(
'Cluster-control recovery evidence timeout must leave at least 250ms for fenced settlement',
);
}
return Object.freeze({
providers: Object.freeze([...(options.providers ?? [])]),
providerTimeoutMs,
ownerId: options.ownerId,
claimLimit: boundedInteger(
'Cluster-control recovery claim limit',
options.claimLimit,
16,
1,
MAX_CLUSTER_CONTROL_RECOVERY_CLAIMS_PER_PASS,
),
claimLeaseMs,
retryDelayMs: boundedInteger(
'Cluster-control recovery retry delay',
options.retryDelayMs,
5_000,
0,
MAX_CLUSTER_CONTROL_RECOVERY_RETRY_DELAY_MS,
),
maxStartupPasses: boundedInteger(
'Cluster-control startup recovery passes',
options.maxStartupPasses,
8,
1,
MAX_CLUSTER_CONTROL_STARTUP_RECOVERY_PASSES,
),
});
}
function prepareSchedulerRuntime(
options: ClusterSchedulerRuntimeOptions | undefined,
fallbackOwnerId: string,
): PreparedSchedulerRuntime {
const allowedKeys = new Set([
'claimLeaseMs',
'intervalMs',
'maxClaimsPerCycle',
'misfireGraceMs',
'onDiagnostic',
'ownerId',
'stopTimeoutMs',
]);
if (
options !== undefined &&
(!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some((key) => !allowedKeys.has(key)))
) {
throw new TypeError('Cluster scheduler configuration is invalid');
}
const ownerId = options?.ownerId ?? fallbackOwnerId;
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(ownerId)) {
throw new TypeError('Cluster scheduler ownerId is invalid');
}
if (
options?.onDiagnostic !== undefined &&
typeof options.onDiagnostic !== 'function'
) {
throw new TypeError('Cluster scheduler diagnostic sink is invalid');
}
return Object.freeze({
ownerId,
claimLeaseMs: boundedInteger(
'Cluster scheduler claim lease',
options?.claimLeaseMs,
30_000,
1_000,
60_000,
),
maxClaimsPerCycle: boundedInteger(
'Cluster scheduler claim budget',
options?.maxClaimsPerCycle,
16,
1,
MAX_CLUSTER_SCHEDULE_CLAIMS_PER_CYCLE,
),
misfireGraceMs: boundedInteger(
'Cluster scheduler misfire grace',
options?.misfireGraceMs,
30_000,
0,
5 * 60_000,
),
intervalMs: boundedInteger(
'Cluster scheduler interval',
options?.intervalMs,
1_000,
250,
60 * 60_000,
),
stopTimeoutMs: boundedInteger(
'Cluster scheduler stop timeout',
options?.stopTimeoutMs,
10_000,
100,
30_000,
),
...(options?.onDiagnostic === undefined
? {}
: { onDiagnostic: options.onDiagnostic }),
});
}
function prepareCancellationConvergenceRuntime(
options: ClusterRunCancellationConvergenceRuntimeOptions | undefined,
): PreparedCancellationConvergenceRuntime {
const allowedKeys = new Set([
'intervalMs',
'maxPages',
'onDiagnostic',
'pageSize',
'stopTimeoutMs',
]);
if (
options !== undefined &&
(!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some((key) => !allowedKeys.has(key)))
) {
throw new TypeError(
'Cluster Run cancellation convergence configuration is invalid',
);
}
if (
options?.onDiagnostic !== undefined &&
typeof options.onDiagnostic !== 'function'
) {
throw new TypeError(
'Cluster Run cancellation convergence diagnostic sink is invalid',
);
}
return Object.freeze({
pageSize: boundedInteger(
'Cluster Run cancellation convergence page size',
options?.pageSize,
32,
1,
MAX_CLUSTER_RUN_CANCELLATION_CONVERGENCE_PAGE_SIZE,
),
maxPages: boundedInteger(
'Cluster Run cancellation convergence page limit',
options?.maxPages,
4,
1,
MAX_CLUSTER_RUN_CANCELLATION_CONVERGENCE_PAGES_PER_CYCLE,
),
intervalMs: boundedInteger(
'Cluster Run cancellation convergence interval',
options?.intervalMs,
1_000,
250,
60 * 60_000,
),
stopTimeoutMs: boundedInteger(
'Cluster Run cancellation convergence stop timeout',
options?.stopTimeoutMs,
10_000,
100,
30_000,
),
...(options?.onDiagnostic === undefined
? {}
: { onDiagnostic: options.onDiagnostic }),
});
}
function readinessEvidence(
report: PostgresSchemaReadinessReport,
): ClusterControlReadinessEvidence {
return Object.freeze({
contractName: report.contractName,
contractVersion: report.contractVersion,
serverMajor: report.serverMajor,
migrationIds: Object.freeze([...report.migrationIds]),
});
}
/**
* Owns the cluster database around the readiness-first activation gate.
* Repository and service construction happens through create() only after the
* runtime role, migration history and catalog contract are proven ready.
*/
export async function bootstrapClusterControlRuntime(
options: ClusterControlBootstrapOptions,
): Promise<ClusterControlRuntimeActivationResult> {
let recoveryRuntime: PreparedRecoveryRuntime | undefined;
let schedulerRuntime: PreparedSchedulerRuntime | undefined;
let cancellationConvergenceRuntime:
| PreparedCancellationConvergenceRuntime
| undefined;
let recoveryRegistry: ClusterControlRecoveryEvidenceRegistry | undefined;
if ((options.enabled ?? false) && options.profile === 'cluster-control') {
assertClusterControlApiCredentialPepper(options.apiCredentialPepper ?? '');
recoveryRuntime = prepareRecoveryRuntime(options.recovery);
schedulerRuntime = prepareSchedulerRuntime(
options.scheduler,
recoveryRuntime.ownerId,
);
cancellationConvergenceRuntime = prepareCancellationConvergenceRuntime(
options.cancellationConvergence,
);
}
let database: PostgresDatabaseResource | undefined;
let closePromise: Promise<void> | undefined;
const closeDatabase = (): Promise<void> => {
if (!database) return Promise.resolve();
closePromise ??= Promise.resolve().then(() => database!.close());
return closePromise;
};
try {
const activation = await activateClusterControlRuntime({
...(options.enabled === undefined ? {} : { enabled: options.enabled }),
profile: options.profile,
readiness: {
async assertReady() {
if (database) {
throw new Error(
'Cluster-control database was opened more than once',
);
}
database = await options.openDatabase();
return readinessEvidence(
await assertPostgresSchemaReady(database.pool),
);
},
},
create(evidence) {
if (!database) {
throw new Error(
'Cluster-control database is unavailable after readiness',
);
}
const recovery = new PostgresClusterControlRecoverySource(
database.pool,
);
const recoveryClaims =
new PostgresClusterControlRecoveryClaimRepository(database.pool);
const recoveryTransitions =
new PostgresClusterControlRecoveryResolutionRepository(database.pool);
if (!recoveryRuntime) {
throw new Error(
'Cluster-control recovery runtime is unavailable after readiness',
);
}
if (!schedulerRuntime) {
throw new Error(
'Cluster scheduler runtime is unavailable after readiness',
);
}
if (!cancellationConvergenceRuntime) {
throw new Error(
'Cluster Run cancellation convergence runtime is unavailable after readiness',
);
}
const remoteAttestations =
new PostgresWorkerExecutionAttestationRepository(database.pool);
recoveryRegistry = new ClusterControlRecoveryEvidenceRegistry(
[
...recoveryRuntime.providers,
new PostgresRemoteWorkerAttestationEvidenceProvider(
database.pool,
remoteAttestations,
),
],
{ timeoutMs: recoveryRuntime.providerTimeoutMs },
);
const recoveryProcessor =
new EvidenceBasedClusterControlRecoveryProcessor(
recoveryTransitions,
recoveryRegistry,
{ retryDelayMs: recoveryRuntime.retryDelayMs },
);
const recoverySupervisor = new ClusterControlRecoverySupervisor(
recoveryClaims,
recoveryProcessor,
{
ownerId: recoveryRuntime.ownerId,
limit: recoveryRuntime.claimLimit,
leaseMs: recoveryRuntime.claimLeaseMs,
retryDelayMs: recoveryRuntime.retryDelayMs,
},
);
const recoveryCoordinator =
new ClusterControlStartupRecoveryCoordinator(recoverySupervisor, {
maxPasses: recoveryRuntime.maxStartupPasses,
});
const runtimeRecoverySupervisor = new ClusterControlRecoverySupervisor(
new PostgresClusterControlRecoveryClaimRepository(
database.pool,
randomUUID,
(queryable) => new PostgresClusterRuntimeRecoverySource(queryable),
),
recoveryProcessor,
{
ownerId: recoveryRuntime.ownerId,
limit: recoveryRuntime.claimLimit,
leaseMs: recoveryRuntime.claimLeaseMs,
retryDelayMs: recoveryRuntime.retryDelayMs,
},
);
const schedules = new PostgresClusterScheduleRepository(database.pool);
const runs = new PostgresRunRepository(database.pool);
const trustedToolStorage: ClusterTrustedToolStorage = Object.freeze({
invocationArtifacts: new PostgresToolInvocationArtifactRepository(
database.pool,
),
stepRuns: new PostgresStepRunRepository(database.pool),
startBarriers: new PostgresToolExecutionStartBarrierRepository(
database.pool,
),
completions: new PostgresToolExecutionCompletionRepository(
database.pool,
),
failureCompletions:
new PostgresToolExecutionFailureCompletionRepository(database.pool),
resultKeyCatalog: new PostgresToolResultKeyCatalogReader(
database.pool,
),
resultRekeys: new PostgresToolResultRekeyReader(database.pool),
toolDefinitionSnapshots:
new PostgresProjectToolDefinitionSnapshotRepository(database.pool),
});
const workflowScheduler = new ClusterWorkflowSchedulerCoordinator(
new ClusterSchedulerCoordinator(schedules, {
ownerId: schedulerRuntime.ownerId,
claimLeaseMs: schedulerRuntime.claimLeaseMs,
maxClaimsPerCycle: schedulerRuntime.maxClaimsPerCycle,
misfireGraceMs: schedulerRuntime.misfireGraceMs,
}),
new PostgresPluginPackageWorkflowFrontierRepository(database.pool),
new PostgresPluginPackageWorkflowTaskAttemptAdmissionRepository(
database.pool,
),
{
frontierPageSize: 32,
frontierMaxPages: 4,
taskAttemptPageSize: 32,
taskAttemptMaxPages: 4,
},
);
const runtimeScheduler = new ClusterRuntimeSchedulerCoordinator(
runtimeRecoverySupervisor,
new ClusterRunLostRetryCoordinator(
new PostgresClusterRunLostRetryRepository(database.pool),
{ pageSize: 16 },
),
workflowScheduler,
);
const schedulerLifecycle = new ClusterSchedulerLifecycle(
runtimeScheduler,
{
intervalMs: schedulerRuntime.intervalMs,
stopTimeoutMs: schedulerRuntime.stopTimeoutMs,
...(schedulerRuntime.onDiagnostic === undefined
? {}
: { onDiagnostic: schedulerRuntime.onDiagnostic }),
},
);
const cancellationConvergenceLifecycle =
new ClusterRunCancellationConvergenceLifecycle(
new ClusterRunCancellationConvergenceCoordinator(
new PostgresClusterRunCancellationConvergenceRepository(
database.pool,
),
{
pageSize: cancellationConvergenceRuntime.pageSize,
maxPages: cancellationConvergenceRuntime.maxPages,
},
),
{
intervalMs: cancellationConvergenceRuntime.intervalMs,
stopTimeoutMs: cancellationConvergenceRuntime.stopTimeoutMs,
...(cancellationConvergenceRuntime.onDiagnostic === undefined
? {}
: {
onDiagnostic: cancellationConvergenceRuntime.onDiagnostic,
}),
},
);
const runCancellation = new PostgresClusterRunCancellationRepository(
database.pool,
);
const taskStart = new PostgresTaskStartRepository(database.pool);
const application = options.create({
evidence,
authenticator: createClusterControlApiCredentialAuthenticator(
new PostgresApiCredentialRepository(database.pool),
options.apiCredentialPepper ?? '',
),
policies: new PostgresProjectPolicyRepository(database.pool),
runs,
runCancellation,
taskStart,
taskDefinitions: new PostgresTaskDefinitionSource(database.pool),
taskExecutionRevisions: new PostgresTaskExecutionRevisionSource(
database.pool,
),
triggers: new PostgresTriggerSource(database.pool),
schedules,
trustedToolStorage,
securityAudit: new PostgresSecurityAuditRepository(database.pool),
workflowAdministration:
createClusterPluginPackageWorkflowAdministrationCapability(
new PostgresPluginPackageAutomationPublicationRepository(
database.pool,
),
new PostgresPluginPackageMaterializedRevisionRepository(
database.pool,
),
new PostgresAuthorizedPluginPackageWorkflowAdmissionRepository(
database.pool,
),
new PostgresAuthorizedPluginPackageWorkflowRunInspectionRepository(
database.pool,
),
new PostgresAuthorizedPluginPackageWorkflowRunListRepository(
database.pool,
),
new PostgresAuthorizedPluginPackageWorkflowStepRunListRepository(
database.pool,
),
new PostgresAuthorizedPluginPackageWorkflowRunEventListRepository(
database.pool,
),
runCancellation,
),
...(options.workerRuntime === undefined
? {}
: {
workerRuntime: createClusterWorkerRuntimePort(
database.pool,
options.workerRuntime,
),
}),
});
const convergence = new ClusterControlRecoveryConvergenceVerifier(
recovery,
);
return {
async reconcile() {
const outstanding = await convergence.verify();
if (!outstanding.safe) {
const system = await recoveryCoordinator.reconcile();
if (
!system.safe ||
system.remaining !== 0 ||
system.failed !== 0
) {
return system;
}
}
const summary = await application.reconcile();
if (
!summary.safe ||
summary.remaining !== 0 ||
summary.failed !== 0
) {
return summary;
}
return convergence.verify();
},
async startLifecycles() {
if (!(await application.startLifecycles())) return false;
schedulerLifecycle.start();
cancellationConvergenceLifecycle.start();
return true;
},
installAdmission: () => application.installAdmission(),
async stop() {
recoveryRegistry?.dispose();
let schedulerStatus: 'stopped' | 'timed_out' = 'stopped';
let cancellationStatus: 'stopped' | 'timed_out' = 'stopped';
let applicationStatus: ClusterControlStopResult = 'stopped';
let primaryError: unknown;
try {
cancellationStatus = (
await cancellationConvergenceLifecycle.stopAndDrain()
).status;
} catch (error) {
primaryError = error;
}
try {
schedulerStatus = (await schedulerLifecycle.stopAndDrain())
.status;
} catch (error) {
primaryError ??= error;
}
try {
applicationStatus = await application.stop();
} catch (error) {
primaryError ??= error;
}
if (primaryError) throw primaryError;
return cancellationStatus === 'timed_out' ||
schedulerStatus === 'timed_out' ||
applicationStatus === 'timed_out'
? 'timed_out'
: 'stopped';
},
};
},
audit: options.audit,
});
if (activation.status === 'disabled') return activation;
let stopPromise: Promise<ClusterControlStopResult> | undefined;
return {
...activation,
stop() {
if (stopPromise) return stopPromise;
stopPromise = (async () => {
let result: ClusterControlStopResult | undefined;
let primaryError: unknown;
try {
result = await activation.stop();
} catch (error) {
primaryError = error;
}
try {
await closeDatabase();
} catch (error) {
primaryError ??= error;
}
if (primaryError) throw primaryError;
return result!;
})();
return stopPromise;
},
};
} catch (error) {
recoveryRegistry?.dispose();
try {
await closeDatabase();
} catch {
// Preserve the readiness/assembly/activation failure.
}
throw error;
}
}
export type {
ClusterControlActivationAudit,
ClusterControlActivationStack,
ClusterControlReadinessEvidence,
ClusterControlRuntimeActivationResult,
ClusterControlStopResult,
DeploymentProfile,
OpenPostgresDatabase,
PostgresDatabaseResource,
PostgresPool,
ProjectPolicyRepository,
RunRepository,
ClusterRunCancellationRepository,
ClusterControlRequestAuthenticator,
ClusterControlSecurityAuditSink,
ClusterControlRecoveryExecutorEvidenceProvider,
ClusterScheduleStore,
};
export { ClusterRunCancellationConvergenceLifecycle } from '../run/runCancellationLifecycle';
export * from '../scheduling/scheduler';
export * from '../scheduling/workflowScheduler';
export * from '../scheduling/runtimeScheduler';
export * from '../remote-execution/remoteWorkerDispatcher';
export * from '../remote-execution/workerRuntimePort';
@@ -0,0 +1,410 @@
import { randomUUID } from 'node:crypto';
import type {
ClusterControlStartupRecoverySummary,
ClusterControlStopResult,
} from '@qinglong/runtime-core';
import type { RemoteWorkerSecretValueProvider } from '@qinglong/runtime-core/remote-secret-delivery';
import {
startClusterControlApplication,
type ClusterControlApplicationOptions,
type ClusterControlApplicationResult,
type ClusterControlApplicationStack,
} from './application';
import {
createClusterControlDatabaseBinding,
type EnabledClusterControlConfig,
} from '../production-process/config';
import {
createClusterControlAdmissionPipeline,
createClusterControlProjectPolicyAuthorizer,
} from '../transport/admissionPipeline';
import { createClusterControlRouteRegistry } from '../transport/routeRegistry';
import { CLUSTER_CONTROL_HTTP_DEFAULTS } from '../transport/httpSurface';
import { createClusterControlRunReadRoute } from '../run/runReadRoute';
import { createClusterControlRunListRoute } from '../run/runListRoute';
import { createClusterControlRunEventListRoute } from '../run/runEventListRoute';
import { createClusterControlRunStepListRoute } from '../run/runStepListRoute';
import { createClusterControlTaskListRoute } from '../task/taskListRoute';
import { createClusterControlTaskReadRoute } from '../task/taskReadRoute';
import { createClusterControlTaskStartRoute } from '../task/taskStartRoute';
import {
createClusterControlPluginPackagePromptExecutionRoute,
type ClusterPluginPackagePromptExecutionCapability,
} from '../plugin-package/prompt/pluginPackagePromptExecutionRoute';
import {
createClusterControlPluginPackagePromptCatalogRoute,
type ClusterPluginPackagePromptCatalogCapability,
} from '../plugin-package/prompt/pluginPackagePromptCatalogRoute';
import {
createClusterControlPluginPackagePromptOutputReadRoute,
type ClusterPluginPackagePromptOutputReadCapability,
} from '../plugin-package/prompt/pluginPackagePromptOutputReadRoute';
import {
createClusterControlPluginPackagePromptExecutionInspectionRoute,
type ClusterPluginPackagePromptExecutionInspectionCapability,
} from '../plugin-package/prompt/pluginPackagePromptExecutionInspectionRoute';
import {
createClusterControlPluginPackagePromptExecutionOutputReadRoute,
type ClusterPluginPackagePromptExecutionOutputReadCapability,
} from '../plugin-package/prompt/pluginPackagePromptExecutionOutputReadRoute';
import {
createClusterControlRunCancellationRoute,
type ClusterRunCancellationEventIdFactory,
} from '../run/runCancellationRoute';
import type { ClusterControlAssemblyInput } from './clusterControlRuntime';
import type { ClusterRemoteWorkerArtifactStore } from '../remote-execution/remoteWorkerCompletionService';
import type { EnabledClusterWorkerIngressConfig } from '../worker-ingress/workerIngressConfig';
import {
startProductionClusterWorkerIngress,
type ProductionClusterWorkerIngressOptions as ProductionClusterWorkerIngressStarterOptions,
} from '../worker-ingress/productionWorkerIngress';
import type { ClusterWorkerIngressApplicationResult } from '../worker-ingress/workerIngressApplication';
import { createClusterControlPluginPackageWorkflowRoutes } from '../plugin-package/workflow/pluginPackageWorkflowRoute';
export const PRODUCTION_CLUSTER_CONTROL_ROUTE_OPERATIONS = Object.freeze([
'task.get',
'task.list',
'task.start',
'run.get',
'run.list',
'run.events.list',
'run.steps.list',
'run.cancel',
'workflow.read',
'workflow.run.read',
'workflow.run.list',
'workflow.step.list',
'workflow.event.list',
'workflow.start',
'workflow.cancel',
] as const);
export const PRODUCTION_CLUSTER_CONTROL_OPTIONAL_ROUTE_OPERATIONS =
Object.freeze([
'prompt.read',
'prompt.execute',
'prompt.execution.read',
'prompt.execution.output.read',
'prompt.output.read',
] as const);
export interface ProductionClusterControlAssemblyOptions {
readonly createEventId?: ClusterRunCancellationEventIdFactory;
readonly promptCatalog?: Readonly<{
readonly capability: ClusterPluginPackagePromptCatalogCapability;
}>;
readonly promptExecution?: Readonly<{
readonly capability: ClusterPluginPackagePromptExecutionCapability;
readonly maxExecutionMs?: number;
readonly now?: () => number;
}>;
readonly promptExecutionInspection?: Readonly<{
readonly capability: ClusterPluginPackagePromptExecutionInspectionCapability;
readonly now?: () => number;
}>;
readonly promptOutputRead?: Readonly<{
readonly capability: ClusterPluginPackagePromptOutputReadCapability;
}>;
readonly promptExecutionOutputRead?: Readonly<{
readonly capability: ClusterPluginPackagePromptExecutionOutputReadCapability;
}>;
readonly workerIngress?: Readonly<{
readonly config: EnabledClusterWorkerIngressConfig;
readonly onDiagnostic?: (error: unknown) => void | Promise<void>;
}>;
readonly startWorkerIngress?: (
options: ProductionClusterWorkerIngressStarterOptions,
) => Promise<ClusterWorkerIngressApplicationResult>;
}
export interface ProductionClusterWorkerIngressOptions {
readonly config: EnabledClusterWorkerIngressConfig;
readonly artifactStore: ClusterRemoteWorkerArtifactStore;
readonly secretProvider?: RemoteWorkerSecretValueProvider;
readonly onDiagnostic?: (error: unknown) => void | Promise<void>;
}
export interface ProductionClusterControlApplicationOptions
extends Omit<
ClusterControlApplicationOptions,
| 'create'
| 'enabled'
| 'profile'
| 'apiCredentialPepper'
| 'openDatabase'
| 'availability'
| 'http'
| 'workerRuntime'
> {
readonly config: EnabledClusterControlConfig;
readonly createEventId?: ClusterRunCancellationEventIdFactory;
readonly promptCatalog?: Readonly<{
readonly capability: ClusterPluginPackagePromptCatalogCapability;
}>;
readonly promptExecution?: Readonly<{
readonly capability: ClusterPluginPackagePromptExecutionCapability;
}>;
readonly promptExecutionInspection?: Readonly<{
readonly capability: ClusterPluginPackagePromptExecutionInspectionCapability;
}>;
readonly promptOutputRead?: Readonly<{
readonly capability: ClusterPluginPackagePromptOutputReadCapability;
}>;
readonly promptExecutionOutputRead?: Readonly<{
readonly capability: ClusterPluginPackagePromptExecutionOutputReadCapability;
}>;
readonly workerIngress?: ProductionClusterWorkerIngressOptions;
}
const SAFE_RECOVERY: Readonly<ClusterControlStartupRecoverySummary> =
Object.freeze({ safe: true, remaining: 0, failed: 0 });
function eventIdFactory(
candidate: ClusterRunCancellationEventIdFactory | undefined,
): ClusterRunCancellationEventIdFactory {
if (candidate !== undefined && typeof candidate !== 'function') {
throw new TypeError(
'Production cluster-control event ID factory is invalid',
);
}
return candidate ?? randomUUID;
}
/**
* The reviewed production business surface. Bootstrap still owns PostgreSQL,
* startup recovery, the scheduler and cancellation convergence; this stack
* owns only the exact route allowlist and its admission pipeline.
*/
export function createProductionClusterControlApplicationStack(
input: ClusterControlAssemblyInput,
options: ProductionClusterControlAssemblyOptions = {},
): ClusterControlApplicationStack {
const createEventId = eventIdFactory(options.createEventId);
const workerIngressStarter =
options.startWorkerIngress ?? startProductionClusterWorkerIngress;
if (typeof workerIngressStarter !== 'function') {
throw new TypeError('Production Worker ingress starter is invalid');
}
const routeDefinitions = [
createClusterControlTaskReadRoute(input.taskDefinitions),
createClusterControlTaskListRoute(input.taskDefinitions),
createClusterControlTaskStartRoute(input.taskStart, createEventId),
createClusterControlRunReadRoute(input.runs),
createClusterControlRunListRoute(input.runs),
createClusterControlRunEventListRoute(input.runs),
createClusterControlRunStepListRoute(
input.runs,
input.trustedToolStorage.stepRuns,
),
createClusterControlRunCancellationRoute(
input.runCancellation,
createEventId,
),
...createClusterControlPluginPackageWorkflowRoutes(
input.workflowAdministration,
Date.now,
createEventId,
),
...(options.promptCatalog === undefined
? []
: [
createClusterControlPluginPackagePromptCatalogRoute(
options.promptCatalog.capability,
),
]),
...(options.promptExecution === undefined
? []
: [
createClusterControlPluginPackagePromptExecutionRoute(
options.promptExecution.capability,
{
...(options.promptExecution.maxExecutionMs === undefined
? {}
: { maxExecutionMs: options.promptExecution.maxExecutionMs }),
...(options.promptExecution.now === undefined
? {}
: { now: options.promptExecution.now }),
createEventId,
},
),
]),
...(options.promptExecutionInspection === undefined
? []
: [
createClusterControlPluginPackagePromptExecutionInspectionRoute(
options.promptExecutionInspection.capability,
{
...(options.promptExecutionInspection.now === undefined
? {}
: { now: options.promptExecutionInspection.now }),
createEventId,
},
),
]),
...(options.promptOutputRead === undefined
? []
: [
createClusterControlPluginPackagePromptOutputReadRoute(
options.promptOutputRead.capability,
),
]),
...(options.promptExecutionOutputRead === undefined
? []
: [
createClusterControlPluginPackagePromptExecutionOutputReadRoute(
options.promptExecutionOutputRead.capability,
),
]),
];
const routes = createClusterControlRouteRegistry(routeDefinitions);
const expectedRouteCount =
PRODUCTION_CLUSTER_CONTROL_ROUTE_OPERATIONS.length +
(options.promptCatalog === undefined ? 0 : 1) +
(options.promptExecution === undefined ? 0 : 1) +
(options.promptExecutionInspection === undefined ? 0 : 1) +
(options.promptOutputRead === undefined ? 0 : 1) +
(options.promptExecutionOutputRead === undefined ? 0 : 1);
if (routes.size !== expectedRouteCount) {
throw new Error('Production cluster-control route allowlist is incomplete');
}
const admission = createClusterControlAdmissionPipeline({
routes,
authenticator: input.authenticator,
policy: createClusterControlProjectPolicyAuthorizer(input.policies),
audit: input.securityAudit,
});
let workerIngress:
| Extract<ClusterWorkerIngressApplicationResult, { status: 'active' }>
| undefined;
let workerIngressStart:
| Promise<
Extract<ClusterWorkerIngressApplicationResult, { status: 'active' }>
>
| undefined;
let workerIngressUnavailable: unknown;
let workerIngressStop: Promise<'stopped'> | undefined;
const stopWorkerIngress = (): Promise<'stopped'> => {
if (!workerIngress) return Promise.resolve('stopped' as const);
workerIngressStop ??= workerIngress.stop();
return workerIngressStop;
};
const startWorkerIngress = async (): Promise<void> => {
const ingress = options.workerIngress;
if (!ingress) return;
if (!input.workerRuntime) {
throw new Error(
'Production Worker ingress requires an injected runtime service port',
);
}
workerIngressStart ??= (async () => {
const result = await workerIngressStarter({
config: ingress.config,
runtime: input.workerRuntime!,
onPoolError(error) {
workerIngressUnavailable ??= error;
void Promise.resolve(ingress.onDiagnostic?.(error)).catch(
() => undefined,
);
void stopWorkerIngress().catch(() => undefined);
},
});
if (result.status !== 'active') {
throw new Error('Production Worker ingress did not activate');
}
workerIngress = result;
if (workerIngressUnavailable !== undefined) {
await stopWorkerIngress();
throw new Error(
'Production Worker ingress database became unavailable during activation',
);
}
return result;
})();
await workerIngressStart;
};
return Object.freeze({
async reconcile(): Promise<ClusterControlStartupRecoverySummary> {
return SAFE_RECOVERY;
},
async startLifecycles(): Promise<boolean> {
await startWorkerIngress();
return true;
},
admission,
async stop(): Promise<ClusterControlStopResult> {
await stopWorkerIngress();
return 'stopped';
},
});
}
/** Starts cluster-control with the reviewed production route allowlist. */
export function startProductionClusterControlApplication(
options: ProductionClusterControlApplicationOptions,
): Promise<ClusterControlApplicationResult> {
const createEventId = eventIdFactory(options.createEventId);
const {
createEventId: _ignoredCreateEventId,
config,
workerIngress,
promptCatalog,
promptExecution,
promptExecutionInspection,
promptOutputRead,
promptExecutionOutputRead,
...applicationOptions
} = options;
const database = createClusterControlDatabaseBinding(config);
return startClusterControlApplication({
...applicationOptions,
enabled: true,
profile: 'cluster-control',
apiCredentialPepper: config.security.apiCredentialPepper,
http: config.http,
...(workerIngress === undefined
? {}
: {
workerRuntime: {
artifactStore: workerIngress.artifactStore,
...(workerIngress.secretProvider === undefined
? {}
: { secretProvider: workerIngress.secretProvider }),
},
}),
...database,
create: (input) =>
createProductionClusterControlApplicationStack(input, {
createEventId,
...(promptCatalog === undefined ? {} : { promptCatalog }),
...(promptExecution === undefined
? {}
: {
promptExecution: {
capability: promptExecution.capability,
maxExecutionMs: Math.max(
1,
(config.http.requestTimeoutMs ??
CLUSTER_CONTROL_HTTP_DEFAULTS.requestTimeoutMs) - 100,
),
},
}),
...(promptExecutionInspection === undefined
? {}
: { promptExecutionInspection }),
...(promptOutputRead === undefined ? {} : { promptOutputRead }),
...(promptExecutionOutputRead === undefined
? {}
: { promptExecutionOutputRead }),
...(workerIngress === undefined
? {}
: {
workerIngress: {
config: workerIngress.config,
...(workerIngress.onDiagnostic === undefined
? {}
: { onDiagnostic: workerIngress.onDiagnostic }),
},
}),
}),
});
}
@@ -0,0 +1,778 @@
// Artifact owns immutable S3 evidence, checksum validation, and conditional promotion.
import { createHash, randomBytes, randomUUID } from 'node:crypto';
import { Readable } from 'node:stream';
import {
ChecksumAlgorithm,
ChecksumMode,
CopyObjectCommand,
DeleteObjectCommand,
HeadObjectCommand,
MetadataDirective,
PutObjectCommand,
S3Client,
ServerSideEncryption,
} from '@aws-sdk/client-s3';
import {
MAX_REMOTE_WORKER_ARTIFACT_BYTES,
REMOTE_WORKER_ARTIFACT_CONTENT_TYPE,
normalizeRemoteWorkerArtifactReceipt,
type RemoteWorkerArtifactReceipt,
} from '@qinglong/runtime-core/remote-worker-completion';
import type {
ClusterRemoteWorkerArtifactLookup,
ClusterRemoteWorkerArtifactStorageCommand,
ClusterRemoteWorkerArtifactStore,
} from '../remote-execution/remoteWorkerCompletionService';
const DEFAULT_PREFIX = 'qinglong/v3/worker-artifacts';
const METADATA_SCHEMA = 'qinglong-remote-worker-artifact-v1';
const TEMPORARY_METADATA_SCHEMA =
'qinglong-remote-worker-artifact-temporary-v1';
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
const BUCKET_PATTERN = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
const PREFIX_PATTERN = /^[A-Za-z0-9][A-Za-z0-9/_=-]{0,254}$/;
const TEMPORARY_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
type S3SendClient = Pick<S3Client, 'send'>;
export type S3ClusterRemoteWorkerArtifactEncryption =
| Readonly<{ readonly mode: 's3' }>
| Readonly<{ readonly mode: 'kms'; readonly keyId: string }>;
export interface S3ClusterRemoteWorkerArtifactStoreDiagnostic {
readonly operation: 'temporary_object_cleanup';
}
export interface S3ClusterRemoteWorkerArtifactStoreOptions {
readonly client: S3SendClient;
readonly bucket: string;
readonly prefix?: string;
readonly expectedBucketOwner?: string;
readonly encryption: S3ClusterRemoteWorkerArtifactEncryption;
readonly createTemporaryId?: () => string;
readonly onDiagnostic?: (
error: unknown,
context: Readonly<S3ClusterRemoteWorkerArtifactStoreDiagnostic>,
) => void | Promise<void>;
}
export interface S3ClusterRemoteWorkerArtifactClientOptions {
readonly region: string;
readonly endpoint?: string;
readonly forcePathStyle?: boolean;
}
export function createS3ClusterRemoteWorkerArtifactClient(
options: S3ClusterRemoteWorkerArtifactClientOptions,
): S3Client {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
!/^[a-z0-9][a-z0-9-]{0,62}$/.test(options.region) ||
(options.endpoint !== undefined &&
typeof options.endpoint !== 'string') ||
(options.forcePathStyle !== undefined &&
typeof options.forcePathStyle !== 'boolean')
) {
throw configurationError('client options are invalid');
}
return new S3Client({
region: options.region,
...(options.endpoint === undefined
? {}
: { endpoint: options.endpoint }),
forcePathStyle: options.forcePathStyle ?? false,
});
}
export class S3ClusterRemoteWorkerArtifactStoreError extends Error {
constructor(
readonly reason: 'unavailable' | 'integrity_mismatch',
options?: ErrorOptions,
) {
super(`S3 Remote Worker Artifact store failed: ${reason}`, options);
this.name = 'S3ClusterRemoteWorkerArtifactStoreError';
}
}
interface PreparedOptions {
readonly client: S3SendClient;
readonly bucket: string;
readonly prefix: string;
readonly expectedBucketOwner?: string;
readonly encryption: Readonly<{
readonly ServerSideEncryption: 'AES256' | 'aws:kms';
readonly SSEKMSKeyId?: string;
}>;
readonly createTemporaryId: () => string;
readonly onDiagnostic?: S3ClusterRemoteWorkerArtifactStoreOptions['onDiagnostic'];
}
type ArtifactAuthority = Readonly<{
projectId: string;
runId: string;
attemptId: string;
logArtifactId: string;
}>;
type NormalizedStorageCommand = ArtifactAuthority &
Readonly<{
byteLength: number;
truncated?: boolean;
}>;
const DIAGNOSTIC_CONTEXT = Object.freeze({
operation: 'temporary_object_cleanup' as const,
});
function configurationError(message: string): TypeError {
return new TypeError(`S3 Remote Worker Artifact store is invalid: ${message}`);
}
function prepareOptions(
options: S3ClusterRemoteWorkerArtifactStoreOptions,
): PreparedOptions {
const allowedKeys = new Set([
'bucket',
'client',
'createTemporaryId',
'encryption',
'expectedBucketOwner',
'onDiagnostic',
'prefix',
]);
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some((key) => !allowedKeys.has(key)) ||
typeof options.client?.send !== 'function'
) {
throw configurationError('options are invalid');
}
if (
!BUCKET_PATTERN.test(options.bucket) ||
options.bucket.includes('..') ||
/^\d{1,3}(?:\.\d{1,3}){3}$/.test(options.bucket)
) {
throw configurationError('bucket is invalid');
}
const prefix = options.prefix ?? DEFAULT_PREFIX;
if (
!PREFIX_PATTERN.test(prefix) ||
prefix.startsWith('/') ||
prefix.endsWith('/') ||
prefix.includes('//') ||
prefix.split('/').some((segment) => segment === '.' || segment === '..')
) {
throw configurationError('prefix is invalid');
}
if (
options.expectedBucketOwner !== undefined &&
!/^\d{12}$/.test(options.expectedBucketOwner)
) {
throw configurationError('expected bucket owner is invalid');
}
const encryption = options.encryption;
if (!encryption || typeof encryption !== 'object' || Array.isArray(encryption)) {
throw configurationError('encryption is required');
}
let preparedEncryption: PreparedOptions['encryption'];
if (
encryption.mode === 's3' &&
Object.keys(encryption).length === 1
) {
preparedEncryption = Object.freeze({
ServerSideEncryption: ServerSideEncryption.AES256,
});
} else if (
encryption.mode === 'kms' &&
Object.keys(encryption).length === 2 &&
typeof encryption.keyId === 'string' &&
encryption.keyId.length >= 1 &&
encryption.keyId.length <= 2048 &&
!/[\u0000-\u001f\u007f]/.test(encryption.keyId)
) {
preparedEncryption = Object.freeze({
ServerSideEncryption: ServerSideEncryption.aws_kms,
SSEKMSKeyId: encryption.keyId,
});
} else {
throw configurationError('encryption is invalid');
}
if (
options.createTemporaryId !== undefined &&
typeof options.createTemporaryId !== 'function'
) {
throw configurationError('temporary ID factory is invalid');
}
if (
options.onDiagnostic !== undefined &&
typeof options.onDiagnostic !== 'function'
) {
throw configurationError('diagnostic sink is invalid');
}
return Object.freeze({
client: options.client,
bucket: options.bucket,
prefix,
...(options.expectedBucketOwner === undefined
? {}
: { expectedBucketOwner: options.expectedBucketOwner }),
encryption: preparedEncryption,
createTemporaryId: options.createTemporaryId ?? randomUUID,
...(options.onDiagnostic === undefined
? {}
: { onDiagnostic: options.onDiagnostic }),
});
}
function receiptCandidate(
authority: ArtifactAuthority,
byteLength: number,
sha256: string,
truncated?: boolean,
status: RemoteWorkerArtifactReceipt['status'] = 'stored',
): Readonly<RemoteWorkerArtifactReceipt> {
return normalizeRemoteWorkerArtifactReceipt({
status,
...authority,
byteLength,
sha256,
...(truncated === undefined ? {} : { truncated }),
});
}
function exactObjectShape(
value: unknown,
required: readonly string[],
optional: readonly string[],
name: string,
): asserts value is Readonly<Record<string, unknown>> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw configurationError(`${name} is invalid`);
}
const keys = Object.keys(value);
const allowed = new Set([...required, ...optional]);
if (
required.some((key) => !Object.hasOwn(value, key)) ||
keys.some((key) => !allowed.has(key))
) {
throw configurationError(`${name} shape is invalid`);
}
}
function normalizeStorageCommand(
command: Readonly<ClusterRemoteWorkerArtifactStorageCommand>,
): NormalizedStorageCommand {
exactObjectShape(
command,
['attemptId', 'byteLength', 'logArtifactId', 'projectId', 'runId'],
['truncated'],
'storage command',
);
const normalized = receiptCandidate(
command as unknown as ClusterRemoteWorkerArtifactStorageCommand,
command.byteLength as number,
'0'.repeat(64),
command.truncated as boolean | undefined,
);
return Object.freeze({
projectId: normalized.projectId,
runId: normalized.runId,
attemptId: normalized.attemptId,
logArtifactId: normalized.logArtifactId,
byteLength: normalized.byteLength,
...(normalized.truncated === undefined
? {}
: { truncated: normalized.truncated }),
});
}
function normalizeLookup(
lookup: Readonly<ClusterRemoteWorkerArtifactLookup>,
): ArtifactAuthority {
exactObjectShape(
lookup,
['attemptId', 'logArtifactId', 'projectId', 'runId'],
[],
'lookup',
);
const normalized = receiptCandidate(
lookup as unknown as ClusterRemoteWorkerArtifactLookup,
0,
'0'.repeat(64),
);
return Object.freeze({
projectId: normalized.projectId,
runId: normalized.runId,
attemptId: normalized.attemptId,
logArtifactId: normalized.logArtifactId,
});
}
function lookupFromCommand(
command: NormalizedStorageCommand,
): ArtifactAuthority {
return Object.freeze({
projectId: command.projectId,
runId: command.runId,
attemptId: command.attemptId,
logArtifactId: command.logArtifactId,
});
}
function identityDigest(authority: ArtifactAuthority): string {
return createHash('sha256')
.update('qinglong/remote-worker-artifact-identity@v1\0', 'utf8')
.update(authority.projectId, 'utf8')
.update('\0', 'utf8')
.update(authority.runId, 'utf8')
.update('\0', 'utf8')
.update(authority.attemptId, 'utf8')
.update('\0', 'utf8')
.update(authority.logArtifactId, 'utf8')
.digest('hex');
}
function fieldDigest(domain: string, value: string): string {
return createHash('sha256')
.update(`qinglong/remote-worker-artifact-${domain}@v1\0`, 'utf8')
.update(value, 'utf8')
.digest('hex');
}
function finalObjectKey(prefix: string, authority: ArtifactAuthority): string {
const digest = identityDigest(authority);
return `${prefix}/objects/${digest.slice(0, 2)}/${digest}`;
}
function temporaryObjectKey(prefix: string, createId: () => string): string {
const id = createId();
if (typeof id !== 'string' || !TEMPORARY_ID_PATTERN.test(id)) {
throw new S3ClusterRemoteWorkerArtifactStoreError('unavailable');
}
return `${prefix}/temporary/${id}`;
}
function temporaryOwnershipDigest(): string {
const authority = randomBytes(32);
try {
return createHash('sha256')
.update('qinglong/remote-worker-artifact-temporary-owner@v1\0', 'utf8')
.update(authority)
.digest('hex');
} finally {
authority.fill(0);
}
}
function finalMetadata(
command: NormalizedStorageCommand,
sha256: string,
): Readonly<Record<string, string>> {
return Object.freeze({
'ql3-schema': METADATA_SCHEMA,
'ql3-project-sha256': fieldDigest('project', command.projectId),
'ql3-run-sha256': fieldDigest('run', command.runId),
'ql3-attempt-sha256': fieldDigest('attempt', command.attemptId),
'ql3-log-artifact-sha256': fieldDigest(
'log-artifact',
command.logArtifactId,
),
'ql3-byte-length': String(command.byteLength),
'ql3-content-sha256': sha256,
'ql3-truncated': command.truncated === undefined
? 'omitted'
: String(command.truncated),
});
}
function canonicalChecksum(value: unknown): string {
if (typeof value !== 'string' || value.length !== 44) {
throw new S3ClusterRemoteWorkerArtifactStoreError('integrity_mismatch');
}
const decoded = Buffer.from(value, 'base64');
try {
if (decoded.byteLength !== 32 || decoded.toString('base64') !== value) {
throw new S3ClusterRemoteWorkerArtifactStoreError('integrity_mismatch');
}
return decoded.toString('hex');
} finally {
decoded.fill(0);
}
}
function metadataValue(
metadata: Readonly<Record<string, string | undefined>> | undefined,
name: string,
): string {
const value = metadata?.[name];
if (typeof value !== 'string') {
throw new S3ClusterRemoteWorkerArtifactStoreError('integrity_mismatch');
}
return value;
}
function parseStoredReceipt(
authority: ArtifactAuthority,
output: Readonly<{
ContentLength?: number | undefined;
ContentType?: string | undefined;
ChecksumSHA256?: string | undefined;
Metadata?: Readonly<Record<string, string | undefined>> | undefined;
}>,
): Readonly<RemoteWorkerArtifactReceipt> {
const metadata = output.Metadata;
const lengthText = metadataValue(metadata, 'ql3-byte-length');
const byteLength = Number(lengthText);
const sha256 = metadataValue(metadata, 'ql3-content-sha256');
const truncatedText = metadataValue(metadata, 'ql3-truncated');
if (
metadataValue(metadata, 'ql3-schema') !== METADATA_SCHEMA ||
metadataValue(metadata, 'ql3-project-sha256') !==
fieldDigest('project', authority.projectId) ||
metadataValue(metadata, 'ql3-run-sha256') !==
fieldDigest('run', authority.runId) ||
metadataValue(metadata, 'ql3-attempt-sha256') !==
fieldDigest('attempt', authority.attemptId) ||
metadataValue(metadata, 'ql3-log-artifact-sha256') !==
fieldDigest('log-artifact', authority.logArtifactId) ||
!Number.isSafeInteger(byteLength) ||
byteLength < 0 ||
byteLength > MAX_REMOTE_WORKER_ARTIFACT_BYTES ||
lengthText !== String(byteLength) ||
output.ContentLength !== byteLength ||
output.ContentType !== REMOTE_WORKER_ARTIFACT_CONTENT_TYPE ||
!SHA256_PATTERN.test(sha256) ||
canonicalChecksum(output.ChecksumSHA256) !== sha256 ||
!['omitted', 'true', 'false'].includes(truncatedText)
) {
throw new S3ClusterRemoteWorkerArtifactStoreError('integrity_mismatch');
}
return receiptCandidate(
authority,
byteLength,
sha256,
truncatedText === 'omitted' ? undefined : truncatedText === 'true',
'already_stored',
);
}
function isNotFound(error: unknown): boolean {
if (!error || typeof error !== 'object') return false;
const value = error as {
name?: unknown;
Code?: unknown;
$metadata?: { httpStatusCode?: unknown };
};
return value.name === 'NotFound' ||
value.name === 'NoSuchKey' ||
value.Code === 'NoSuchKey' ||
value.$metadata?.httpStatusCode === 404;
}
function requestOptions(signal?: AbortSignal): { abortSignal: AbortSignal } | undefined {
return signal === undefined ? undefined : { abortSignal: signal };
}
function copySource(bucket: string, key: string): string {
return [bucket, ...key.split('/')]
.map((segment) => encodeURIComponent(segment))
.join('/');
}
class ArtifactContentDigest {
private readonly hash = createHash('sha256');
private consumedBytes = 0;
private complete = false;
private digestValue?: string;
constructor(
private readonly content: AsyncIterable<Uint8Array>,
private readonly expectedBytes: number,
private readonly signal?: AbortSignal,
) {
if (!content || typeof content[Symbol.asyncIterator] !== 'function') {
throw new S3ClusterRemoteWorkerArtifactStoreError('unavailable');
}
}
async *stream(): AsyncGenerator<Buffer> {
if (this.complete || this.consumedBytes !== 0) {
throw new S3ClusterRemoteWorkerArtifactStoreError('unavailable');
}
if (this.signal?.aborted) throw this.signal.reason;
for await (const chunk of this.content) {
if (this.signal?.aborted) throw this.signal.reason;
if (!(chunk instanceof Uint8Array) || chunk.byteLength === 0) {
throw new S3ClusterRemoteWorkerArtifactStoreError('integrity_mismatch');
}
this.consumedBytes += chunk.byteLength;
if (this.consumedBytes > this.expectedBytes) {
throw new S3ClusterRemoteWorkerArtifactStoreError('integrity_mismatch');
}
this.hash.update(chunk);
yield Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
}
if (this.consumedBytes !== this.expectedBytes) {
throw new S3ClusterRemoteWorkerArtifactStoreError('integrity_mismatch');
}
if (this.signal?.aborted) throw this.signal.reason;
this.digestValue = this.hash.digest('hex');
this.complete = true;
}
async consume(): Promise<string> {
for await (const _chunk of this.stream()) {
// Hash a replay without allocating or contacting object storage.
}
return this.digest();
}
digest(): string {
if (!this.complete || !this.digestValue) {
throw new S3ClusterRemoteWorkerArtifactStoreError('unavailable');
}
return this.digestValue;
}
isComplete(): boolean {
return this.complete;
}
}
/**
* Shared immutable S3 adapter. A unique temporary upload is checksummed first,
* then promoted by one destination-conditional server-side copy. Permanent
* objects are never overwritten or deleted by this adapter.
*/
export class S3ClusterRemoteWorkerArtifactStore
implements ClusterRemoteWorkerArtifactStore {
private readonly options: PreparedOptions;
constructor(options: S3ClusterRemoteWorkerArtifactStoreOptions) {
this.options = prepareOptions(options);
}
async inspect(
lookup: Readonly<ClusterRemoteWorkerArtifactLookup>,
signal?: AbortSignal,
): Promise<Readonly<RemoteWorkerArtifactReceipt> | undefined> {
const authority = normalizeLookup(lookup);
if (signal?.aborted) throw signal.reason;
try {
const output = await this.options.client.send(
new HeadObjectCommand({
Bucket: this.options.bucket,
Key: finalObjectKey(this.options.prefix, authority),
ChecksumMode: ChecksumMode.ENABLED,
...(this.options.expectedBucketOwner === undefined
? {}
: { ExpectedBucketOwner: this.options.expectedBucketOwner }),
}),
requestOptions(signal),
);
return parseStoredReceipt(authority, output);
} catch (error) {
if (isNotFound(error)) return undefined;
if (error instanceof S3ClusterRemoteWorkerArtifactStoreError) throw error;
throw new S3ClusterRemoteWorkerArtifactStoreError('unavailable', {
cause: error,
});
}
}
async put(
value: Readonly<ClusterRemoteWorkerArtifactStorageCommand>,
content: AsyncIterable<Uint8Array>,
signal?: AbortSignal,
): Promise<Readonly<RemoteWorkerArtifactReceipt>> {
const command = normalizeStorageCommand(value);
const digest = new ArtifactContentDigest(
content,
command.byteLength,
signal,
);
const lookup = lookupFromCommand(command);
const existing = await this.inspect(lookup, signal);
if (existing) {
const incomingSha256 = await digest.consume();
if (
existing.byteLength !== command.byteLength ||
existing.truncated !== command.truncated ||
existing.sha256 !== incomingSha256
) {
throw new S3ClusterRemoteWorkerArtifactStoreError(
'integrity_mismatch',
);
}
return existing;
}
const temporaryKey = temporaryObjectKey(
this.options.prefix,
this.options.createTemporaryId,
);
const temporaryOwner = temporaryOwnershipDigest();
let temporaryOwned = false;
let result: Readonly<RemoteWorkerArtifactReceipt> | undefined;
let primaryError: unknown;
try {
const body = Readable.from(digest.stream(), { objectMode: false });
try {
await this.options.client.send(
new PutObjectCommand({
Bucket: this.options.bucket,
Key: temporaryKey,
Body: body,
ContentLength: command.byteLength,
ContentType: REMOTE_WORKER_ARTIFACT_CONTENT_TYPE,
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
IfNoneMatch: '*',
Metadata: {
'ql3-schema': TEMPORARY_METADATA_SCHEMA,
'ql3-owner-sha256': temporaryOwner,
},
...this.options.encryption,
...(this.options.expectedBucketOwner === undefined
? {}
: { ExpectedBucketOwner: this.options.expectedBucketOwner }),
}),
requestOptions(signal),
);
temporaryOwned = true;
} catch (error) {
if (!digest.isComplete()) throw error;
} finally {
body.destroy();
}
const sha256 = digest.digest();
await this.assertTemporaryObject(
temporaryKey,
temporaryOwner,
command.byteLength,
sha256,
signal,
);
temporaryOwned = true;
let copied = false;
try {
await this.options.client.send(
new CopyObjectCommand({
Bucket: this.options.bucket,
Key: finalObjectKey(this.options.prefix, command),
CopySource: copySource(this.options.bucket, temporaryKey),
ContentType: REMOTE_WORKER_ARTIFACT_CONTENT_TYPE,
MetadataDirective: MetadataDirective.REPLACE,
Metadata: finalMetadata(command, sha256),
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
IfNoneMatch: '*',
...this.options.encryption,
...(this.options.expectedBucketOwner === undefined
? {}
: {
ExpectedBucketOwner: this.options.expectedBucketOwner,
CopySourceExpectedBucketOwner:
this.options.expectedBucketOwner,
}),
}),
requestOptions(signal),
);
copied = true;
} catch {
// A 409/412 race or a lost successful response is resolved only by
// inspecting the immutable destination below.
}
const stored = await this.inspect(lookup, signal);
if (
!stored ||
stored.byteLength !== command.byteLength ||
stored.truncated !== command.truncated ||
stored.sha256 !== sha256
) {
throw new S3ClusterRemoteWorkerArtifactStoreError(
'integrity_mismatch',
);
}
result = Object.freeze({
...stored,
status: copied ? 'stored' as const : 'already_stored' as const,
});
} catch (error) {
primaryError = error;
}
if (temporaryOwned) {
try {
await this.options.client.send(
new DeleteObjectCommand({
Bucket: this.options.bucket,
Key: temporaryKey,
...(this.options.expectedBucketOwner === undefined
? {}
: { ExpectedBucketOwner: this.options.expectedBucketOwner }),
}),
requestOptions(signal),
);
} catch (error) {
if (primaryError === undefined) {
try {
await this.options.onDiagnostic?.(error, DIAGNOSTIC_CONTEXT);
} catch {
// Diagnostics cannot reverse a durable immutable promotion.
}
}
}
}
if (primaryError !== undefined) {
if (primaryError instanceof S3ClusterRemoteWorkerArtifactStoreError) {
throw primaryError;
}
throw new S3ClusterRemoteWorkerArtifactStoreError('unavailable', {
cause: primaryError,
});
}
return result!;
}
private async assertTemporaryObject(
key: string,
ownerSha256: string,
byteLength: number,
sha256: string,
signal?: AbortSignal,
): Promise<void> {
let output;
try {
output = await this.options.client.send(
new HeadObjectCommand({
Bucket: this.options.bucket,
Key: key,
ChecksumMode: ChecksumMode.ENABLED,
...(this.options.expectedBucketOwner === undefined
? {}
: { ExpectedBucketOwner: this.options.expectedBucketOwner }),
}),
requestOptions(signal),
);
} catch (error) {
throw new S3ClusterRemoteWorkerArtifactStoreError('unavailable', {
cause: error,
});
}
if (
output.ContentLength !== byteLength ||
output.ContentType !== REMOTE_WORKER_ARTIFACT_CONTENT_TYPE ||
output.Metadata?.['ql3-schema'] !== TEMPORARY_METADATA_SCHEMA ||
output.Metadata?.['ql3-owner-sha256'] !== ownerSha256 ||
canonicalChecksum(output.ChecksumSHA256) !== sha256
) {
throw new S3ClusterRemoteWorkerArtifactStoreError(
'integrity_mismatch',
);
}
}
}
@@ -0,0 +1,45 @@
// Artifact owns lazy production binding without widening the Worker runtime port.
import {
createS3ClusterRemoteWorkerArtifactClient,
S3ClusterRemoteWorkerArtifactStore,
} from './s3ArtifactStore';
import type { ClusterRemoteWorkerArtifactStore } from '../remote-execution/remoteWorkerCompletionService';
import type { ClusterWorkerArtifactS3Config } from '../worker-ingress/workerIngressConfig';
export interface ClusterWorkerArtifactBinding {
readonly store: ClusterRemoteWorkerArtifactStore;
close(): Promise<void>;
}
export function createClusterWorkerArtifactBinding(
config: ClusterWorkerArtifactS3Config,
): Readonly<ClusterWorkerArtifactBinding> {
if (!config || typeof config !== 'object' || Array.isArray(config)) {
throw new TypeError('Cluster Worker Artifact binding config is invalid');
}
const client = createS3ClusterRemoteWorkerArtifactClient({
region: config.region,
...(config.endpoint === undefined
? {}
: { endpoint: config.endpoint }),
forcePathStyle: config.forcePathStyle,
});
const store = new S3ClusterRemoteWorkerArtifactStore({
client,
bucket: config.bucket,
...(config.prefix === undefined ? {} : { prefix: config.prefix }),
...(config.expectedBucketOwner === undefined
? {}
: { expectedBucketOwner: config.expectedBucketOwner }),
encryption: config.encryption,
});
let closed = false;
return Object.freeze({
store,
async close() {
if (closed) return;
closed = true;
client.destroy();
},
});
}
@@ -0,0 +1,253 @@
// Authentication owns credential verification and bounded Principal issuance.
import { createHmac, timingSafeEqual } from 'node:crypto';
import {
ApiCredentialUnavailableError,
LEGACY_API_CREDENTIAL_PEPPER_KEY_ID,
assertApiCredentialPepperKeyId,
normalizeApiCredentialRecord,
type ApiCredentialRepository,
} from '@qinglong/runtime-core/api-credential';
import {
normalizeSecurityPrincipal,
type SecurityPrincipal,
} from '@qinglong/runtime-core/security';
import type { ClusterControlAdmissionMetadata } from '../transport/httpSurface';
import type { ClusterControlRequestAuthenticator } from '../transport/admissionPipeline';
export const CLUSTER_CONTROL_API_CREDENTIAL_LIMITS = Object.freeze({
principalTtlMs: 60_000,
maxPrincipalTtlMs: 300_000,
secretBytes: 32,
});
export class ClusterControlApiCredentialConfigurationError extends TypeError {
constructor(message: string) {
super(
`Cluster-control API credential configuration is invalid: ${message}`,
);
this.name = 'ClusterControlApiCredentialConfigurationError';
}
}
export class ClusterControlApiCredentialUnavailableError extends Error {
readonly code = 'CLUSTER_CONTROL_API_CREDENTIAL_UNAVAILABLE';
constructor() {
super('Cluster-control API credential authentication is unavailable');
this.name = 'ClusterControlApiCredentialUnavailableError';
}
}
export interface ClusterControlApiCredentialAuthenticatorOptions {
readonly principalTtlMs?: number;
readonly pepperKeyId?: string;
readonly now?: () => number;
}
const AUTHORIZATION_PATTERN =
/^Bearer ql3c_([A-Za-z0-9][A-Za-z0-9._:-]{0,63})_([A-Za-z0-9_-]{43})$/;
const PEPPER_PATTERN = /^[A-Za-z0-9_-]{43}$/;
const DIGEST_DOMAIN = Buffer.from('qinglong-api-credential-v1\0', 'utf8');
function decodeSecret(name: string, value: string): Buffer {
if (typeof value !== 'string' || !PEPPER_PATTERN.test(value)) {
throw new ClusterControlApiCredentialConfigurationError(
`${name} must be canonical base64url for 32 bytes`,
);
}
const decoded = Buffer.from(value, 'base64url');
if (
decoded.byteLength !== CLUSTER_CONTROL_API_CREDENTIAL_LIMITS.secretBytes ||
decoded.toString('base64url') !== value
) {
throw new ClusterControlApiCredentialConfigurationError(
`${name} must be canonical base64url for 32 bytes`,
);
}
return decoded;
}
export function assertClusterControlApiCredentialPepper(value: string): void {
const decoded = decodeSecret('pepper', value);
decoded.fill(0);
}
function principalTtl(value: number | undefined): number {
const resolved =
value ?? CLUSTER_CONTROL_API_CREDENTIAL_LIMITS.principalTtlMs;
if (
!Number.isSafeInteger(resolved) ||
resolved < 1_000 ||
resolved > CLUSTER_CONTROL_API_CREDENTIAL_LIMITS.maxPrincipalTtlMs
) {
throw new ClusterControlApiCredentialConfigurationError(
'principalTtlMs is invalid',
);
}
return resolved;
}
function digest(pepper: Buffer, credentialId: string, secret: Buffer): Buffer {
return createHmac('sha256', pepper)
.update(DIGEST_DOMAIN)
.update(credentialId, 'utf8')
.update('\0', 'utf8')
.update(secret)
.digest();
}
export function apiCredentialSecretDigest(
pepperBase64Url: string,
credentialId: string,
secretBase64Url: string,
): string {
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/.test(credentialId)) {
throw new ClusterControlApiCredentialConfigurationError(
'credentialId is invalid',
);
}
const pepper = decodeSecret('pepper', pepperBase64Url);
const secret = decodeSecret('secret', secretBase64Url);
let result: Buffer | undefined;
try {
result = digest(pepper, credentialId, secret);
return result.toString('hex');
} finally {
result?.fill(0);
pepper.fill(0);
secret.fill(0);
}
}
function parseAuthorization(
metadata: ClusterControlAdmissionMetadata,
): { readonly credentialId: string; readonly secret: Buffer } | null {
const value = metadata.headers.authorization;
if (typeof value !== 'string') return null;
const match = AUTHORIZATION_PATTERN.exec(value);
if (!match) return null;
let secret: Buffer;
try {
secret = decodeSecret('bearer secret', match[2]!);
} catch {
return null;
}
return Object.freeze({ credentialId: match[1]!, secret });
}
export function createClusterControlApiCredentialAuthenticator(
repository: ApiCredentialRepository,
pepperBase64Url: string,
options: ClusterControlApiCredentialAuthenticatorOptions = {},
): ClusterControlRequestAuthenticator {
if (!repository || typeof repository.resolve !== 'function') {
throw new ClusterControlApiCredentialConfigurationError(
'repository is invalid',
);
}
if (!options || typeof options !== 'object' || Array.isArray(options)) {
throw new ClusterControlApiCredentialConfigurationError(
'options are invalid',
);
}
const keys = Object.keys(options);
if (
keys.some(
(key) =>
key !== 'principalTtlMs' && key !== 'pepperKeyId' && key !== 'now',
)
) {
throw new ClusterControlApiCredentialConfigurationError(
'options shape is invalid',
);
}
if (options.now !== undefined && typeof options.now !== 'function') {
throw new ClusterControlApiCredentialConfigurationError('now is invalid');
}
const pepperKeyId =
options.pepperKeyId ?? LEGACY_API_CREDENTIAL_PEPPER_KEY_ID;
try {
assertApiCredentialPepperKeyId(pepperKeyId);
} catch {
throw new ClusterControlApiCredentialConfigurationError(
'pepperKeyId is invalid',
);
}
const pepper = decodeSecret('pepper', pepperBase64Url);
const ttlMs = principalTtl(options.principalTtlMs);
const now = options.now ?? Date.now;
return Object.freeze({
async authenticate(
metadata: ClusterControlAdmissionMetadata,
): Promise<Readonly<SecurityPrincipal> | null> {
const parsed = parseAuthorization(metadata);
if (!parsed) return null;
const presentedDigest = digest(
pepper,
parsed.credentialId,
parsed.secret,
);
parsed.secret.fill(0);
let candidate;
try {
candidate = await repository.resolve(parsed.credentialId);
} catch (error) {
presentedDigest.fill(0);
if (error instanceof ApiCredentialUnavailableError) {
throw new ClusterControlApiCredentialUnavailableError();
}
throw new ClusterControlApiCredentialUnavailableError();
}
if (metadata.signal.aborted) {
presentedDigest.fill(0);
throw new ClusterControlApiCredentialUnavailableError();
}
let record;
try {
record = candidate ? normalizeApiCredentialRecord(candidate) : null;
} catch {
presentedDigest.fill(0);
throw new ClusterControlApiCredentialUnavailableError();
}
if (record && record.pepperKeyId !== pepperKeyId) {
presentedDigest.fill(0);
throw new ClusterControlApiCredentialUnavailableError();
}
const storedDigest = record
? Buffer.from(record.secretDigest, 'hex')
: Buffer.alloc(32);
const matches = timingSafeEqual(presentedDigest, storedDigest);
presentedDigest.fill(0);
storedDigest.fill(0);
if (!record || !matches) return null;
const nowMs = now();
if (
!Number.isSafeInteger(nowMs) ||
nowMs < 0 ||
record.state !== 'active' ||
record.subjectStatus !== 'active' ||
record.notBeforeAtMs > nowMs ||
record.expiresAtMs <= nowMs
) {
return null;
}
const expiresAtMs = Math.min(record.expiresAtMs, nowMs + ttlMs);
try {
return normalizeSecurityPrincipal(
{
subject: record.subject,
authenticationId: `api_credential:${record.credentialId}:${record.version}`,
authenticatedAtMs: nowMs,
expiresAtMs,
assurance:
record.subject.type === 'user' ? 'single_factor' : 'service',
},
nowMs,
);
} catch {
throw new ClusterControlApiCredentialUnavailableError();
}
},
});
}
@@ -0,0 +1,244 @@
// Authentication owns its bounded pre-body overload shield.
import { createHmac, randomBytes } from 'node:crypto';
import { performance } from 'node:perf_hooks';
export interface ClusterControlAuthenticationShieldOptions {
readonly windowMs: number;
readonly maxRequestsPerPeer: number;
readonly maxRequestsGlobal: number;
readonly maxTrackedPeers: number;
readonly now?: () => number;
}
export type ClusterControlAuthenticationShieldRejectionReason =
| 'capacity'
| 'clock'
| 'global'
| 'peer';
export type ClusterControlAuthenticationShieldResult =
| {
readonly allowed: true;
/**
* Returns this provisional attempt budget after the pre-body admission
* preflight has succeeded. Idempotent and scoped to the exact windows
* consumed by this result.
*/
refund(): void;
}
| {
readonly allowed: false;
readonly reason: ClusterControlAuthenticationShieldRejectionReason;
readonly retryAfterMs: number;
};
export interface ClusterControlAuthenticationShield {
consume(
peerAddress: string | undefined,
): ClusterControlAuthenticationShieldResult;
close(): void;
}
interface PeerWindow {
readonly startedAt: number;
readonly count: number;
}
const FINGERPRINT_KEY_BYTES = 32;
const MAX_PEER_ADDRESS_BYTES = 128;
const MAX_PRUNE_PER_ATTEMPT = 64;
const UNKNOWN_PEER = '<unknown-transport-peer>';
function normalizedPeerAddress(peerAddress: string | undefined): string {
if (
typeof peerAddress !== 'string' ||
peerAddress.length === 0 ||
Buffer.byteLength(peerAddress) > MAX_PEER_ADDRESS_BYTES ||
/[\0\r\n]/.test(peerAddress)
) {
return UNKNOWN_PEER;
}
return peerAddress;
}
function remainingWindow(
now: number,
startedAt: number,
windowMs: number,
): number {
return Math.max(1, Math.ceil(windowMs - (now - startedAt)));
}
/**
* Creates a process-local overload shield for authentication attempts. It is
* deliberately not an authorization or distributed quota authority: every
* cluster-control replica owns a bounded, disposable window.
*/
export function createClusterControlAuthenticationShield(
options: ClusterControlAuthenticationShieldOptions,
): ClusterControlAuthenticationShield {
const now = options.now ?? (() => performance.now());
const fingerprintKey = randomBytes(FINGERPRINT_KEY_BYTES);
const peers = new Map<string, PeerWindow>();
let globalWindow: PeerWindow | undefined;
let lastNow = 0;
let closed = false;
const fingerprint = (peerAddress: string | undefined): string =>
createHmac('sha256', fingerprintKey)
.update('qinglong.cluster-control.authentication-peer\0')
.update(normalizedPeerAddress(peerAddress))
.digest('base64url');
const pruneExpired = (currentTime: number): void => {
let scanned = 0;
for (const [key, window] of peers) {
if (scanned >= MAX_PRUNE_PER_ATTEMPT) return;
scanned += 1;
if (currentTime - window.startedAt >= options.windowMs) {
peers.delete(key);
}
}
};
const accepted = (
peer: string,
peerStartedAt: number,
globalStartedAt: number,
): ClusterControlAuthenticationShieldResult => {
let completed = false;
return Object.freeze({
allowed: true as const,
refund() {
if (completed || closed) return;
completed = true;
if (
globalWindow?.startedAt === globalStartedAt &&
globalWindow.count > 0
) {
globalWindow = {
startedAt: globalWindow.startedAt,
count: globalWindow.count - 1,
};
}
const currentPeerWindow = peers.get(peer);
if (
currentPeerWindow?.startedAt === peerStartedAt &&
currentPeerWindow.count > 0
) {
if (currentPeerWindow.count === 1) peers.delete(peer);
else {
peers.set(peer, {
startedAt: currentPeerWindow.startedAt,
count: currentPeerWindow.count - 1,
});
}
}
},
});
};
return {
consume(peerAddress) {
if (closed) {
return Object.freeze({
allowed: false,
reason: 'clock',
retryAfterMs: options.windowMs,
});
}
let currentTime: number;
try {
currentTime = now();
} catch {
return Object.freeze({
allowed: false,
reason: 'clock',
retryAfterMs: options.windowMs,
});
}
if (
!Number.isFinite(currentTime) ||
currentTime < 0 ||
currentTime < lastNow
) {
return Object.freeze({
allowed: false,
reason: 'clock',
retryAfterMs: options.windowMs,
});
}
lastNow = currentTime;
if (
!globalWindow ||
currentTime - globalWindow.startedAt >= options.windowMs
) {
globalWindow = { startedAt: currentTime, count: 0 };
}
if (globalWindow.count >= options.maxRequestsGlobal) {
return Object.freeze({
allowed: false,
reason: 'global',
retryAfterMs: remainingWindow(
currentTime,
globalWindow.startedAt,
options.windowMs,
),
});
}
globalWindow = {
startedAt: globalWindow.startedAt,
count: globalWindow.count + 1,
};
const peer = fingerprint(peerAddress);
let peerWindow = peers.get(peer);
if (
peerWindow &&
currentTime - peerWindow.startedAt >= options.windowMs
) {
peers.delete(peer);
peerWindow = undefined;
}
if (peerWindow) {
if (peerWindow.count >= options.maxRequestsPerPeer) {
return Object.freeze({
allowed: false,
reason: 'peer',
retryAfterMs: remainingWindow(
currentTime,
peerWindow.startedAt,
options.windowMs,
),
});
}
peers.delete(peer);
peers.set(peer, {
startedAt: peerWindow.startedAt,
count: peerWindow.count + 1,
});
return accepted(peer, peerWindow.startedAt, globalWindow.startedAt);
}
if (peers.size >= options.maxTrackedPeers) pruneExpired(currentTime);
if (peers.size >= options.maxTrackedPeers) {
return Object.freeze({
allowed: false,
reason: 'capacity',
retryAfterMs: options.windowMs,
});
}
peers.set(peer, { startedAt: currentTime, count: 1 });
return accepted(peer, currentTime, globalWindow.startedAt);
},
close() {
if (closed) return;
closed = true;
peers.clear();
globalWindow = undefined;
fingerprintKey.fill(0);
},
};
}
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env node
import {
runProductionClusterControlProcess,
type ClusterControlProcessEvent,
type ClusterControlProcessSignal,
type ClusterControlProcessSignalSource,
} from './production-process/processApplication';
const USAGE = 'Usage: ql3-cluster-control';
const nodeSignals: ClusterControlProcessSignalSource = Object.freeze({
subscribe(
listener: (signal: ClusterControlProcessSignal) => void,
) {
const handlers: Readonly<
Record<ClusterControlProcessSignal, () => void>
> = Object.freeze({
SIGINT: () => listener('SIGINT'),
SIGTERM: () => listener('SIGTERM'),
});
process.once('SIGINT', handlers.SIGINT);
process.once('SIGTERM', handlers.SIGTERM);
return () => {
process.off('SIGINT', handlers.SIGINT);
process.off('SIGTERM', handlers.SIGTERM);
};
},
});
function emit(record: ClusterControlProcessEvent): void {
process.stdout.write(`${JSON.stringify(record)}\n`);
}
function failureFact(error: unknown): Readonly<Record<string, unknown>> {
const candidate = error as {
readonly name?: unknown;
readonly code?: unknown;
};
return Object.freeze({
schemaVersion: 1,
component: 'qinglong3-cluster-control',
level: 'error',
event: 'process_failed',
name:
typeof candidate?.name === 'string' && candidate.name.length <= 128
? candidate.name
: 'Error',
...(typeof candidate?.code === 'string' && candidate.code.length <= 128
? { code: candidate.code }
: {}),
});
}
async function main(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_CLUSTER_CONTROL_CLI_USAGE_INVALID',
message: USAGE,
})}\n`,
);
process.exitCode = 64;
return;
}
try {
const stopResult = await runProductionClusterControlProcess({
environment: process.env,
signals: nodeSignals,
emit,
});
if (stopResult !== 'stopped') process.exitCode = 1;
} catch (error) {
process.stderr.write(`${JSON.stringify(failureFact(error))}\n`);
process.exitCode = 1;
}
}
void main(process.argv.slice(2));
@@ -0,0 +1,93 @@
// Database owns the one-way Pool failure to admission-withdrawal fence.
export type ClusterControlAvailabilityStatus =
| 'available'
| 'unavailable'
| 'disposed';
export type ClusterControlUnavailableListener = (
error: Error,
) => void | Promise<void>;
export interface ClusterControlAvailabilitySource {
subscribe(listener: ClusterControlUnavailableListener): () => void;
}
export type ClusterControlAvailabilitySignalResult =
| 'signaled'
| 'already_unavailable'
| 'disposed';
/**
* A bounded one-way bridge from pg.Pool availability errors to the application
* admission owner. It deliberately has one listener and no timer, retry loop,
* error history or path back to available.
*/
export class ClusterControlAvailabilityFence
implements ClusterControlAvailabilitySource
{
private currentStatus: ClusterControlAvailabilityStatus = 'available';
private listener: ClusterControlUnavailableListener | undefined;
private reason: Error | undefined;
private notification: Promise<void> | undefined;
get status(): ClusterControlAvailabilityStatus {
return this.currentStatus;
}
subscribe(listener: ClusterControlUnavailableListener): () => void {
if (typeof listener !== 'function') {
throw new TypeError('Cluster-control availability listener is invalid');
}
if (this.currentStatus === 'disposed') {
throw new Error('Cluster-control availability fence is disposed');
}
if (this.listener) {
throw new Error('Cluster-control availability listener is already bound');
}
this.listener = listener;
if (this.currentStatus === 'unavailable') {
// An early signal has already returned to its producer. The subscriber
// owns the delayed notification, so contain its rejection here.
void this.notify().catch(() => undefined);
}
let subscribed = true;
return () => {
if (!subscribed) return;
subscribed = false;
if (this.listener === listener) this.listener = undefined;
};
}
signal(error: Error): Promise<ClusterControlAvailabilitySignalResult> {
if (!(error instanceof Error)) {
return Promise.reject(
new TypeError('Cluster-control availability error is invalid'),
);
}
if (this.currentStatus === 'disposed') return Promise.resolve('disposed');
if (this.currentStatus === 'unavailable') {
return (this.notification ?? Promise.resolve()).then(
() => 'already_unavailable' as const,
);
}
this.currentStatus = 'unavailable';
this.reason = error;
return this.notify().then(() => 'signaled' as const);
}
dispose(): void {
if (this.currentStatus === 'disposed') return;
this.currentStatus = 'disposed';
this.listener = undefined;
this.reason = undefined;
}
private notify(): Promise<void> {
if (this.notification) return this.notification;
if (!this.listener || !this.reason) return Promise.resolve();
const listener = this.listener;
const reason = this.reason;
this.notification = Promise.resolve().then(() => listener(reason));
return this.notification;
}
}
@@ -0,0 +1,88 @@
import type { ClusterControlAdmissionResponse } from '../../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../../transport/routeRegistry';
export const CLUSTER_PLUGIN_PACKAGE_PROMPT_CATALOG_RESPONSE_SCHEMA =
'qinglong/plugin-package-prompt-catalog@v1' as const;
export interface ClusterPluginPackagePromptCatalogCapability {
inspect(
projectId: string,
packageName: string,
): Promise<
Readonly<{
schema: typeof CLUSTER_PLUGIN_PACKAGE_PROMPT_CATALOG_RESPONSE_SCHEMA;
projectId: string;
packageName: string;
found: boolean;
publicationState: 'active' | 'withdrawn' | 'absent' | null;
prompts: readonly Readonly<{
id: string;
name: string;
description: string | null;
parameters: readonly Readonly<{
name: string;
description: string | null;
required: boolean;
}>[];
}>[];
}>
>;
}
const PACKAGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
export function createClusterControlPluginPackagePromptCatalogRoute(
capability: ClusterPluginPackagePromptCatalogCapability,
): Readonly<ClusterControlRouteDefinition> {
if (!capability || typeof capability.inspect !== 'function') {
throw new TypeError('Cluster-control Prompt catalog capability is invalid');
}
return Object.freeze({
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/packages/{packageName}/prompts',
operationId: 'prompt.read',
permission: 'model.invoke',
projectParameter: 'projectId' as const,
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
if (
authorized.request.body !== null ||
authorized.projectId === null ||
typeof parameters.packageName !== 'string' ||
!PACKAGE_NAME.test(parameters.packageName)
) {
return response(400, { code: 'invalid_prompt_catalog_request' });
}
try {
const result = await capability.inspect(
authorized.projectId,
parameters.packageName,
);
if (
result.schema !==
CLUSTER_PLUGIN_PACKAGE_PROMPT_CATALOG_RESPONSE_SCHEMA ||
result.projectId !== authorized.projectId ||
result.packageName !== parameters.packageName
) {
return response(503, { code: 'prompt_catalog_unavailable' });
}
return response(200, result);
} catch {
return response(503, { code: 'prompt_catalog_unavailable' });
}
},
});
}
@@ -0,0 +1,150 @@
import { randomUUID } from 'node:crypto';
import {
normalizeSecurityAuditRecord,
type SecurityAuditRecord,
} from '@qinglong/runtime-core/security-audit';
import type {
SecurityPolicyFence,
SecuritySubject,
} from '@qinglong/runtime-core/security';
import type { ClusterControlAdmissionResponse } from '../../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../../transport/routeRegistry';
export const CLUSTER_CONTROL_PLUGIN_PACKAGE_PROMPT_EXECUTION_INSPECTION_ROUTE =
Object.freeze({
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/packages/{packageName}/prompts/{promptId}/executions/{executionRequestId}',
operationId: 'prompt.execution.read',
permission: 'run.read',
projectParameter: 'projectId',
});
export interface ClusterPluginPackagePromptExecutionInspectionRouteOptions {
readonly now?: () => number;
readonly createEventId?: () => string;
}
export interface ClusterPluginPackagePromptExecutionInspectionCapability {
inspectAuthorized(input: Readonly<{
projectId: string;
packageName: string;
promptId: string;
executionRequestId: string;
actor: Readonly<SecuritySubject>;
fence: Readonly<SecurityPolicyFence>;
audit: Readonly<SecurityAuditRecord>;
}>): Promise<Readonly<{ found: boolean }>>;
}
const PACKAGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
const PROMPT_ID = /^[a-z][a-z0-9-]{0,62}$/;
const IDENTITY = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const UUID_V4 =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
function errorCode(error: unknown): string | null {
return error &&
typeof error === 'object' &&
'code' in error &&
typeof error.code === 'string'
? error.code
: null;
}
/** Exact, content-free recovery read keyed by the caller-known requestId. */
export function createClusterControlPluginPackagePromptExecutionInspectionRoute(
capability: ClusterPluginPackagePromptExecutionInspectionCapability,
options: ClusterPluginPackagePromptExecutionInspectionRouteOptions = {},
): Readonly<ClusterControlRouteDefinition> {
if (
!capability ||
typeof capability.inspectAuthorized !== 'function' ||
(options.now !== undefined && typeof options.now !== 'function') ||
(options.createEventId !== undefined &&
typeof options.createEventId !== 'function')
) {
throw new TypeError(
'Cluster-control Prompt execution inspection capability is invalid',
);
}
const now = options.now ?? Date.now;
const createEventId = options.createEventId ?? randomUUID;
return Object.freeze({
...CLUSTER_CONTROL_PLUGIN_PACKAGE_PROMPT_EXECUTION_INSPECTION_ROUTE,
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
const observedAtMs = now();
const auditEventId = createEventId();
if (authorized.request.body !== null) {
return response(400, { code: 'invalid_request_body' });
}
if (
authorized.projectId === null ||
typeof parameters.packageName !== 'string' ||
!PACKAGE_NAME.test(parameters.packageName) ||
typeof parameters.promptId !== 'string' ||
!PROMPT_ID.test(parameters.promptId) ||
typeof parameters.executionRequestId !== 'string' ||
!IDENTITY.test(parameters.executionRequestId) ||
!authorized.policyFence ||
authorized.policyFence.bindingVersion === null ||
!Number.isSafeInteger(observedAtMs) ||
observedAtMs < 0 ||
typeof auditEventId !== 'string' ||
!UUID_V4.test(auditEventId)
) {
return response(503, { code: 'prompt_execution_inspection_unavailable' });
}
try {
const result = await capability.inspectAuthorized({
projectId: authorized.projectId,
packageName: parameters.packageName,
promptId: parameters.promptId,
executionRequestId: parameters.executionRequestId,
actor: authorized.principal.subject,
fence: {
projectVersion: authorized.policyFence.projectVersion,
bindingVersion: authorized.policyFence.bindingVersion,
},
audit: normalizeSecurityAuditRecord({
eventId: auditEventId,
requestId: authorized.request.requestId,
operationId: 'prompt.execution.read',
projectId: authorized.projectId,
subject: authorized.principal.subject,
authenticationId: authorized.principal.authenticationId,
outcome: 'allowed',
reasons: ['project_policy_allowed'],
fence: authorized.policyFence,
occurredAtMs: observedAtMs,
}),
});
return result.found
? response(200, { ...result })
: response(404, { code: 'prompt_execution_not_found' });
} catch (error) {
return errorCode(error) ===
'PLUGIN_PACKAGE_PROMPT_EXECUTION_INSPECTION_AUTHORIZATION_FENCE_CONFLICT'
? response(409, { code: 'authorization_fence_conflict' })
: response(503, {
code: 'prompt_execution_inspection_unavailable',
});
}
},
});
}
@@ -0,0 +1,277 @@
// Plugin Package Prompt owns request-keyed durable output recovery.
import type { SecurityPrincipal } from '@qinglong/runtime-core/security';
import type { ClusterControlAdmissionResponse } from '../../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../../transport/routeRegistry';
export const CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_OUTPUT_READ_RESPONSE_SCHEMA =
'qinglong/cluster-plugin-package-prompt-execution-output-read-response@v1' as const;
export const CLUSTER_CONTROL_PLUGIN_PACKAGE_PROMPT_EXECUTION_OUTPUT_READ_ROUTE =
Object.freeze({
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/packages/{packageName}/prompts/{promptId}/executions/{executionRequestId}/output',
operationId: 'prompt.execution.output.read',
permission: 'artifact.read',
projectParameter: 'projectId',
});
export interface ClusterPluginPackagePromptExecutionOutputReadCapability {
read(command: Readonly<{
principal: Readonly<SecurityPrincipal>;
projectId: string;
packageName: string;
promptId: string;
executionRequestId: string;
}>): Promise<unknown>;
}
const IDENTITY = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$/;
const PACKAGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
const PROMPT_ID = /^[a-z][a-z0-9-]{0,62}$/;
const MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/;
const DIGEST = /^[0-9a-f]{64}$/;
const KEY_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
const FINISH_REASONS = new Set([
'stop',
'length',
'content_filter',
'tool_call',
'unknown',
]);
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
function exactRecord(
value: unknown,
required: readonly string[],
optional: readonly string[] = [],
): Record<string, unknown> | null {
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
const record = value as Record<string, unknown>;
const keys = Object.keys(record).sort();
const expected = [
...required,
...optional.filter((key) => key in record),
].sort();
return keys.length === expected.length &&
keys.every((key, index) => key === expected[index])
? record
: null;
}
function nonNegativeInteger(value: unknown): value is number {
return Number.isSafeInteger(value) && (value as number) >= 0;
}
function exactTarget(
value: Record<string, unknown>,
expected: Readonly<{
projectId: string;
packageName: string;
promptId: string;
executionRequestId: string;
}>,
): boolean {
return (
value.projectId === expected.projectId &&
value.packageName === expected.packageName &&
value.promptId === expected.promptId &&
value.executionRequestId === expected.executionRequestId
);
}
function availableView(
value: unknown,
expected: Readonly<{
projectId: string;
packageName: string;
promptId: string;
executionRequestId: string;
}>,
): Readonly<Record<string, unknown>> | null {
const envelope = exactRecord(value, [
'executionRequestId',
'packageName',
'projectId',
'promptId',
'reference',
'result',
'schema',
'status',
]);
if (
!envelope ||
envelope.schema !==
'qinglong/plugin-package-prompt-execution-output-read-result@v1' ||
envelope.status !== 'available' ||
!exactTarget(envelope, expected)
) {
return null;
}
const reference = exactRecord(envelope.reference, [
'algorithm',
'artifactDigest',
'artifactId',
'contentDigest',
'invocationId',
'keyId',
'outputBytes',
'projectId',
'retentionEligibleAtMs',
'retentionPolicyDigest',
'runId',
'schema',
'stepRunId',
]);
const result = exactRecord(envelope.result, [
'finishReason',
'model',
'provider',
'text',
'usage',
]);
const usage = result
? exactRecord(
result.usage,
['inputTokens', 'outputTokens', 'totalTokens'],
['costMicros'],
)
: null;
if (
!reference ||
!result ||
!usage ||
reference.schema !==
'qinglong/plugin-package-prompt-output-artifact-reference@v1' ||
reference.algorithm !== 'aes-256-gcm' ||
reference.projectId !== expected.projectId ||
typeof reference.runId !== 'string' ||
!RUN_ID.test(reference.runId) ||
typeof reference.artifactId !== 'string' ||
!IDENTITY.test(reference.artifactId) ||
typeof reference.artifactDigest !== 'string' ||
!DIGEST.test(reference.artifactDigest) ||
typeof reference.stepRunId !== 'string' ||
!IDENTITY.test(reference.stepRunId) ||
typeof reference.invocationId !== 'string' ||
!IDENTITY.test(reference.invocationId) ||
typeof reference.contentDigest !== 'string' ||
!DIGEST.test(reference.contentDigest) ||
!nonNegativeInteger(reference.outputBytes) ||
reference.outputBytes > 1024 * 1024 ||
typeof reference.retentionPolicyDigest !== 'string' ||
!DIGEST.test(reference.retentionPolicyDigest) ||
!nonNegativeInteger(reference.retentionEligibleAtMs) ||
typeof reference.keyId !== 'string' ||
!KEY_ID.test(reference.keyId) ||
typeof result.provider !== 'string' ||
!MODEL_ID.test(result.provider) ||
typeof result.model !== 'string' ||
!MODEL_ID.test(result.model) ||
typeof result.text !== 'string' ||
Buffer.byteLength(result.text, 'utf8') > 1024 * 1024 ||
!FINISH_REASONS.has(result.finishReason as string) ||
!nonNegativeInteger(usage.inputTokens) ||
!nonNegativeInteger(usage.outputTokens) ||
!nonNegativeInteger(usage.totalTokens) ||
usage.totalTokens !== usage.inputTokens + usage.outputTokens ||
(usage.costMicros !== undefined && !nonNegativeInteger(usage.costMicros))
) {
return null;
}
return Object.freeze({
schema:
CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_OUTPUT_READ_RESPONSE_SCHEMA,
status: 'available',
...expected,
reference: Object.freeze({ ...reference }),
result: Object.freeze({
provider: result.provider,
model: result.model,
text: result.text,
finishReason: result.finishReason,
usage: Object.freeze({ ...usage }),
}),
});
}
export function createClusterControlPluginPackagePromptExecutionOutputReadRoute(
capability: ClusterPluginPackagePromptExecutionOutputReadCapability,
): Readonly<ClusterControlRouteDefinition> {
if (!capability || typeof capability.read !== 'function') {
throw new TypeError(
'Cluster-control Prompt execution output read capability is invalid',
);
}
return Object.freeze({
...CLUSTER_CONTROL_PLUGIN_PACKAGE_PROMPT_EXECUTION_OUTPUT_READ_ROUTE,
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
if (
authorized.request.body !== null ||
authorized.projectId === null ||
typeof parameters.packageName !== 'string' ||
!PACKAGE_NAME.test(parameters.packageName) ||
typeof parameters.promptId !== 'string' ||
!PROMPT_ID.test(parameters.promptId) ||
typeof parameters.executionRequestId !== 'string' ||
!IDENTITY.test(parameters.executionRequestId)
) {
return response(400, {
code: 'invalid_prompt_execution_output_read_request',
});
}
const expected = Object.freeze({
projectId: authorized.projectId,
packageName: parameters.packageName,
promptId: parameters.promptId,
executionRequestId: parameters.executionRequestId,
});
try {
const result = await capability.read({
principal: authorized.principal,
...expected,
});
const notFound = exactRecord(result, [
'executionRequestId',
'packageName',
'projectId',
'promptId',
'schema',
'status',
]);
if (
notFound &&
notFound.schema ===
'qinglong/plugin-package-prompt-execution-output-read-result@v1' &&
notFound.status === 'not_found' &&
exactTarget(notFound, expected)
) {
return response(404, { code: 'prompt_execution_output_not_found' });
}
const view = availableView(result, expected);
return view
? response(200, view)
: response(503, {
code: 'prompt_execution_output_read_unavailable',
});
} catch {
return response(503, {
code: 'prompt_execution_output_read_unavailable',
});
}
},
});
}
@@ -0,0 +1,400 @@
// Plugin Package Prompt owns bounded, Policy-fenced model execution admission.
import { randomUUID } from 'node:crypto';
import type { SecurityPrincipal } from '@qinglong/runtime-core/security';
import type { ClusterControlAdmissionResponse } from '../../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../../transport/routeRegistry';
export const CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_REQUEST_SCHEMA =
'qinglong/cluster-plugin-package-prompt-execution-request@v2' as const;
export const CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_RESPONSE_SCHEMA =
'qinglong/cluster-plugin-package-prompt-execution-response@v2' as const;
export const CLUSTER_CONTROL_PLUGIN_PACKAGE_PROMPT_EXECUTION_ROUTE =
Object.freeze({
method: 'POST' as const,
path: '/api/v3/projects/{projectId}/packages/{packageName}/prompts/{promptId}/executions',
operationId: 'prompt.execute',
permission: 'model.invoke',
projectParameter: 'projectId',
});
export const CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_LIMITS = Object.freeze({
maxParameters: 64,
maxParameterValueBytes: 64 * 1024,
maxOutputTokens: 32_768,
maxExecutionMs: 120_000,
minOutputRetentionMs: 60 * 60_000,
maxOutputRetentionMs: 365 * 24 * 60 * 60_000,
});
export type ClusterPluginPackagePromptOutputIntent =
| Readonly<{ mode: 'live_only' }>
| Readonly<{
mode: 'durable_artifact';
retentionPolicy: Readonly<{
revision: string;
retentionMs: number;
}>;
}>;
export interface ClusterPluginPackagePromptExecutionCommand {
readonly projectId: string;
readonly packageName: string;
readonly promptId: string;
readonly requestId: string;
readonly traceId: string;
readonly auditEventId: string;
readonly principal: Readonly<SecurityPrincipal>;
readonly policyFence: Readonly<{
readonly projectVersion: number;
readonly bindingVersion: number;
}>;
readonly parameters: Readonly<Record<string, string>>;
readonly provider: string;
readonly model: string;
readonly maxOutputTokens: number;
readonly temperature?: number;
readonly deadlineAtMs: number;
readonly plannedAtMs: number;
readonly output?: Readonly<ClusterPluginPackagePromptOutputIntent>;
readonly signal: AbortSignal;
}
export interface ClusterPluginPackagePromptExecutionCapability {
execute(command: Readonly<ClusterPluginPackagePromptExecutionCommand>): Promise<
Readonly<{
readonly status: 'executed' | 'resumed' | 'existing';
readonly admission: Readonly<{
readonly requestId: string;
readonly invocationId: string;
readonly runId: string;
readonly stepRunId: string;
}>;
readonly finalization: Readonly<{ readonly runStatus: string }>;
readonly result: unknown | null;
readonly outputArtifact?: unknown;
}>
>;
}
export interface ClusterPluginPackagePromptExecutionRouteOptions {
readonly maxExecutionMs?: number;
readonly now?: () => number;
readonly createEventId?: () => string;
}
class InvalidPromptExecutionRequestError extends TypeError {}
const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const PACKAGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
const UUID_V4 =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
function invalid(): never {
throw new InvalidPromptExecutionRequestError();
}
function dataRecord(value: unknown): Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
(Object.getPrototypeOf(value) !== Object.prototype &&
Object.getPrototypeOf(value) !== null)
) {
return invalid();
}
return value as Record<string, unknown>;
}
function exactKeys(
value: Record<string, unknown>,
required: readonly string[],
optional: readonly string[] = [],
): void {
const allowed = new Set([...required, ...optional]);
const keys = Object.keys(value);
if (
required.some((key) => !keys.includes(key)) ||
keys.some((key) => !allowed.has(key))
) {
invalid();
}
}
function identifier(value: unknown): string {
if (typeof value !== 'string' || !IDENTIFIER.test(value)) return invalid();
return value;
}
function positiveInteger(value: unknown, maximum: number): number {
if (
typeof value !== 'number' ||
!Number.isSafeInteger(value) ||
value < 1 ||
value > maximum
) {
return invalid();
}
return value as number;
}
function parameters(value: unknown): Readonly<Record<string, string>> {
const record = dataRecord(value);
const names = Object.keys(record).sort();
if (
names.length > CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_LIMITS.maxParameters
) {
return invalid();
}
const normalized = Object.create(null) as Record<string, string>;
for (const name of names) {
const parameter = record[name];
if (
!/^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(name) ||
typeof parameter !== 'string' ||
Buffer.byteLength(parameter, 'utf8') >
CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_LIMITS.maxParameterValueBytes
) {
return invalid();
}
normalized[name] = parameter;
}
return Object.freeze(normalized);
}
function outputIntent(
value: unknown,
): Readonly<ClusterPluginPackagePromptOutputIntent> {
const output = dataRecord(value);
if (output.mode === 'live_only') {
exactKeys(output, ['mode']);
return Object.freeze({ mode: 'live_only' as const });
}
if (output.mode !== 'durable_artifact') return invalid();
exactKeys(output, ['mode', 'retentionPolicy']);
const retention = dataRecord(output.retentionPolicy);
exactKeys(retention, ['retentionMs', 'revision']);
if (
typeof retention.revision !== 'string' ||
!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(retention.revision) ||
!Number.isSafeInteger(retention.retentionMs) ||
(retention.retentionMs as number) <
CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_LIMITS.minOutputRetentionMs ||
(retention.retentionMs as number) >
CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_LIMITS.maxOutputRetentionMs
) {
return invalid();
}
return Object.freeze({
mode: 'durable_artifact' as const,
retentionPolicy: Object.freeze({
revision: retention.revision,
retentionMs: retention.retentionMs as number,
}),
});
}
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
function errorCode(error: unknown): string | null {
if (
!error ||
typeof error !== 'object' ||
!('code' in error) ||
typeof error.code !== 'string'
) {
return null;
}
return error.code;
}
function executionError(error: unknown): ClusterControlAdmissionResponse {
const code = errorCode(error);
if (
code === 'PLUGIN_PACKAGE_PROMPT_ADMISSION_NOT_ALLOWED' ||
code === 'PLUGIN_PACKAGE_PROMPT_ADMISSION_CONFLICT' ||
code === 'PLUGIN_PACKAGE_PROMPT_EXECUTION_IN_PROGRESS' ||
code === 'PLUGIN_PACKAGE_PROMPT_RESOLUTION_REQUIRED' ||
code === 'MODEL_INVOCATION_CONFLICT' ||
code === 'MODEL_INVOCATION_REPLAY_BLOCKED'
) {
return response(409, { code: 'prompt_execution_conflict' });
}
if (code === 'MODEL_GATEWAY_BUSY' || code === 'MODEL_PROJECT_QUOTA_EXCEEDED') {
return response(429, { code: 'prompt_execution_capacity_exceeded' });
}
if (code === 'MODEL_POLICY_DENIED' || code === 'MODEL_BUDGET_EXCEEDED') {
return response(422, { code: 'prompt_execution_policy_rejected' });
}
if (code === 'MODEL_INVOCATION_DEADLINE_EXCEEDED') {
return response(504, { code: 'prompt_execution_deadline_exceeded' });
}
if (code === 'MODEL_INVOCATION_ABORTED') {
return response(408, { code: 'prompt_execution_aborted' });
}
if (code === 'PLUGIN_PACKAGE_PROMPT_EXECUTION_PLAN_INVALID') {
return response(400, { code: 'invalid_prompt_execution_request' });
}
return response(503, { code: 'prompt_execution_unavailable' });
}
function parseBody(value: unknown, maximumExecutionMs: number) {
const body = dataRecord(value);
exactKeys(
body,
[
'schema',
'requestId',
'traceId',
'parameters',
'provider',
'model',
'maxOutputTokens',
'timeoutMs',
],
['output', 'temperature'],
);
if (body.schema !== CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_REQUEST_SCHEMA) {
return invalid();
}
const temperature = body.temperature;
const output =
body.output === undefined ? undefined : outputIntent(body.output);
if (
temperature !== undefined &&
(typeof temperature !== 'number' ||
!Number.isFinite(temperature) ||
temperature < 0 ||
temperature > 2)
) {
return invalid();
}
return Object.freeze({
requestId: identifier(body.requestId),
traceId: identifier(body.traceId),
parameters: parameters(body.parameters),
provider: identifier(body.provider),
model: identifier(body.model),
maxOutputTokens: positiveInteger(
body.maxOutputTokens,
CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_LIMITS.maxOutputTokens,
),
timeoutMs: positiveInteger(body.timeoutMs, maximumExecutionMs),
...(output === undefined ? {} : { output }),
...(temperature === undefined ? {} : { temperature }),
});
}
export function createClusterControlPluginPackagePromptExecutionRoute(
capability: ClusterPluginPackagePromptExecutionCapability,
options: ClusterPluginPackagePromptExecutionRouteOptions = {},
): Readonly<ClusterControlRouteDefinition> {
if (!capability || typeof capability.execute !== 'function') {
throw new TypeError('Cluster-control Prompt execution capability is invalid');
}
if (!options || typeof options !== 'object' || Array.isArray(options)) {
throw new TypeError('Cluster-control Prompt execution route options are invalid');
}
const maximumExecutionMs =
options.maxExecutionMs ??
CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_LIMITS.maxExecutionMs;
if (
!Number.isSafeInteger(maximumExecutionMs) ||
maximumExecutionMs < 1 ||
maximumExecutionMs >
CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_LIMITS.maxExecutionMs ||
(options.now !== undefined && typeof options.now !== 'function') ||
(options.createEventId !== undefined &&
typeof options.createEventId !== 'function')
) {
throw new TypeError('Cluster-control Prompt execution route options are invalid');
}
const now = options.now ?? Date.now;
const createEventId = options.createEventId ?? randomUUID;
return Object.freeze({
...CLUSTER_CONTROL_PLUGIN_PACKAGE_PROMPT_EXECUTION_ROUTE,
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
routeParameters: ClusterControlRouteParameters,
) {
let body;
try {
body = parseBody(authorized.request.body, maximumExecutionMs);
} catch {
return response(400, { code: 'invalid_prompt_execution_request' });
}
const projectId = authorized.projectId;
const packageName = routeParameters.packageName;
const promptId = routeParameters.promptId;
const fence = authorized.policyFence;
const plannedAtMs = now();
const auditEventId = createEventId();
if (
projectId === null ||
typeof packageName !== 'string' ||
!PACKAGE_NAME.test(packageName) ||
typeof promptId !== 'string' ||
!IDENTIFIER.test(promptId) ||
!fence ||
fence.bindingVersion === null ||
!Number.isSafeInteger(plannedAtMs) ||
plannedAtMs < 0 ||
typeof auditEventId !== 'string' ||
!UUID_V4.test(auditEventId)
) {
return response(503, { code: 'prompt_execution_unavailable' });
}
try {
const result = await capability.execute({
projectId,
packageName,
promptId,
requestId: body.requestId,
traceId: body.traceId,
auditEventId,
principal: authorized.principal,
policyFence: Object.freeze({
projectVersion: fence.projectVersion,
bindingVersion: fence.bindingVersion,
}),
parameters: body.parameters,
provider: body.provider,
model: body.model,
maxOutputTokens: body.maxOutputTokens,
...(body.temperature === undefined
? {}
: { temperature: body.temperature }),
...(body.output === undefined ? {} : { output: body.output }),
plannedAtMs,
deadlineAtMs: plannedAtMs + body.timeoutMs,
signal: authorized.request.signal,
});
return response(200, {
schema: CLUSTER_PLUGIN_PACKAGE_PROMPT_EXECUTION_RESPONSE_SCHEMA,
status: result.status,
replayed: result.status === 'existing',
requestId: result.admission.requestId,
invocationId: result.admission.invocationId,
runId: result.admission.runId,
stepRunId: result.admission.stepRunId,
runStatus: result.finalization.runStatus,
result: result.result,
...(result.outputArtifact === undefined
? {}
: { outputArtifact: result.outputArtifact }),
});
} catch (error) {
return executionError(error);
}
},
});
}
@@ -0,0 +1,232 @@
// Plugin Package Prompt owns its capability-free durable output projection.
import type { SecurityPrincipal } from '@qinglong/runtime-core/security';
import type { ClusterControlAdmissionResponse } from '../../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../../transport/routeRegistry';
export const CLUSTER_PLUGIN_PACKAGE_PROMPT_OUTPUT_READ_RESPONSE_SCHEMA =
'qinglong/cluster-plugin-package-prompt-output-read-response@v1' as const;
export const CLUSTER_CONTROL_PLUGIN_PACKAGE_PROMPT_OUTPUT_READ_ROUTE =
Object.freeze({
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/runs/{runId}/prompt-output-artifacts/{artifactId}',
operationId: 'prompt.output.read',
permission: 'artifact.read',
projectParameter: 'projectId',
allowedQuery: Object.freeze(['artifact_digest']),
});
export interface ClusterPluginPackagePromptOutputReadCapability {
read(command: Readonly<{
principal: Readonly<SecurityPrincipal>;
projectId: string;
runId: string;
artifactId: string;
artifactDigest: string;
}>): Promise<unknown>;
}
const IDENTITY = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$/;
const MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/;
const DIGEST = /^[0-9a-f]{64}$/;
const KEY_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
const FINISH_REASONS = new Set([
'stop',
'length',
'content_filter',
'tool_call',
'unknown',
]);
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
function exactRecord(
value: unknown,
required: readonly string[],
optional: readonly string[] = [],
): Record<string, unknown> | null {
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
const record = value as Record<string, unknown>;
const keys = Object.keys(record).sort();
const expected = [...required, ...optional.filter((key) => key in record)].sort();
return keys.length === expected.length &&
keys.every((key, index) => key === expected[index])
? record
: null;
}
function nonNegativeInteger(value: unknown): value is number {
return Number.isSafeInteger(value) && (value as number) >= 0;
}
function availableView(
value: unknown,
expected: Readonly<{
projectId: string;
runId: string;
artifactId: string;
artifactDigest: string;
}>,
): Readonly<Record<string, unknown>> | null {
const resultEnvelope = exactRecord(value, [
'schema',
'status',
'reference',
'result',
]);
if (
!resultEnvelope ||
resultEnvelope.schema !==
'qinglong/plugin-package-prompt-output-read-result@v1' ||
resultEnvelope.status !== 'available'
) {
return null;
}
const reference = exactRecord(resultEnvelope.reference, [
'algorithm',
'artifactDigest',
'artifactId',
'contentDigest',
'invocationId',
'keyId',
'outputBytes',
'projectId',
'retentionEligibleAtMs',
'retentionPolicyDigest',
'runId',
'schema',
'stepRunId',
]);
const result = exactRecord(resultEnvelope.result, [
'finishReason',
'model',
'provider',
'text',
'usage',
]);
const usage = result ? exactRecord(result.usage, [
'inputTokens',
'outputTokens',
'totalTokens',
], ['costMicros']) : null;
if (
!reference ||
!result ||
!usage ||
reference.schema !==
'qinglong/plugin-package-prompt-output-artifact-reference@v1' ||
reference.algorithm !== 'aes-256-gcm' ||
reference.projectId !== expected.projectId ||
reference.runId !== expected.runId ||
reference.artifactId !== expected.artifactId ||
reference.artifactDigest !== expected.artifactDigest ||
typeof reference.stepRunId !== 'string' ||
!IDENTITY.test(reference.stepRunId) ||
typeof reference.invocationId !== 'string' ||
!IDENTITY.test(reference.invocationId) ||
typeof reference.contentDigest !== 'string' ||
!DIGEST.test(reference.contentDigest) ||
!nonNegativeInteger(reference.outputBytes) ||
reference.outputBytes > 1024 * 1024 ||
typeof reference.retentionPolicyDigest !== 'string' ||
!DIGEST.test(reference.retentionPolicyDigest) ||
!nonNegativeInteger(reference.retentionEligibleAtMs) ||
typeof reference.keyId !== 'string' ||
!KEY_ID.test(reference.keyId) ||
typeof result.provider !== 'string' ||
!MODEL_ID.test(result.provider) ||
typeof result.model !== 'string' ||
!MODEL_ID.test(result.model) ||
typeof result.text !== 'string' ||
Buffer.byteLength(result.text, 'utf8') > 1024 * 1024 ||
!FINISH_REASONS.has(result.finishReason as string) ||
!nonNegativeInteger(usage.inputTokens) ||
!nonNegativeInteger(usage.outputTokens) ||
!nonNegativeInteger(usage.totalTokens) ||
usage.totalTokens !== usage.inputTokens + usage.outputTokens ||
(usage.costMicros !== undefined && !nonNegativeInteger(usage.costMicros))
) {
return null;
}
return Object.freeze({
schema: CLUSTER_PLUGIN_PACKAGE_PROMPT_OUTPUT_READ_RESPONSE_SCHEMA,
status: 'available',
reference: Object.freeze({ ...reference }),
result: Object.freeze({
provider: result.provider,
model: result.model,
text: result.text,
finishReason: result.finishReason,
usage: Object.freeze({ ...usage }),
}),
});
}
export function createClusterControlPluginPackagePromptOutputReadRoute(
capability: ClusterPluginPackagePromptOutputReadCapability,
): Readonly<ClusterControlRouteDefinition> {
if (!capability || typeof capability.read !== 'function') {
throw new TypeError('Cluster-control Prompt output read capability is invalid');
}
return Object.freeze({
...CLUSTER_CONTROL_PLUGIN_PACKAGE_PROMPT_OUTPUT_READ_ROUTE,
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
const artifactDigestValues =
authorized.request.query.artifact_digest;
if (
authorized.request.body !== null ||
authorized.projectId === null ||
typeof parameters.runId !== 'string' ||
!RUN_ID.test(parameters.runId) ||
typeof parameters.artifactId !== 'string' ||
!IDENTITY.test(parameters.artifactId) ||
!Array.isArray(artifactDigestValues) ||
artifactDigestValues.length !== 1 ||
typeof artifactDigestValues[0] !== 'string' ||
!DIGEST.test(artifactDigestValues[0])
) {
return response(400, { code: 'invalid_prompt_output_read_request' });
}
const expected = Object.freeze({
projectId: authorized.projectId,
runId: parameters.runId,
artifactId: parameters.artifactId,
artifactDigest: artifactDigestValues[0],
});
try {
const result = await capability.read({
principal: authorized.principal,
...expected,
});
const notFound = exactRecord(result, ['schema', 'status']);
if (
notFound &&
notFound.schema ===
'qinglong/plugin-package-prompt-output-read-result@v1' &&
notFound.status === 'not_found'
) {
return response(404, { code: 'prompt_output_not_found' });
}
const view = availableView(result, expected);
return view
? response(200, view)
: response(503, { code: 'prompt_output_read_unavailable' });
} catch {
return response(503, { code: 'prompt_output_read_unavailable' });
}
},
});
}
@@ -0,0 +1,6 @@
// Plugin Package Prompt owns its stable execution and output-read route surface.
export * from './pluginPackagePromptExecutionRoute';
export * from './pluginPackagePromptCatalogRoute';
export * from './pluginPackagePromptExecutionInspectionRoute';
export * from './pluginPackagePromptExecutionOutputReadRoute';
export * from './pluginPackagePromptOutputReadRoute';
@@ -0,0 +1,535 @@
// Plugin Package Workflow owns inspection and durable authorized admission.
import type {
SecurityPolicyFence,
SecurityPrincipal,
} from '@qinglong/runtime-core/security';
import { normalizeSecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
import type {
PluginPackageAutomationPublication,
PluginPackageAutomationPublicationRepository,
} from '@qinglong/runtime-core/plugin-package-automation-publication';
import type {
PluginPackageMaterializedRevision,
PluginPackageMaterializedRevisionRepository,
PluginPackageWorkflowResource,
} from '@qinglong/runtime-core/plugin-package-resource-materialization';
import type {
PluginPackageWorkflowAdministrationRepository,
PluginPackageWorkflowRunEventListRepository,
PluginPackageWorkflowRunEventListResult,
PluginPackageWorkflowRunInspectionRepository,
PluginPackageWorkflowRunInspectionResult,
PluginPackageWorkflowRunListRepository,
PluginPackageWorkflowRunListResult,
PluginPackageWorkflowStepRunListRepository,
PluginPackageWorkflowStepRunListResult,
} from '@qinglong/runtime-core/plugin-package-workflow-administration';
import type {
ClusterRunCancellationRepository,
ClusterRunCancellationResult,
} from '@qinglong/runtime-core/cluster-run-cancellation';
import {
createPluginPackageWorkflowExecutionPlan,
type PluginPackageWorkflowAdmissionReceipt,
type PluginPackageWorkflowExecutionPlan,
} from '@qinglong/runtime-core/plugin-package-workflow-execution-plan';
import {
TaskSpecSemanticRegistry,
createBuiltInTaskSpecSemanticRegistry,
} from '@qinglong/runtime-core/task-spec-semantic';
export interface ClusterPluginPackageWorkflowSummary {
readonly id: string;
readonly name: string;
readonly enabled: boolean;
readonly steps: readonly Readonly<{
id: string;
task: string;
needs: readonly string[];
}>[];
}
export interface StartClusterPluginPackageWorkflowCommand {
readonly projectId: string;
readonly packageName: string;
readonly workflowId: string;
readonly planId: string;
readonly runId: string;
readonly stepRunIds: Readonly<Record<string, string>>;
readonly principal: Readonly<SecurityPrincipal>;
readonly policyFence: Readonly<SecurityPolicyFence>;
readonly plannedAtMs: number;
}
export interface CancelClusterPluginPackageWorkflowCommand {
readonly projectId: string;
readonly packageName: string;
readonly workflowId: string;
readonly runId: string;
readonly mutationId: string;
readonly eventId: string;
readonly principal: Readonly<SecurityPrincipal>;
readonly policyFence: Readonly<SecurityPolicyFence>;
}
export interface InspectClusterPluginPackageWorkflowRunCommand {
readonly projectId: string;
readonly packageName: string;
readonly workflowId: string;
readonly runId: string;
readonly requestId: string;
readonly auditEventId: string;
readonly principal: Readonly<SecurityPrincipal>;
readonly policyFence: Readonly<SecurityPolicyFence>;
readonly observedAtMs: number;
}
export interface ListClusterPluginPackageWorkflowRunsCommand {
readonly projectId: string;
readonly packageName: string;
readonly workflowId: string;
readonly limit: number;
readonly after: Readonly<{ admittedAtMs: number; runId: string }> | null;
readonly requestId: string;
readonly auditEventId: string;
readonly principal: Readonly<SecurityPrincipal>;
readonly policyFence: Readonly<SecurityPolicyFence>;
readonly observedAtMs: number;
}
export interface ListClusterPluginPackageWorkflowStepRunsCommand {
readonly projectId: string;
readonly packageName: string;
readonly workflowId: string;
readonly runId: string;
readonly limit: number;
readonly after: Readonly<{ stepKey: string; id: string }> | null;
readonly requestId: string;
readonly auditEventId: string;
readonly principal: Readonly<SecurityPrincipal>;
readonly policyFence: Readonly<SecurityPolicyFence>;
readonly observedAtMs: number;
}
export interface ListClusterPluginPackageWorkflowRunEventsCommand {
readonly projectId: string;
readonly packageName: string;
readonly workflowId: string;
readonly runId: string;
readonly limit: number;
readonly afterSequence: number;
readonly requestId: string;
readonly auditEventId: string;
readonly principal: Readonly<SecurityPrincipal>;
readonly policyFence: Readonly<SecurityPolicyFence>;
readonly observedAtMs: number;
}
export interface ClusterPluginPackageWorkflowAdministrationCapability {
inspect(
projectId: string,
packageName: string,
): Promise<
Readonly<{
found: boolean;
publicationState: PluginPackageAutomationPublication['state'] | null;
workflows: readonly Readonly<ClusterPluginPackageWorkflowSummary>[];
}>
>;
start(command: Readonly<StartClusterPluginPackageWorkflowCommand>): Promise<
Readonly<{
status: 'created' | 'existing';
plan: Readonly<PluginPackageWorkflowExecutionPlan>;
receipt: Readonly<PluginPackageWorkflowAdmissionReceipt>;
}>
>;
cancel(
command: Readonly<CancelClusterPluginPackageWorkflowCommand>,
): Promise<Readonly<ClusterRunCancellationResult>>;
inspectRun(
command: Readonly<InspectClusterPluginPackageWorkflowRunCommand>,
): Promise<Readonly<PluginPackageWorkflowRunInspectionResult>>;
listRuns(
command: Readonly<ListClusterPluginPackageWorkflowRunsCommand>,
): Promise<Readonly<PluginPackageWorkflowRunListResult>>;
listStepRuns(
command: Readonly<ListClusterPluginPackageWorkflowStepRunsCommand>,
): Promise<Readonly<PluginPackageWorkflowStepRunListResult>>;
listRunEvents(
command: Readonly<ListClusterPluginPackageWorkflowRunEventsCommand>,
): Promise<Readonly<PluginPackageWorkflowRunEventListResult>>;
}
export class ClusterPluginPackageWorkflowNotFoundError extends Error {
readonly code = 'CLUSTER_PLUGIN_PACKAGE_WORKFLOW_NOT_FOUND';
constructor() {
super('Active Plugin Package Workflow is not available');
this.name = 'ClusterPluginPackageWorkflowNotFoundError';
}
}
export class ClusterPluginPackageWorkflowConflictError extends Error {
readonly code = 'CLUSTER_PLUGIN_PACKAGE_WORKFLOW_CONFLICT';
constructor() {
super('Plugin Package Workflow request conflicts with durable identity');
this.name = 'ClusterPluginPackageWorkflowConflictError';
}
}
export class ClusterPluginPackageWorkflowUnavailableError extends Error {
readonly code = 'CLUSTER_PLUGIN_PACKAGE_WORKFLOW_UNAVAILABLE';
constructor() {
super('Plugin Package Workflow administration is unavailable');
this.name = 'ClusterPluginPackageWorkflowUnavailableError';
}
}
function summary(
workflow: Readonly<PluginPackageWorkflowResource>,
): Readonly<ClusterPluginPackageWorkflowSummary> {
return Object.freeze({
id: workflow.id,
name: workflow.name,
enabled: workflow.enabled,
steps: Object.freeze(
workflow.steps.map((step) =>
Object.freeze({
id: step.id,
task: step.task,
needs: Object.freeze([...step.needs]),
}),
),
),
});
}
function sameReplay(
plan: Readonly<PluginPackageWorkflowExecutionPlan>,
command: Readonly<StartClusterPluginPackageWorkflowCommand>,
): boolean {
const requested = Object.entries(command.stepRunIds).sort(([a], [b]) =>
a.localeCompare(b),
);
const stored = plan.steps
.map((step) => [step.stepKey, step.stepRunId] as const)
.sort(([a], [b]) => a.localeCompare(b));
return (
plan.planId === command.planId &&
plan.runId === command.runId &&
plan.target.projectId === command.projectId &&
plan.target.packageName === command.packageName &&
plan.target.workflowId === command.workflowId &&
stored.length === requested.length &&
stored.every(
([key, id], index) =>
key === requested[index]?.[0] && id === requested[index]?.[1],
)
);
}
export function createClusterPluginPackageWorkflowAdministrationCapability(
publications: Pick<
PluginPackageAutomationPublicationRepository,
'findCurrent'
>,
revisions: Pick<PluginPackageMaterializedRevisionRepository, 'find'>,
admissions: PluginPackageWorkflowAdministrationRepository,
runInspections: PluginPackageWorkflowRunInspectionRepository,
runLists: PluginPackageWorkflowRunListRepository,
stepRunLists: PluginPackageWorkflowStepRunListRepository,
runEventLists: PluginPackageWorkflowRunEventListRepository,
cancellations: ClusterRunCancellationRepository,
taskSpecSemanticRegistry: TaskSpecSemanticRegistry = createBuiltInTaskSpecSemanticRegistry(),
): ClusterPluginPackageWorkflowAdministrationCapability {
if (
!publications ||
typeof publications.findCurrent !== 'function' ||
!revisions ||
typeof revisions.find !== 'function' ||
!admissions ||
typeof admissions.findPlanByPlanId !== 'function' ||
typeof admissions.admitAuthorized !== 'function' ||
!runInspections ||
typeof runInspections.inspectRunAuthorized !== 'function' ||
!runLists ||
typeof runLists.listRunsAuthorized !== 'function' ||
!stepRunLists ||
typeof stepRunLists.listStepRunsAuthorized !== 'function' ||
!runEventLists ||
typeof runEventLists.listRunEventsAuthorized !== 'function' ||
!cancellations ||
typeof cancellations.requestUserCancellation !== 'function' ||
!(taskSpecSemanticRegistry instanceof TaskSpecSemanticRegistry)
) {
throw new TypeError(
'Cluster Plugin Package Workflow administration dependencies are invalid',
);
}
async function currentTarget(
projectId: string,
packageName: string,
): Promise<Readonly<{
publication: Readonly<PluginPackageAutomationPublication>;
revision: Readonly<PluginPackageMaterializedRevision>;
}> | null> {
try {
const publication = await publications.findCurrent(
projectId,
packageName,
);
if (!publication) return null;
const revision = await revisions.find(
publication.target.generationDigest,
);
if (
!revision ||
revision.revisionDigest !==
publication.target.materializedRevisionDigest
) {
throw new ClusterPluginPackageWorkflowUnavailableError();
}
return Object.freeze({ publication, revision });
} catch (error) {
if (error instanceof ClusterPluginPackageWorkflowUnavailableError) {
throw error;
}
throw new ClusterPluginPackageWorkflowUnavailableError();
}
}
return Object.freeze({
async inspect(projectId: string, packageName: string) {
const target = await currentTarget(projectId, packageName);
return target
? Object.freeze({
found: true,
publicationState: target.publication.state,
workflows: Object.freeze(
target.publication.definitions.workflows.map(summary),
),
})
: Object.freeze({
found: false,
publicationState: null,
workflows: Object.freeze([]),
});
},
async start(command: Readonly<StartClusterPluginPackageWorkflowCommand>) {
let plan = await admissions.findPlanByPlanId(command.planId);
if (plan) {
if (!sameReplay(plan, command)) {
throw new ClusterPluginPackageWorkflowConflictError();
}
} else {
const target = await currentTarget(
command.projectId,
command.packageName,
);
const workflow = target?.publication.definitions.workflows.find(
({ id }) => id === command.workflowId,
);
if (
!target ||
target.publication.state !== 'active' ||
!workflow?.enabled
) {
throw new ClusterPluginPackageWorkflowNotFoundError();
}
try {
plan = createPluginPackageWorkflowExecutionPlan({
planId: command.planId,
runId: command.runId,
workflowId: command.workflowId,
stepRunIds: command.stepRunIds,
publication: target.publication,
revision: target.revision,
taskSpecSemanticRegistry,
plannedAtMs: command.plannedAtMs,
});
} catch {
throw new ClusterPluginPackageWorkflowConflictError();
}
}
if (command.policyFence.bindingVersion === null) {
throw new ClusterPluginPackageWorkflowUnavailableError();
}
const admitted = await admissions.admitAuthorized({
plan,
actor: command.principal.subject,
fence: {
projectVersion: command.policyFence.projectVersion,
bindingVersion: command.policyFence.bindingVersion,
},
audit: normalizeSecurityAuditRecord({
eventId: command.planId,
requestId: command.planId,
operationId: 'workflow.start',
projectId: command.projectId,
subject: command.principal.subject,
authenticationId: command.principal.authenticationId,
outcome: 'allowed',
reasons: ['project_policy_allowed'],
fence: command.policyFence,
occurredAtMs: plan.plannedAtMs,
}),
});
return Object.freeze({
status: admitted.status,
plan,
receipt: admitted.receipt,
});
},
async cancel(command: Readonly<CancelClusterPluginPackageWorkflowCommand>) {
if (command.policyFence.bindingVersion === null) {
throw new ClusterPluginPackageWorkflowUnavailableError();
}
return cancellations.requestUserCancellation({
projectId: command.projectId,
runId: command.runId,
mutationId: command.mutationId,
eventId: command.eventId,
subject: command.principal.subject,
policyFence: command.policyFence,
workflowTarget: {
packageName: command.packageName,
workflowId: command.workflowId,
},
});
},
async inspectRun(
command: Readonly<InspectClusterPluginPackageWorkflowRunCommand>,
) {
if (command.policyFence.bindingVersion === null) {
throw new ClusterPluginPackageWorkflowUnavailableError();
}
return runInspections.inspectRunAuthorized({
projectId: command.projectId,
packageName: command.packageName,
workflowId: command.workflowId,
runId: command.runId,
actor: command.principal.subject,
fence: {
projectVersion: command.policyFence.projectVersion,
bindingVersion: command.policyFence.bindingVersion,
},
audit: normalizeSecurityAuditRecord({
eventId: command.auditEventId,
requestId: command.requestId,
operationId: 'workflow.run.read',
projectId: command.projectId,
subject: command.principal.subject,
authenticationId: command.principal.authenticationId,
outcome: 'allowed',
reasons: ['project_policy_allowed'],
fence: command.policyFence,
occurredAtMs: command.observedAtMs,
}),
});
},
async listRuns(
command: Readonly<ListClusterPluginPackageWorkflowRunsCommand>,
) {
if (command.policyFence.bindingVersion === null) {
throw new ClusterPluginPackageWorkflowUnavailableError();
}
return runLists.listRunsAuthorized({
projectId: command.projectId,
packageName: command.packageName,
workflowId: command.workflowId,
limit: command.limit,
after: command.after,
actor: command.principal.subject,
fence: {
projectVersion: command.policyFence.projectVersion,
bindingVersion: command.policyFence.bindingVersion,
},
audit: normalizeSecurityAuditRecord({
eventId: command.auditEventId,
requestId: command.requestId,
operationId: 'workflow.run.list',
projectId: command.projectId,
subject: command.principal.subject,
authenticationId: command.principal.authenticationId,
outcome: 'allowed',
reasons: ['project_policy_allowed'],
fence: command.policyFence,
occurredAtMs: command.observedAtMs,
}),
});
},
async listStepRuns(
command: Readonly<ListClusterPluginPackageWorkflowStepRunsCommand>,
) {
if (command.policyFence.bindingVersion === null) {
throw new ClusterPluginPackageWorkflowUnavailableError();
}
return stepRunLists.listStepRunsAuthorized({
projectId: command.projectId,
packageName: command.packageName,
workflowId: command.workflowId,
runId: command.runId,
limit: command.limit,
after: command.after,
actor: command.principal.subject,
fence: {
projectVersion: command.policyFence.projectVersion,
bindingVersion: command.policyFence.bindingVersion,
},
audit: normalizeSecurityAuditRecord({
eventId: command.auditEventId,
requestId: command.requestId,
operationId: 'workflow.step.list',
projectId: command.projectId,
subject: command.principal.subject,
authenticationId: command.principal.authenticationId,
outcome: 'allowed',
reasons: ['project_policy_allowed'],
fence: command.policyFence,
occurredAtMs: command.observedAtMs,
}),
});
},
async listRunEvents(
command: Readonly<ListClusterPluginPackageWorkflowRunEventsCommand>,
) {
if (command.policyFence.bindingVersion === null) {
throw new ClusterPluginPackageWorkflowUnavailableError();
}
return runEventLists.listRunEventsAuthorized({
projectId: command.projectId,
packageName: command.packageName,
workflowId: command.workflowId,
runId: command.runId,
limit: command.limit,
afterSequence: command.afterSequence,
actor: command.principal.subject,
fence: {
projectVersion: command.policyFence.projectVersion,
bindingVersion: command.policyFence.bindingVersion,
},
audit: normalizeSecurityAuditRecord({
eventId: command.auditEventId,
requestId: command.requestId,
operationId: 'workflow.event.list',
projectId: command.projectId,
subject: command.principal.subject,
authenticationId: command.principal.authenticationId,
outcome: 'allowed',
reasons: ['project_policy_allowed'],
fence: command.policyFence,
occurredAtMs: command.observedAtMs,
}),
});
},
});
}
@@ -0,0 +1,707 @@
// Plugin Package Workflow owns its bounded inspect/start/cancel transport adapter.
import { randomUUID } from 'node:crypto';
import {
CLUSTER_RUN_CANCELLATION_SCHEMA,
createClusterRunCancellationResponseBody,
parseClusterRunCancellationRequestBody,
} from '@qinglong/runtime-core/cluster-run-cancellation';
import {
DEFAULT_PLUGIN_PACKAGE_WORKFLOW_RUN_EVENT_LIST_PAGE_SIZE,
DEFAULT_PLUGIN_PACKAGE_WORKFLOW_RUN_LIST_PAGE_SIZE,
DEFAULT_PLUGIN_PACKAGE_WORKFLOW_STEP_RUN_LIST_PAGE_SIZE,
MAX_PLUGIN_PACKAGE_WORKFLOW_RUN_EVENT_LIST_PAGE_SIZE,
MAX_PLUGIN_PACKAGE_WORKFLOW_RUN_LIST_PAGE_SIZE,
MAX_PLUGIN_PACKAGE_WORKFLOW_STEP_RUN_LIST_PAGE_SIZE,
} from '@qinglong/runtime-core/plugin-package-workflow-administration';
import type { ClusterControlAdmissionResponse } from '../../transport/httpSurface';
import type { ClusterPluginPackageWorkflowAdministrationCapability } from './pluginPackageWorkflowAdministration';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../../transport/routeRegistry';
export const CLUSTER_PLUGIN_PACKAGE_WORKFLOW_LIST_RESPONSE_SCHEMA =
'qinglong/cluster-plugin-package-workflow-list@v1' as const;
export const CLUSTER_PLUGIN_PACKAGE_WORKFLOW_START_REQUEST_SCHEMA =
'qinglong/cluster-plugin-package-workflow-start-request@v1' as const;
export const CLUSTER_PLUGIN_PACKAGE_WORKFLOW_START_RESPONSE_SCHEMA =
'qinglong/cluster-plugin-package-workflow-start-response@v1' as const;
const UUID_V4 =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
const PACKAGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
const RESOURCE_ID = /^[a-z][a-z0-9-]{0,62}$/;
function parseRunListQuery(
query: Readonly<Record<string, readonly string[]>>,
): Readonly<{
limit: number;
after: Readonly<{ admittedAtMs: number; runId: string }> | null;
}> {
const limitValues = query.limit;
const admittedAtValues = query.after_admitted_at_ms;
const runIdValues = query.after_run_id;
if (
(limitValues !== undefined && limitValues.length !== 1) ||
(admittedAtValues !== undefined && admittedAtValues.length !== 1) ||
(runIdValues !== undefined && runIdValues.length !== 1) ||
(admittedAtValues === undefined) !== (runIdValues === undefined)
) {
throw new TypeError();
}
const limit =
limitValues === undefined
? DEFAULT_PLUGIN_PACKAGE_WORKFLOW_RUN_LIST_PAGE_SIZE
: Number(limitValues[0]);
if (
!Number.isSafeInteger(limit) ||
limit < 1 ||
limit > MAX_PLUGIN_PACKAGE_WORKFLOW_RUN_LIST_PAGE_SIZE ||
(limitValues !== undefined && String(limit) !== limitValues[0])
) {
throw new TypeError();
}
if (admittedAtValues === undefined || runIdValues === undefined) {
return Object.freeze({ limit, after: null });
}
const admittedAtMs = Number(admittedAtValues[0]);
const runId = runIdValues[0]!;
if (
!Number.isSafeInteger(admittedAtMs) ||
admittedAtMs < 0 ||
String(admittedAtMs) !== admittedAtValues[0] ||
!UUID_V4.test(runId)
) {
throw new TypeError();
}
return Object.freeze({
limit,
after: Object.freeze({ admittedAtMs, runId }),
});
}
function parseStepRunListQuery(
query: Readonly<Record<string, readonly string[]>>,
): Readonly<{
limit: number;
after: Readonly<{ stepKey: string; id: string }> | null;
}> {
const limitValues = query.limit;
const stepKeyValues = query.after_step_key;
const stepRunIdValues = query.after_step_run_id;
if (
(limitValues !== undefined && limitValues.length !== 1) ||
(stepKeyValues !== undefined && stepKeyValues.length !== 1) ||
(stepRunIdValues !== undefined && stepRunIdValues.length !== 1) ||
(stepKeyValues === undefined) !== (stepRunIdValues === undefined)
) {
throw new TypeError();
}
const limit =
limitValues === undefined
? DEFAULT_PLUGIN_PACKAGE_WORKFLOW_STEP_RUN_LIST_PAGE_SIZE
: Number(limitValues[0]);
if (
!Number.isSafeInteger(limit) ||
limit < 1 ||
limit > MAX_PLUGIN_PACKAGE_WORKFLOW_STEP_RUN_LIST_PAGE_SIZE ||
(limitValues !== undefined && String(limit) !== limitValues[0])
) {
throw new TypeError();
}
if (stepKeyValues === undefined || stepRunIdValues === undefined) {
return Object.freeze({ limit, after: null });
}
const stepKey = stepKeyValues[0]!;
const id = stepRunIdValues[0]!;
if (!RESOURCE_ID.test(stepKey) || !UUID_V4.test(id)) {
throw new TypeError();
}
return Object.freeze({
limit,
after: Object.freeze({ stepKey, id }),
});
}
function parseRunEventListQuery(
query: Readonly<Record<string, readonly string[]>>,
): Readonly<{ limit: number; afterSequence: number }> {
const limitValues = query.limit;
const afterValues = query.after_sequence;
if (
(limitValues !== undefined && limitValues.length !== 1) ||
(afterValues !== undefined && afterValues.length !== 1)
) {
throw new TypeError();
}
const limit =
limitValues === undefined
? DEFAULT_PLUGIN_PACKAGE_WORKFLOW_RUN_EVENT_LIST_PAGE_SIZE
: Number(limitValues[0]);
const afterSequence = afterValues === undefined ? 0 : Number(afterValues[0]);
if (
!Number.isSafeInteger(limit) ||
limit < 1 ||
limit > MAX_PLUGIN_PACKAGE_WORKFLOW_RUN_EVENT_LIST_PAGE_SIZE ||
(limitValues !== undefined && String(limit) !== limitValues[0]) ||
!Number.isSafeInteger(afterSequence) ||
afterSequence < 0 ||
(afterValues !== undefined && String(afterSequence) !== afterValues[0])
) {
throw new TypeError();
}
return Object.freeze({ limit, afterSequence });
}
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
function parseBody(value: unknown): Readonly<{
planId: string;
runId: string;
stepRunIds: Readonly<Record<string, string>>;
}> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new TypeError();
}
const body = value as Record<string, unknown>;
if (
Object.keys(body).sort().join(',') !== 'planId,runId,schema,stepRunIds' ||
body.schema !== CLUSTER_PLUGIN_PACKAGE_WORKFLOW_START_REQUEST_SCHEMA ||
typeof body.planId !== 'string' ||
!UUID_V4.test(body.planId) ||
typeof body.runId !== 'string' ||
!UUID_V4.test(body.runId) ||
!body.stepRunIds ||
typeof body.stepRunIds !== 'object' ||
Array.isArray(body.stepRunIds) ||
Object.getPrototypeOf(body.stepRunIds) !== Object.prototype
) {
throw new TypeError();
}
const entries = Object.entries(body.stepRunIds as Record<string, unknown>);
if (
entries.length < 1 ||
entries.length > 128 ||
entries.some(
([key, id]) =>
!RESOURCE_ID.test(key) || typeof id !== 'string' || !UUID_V4.test(id),
) ||
new Set(entries.map(([, id]) => id)).size !== entries.length
) {
throw new TypeError();
}
return Object.freeze({
planId: body.planId,
runId: body.runId,
stepRunIds: Object.freeze(
Object.fromEntries(entries) as Record<string, string>,
),
});
}
function errorResponse(error: unknown): ClusterControlAdmissionResponse {
const code =
error && typeof error === 'object' && 'code' in error
? (error as { code?: unknown }).code
: null;
if (code === 'CLUSTER_PLUGIN_PACKAGE_WORKFLOW_NOT_FOUND') {
return response(404, { code: 'workflow_not_found' });
}
if (
code === 'CLUSTER_PLUGIN_PACKAGE_WORKFLOW_CONFLICT' ||
code === 'PLUGIN_PACKAGE_WORKFLOW_ADMINISTRATION_MUTATION_CONFLICT' ||
code === 'PLUGIN_PACKAGE_WORKFLOW_ADMISSION_CONFLICT'
) {
return response(409, { code: 'workflow_start_conflict' });
}
if (
code ===
'PLUGIN_PACKAGE_WORKFLOW_ADMINISTRATION_AUTHORIZATION_FENCE_CONFLICT'
) {
return response(409, { code: 'authorization_fence_changed' });
}
if (code === 'CLUSTER_RUN_CANCELLATION_NOT_FOUND') {
return response(404, { code: 'workflow_run_not_found' });
}
if (code === 'CLUSTER_RUN_CANCELLATION_FENCE_REJECTED') {
const candidateReason =
error && typeof error === 'object' && 'reason' in error
? (error as { reason?: unknown }).reason
: null;
const reason =
candidateReason === 'authorization_changed' ||
candidateReason === 'project_mismatch' ||
candidateReason === 'state_mismatch'
? candidateReason
: 'state_mismatch';
return response(409, {
code: 'workflow_cancellation_fence_rejected',
reason,
});
}
return response(503, { code: 'workflow_administration_unavailable' });
}
function runInspectionErrorResponse(
error: unknown,
): ClusterControlAdmissionResponse {
const code =
error && typeof error === 'object' && 'code' in error
? (error as { code?: unknown }).code
: null;
if (
code ===
'PLUGIN_PACKAGE_WORKFLOW_ADMINISTRATION_AUTHORIZATION_FENCE_CONFLICT'
) {
return response(409, { code: 'authorization_fence_changed' });
}
return response(503, { code: 'workflow_run_query_unavailable' });
}
function runListErrorResponse(error: unknown): ClusterControlAdmissionResponse {
const code =
error && typeof error === 'object' && 'code' in error
? (error as { code?: unknown }).code
: null;
if (
code ===
'PLUGIN_PACKAGE_WORKFLOW_ADMINISTRATION_AUTHORIZATION_FENCE_CONFLICT'
) {
return response(409, { code: 'authorization_fence_changed' });
}
return response(503, { code: 'workflow_run_list_unavailable' });
}
function stepRunListErrorResponse(
error: unknown,
): ClusterControlAdmissionResponse {
const code =
error && typeof error === 'object' && 'code' in error
? (error as { code?: unknown }).code
: null;
if (
code ===
'PLUGIN_PACKAGE_WORKFLOW_ADMINISTRATION_AUTHORIZATION_FENCE_CONFLICT'
) {
return response(409, { code: 'authorization_fence_changed' });
}
return response(503, { code: 'workflow_step_run_query_unavailable' });
}
function runEventListErrorResponse(
error: unknown,
): ClusterControlAdmissionResponse {
const code =
error && typeof error === 'object' && 'code' in error
? (error as { code?: unknown }).code
: null;
if (
code ===
'PLUGIN_PACKAGE_WORKFLOW_ADMINISTRATION_AUTHORIZATION_FENCE_CONFLICT'
) {
return response(409, { code: 'authorization_fence_changed' });
}
return response(503, { code: 'workflow_run_event_query_unavailable' });
}
export function createClusterControlPluginPackageWorkflowRoutes(
capability: ClusterPluginPackageWorkflowAdministrationCapability,
now: () => number = Date.now,
createEventId: () => string = randomUUID,
): readonly Readonly<ClusterControlRouteDefinition>[] {
if (
!capability ||
typeof capability.inspect !== 'function' ||
typeof capability.inspectRun !== 'function' ||
typeof capability.listRuns !== 'function' ||
typeof capability.listStepRuns !== 'function' ||
typeof capability.listRunEvents !== 'function' ||
typeof capability.start !== 'function' ||
typeof capability.cancel !== 'function' ||
typeof now !== 'function' ||
typeof createEventId !== 'function'
) {
throw new TypeError('Cluster-control Workflow capability is invalid');
}
const common = {
projectParameter: 'projectId' as const,
};
return Object.freeze([
Object.freeze({
...common,
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/packages/{packageName}/workflows',
operationId: 'workflow.read',
permission: 'run.read',
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
if (
authorized.projectId === null ||
typeof parameters.packageName !== 'string' ||
!PACKAGE_NAME.test(parameters.packageName)
) {
return response(503, { code: 'workflow_administration_unavailable' });
}
try {
const result = await capability.inspect(
authorized.projectId,
parameters.packageName,
);
return response(200, {
schema: CLUSTER_PLUGIN_PACKAGE_WORKFLOW_LIST_RESPONSE_SCHEMA,
...result,
});
} catch (error) {
return errorResponse(error);
}
},
}),
Object.freeze({
...common,
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/packages/{packageName}/workflows/{workflowId}/runs',
operationId: 'workflow.run.list',
permission: 'run.read',
allowedQuery: Object.freeze([
'after_admitted_at_ms',
'after_run_id',
'limit',
]),
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
if (authorized.request.body !== null) {
return response(400, { code: 'invalid_request_body' });
}
let page;
try {
page = parseRunListQuery(authorized.request.query);
} catch {
return response(400, { code: 'invalid_workflow_run_query' });
}
const observedAtMs = now();
if (
authorized.projectId === null ||
typeof parameters.packageName !== 'string' ||
!PACKAGE_NAME.test(parameters.packageName) ||
typeof parameters.workflowId !== 'string' ||
!RESOURCE_ID.test(parameters.workflowId) ||
!authorized.policyFence ||
authorized.policyFence.bindingVersion === null ||
!Number.isSafeInteger(observedAtMs) ||
observedAtMs < 0
) {
return response(503, { code: 'workflow_run_list_unavailable' });
}
try {
const result = await capability.listRuns({
projectId: authorized.projectId,
packageName: parameters.packageName,
workflowId: parameters.workflowId,
limit: page.limit,
after: page.after,
requestId: authorized.request.requestId,
auditEventId: createEventId(),
principal: authorized.principal,
policyFence: authorized.policyFence,
observedAtMs,
});
return response(200, { ...result });
} catch (error) {
return runListErrorResponse(error);
}
},
}),
Object.freeze({
...common,
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/packages/{packageName}/workflows/{workflowId}/runs/{runId}',
operationId: 'workflow.run.read',
permission: 'run.read',
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
const observedAtMs = now();
if (authorized.request.body !== null) {
return response(400, { code: 'invalid_request_body' });
}
if (
authorized.projectId === null ||
typeof parameters.packageName !== 'string' ||
!PACKAGE_NAME.test(parameters.packageName) ||
typeof parameters.workflowId !== 'string' ||
!RESOURCE_ID.test(parameters.workflowId) ||
typeof parameters.runId !== 'string' ||
!UUID_V4.test(parameters.runId) ||
!authorized.policyFence ||
authorized.policyFence.bindingVersion === null ||
!Number.isSafeInteger(observedAtMs) ||
observedAtMs < 0
) {
return response(503, { code: 'workflow_run_query_unavailable' });
}
try {
const result = await capability.inspectRun({
projectId: authorized.projectId,
packageName: parameters.packageName,
workflowId: parameters.workflowId,
runId: parameters.runId,
requestId: authorized.request.requestId,
auditEventId: createEventId(),
principal: authorized.principal,
policyFence: authorized.policyFence,
observedAtMs,
});
return result.found
? response(200, { ...result })
: response(404, { code: 'workflow_run_not_found' });
} catch (error) {
return runInspectionErrorResponse(error);
}
},
}),
Object.freeze({
...common,
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/packages/{packageName}/workflows/{workflowId}/runs/{runId}/steps',
operationId: 'workflow.step.list',
permission: 'run.read',
allowedQuery: Object.freeze([
'after_step_key',
'after_step_run_id',
'limit',
]),
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
if (authorized.request.body !== null) {
return response(400, { code: 'invalid_request_body' });
}
let page;
try {
page = parseStepRunListQuery(authorized.request.query);
} catch {
return response(400, { code: 'invalid_step_run_query' });
}
const observedAtMs = now();
if (
authorized.projectId === null ||
typeof parameters.packageName !== 'string' ||
!PACKAGE_NAME.test(parameters.packageName) ||
typeof parameters.workflowId !== 'string' ||
!RESOURCE_ID.test(parameters.workflowId) ||
typeof parameters.runId !== 'string' ||
!UUID_V4.test(parameters.runId) ||
!authorized.policyFence ||
authorized.policyFence.bindingVersion === null ||
!Number.isSafeInteger(observedAtMs) ||
observedAtMs < 0
) {
return response(503, { code: 'workflow_step_run_query_unavailable' });
}
try {
const result = await capability.listStepRuns({
projectId: authorized.projectId,
packageName: parameters.packageName,
workflowId: parameters.workflowId,
runId: parameters.runId,
limit: page.limit,
after: page.after,
requestId: authorized.request.requestId,
auditEventId: createEventId(),
principal: authorized.principal,
policyFence: authorized.policyFence,
observedAtMs,
});
return result.found
? response(200, { ...result })
: response(404, { code: 'workflow_run_not_found' });
} catch (error) {
return stepRunListErrorResponse(error);
}
},
}),
Object.freeze({
...common,
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/packages/{packageName}/workflows/{workflowId}/runs/{runId}/events',
operationId: 'workflow.event.list',
permission: 'run.read',
allowedQuery: Object.freeze(['after_sequence', 'limit']),
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
if (authorized.request.body !== null) {
return response(400, { code: 'invalid_request_body' });
}
let page;
try {
page = parseRunEventListQuery(authorized.request.query);
} catch {
return response(400, { code: 'invalid_run_event_query' });
}
const observedAtMs = now();
if (
authorized.projectId === null ||
typeof parameters.packageName !== 'string' ||
!PACKAGE_NAME.test(parameters.packageName) ||
typeof parameters.workflowId !== 'string' ||
!RESOURCE_ID.test(parameters.workflowId) ||
typeof parameters.runId !== 'string' ||
!UUID_V4.test(parameters.runId) ||
!authorized.policyFence ||
authorized.policyFence.bindingVersion === null ||
!Number.isSafeInteger(observedAtMs) ||
observedAtMs < 0
) {
return response(503, {
code: 'workflow_run_event_query_unavailable',
});
}
try {
const result = await capability.listRunEvents({
projectId: authorized.projectId,
packageName: parameters.packageName,
workflowId: parameters.workflowId,
runId: parameters.runId,
limit: page.limit,
afterSequence: page.afterSequence,
requestId: authorized.request.requestId,
auditEventId: createEventId(),
principal: authorized.principal,
policyFence: authorized.policyFence,
observedAtMs,
});
return result.found
? response(200, { ...result })
: response(404, { code: 'workflow_run_not_found' });
} catch (error) {
return runEventListErrorResponse(error);
}
},
}),
Object.freeze({
...common,
method: 'POST' as const,
path: '/api/v3/projects/{projectId}/packages/{packageName}/workflows/{workflowId}/runs',
operationId: 'workflow.start',
permission: 'run.start',
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
let body;
try {
body = parseBody(authorized.request.body);
} catch {
return response(400, { code: 'invalid_workflow_start_request' });
}
const plannedAtMs = now();
if (
authorized.projectId === null ||
typeof parameters.packageName !== 'string' ||
!PACKAGE_NAME.test(parameters.packageName) ||
typeof parameters.workflowId !== 'string' ||
!RESOURCE_ID.test(parameters.workflowId) ||
!authorized.policyFence ||
authorized.policyFence.bindingVersion === null ||
!Number.isSafeInteger(plannedAtMs) ||
plannedAtMs < 0
) {
return response(503, { code: 'workflow_administration_unavailable' });
}
try {
const result = await capability.start({
projectId: authorized.projectId,
packageName: parameters.packageName,
workflowId: parameters.workflowId,
planId: body.planId,
runId: body.runId,
stepRunIds: body.stepRunIds,
principal: authorized.principal,
policyFence: authorized.policyFence,
plannedAtMs,
});
return response(result.status === 'created' ? 201 : 200, {
schema: CLUSTER_PLUGIN_PACKAGE_WORKFLOW_START_RESPONSE_SCHEMA,
status: result.status,
replayed: result.status === 'existing',
planId: result.plan.planId,
runId: result.plan.runId,
receiptDigest: result.receipt.receiptDigest,
});
} catch (error) {
return errorResponse(error);
}
},
}),
Object.freeze({
...common,
method: 'POST' as const,
path: '/api/v3/projects/{projectId}/packages/{packageName}/workflows/{workflowId}/runs/{runId}/cancellation',
operationId: 'workflow.cancel',
permission: 'run.stop',
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
let body;
try {
body = parseClusterRunCancellationRequestBody(
authorized.request.body,
);
} catch {
return response(400, {
code: 'invalid_workflow_cancellation_request',
schema: CLUSTER_RUN_CANCELLATION_SCHEMA,
});
}
if (
authorized.projectId === null ||
typeof parameters.packageName !== 'string' ||
!PACKAGE_NAME.test(parameters.packageName) ||
typeof parameters.workflowId !== 'string' ||
!RESOURCE_ID.test(parameters.workflowId) ||
typeof parameters.runId !== 'string' ||
!UUID_V4.test(parameters.runId) ||
!authorized.policyFence ||
authorized.policyFence.bindingVersion === null
) {
return response(503, {
code: 'workflow_administration_unavailable',
});
}
try {
const result = await capability.cancel({
projectId: authorized.projectId,
packageName: parameters.packageName,
workflowId: parameters.workflowId,
runId: parameters.runId,
mutationId: body.mutationId,
eventId: createEventId(),
principal: authorized.principal,
policyFence: authorized.policyFence,
});
return response(
result.status === 'accepted' ? 202 : 200,
createClusterRunCancellationResponseBody(result),
);
} catch (error) {
return errorResponse(error);
}
},
}),
]);
}
@@ -0,0 +1,372 @@
import type {
DeploymentProfile,
OpenPostgresDatabase,
} from '@qinglong/runtime-core';
import {
createPostgresDatabaseOpener,
isPostgresTlsDnsServername,
loadPostgresConnectionEnvironment,
loadPostgresCertificateAuthorityFile,
type PostgresConnectionOptions,
type PostgresPoolOptions,
} from '@qinglong/cluster-postgres/runtime';
import { ClusterControlAvailabilityFence } from '../database/availability';
import type { ClusterControlHttpSurfaceOptions } from '../transport/httpSurface';
export type ClusterControlEnvironment = Readonly<
Record<string, string | undefined>
>;
export interface DisabledClusterControlConfig {
readonly enabled: false;
readonly profile: DeploymentProfile;
}
export interface EnabledClusterControlConfig {
readonly enabled: true;
readonly profile: 'cluster-control';
readonly http: ClusterControlHttpSurfaceOptions;
readonly database: Readonly<{
connection: PostgresConnectionOptions;
pool: PostgresPoolOptions;
}>;
readonly security: Readonly<{
apiCredentialPepper: string;
}>;
}
export type ClusterControlConfig =
| DisabledClusterControlConfig
| EnabledClusterControlConfig;
export interface ClusterControlDatabaseBinding {
readonly availability: ClusterControlAvailabilityFence;
readonly openDatabase: OpenPostgresDatabase;
}
export class ClusterControlConfigError extends TypeError {
constructor(message: string) {
super(`Cluster-control configuration is invalid: ${message}`);
this.name = 'ClusterControlConfigError';
}
}
const PROFILES = new Set<DeploymentProfile>([
'edge',
'standalone',
'cluster-control',
'worker',
]);
function booleanValue(
environment: ClusterControlEnvironment,
name: string,
defaultValue: boolean,
): boolean {
const value = environment[name];
if (value === undefined || value === '') return defaultValue;
if (value === 'true') return true;
if (value === 'false') return false;
throw new ClusterControlConfigError(`${name} must be true or false`);
}
function integerValue(
environment: ClusterControlEnvironment,
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 ClusterControlConfigError(`${name} must be an integer`);
}
const parsed = Number(value);
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
throw new ClusterControlConfigError(
`${name} must be between ${minimum} and ${maximum}`,
);
}
return parsed;
}
function boundedValue(
environment: ClusterControlEnvironment,
name: string,
maximumLength: number,
required = false,
): string | undefined {
const value = environment[name];
if (value === undefined || value === '') {
if (required) throw new ClusterControlConfigError(`${name} is required`);
return undefined;
}
if (value.length > maximumLength || /[\0\r\n]/.test(value)) {
throw new ClusterControlConfigError(`${name} is invalid`);
}
return value;
}
function deploymentProfile(
environment: ClusterControlEnvironment,
): DeploymentProfile {
const value = environment.QL_DEPLOYMENT_PROFILE ?? 'standalone';
if (!PROFILES.has(value as DeploymentProfile)) {
throw new ClusterControlConfigError('QL_DEPLOYMENT_PROFILE is invalid');
}
return value as DeploymentProfile;
}
function runtimeConnection(
environment: ClusterControlEnvironment,
): PostgresConnectionOptions {
let connection: PostgresConnectionOptions;
try {
connection = loadPostgresConnectionEnvironment(environment, {
connectionString: 'QL3_POSTGRES_RUNTIME_URL',
host: 'QL3_POSTGRES_RUNTIME_HOST',
port: 'QL3_POSTGRES_RUNTIME_PORT',
database: 'QL3_POSTGRES_RUNTIME_DATABASE',
user: 'QL3_POSTGRES_RUNTIME_USER',
password: 'QL3_POSTGRES_RUNTIME_PASSWORD',
});
} catch (error) {
throw new ClusterControlConfigError(
error instanceof Error
? error.message
: 'PostgreSQL runtime connection is invalid',
);
}
const mode = environment.QL3_POSTGRES_TLS_MODE ?? 'verify-full';
if (mode !== 'verify-full' && mode !== 'disable') {
throw new ClusterControlConfigError(
'QL3_POSTGRES_TLS_MODE must be verify-full or disable',
);
}
if (
mode === 'disable' &&
!booleanValue(environment, 'QL3_POSTGRES_ALLOW_INSECURE', false)
) {
throw new ClusterControlConfigError(
'disabling PostgreSQL TLS requires QL3_POSTGRES_ALLOW_INSECURE=true',
);
}
const servername = boundedValue(
environment,
'QL3_POSTGRES_TLS_SERVERNAME',
253,
);
if (mode === 'verify-full' && !isPostgresTlsDnsServername(servername)) {
throw new ClusterControlConfigError(
'QL3_POSTGRES_TLS_SERVERNAME must be an explicit DNS name for verify-full',
);
}
const certificateAuthorityFile = boundedValue(
environment,
'QL3_POSTGRES_TLS_CA_FILE',
4096,
);
if (mode === 'disable' && certificateAuthorityFile !== undefined) {
throw new ClusterControlConfigError(
'QL3_POSTGRES_TLS_CA_FILE cannot be used when TLS is disabled',
);
}
let certificateAuthority: string | undefined;
if (certificateAuthorityFile !== undefined) {
try {
certificateAuthority = loadPostgresCertificateAuthorityFile(
certificateAuthorityFile,
);
} catch {
throw new ClusterControlConfigError(
'QL3_POSTGRES_TLS_CA_FILE must contain a bounded trusted CA bundle',
);
}
}
return Object.freeze({
...connection,
tls:
mode === 'disable'
? Object.freeze({ mode: 'disable' as const })
: Object.freeze({
mode: 'verify-full' as const,
...(certificateAuthority === undefined
? {}
: { ca: certificateAuthority }),
servername: servername!,
}),
});
}
function apiCredentialPepper(environment: ClusterControlEnvironment): string {
const value = boundedValue(
environment,
'QL3_API_CREDENTIAL_PEPPER',
64,
true,
)!;
if (!/^[A-Za-z0-9_-]{43}$/.test(value)) {
throw new ClusterControlConfigError(
'QL3_API_CREDENTIAL_PEPPER must be canonical base64url for 32 bytes',
);
}
const decoded = Buffer.from(value, 'base64url');
if (decoded.byteLength !== 32 || decoded.toString('base64url') !== value) {
throw new ClusterControlConfigError(
'QL3_API_CREDENTIAL_PEPPER must be canonical base64url for 32 bytes',
);
}
decoded.fill(0);
return value;
}
/**
* Parses the profile gate before reading PostgreSQL configuration. A disabled
* cluster-control therefore does not touch its runtime credential source.
*/
export function loadClusterControlConfig(
environment: ClusterControlEnvironment,
): ClusterControlConfig {
if (
!environment ||
typeof environment !== 'object' ||
Array.isArray(environment)
) {
throw new ClusterControlConfigError('environment must be an object');
}
const profile = deploymentProfile(environment);
const enabled = booleanValue(
environment,
'QL3_CLUSTER_CONTROL_ENABLED',
false,
);
if (!enabled) return Object.freeze({ enabled: false, profile });
if (profile !== 'cluster-control') {
throw new ClusterControlConfigError(
'enabled runtime requires QL_DEPLOYMENT_PROFILE=cluster-control',
);
}
const applicationName =
boundedValue(environment, 'QL3_POSTGRES_APPLICATION_NAME', 63) ??
'qinglong-cluster-runtime';
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,62}$/.test(applicationName)) {
throw new ClusterControlConfigError(
'QL3_POSTGRES_APPLICATION_NAME is invalid',
);
}
const host =
boundedValue(environment, 'QL3_CLUSTER_HTTP_HOST', 253) ?? '0.0.0.0';
const config: EnabledClusterControlConfig = {
enabled: true,
profile: 'cluster-control',
http: Object.freeze({
host,
port: integerValue(environment, 'QL3_CLUSTER_HTTP_PORT', 5800, 1, 65_535),
maxBodyBytes: integerValue(
environment,
'QL3_CLUSTER_HTTP_MAX_BODY_BYTES',
1024 * 1024,
1024,
4 * 1024 * 1024,
),
maxInFlightRequests: integerValue(
environment,
'QL3_CLUSTER_HTTP_MAX_IN_FLIGHT',
64,
1,
1024,
),
authenticationRateWindowMs: integerValue(
environment,
'QL3_CLUSTER_AUTH_RATE_WINDOW_MS',
60_000,
1_000,
60 * 60_000,
),
authenticationRatePerPeer: integerValue(
environment,
'QL3_CLUSTER_AUTH_RATE_PER_PEER',
300,
1,
1_000_000,
),
authenticationRateGlobal: integerValue(
environment,
'QL3_CLUSTER_AUTH_RATE_GLOBAL',
1_200,
1,
1_000_000,
),
authenticationRateMaxPeers: integerValue(
environment,
'QL3_CLUSTER_AUTH_RATE_MAX_PEERS',
4_096,
1,
65_536,
),
requestTimeoutMs: integerValue(
environment,
'QL3_CLUSTER_HTTP_REQUEST_TIMEOUT_MS',
15_000,
100,
120_000,
),
drainTimeoutMs: integerValue(
environment,
'QL3_CLUSTER_HTTP_DRAIN_TIMEOUT_MS',
10_000,
100,
120_000,
),
}),
database: Object.freeze({
connection: runtimeConnection(environment),
pool: Object.freeze({
applicationName,
maxConnections: integerValue(
environment,
'QL3_POSTGRES_MAX_CONNECTIONS',
8,
1,
64,
),
connectionTimeoutMs: integerValue(
environment,
'QL3_POSTGRES_CONNECTION_TIMEOUT_MS',
5_000,
100,
60_000,
),
}),
}),
security: Object.freeze({
apiCredentialPepper: apiCredentialPepper(environment),
}),
};
return Object.freeze(config);
}
export function createClusterControlDatabaseBinding(
config: EnabledClusterControlConfig,
): ClusterControlDatabaseBinding {
if (!config?.enabled || config.profile !== 'cluster-control') {
throw new ClusterControlConfigError(
'database binding requires an enabled cluster-control config',
);
}
const availability = new ClusterControlAvailabilityFence();
const openDatabase = createPostgresDatabaseOpener({
role: 'runtime',
connection: config.database.connection,
pool: config.database.pool,
onPoolError(error) {
// pg emits idle-client errors outside a request Promise. They are an
// availability signal, never a callback exception or transaction retry.
void availability.signal(error).catch(() => undefined);
},
});
return Object.freeze({ availability, openDatabase });
}
@@ -0,0 +1,416 @@
import type {
ClusterControlActivationAudit,
ClusterControlStopResult,
} from '@qinglong/runtime-core';
import {
loadClusterControlConfig,
type ClusterControlEnvironment,
type EnabledClusterControlConfig,
} from './config';
import {
startProductionClusterControlApplication,
type ProductionClusterControlApplicationOptions,
} from '../application-runtime/productionApplication';
import {
ClusterControlDatabaseUnavailableError,
type ClusterControlApplicationResult,
} from '../application-runtime/application';
import {
loadClusterWorkerIngressConfig,
type EnabledClusterWorkerIngressConfig,
} from '../worker-ingress/workerIngressConfig';
import type { ClusterWorkerArtifactBinding } from '../artifact/workerArtifactBinding';
import type { RemoteWorkerSecretValueProvider } from '@qinglong/runtime-core/remote-secret-delivery';
export type ClusterControlProcessSignal = 'SIGINT' | 'SIGTERM';
export interface ClusterControlProcessEvent {
readonly schemaVersion: 1;
readonly component: 'qinglong3-cluster-control';
readonly level: 'info' | 'error';
readonly event: string;
readonly replicaId: string;
readonly signal?: ClusterControlProcessSignal;
readonly stopResult?: ClusterControlStopResult;
readonly address?: Readonly<{ host: string; port: number }>;
readonly activation?: ClusterControlActivationAudit;
readonly diagnostic?: Readonly<{
scope:
| 'scheduler'
| 'cancellation-convergence'
| 'database'
| 'worker-ingress';
name: string;
code?: string;
}>;
}
export interface ClusterControlProcessSignalSource {
subscribe(
listener: (signal: ClusterControlProcessSignal) => void,
): () => void;
}
export type ProductionClusterControlStarter = (
options: ProductionClusterControlApplicationOptions,
) => Promise<ClusterControlApplicationResult>;
export type ClusterWorkerArtifactBindingFactory = (
config: EnabledClusterWorkerIngressConfig['artifact'],
) => Promise<Readonly<ClusterWorkerArtifactBinding>>;
export type ClusterWorkerSecretProviderFactory = (
config: NonNullable<EnabledClusterWorkerIngressConfig['secret']>,
) => Promise<Readonly<RemoteWorkerSecretValueProvider>>;
export interface ProductionClusterControlProcessOptions {
readonly environment: ClusterControlEnvironment;
readonly signals: ClusterControlProcessSignalSource;
readonly emit: (event: ClusterControlProcessEvent) => void | Promise<void>;
readonly start?: ProductionClusterControlStarter;
readonly createWorkerArtifactBinding?: ClusterWorkerArtifactBindingFactory;
readonly createWorkerSecretProvider?: ClusterWorkerSecretProviderFactory;
readonly workerSecretProvider?: RemoteWorkerSecretValueProvider;
}
export class ClusterControlProcessError extends Error {
readonly code:
| 'QL3_CLUSTER_CONTROL_PROCESS_CONFIG_INVALID'
| 'QL3_CLUSTER_CONTROL_PROCESS_DISABLED';
constructor(
code: ClusterControlProcessError['code'],
message: string,
) {
super(message);
this.name = 'ClusterControlProcessError';
this.code = code;
}
}
const REPLICA_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
function processConfiguration(environment: ClusterControlEnvironment): {
readonly config: EnabledClusterControlConfig;
readonly workerIngress?: EnabledClusterWorkerIngressConfig;
readonly replicaId: string;
} {
const config = loadClusterControlConfig(environment);
if (!config.enabled) {
throw new ClusterControlProcessError(
'QL3_CLUSTER_CONTROL_PROCESS_DISABLED',
'The cluster-control process requires an enabled cluster-control profile',
);
}
const replicaId = environment.QL3_CLUSTER_REPLICA_ID;
if (
typeof replicaId !== 'string' ||
!REPLICA_ID_PATTERN.test(replicaId)
) {
throw new ClusterControlProcessError(
'QL3_CLUSTER_CONTROL_PROCESS_CONFIG_INVALID',
'QL3_CLUSTER_REPLICA_ID must be a stable safe identifier',
);
}
const workerIngress = loadClusterWorkerIngressConfig(environment);
return Object.freeze({
config,
replicaId,
...(workerIngress.enabled ? { workerIngress } : {}),
});
}
async function createWorkerArtifactBinding(
config: EnabledClusterWorkerIngressConfig['artifact'],
): Promise<Readonly<ClusterWorkerArtifactBinding>> {
const binding = await import('../artifact/workerArtifactBinding.js');
return binding.createClusterWorkerArtifactBinding(config);
}
async function createWorkerSecretProvider(
config: NonNullable<EnabledClusterWorkerIngressConfig['secret']>,
): Promise<Readonly<RemoteWorkerSecretValueProvider>> {
if (config.provider !== 'mounted-files') {
throw new TypeError('Cluster Worker Secret provider is unsupported');
}
const provider = await import('../remote-execution/mountedSecretProvider.js');
return provider.createClusterMountedSecretProvider({
rootDirectory: config.rootDirectory,
});
}
function diagnosticFact(
scope: ClusterControlProcessEvent['diagnostic'] extends infer T
? T extends { readonly scope: infer TScope }
? TScope
: never
: never,
error: unknown,
): NonNullable<ClusterControlProcessEvent['diagnostic']> {
const candidate = error as {
readonly name?: unknown;
readonly code?: unknown;
};
return Object.freeze({
scope,
name:
typeof candidate?.name === 'string' && candidate.name.length <= 128
? candidate.name
: 'Error',
...(typeof candidate?.code === 'string' && candidate.code.length <= 128
? { code: candidate.code }
: {}),
});
}
function event(
replicaId: string,
values: Omit<
ClusterControlProcessEvent,
'schemaVersion' | 'component' | 'replicaId'
>,
): ClusterControlProcessEvent {
return Object.freeze({
schemaVersion: 1,
component: 'qinglong3-cluster-control',
replicaId,
...values,
});
}
/**
* Owns exactly one production cluster-control process. It installs signal
* handling before startup, derives every lease owner from the stable replica
* identity, and withdraws admission through the application stop contract.
*/
export async function runProductionClusterControlProcess(
options: ProductionClusterControlProcessOptions,
): Promise<ClusterControlStopResult> {
if (
!options ||
typeof options !== 'object' ||
typeof options.emit !== 'function' ||
typeof options.signals?.subscribe !== 'function'
) {
throw new TypeError('Cluster-control process options are invalid');
}
const { config, replicaId, workerIngress } = processConfiguration(
options.environment,
);
const start = options.start ?? startProductionClusterControlApplication;
if (typeof start !== 'function') {
throw new TypeError('Cluster-control process starter is invalid');
}
let resolveSignal:
| ((signal: ClusterControlProcessSignal) => void)
| undefined;
const requestedSignal = new Promise<ClusterControlProcessSignal>((resolve) => {
resolveSignal = resolve;
});
let acceptedSignal = false;
const unsubscribe = options.signals.subscribe((signal) => {
if (acceptedSignal) return;
acceptedSignal = true;
resolveSignal?.(signal);
});
let artifactBinding: Readonly<ClusterWorkerArtifactBinding> | undefined;
let workerSecretProvider = options.workerSecretProvider;
let application: ClusterControlApplicationResult | undefined;
let applicationStopStarted = false;
let primaryError: unknown;
try {
if (workerIngress) {
const createBinding =
options.createWorkerArtifactBinding ?? createWorkerArtifactBinding;
if (typeof createBinding !== 'function') {
throw new TypeError(
'Cluster Worker Artifact binding factory is invalid',
);
}
artifactBinding = await createBinding(workerIngress.artifact);
if (
workerIngress.secret !== undefined &&
workerSecretProvider === undefined
) {
const createProvider =
options.createWorkerSecretProvider ?? createWorkerSecretProvider;
if (typeof createProvider !== 'function') {
throw new TypeError(
'Cluster Worker Secret provider factory is invalid',
);
}
workerSecretProvider = await createProvider(workerIngress.secret);
}
if (
workerSecretProvider !== undefined &&
typeof workerSecretProvider.resolve !== 'function'
) {
throw new TypeError('Cluster Worker Secret provider is invalid');
}
}
application = await start({
config,
recovery: { ownerId: replicaId },
scheduler: {
ownerId: replicaId,
onDiagnostic(error) {
void Promise.resolve(
options.emit(
event(replicaId, {
level: 'error',
event: 'runtime_diagnostic',
diagnostic: diagnosticFact('scheduler', error),
}),
),
).catch(() => undefined);
},
},
cancellationConvergence: {
onDiagnostic(error) {
void Promise.resolve(
options.emit(
event(replicaId, {
level: 'error',
event: 'runtime_diagnostic',
diagnostic: diagnosticFact(
'cancellation-convergence',
error,
),
}),
),
).catch(() => undefined);
},
},
...(workerIngress === undefined
? {}
: {
workerIngress: {
config: workerIngress,
artifactStore: artifactBinding!.store,
...(workerSecretProvider === undefined
? {}
: { secretProvider: workerSecretProvider }),
onDiagnostic(error: unknown) {
void Promise.resolve(
options.emit(
event(replicaId, {
level: 'error',
event: 'runtime_diagnostic',
diagnostic: diagnosticFact(
'worker-ingress',
error,
),
}),
),
).catch(() => undefined);
},
},
}),
audit(record) {
return options.emit(
event(replicaId, {
level: record.state === 'failed' ? 'error' : 'info',
event: 'activation',
activation: Object.freeze({ ...record }),
}),
);
},
});
if (application.status !== 'active') {
throw new ClusterControlProcessError(
'QL3_CLUSTER_CONTROL_PROCESS_DISABLED',
'The cluster-control process did not activate',
);
}
await options.emit(
event(replicaId, {
level: 'info',
event: 'listening',
address: application.address,
}),
);
if (workerIngress) {
await options.emit(
event(replicaId, {
level: 'info',
event: 'worker_ingress_listening',
address: Object.freeze({
host: workerIngress.http.host ?? '0.0.0.0',
port: workerIngress.http.port ?? 5801,
}),
}),
);
}
const termination = await Promise.race([
requestedSignal.then((signal) =>
Object.freeze({ kind: 'signal' as const, signal }),
),
application.unavailable.then((error) =>
Object.freeze({ kind: 'database-unavailable' as const, error }),
),
]);
if (termination.kind === 'database-unavailable') {
await options.emit(
event(replicaId, {
level: 'error',
event: 'database_unavailable',
diagnostic: diagnosticFact('database', termination.error),
}),
);
applicationStopStarted = true;
const stopResult = await application.stop();
await options.emit(
event(replicaId, {
level: stopResult === 'stopped' ? 'info' : 'error',
event: 'stopped',
stopResult,
}),
);
throw new ClusterControlDatabaseUnavailableError();
}
const signal = termination.signal;
await options.emit(
event(replicaId, {
level: 'info',
event: 'shutdown_requested',
signal,
}),
);
applicationStopStarted = true;
const stopResult = await application.stop();
await options.emit(
event(replicaId, {
level: stopResult === 'stopped' ? 'info' : 'error',
event: 'stopped',
stopResult,
}),
);
return stopResult;
} catch (error) {
primaryError = error;
throw error;
} finally {
unsubscribe();
resolveSignal = undefined;
let cleanupError: unknown;
if (
application?.status === 'active' &&
!applicationStopStarted
) {
try {
applicationStopStarted = true;
await application.stop();
} catch (error) {
cleanupError = error;
}
}
try {
await artifactBinding?.close();
} catch (error) {
cleanupError ??= error;
}
if (cleanupError && primaryError === undefined) throw cleanupError;
}
}
@@ -0,0 +1,260 @@
// Remote Execution owns mounted Secret resolution for authenticated delivery.
import { createHash } from 'node:crypto';
import { constants } from 'node:fs';
import {
lstat,
open,
realpath,
} from 'node:fs/promises';
import {
isAbsolute,
join,
normalize,
parse,
relative,
} from 'node:path';
import {
MAX_REMOTE_SECRET_DELIVERY_TOTAL_VALUE_BYTES,
MAX_REMOTE_SECRET_VALUE_BYTES,
normalizeRemoteWorkerSecretDeliveryAuthority,
type RemoteWorkerSecretDeliveryAuthority,
type RemoteWorkerSecretResolution,
type RemoteWorkerSecretValueProvider,
} from '@qinglong/runtime-core/remote-secret-delivery';
import { parseSecretRef } from '@qinglong/runtime-core/secret-reference';
const MAX_SECRET_ROOT_BYTES = 4096;
const SECRET_FILE_NAME = /^[0-9a-f]{64}$/;
export interface ClusterMountedSecretProviderOptions {
/**
* Read-only directory whose file names are SHA-256(canonical SecretRef).
* Kubernetes projected-volume symlinks are accepted only when their resolved
* regular file remains below this directory.
*/
readonly rootDirectory: string;
}
export class ClusterMountedSecretProviderError extends Error {
readonly code = 'QL3_CLUSTER_MOUNTED_SECRET_UNAVAILABLE';
constructor(
readonly reason:
| 'invalid_configuration'
| 'root_unavailable'
| 'material_unavailable',
options?: ErrorOptions,
) {
super(`Cluster mounted Secret provider failed: ${reason}`, options);
this.name = 'ClusterMountedSecretProviderError';
}
}
function rootDirectory(value: string): string {
if (
typeof value !== 'string' ||
!isAbsolute(value) ||
parse(value).root === value ||
normalize(value) !== value ||
value.includes('\0') ||
Buffer.byteLength(value, 'utf8') > MAX_SECRET_ROOT_BYTES
) {
throw new ClusterMountedSecretProviderError('invalid_configuration');
}
return value;
}
/**
* Kubernetes Secret keys cannot contain a SecretRef directly. This stable,
* non-reversible name also prevents Project/name input from becoming a path.
*/
export function clusterMountedSecretFileName(secretRef: string): string {
let canonical: string;
try {
const parsed = parseSecretRef(secretRef);
canonical = secretRef;
if (
parsed.projectId.length < 1 ||
parsed.name.length < 1
) throw new Error('invalid SecretRef');
} catch (error) {
throw new ClusterMountedSecretProviderError(
'invalid_configuration',
{ cause: error },
);
}
const name = createHash('sha256').update(canonical, 'utf8').digest('hex');
if (!SECRET_FILE_NAME.test(name)) {
throw new ClusterMountedSecretProviderError('invalid_configuration');
}
return name;
}
function remainsBelow(root: string, candidate: string): boolean {
const suffix = relative(root, candidate);
return (
suffix.length > 0 &&
!isAbsolute(suffix) &&
suffix !== '..' &&
!suffix.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`)
);
}
async function resolvedRoot(path: string): Promise<string> {
try {
const configured = await lstat(path);
if (!configured.isDirectory() || configured.isSymbolicLink()) {
throw new Error('root is not a direct directory');
}
return await realpath(path);
} catch (error) {
throw new ClusterMountedSecretProviderError(
'root_unavailable',
{ cause: error },
);
}
}
async function readMaterial(
root: string,
secretRef: string,
): Promise<Buffer> {
const candidate = join(root, clusterMountedSecretFileName(secretRef));
let handle;
try {
const target = await realpath(candidate);
if (!remainsBelow(root, target)) {
throw new Error('material escaped its root');
}
handle = await open(
target,
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0),
);
const stat = await handle.stat();
if (
!stat.isFile() ||
stat.nlink !== 1 ||
stat.size < 0 ||
stat.size > MAX_REMOTE_SECRET_VALUE_BYTES ||
(stat.mode & 0o111) !== 0 ||
(stat.mode & 0o027) !== 0
) {
throw new Error('material metadata is unsafe');
}
const bytes = await handle.readFile();
if (
bytes.byteLength !== stat.size ||
bytes.byteLength > MAX_REMOTE_SECRET_VALUE_BYTES ||
(await realpath(candidate)) !== target
) {
bytes.fill(0);
throw new Error('material changed while reading');
}
return bytes;
} catch (error) {
throw new ClusterMountedSecretProviderError(
'material_unavailable',
{ cause: error },
);
} finally {
await handle?.close().catch(() => undefined);
}
}
function secretValue(bytes: Buffer): string {
try {
const value = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
if (value.includes('\0')) {
throw new Error('Secret contains NUL');
}
return value;
} catch (error) {
throw new ClusterMountedSecretProviderError(
'material_unavailable',
{ cause: error },
);
}
}
/**
* A zero-client, zero-watcher Cluster provider for Kubernetes Secret, CSI or
* operator-managed projected files. Every authorized delivery resolves the
* active files again, so atomic projection replacement rotates material
* without a timer, cache, control restart or Kubernetes API permission.
*/
export class ClusterMountedSecretProvider
implements RemoteWorkerSecretValueProvider
{
private readonly rootDirectory: string;
constructor(options: ClusterMountedSecretProviderOptions) {
if (!options || typeof options !== 'object' || Array.isArray(options)) {
throw new ClusterMountedSecretProviderError('invalid_configuration');
}
this.rootDirectory = rootDirectory(options.rootDirectory);
}
async verify(): Promise<void> {
await resolvedRoot(this.rootDirectory);
}
async resolve(
authority: Readonly<RemoteWorkerSecretDeliveryAuthority>,
): Promise<Readonly<RemoteWorkerSecretResolution>> {
let normalized: Readonly<RemoteWorkerSecretDeliveryAuthority>;
try {
normalized = normalizeRemoteWorkerSecretDeliveryAuthority(authority);
} catch (error) {
throw new ClusterMountedSecretProviderError(
'material_unavailable',
{ cause: error },
);
}
const root = await resolvedRoot(this.rootDirectory);
const buffers: Buffer[] = [];
try {
const values = [];
let totalBytes = 0;
for (const secretRef of normalized.secretRefs) {
const bytes = await readMaterial(root, secretRef);
buffers.push(bytes);
totalBytes += bytes.byteLength;
if (totalBytes > MAX_REMOTE_SECRET_DELIVERY_TOTAL_VALUE_BYTES) {
throw new ClusterMountedSecretProviderError(
'material_unavailable',
);
}
values.push(
Object.freeze({
secretRef,
value: secretValue(bytes),
}),
);
}
let disposed = false;
return Object.freeze({
values: Object.freeze(values),
dispose() {
if (disposed) return;
disposed = true;
for (const bytes of buffers) bytes.fill(0);
},
});
} catch (error) {
for (const bytes of buffers) bytes.fill(0);
if (error instanceof ClusterMountedSecretProviderError) throw error;
throw new ClusterMountedSecretProviderError(
'material_unavailable',
{ cause: error },
);
}
}
}
export async function createClusterMountedSecretProvider(
options: ClusterMountedSecretProviderOptions,
): Promise<Readonly<ClusterMountedSecretProvider>> {
const provider = new ClusterMountedSecretProvider(options);
await provider.verify();
return provider;
}
@@ -0,0 +1,114 @@
// Remote execution owns Worker-bound activation acknowledgements and start failure fencing.
import { randomUUID } from 'node:crypto';
import type {
AcknowledgeRemoteRunRunningCommand,
AcknowledgeRemoteRunStartingCommand,
FailRemoteRunStartCommand,
RemoteRunActivationRepository,
RemoteRunActivationResult,
} from '@qinglong/runtime-core/remote-activation';
export interface ClusterRemoteRunActivationPrincipal {
readonly workerId: string;
}
type ServerOwnedStartingFields = 'workerId' | 'eventId';
type ServerOwnedRunningFields = 'workerId' | 'attemptEventId' | 'runEventId';
export type AcknowledgeClusterRemoteRunStartingCommand = Omit<
AcknowledgeRemoteRunStartingCommand,
ServerOwnedStartingFields
>;
export type AcknowledgeClusterRemoteRunRunningCommand = Omit<
AcknowledgeRemoteRunRunningCommand,
ServerOwnedRunningFields
>;
export type FailClusterRemoteRunStartCommand = Omit<
FailRemoteRunStartCommand,
ServerOwnedRunningFields
>;
export interface ClusterRemoteRunActivationServiceOptions {
readonly createEventId?: () => string;
}
export class ClusterRemoteRunActivationService {
private readonly createEventId: () => string;
constructor(
private readonly repository: RemoteRunActivationRepository,
options: ClusterRemoteRunActivationServiceOptions = {},
) {
if (
!repository ||
typeof repository.acknowledgeStarting !== 'function' ||
typeof repository.acknowledgeRunning !== 'function' ||
typeof repository.failStart !== 'function'
) {
throw new TypeError('Remote Run activation repository is invalid');
}
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some((key) => key !== 'createEventId')
) {
throw new TypeError('Remote Run activation service options are invalid');
}
this.createEventId = options.createEventId ?? randomUUID;
if (typeof this.createEventId !== 'function') {
throw new TypeError('Remote Run activation event ID factory is invalid');
}
}
acknowledgeStarting(
principal: ClusterRemoteRunActivationPrincipal,
command: AcknowledgeClusterRemoteRunStartingCommand,
): Promise<Readonly<RemoteRunActivationResult>> {
this.assertPrincipal(principal);
return this.repository.acknowledgeStarting({
...command,
workerId: principal.workerId,
eventId: this.createEventId(),
});
}
acknowledgeRunning(
principal: ClusterRemoteRunActivationPrincipal,
command: AcknowledgeClusterRemoteRunRunningCommand,
): Promise<Readonly<RemoteRunActivationResult>> {
this.assertPrincipal(principal);
return this.repository.acknowledgeRunning({
...command,
workerId: principal.workerId,
attemptEventId: this.createEventId(),
runEventId: this.createEventId(),
});
}
failStart(
principal: ClusterRemoteRunActivationPrincipal,
command: FailClusterRemoteRunStartCommand,
): Promise<Readonly<RemoteRunActivationResult>> {
this.assertPrincipal(principal);
return this.repository.failStart({
...command,
workerId: principal.workerId,
attemptEventId: this.createEventId(),
runEventId: this.createEventId(),
});
}
private assertPrincipal(principal: ClusterRemoteRunActivationPrincipal): void {
if (
!principal ||
typeof principal !== 'object' ||
Array.isArray(principal) ||
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(principal.workerId)
) {
throw new TypeError('Remote Run activation principal is invalid');
}
}
}
@@ -0,0 +1,380 @@
// Remote execution owns immutable Artifact admission and fenced Worker completion.
import { randomUUID } from 'node:crypto';
import {
InvalidRemoteWorkerCompletionError,
MAX_REMOTE_WORKER_ARTIFACT_HEADER_BYTES,
RemoteWorkerCompletionFenceRejectedError,
RemoteWorkerCompletionUnavailableError,
normalizeRemoteWorkerArtifactReceipt,
normalizeRemoteWorkerCompletionCommand,
normalizeRemoteWorkerCompletionResult,
parseRemoteWorkerArtifactUploadHeader,
type RemoteWorkerArtifactReceipt,
type RemoteWorkerArtifactUploadAuthorityRepository,
type RemoteWorkerArtifactUploadCommand,
type RemoteWorkerCompletionCommand,
type RemoteWorkerCompletionRepository,
type RemoteWorkerCompletionResult,
} from '@qinglong/runtime-core/remote-worker-completion';
export interface ClusterRemoteWorkerArtifactStorageCommand {
readonly projectId: string;
readonly runId: string;
readonly attemptId: string;
readonly logArtifactId: string;
readonly byteLength: number;
readonly truncated?: boolean;
}
export interface ClusterRemoteWorkerArtifactLookup {
readonly projectId: string;
readonly runId: string;
readonly attemptId: string;
readonly logArtifactId: string;
}
/**
* Production implementations must be shared by every cluster-control replica
* and provide immutable, digest-authenticated put-if-absent semantics.
*/
export interface ClusterRemoteWorkerArtifactStore {
put(
command: Readonly<ClusterRemoteWorkerArtifactStorageCommand>,
content: AsyncIterable<Uint8Array>,
signal?: AbortSignal,
): Promise<Readonly<RemoteWorkerArtifactReceipt>>;
inspect(
lookup: Readonly<ClusterRemoteWorkerArtifactLookup>,
signal?: AbortSignal,
): Promise<Readonly<RemoteWorkerArtifactReceipt> | undefined>;
}
export interface ClusterRemoteWorkerArtifactUploadInput {
readonly workerId: string;
readonly workerSessionId: string;
readonly contentLength: number;
readonly chunks: AsyncIterable<Uint8Array>;
readonly signal?: AbortSignal;
}
export interface ClusterRemoteWorkerCompletionServiceOptions {
readonly createEventId?: () => string;
}
class BoundedArtifactStreamReader {
private readonly iterator: AsyncIterator<Uint8Array>;
private pending: Uint8Array | undefined;
private pendingOffset = 0;
constructor(
source: AsyncIterable<Uint8Array>,
private readonly signal?: AbortSignal,
) {
if (!source || typeof source[Symbol.asyncIterator] !== 'function') {
throw new InvalidRemoteWorkerCompletionError(
'Artifact upload stream is invalid',
);
}
this.iterator = source[Symbol.asyncIterator]();
}
async readExactly(byteLength: number): Promise<Buffer> {
const result = Buffer.allocUnsafe(byteLength);
let written = 0;
try {
while (written < byteLength) {
const chunk = await this.nextChunk();
if (!chunk) {
throw new InvalidRemoteWorkerCompletionError(
'Artifact upload stream ended before its header',
);
}
const available = chunk.byteLength - this.pendingOffset;
const copied = Math.min(available, byteLength - written);
Buffer.from(
chunk.buffer,
chunk.byteOffset + this.pendingOffset,
copied,
).copy(result, written);
written += copied;
this.pendingOffset += copied;
if (this.pendingOffset === chunk.byteLength) {
this.pending = undefined;
this.pendingOffset = 0;
}
}
return result;
} catch (error) {
result.fill(0);
throw error;
}
}
content(byteLength: number): Readonly<{
chunks: AsyncIterable<Uint8Array>;
isComplete(): boolean;
}> {
let complete = false;
let started = false;
const self = this;
const chunks = Object.freeze({
async *[Symbol.asyncIterator](): AsyncGenerator<Uint8Array> {
if (started) {
throw new InvalidRemoteWorkerCompletionError(
'Artifact content can only be consumed once',
);
}
started = true;
let total = 0;
while (true) {
const chunk = await self.nextChunk();
if (!chunk) break;
const bytes = chunk.subarray(self.pendingOffset);
self.pending = undefined;
self.pendingOffset = 0;
total += bytes.byteLength;
if (total > byteLength) {
throw new InvalidRemoteWorkerCompletionError(
'Artifact content exceeds its declared length',
);
}
yield bytes;
}
if (total !== byteLength) {
throw new InvalidRemoteWorkerCompletionError(
'Artifact content does not match its declared length',
);
}
complete = true;
},
});
return Object.freeze({ chunks, isComplete: () => complete });
}
private async nextChunk(): Promise<Uint8Array | undefined> {
if (this.signal?.aborted) throw this.signal.reason;
if (this.pending) return this.pending;
const next = await this.iterator.next();
if (next.done) return undefined;
if (!(next.value instanceof Uint8Array) || next.value.byteLength === 0) {
throw new InvalidRemoteWorkerCompletionError(
'Artifact upload chunk is invalid',
);
}
this.pending = next.value;
this.pendingOffset = 0;
return this.pending;
}
}
function storageCommand(
command: RemoteWorkerArtifactUploadCommand,
): Readonly<ClusterRemoteWorkerArtifactStorageCommand> {
return Object.freeze({
projectId: command.projectId,
runId: command.runId,
attemptId: command.attemptId,
logArtifactId: command.logArtifactId,
byteLength: command.byteLength,
...(command.truncated === undefined
? {}
: { truncated: command.truncated }),
});
}
function assertReceiptMatches(
command: ClusterRemoteWorkerArtifactStorageCommand,
value: RemoteWorkerArtifactReceipt,
): Readonly<RemoteWorkerArtifactReceipt> {
const receipt = normalizeRemoteWorkerArtifactReceipt(value);
if (
receipt.projectId !== command.projectId ||
receipt.runId !== command.runId ||
receipt.attemptId !== command.attemptId ||
receipt.logArtifactId !== command.logArtifactId ||
receipt.byteLength !== command.byteLength ||
receipt.truncated !== command.truncated
) {
throw new RemoteWorkerCompletionUnavailableError();
}
return receipt;
}
function eventId(factory: () => string): string {
const value = factory();
if (
typeof value !== 'string' ||
value.length < 1 ||
value.length > 36 ||
/[\u0000-\u001f\u007f]/.test(value)
) {
throw new RemoteWorkerCompletionUnavailableError();
}
return value;
}
export class ClusterRemoteWorkerArtifactService {
constructor(
private readonly authority: RemoteWorkerArtifactUploadAuthorityRepository,
private readonly store: ClusterRemoteWorkerArtifactStore,
) {
if (
typeof authority?.authorizeArtifactUpload !== 'function' ||
typeof store?.put !== 'function' ||
typeof store?.inspect !== 'function'
) {
throw new TypeError('Remote Worker Artifact service is invalid');
}
}
async upload(
input: ClusterRemoteWorkerArtifactUploadInput,
): Promise<Readonly<RemoteWorkerArtifactReceipt>> {
if (
!input ||
!Number.isSafeInteger(input.contentLength) ||
input.contentLength < 6
) {
throw new InvalidRemoteWorkerCompletionError(
'Artifact upload envelope is invalid',
);
}
const reader = new BoundedArtifactStreamReader(input.chunks, input.signal);
const prefix = await reader.readExactly(4);
const headerLength = prefix.readUInt32BE(0);
prefix.fill(0);
if (
headerLength < 2 ||
headerLength > MAX_REMOTE_WORKER_ARTIFACT_HEADER_BYTES
) {
throw new InvalidRemoteWorkerCompletionError(
'Artifact upload header length is invalid',
);
}
const header = await reader.readExactly(headerLength);
let command: Readonly<RemoteWorkerArtifactUploadCommand>;
try {
command = parseRemoteWorkerArtifactUploadHeader(header, {
workerId: input.workerId,
workerSessionId: input.workerSessionId,
});
} finally {
header.fill(0);
}
if (input.contentLength !== 4 + headerLength + command.byteLength) {
throw new InvalidRemoteWorkerCompletionError(
'Artifact upload envelope length does not match its header',
);
}
try {
await this.authority.authorizeArtifactUpload(command);
const target = storageCommand(command);
const content = reader.content(command.byteLength);
const receipt = await this.store.put(
target,
content.chunks,
input.signal,
);
if (!content.isComplete()) {
throw new Error('Artifact store did not consume the complete body');
}
return assertReceiptMatches(target, receipt);
} catch (error) {
if (
error instanceof InvalidRemoteWorkerCompletionError ||
error instanceof RemoteWorkerCompletionFenceRejectedError ||
error instanceof RemoteWorkerCompletionUnavailableError
) {
throw error;
}
throw new RemoteWorkerCompletionUnavailableError({ cause: error });
}
}
}
export class ClusterRemoteWorkerCompletionService {
private readonly createEventId: () => string;
constructor(
private readonly repository: RemoteWorkerCompletionRepository,
private readonly store: Pick<ClusterRemoteWorkerArtifactStore, 'inspect'>,
options: ClusterRemoteWorkerCompletionServiceOptions = {},
) {
if (
typeof repository?.complete !== 'function' ||
typeof store?.inspect !== 'function' ||
(options.createEventId !== undefined &&
typeof options.createEventId !== 'function')
) {
throw new TypeError('Remote Worker completion service is invalid');
}
this.createEventId = options.createEventId ?? randomUUID;
}
async complete(
value: RemoteWorkerCompletionCommand,
signal?: AbortSignal,
): Promise<Readonly<RemoteWorkerCompletionResult>> {
const command = normalizeRemoteWorkerCompletionCommand(value);
if (signal?.aborted) throw signal.reason;
const lookup = Object.freeze({
projectId: command.projectId,
runId: command.runId,
attemptId: command.attemptId,
logArtifactId: command.artifact.logArtifactId,
});
let stored: Readonly<RemoteWorkerArtifactReceipt> | undefined;
try {
stored = await this.store.inspect(lookup, signal);
} catch (error) {
throw new RemoteWorkerCompletionUnavailableError({ cause: error });
}
if (!stored) {
throw new RemoteWorkerCompletionFenceRejectedError(
command.attemptId,
'state_mismatch',
);
}
const receipt = assertReceiptMatches(
{
...lookup,
byteLength: command.artifact.byteLength,
...(command.artifact.truncated === undefined
? {}
: { truncated: command.artifact.truncated }),
},
stored,
);
if (receipt.sha256 !== command.artifact.sha256) {
throw new RemoteWorkerCompletionFenceRejectedError(
command.attemptId,
'replay_mismatch',
);
}
try {
const result = normalizeRemoteWorkerCompletionResult(
await this.repository.complete(Object.freeze({
...command,
attemptEventId: eventId(this.createEventId),
runEventId: eventId(this.createEventId),
})),
);
if (
result.runId !== command.runId ||
result.attemptId !== command.attemptId ||
result.callbackSequence !== command.callbackSequence
) {
throw new Error('Remote Worker completion authority drifted');
}
return result;
} catch (error) {
if (
error instanceof RemoteWorkerCompletionFenceRejectedError ||
error instanceof RemoteWorkerCompletionUnavailableError
) {
throw error;
}
throw new RemoteWorkerCompletionUnavailableError({ cause: error });
}
}
}
@@ -0,0 +1,380 @@
// Remote execution owns bounded offer selection, placement, and lease claiming.
import { randomBytes, randomUUID } from 'node:crypto';
import type {
ClusterDispatchCandidate,
ClusterDispatchCandidateCursor,
ClusterDispatchSource,
ClusterRemoteExecutionOffer,
} from '@qinglong/runtime-core/remote-dispatch';
import {
assertRemoteDispatchPageSize,
createClusterRemoteExecutionOffer,
evaluateRemoteWorkerPlacement,
leaseTokenMatchesDigest,
normalizeClusterDispatchCandidate,
} from '@qinglong/runtime-core/remote-dispatch';
import type {
ClusterTaskExecutionRevision,
ClusterTaskExecutionRevisionSource,
} from '@qinglong/runtime-core/cluster-execution-revision';
import type {
ClaimRunDispatchLeaseResult,
RunDispatchLeaseRepository,
WorkerSessionRecord,
WorkerSessionRepository,
} from '@qinglong/runtime-core';
import {
assertRunDispatchId,
assertRunDispatchLeaseDuration,
assertRunDispatchLeaseToken,
assertWorkerId,
assertWorkerSessionId,
} from '@qinglong/runtime-core';
import { parseTaskDefinitionRevisionRef } from '@qinglong/runtime-core/task-definition-execution-compiler';
const DEFAULT_PAGE_SIZE = 8;
const DEFAULT_MAX_PAGES = 2;
const DEFAULT_MAX_CLAIMS = 8;
const DEFAULT_LEASE_MS = 30_000;
const MAX_PAGES = 16;
const MAX_CLAIMS = 64;
export interface ClusterRemoteWorkerOfferPrincipal {
readonly workerId: string;
}
export interface ClaimClusterRemoteWorkerOfferCommand {
readonly workerSessionId: string;
readonly workerGeneration: number;
/** Worker-generated stable idempotency key for this poll attempt. */
readonly offerId: string;
/** Worker-generated high-entropy capability; PostgreSQL stores only its digest. */
readonly leaseToken: string;
}
export interface ClusterRemoteWorkerOfferStats {
readonly pages: number;
readonly candidates: number;
readonly plansUnavailable: number;
readonly placementMismatches: number;
readonly claimAttempts: number;
readonly claimRaces: number;
}
type MutableClusterRemoteWorkerOfferStats = {
-readonly [Key in keyof ClusterRemoteWorkerOfferStats]: ClusterRemoteWorkerOfferStats[Key];
};
export type ClaimClusterRemoteWorkerOfferResult =
| Readonly<{
status: 'offered';
offer: ClusterRemoteExecutionOffer;
stats: ClusterRemoteWorkerOfferStats;
truncated: boolean;
}>
| Readonly<{
status: 'idle';
reason:
| 'worker_unavailable'
| 'no_candidates'
| 'no_match'
| 'plans_unavailable'
| 'claim_raced'
| 'claim_budget_exhausted'
| 'scan_budget_exhausted';
stats: ClusterRemoteWorkerOfferStats;
truncated: boolean;
}>;
export class ClusterRemoteWorkerOfferFenceRejectedError extends Error {
readonly code = 'REMOTE_WORKER_OFFER_FENCED';
constructor() {
super('Remote Worker offer authority was fenced');
this.name = 'ClusterRemoteWorkerOfferFenceRejectedError';
}
}
export interface ClusterRemoteWorkerOfferClaimServiceOptions {
readonly pageSize?: number;
readonly maxPages?: number;
readonly maxClaimAttempts?: number;
readonly leaseDurationMs?: number;
readonly createEventId?: () => string;
}
function bounded(name: string, value: number, minimum: number, maximum: number): number {
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
throw new RangeError(`${name} must be between ${minimum} and ${maximum}`);
}
return value;
}
function emptyStats(): MutableClusterRemoteWorkerOfferStats {
return {
pages: 0,
candidates: 0,
plansUnavailable: 0,
placementMismatches: 0,
claimAttempts: 0,
claimRaces: 0,
};
}
function cursor(candidate: ClusterDispatchCandidate): ClusterDispatchCandidateCursor {
return Object.freeze({
priority: candidate.priority,
queuedAtMs: candidate.queuedAtMs,
attemptCreatedAtMs: candidate.attemptCreatedAtMs,
attemptId: candidate.attemptId,
});
}
export class ClusterRemoteWorkerOfferClaimService {
private readonly pageSize: number;
private readonly maxPages: number;
private readonly maxClaimAttempts: number;
private readonly leaseDurationMs: number;
private readonly createEventId: () => string;
constructor(
private readonly source: ClusterDispatchSource,
private readonly workers: Pick<WorkerSessionRepository, 'findById'>,
private readonly revisions: ClusterTaskExecutionRevisionSource,
private readonly leases: Pick<RunDispatchLeaseRepository, 'claim'>,
options: ClusterRemoteWorkerOfferClaimServiceOptions = {},
) {
if (
!source || typeof source.listClusterDispatchCandidates !== 'function' ||
typeof source.findClusterDispatchRecovery !== 'function' ||
!workers || typeof workers.findById !== 'function' ||
!revisions || typeof revisions.resolveClusterTaskExecutionRevision !== 'function' ||
!leases || typeof leases.claim !== 'function'
) throw new TypeError('Remote Worker offer service dependencies are invalid');
const allowed = new Set([
'createEventId', 'leaseDurationMs', 'maxClaimAttempts', 'maxPages', 'pageSize',
]);
if (!options || typeof options !== 'object' || Array.isArray(options) || Object.keys(options).some((key) => !allowed.has(key))) {
throw new TypeError('Remote Worker offer service options are invalid');
}
this.pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
assertRemoteDispatchPageSize(this.pageSize);
this.maxPages = bounded('Remote Worker offer maxPages', options.maxPages ?? DEFAULT_MAX_PAGES, 1, MAX_PAGES);
this.maxClaimAttempts = bounded('Remote Worker offer maxClaimAttempts', options.maxClaimAttempts ?? DEFAULT_MAX_CLAIMS, 1, MAX_CLAIMS);
this.leaseDurationMs = options.leaseDurationMs ?? DEFAULT_LEASE_MS;
assertRunDispatchLeaseDuration(this.leaseDurationMs);
this.createEventId = options.createEventId ?? randomUUID;
if (typeof this.createEventId !== 'function') {
throw new TypeError('Remote Worker offer event ID factory is invalid');
}
}
async claimNext(
principal: ClusterRemoteWorkerOfferPrincipal,
command: ClaimClusterRemoteWorkerOfferCommand,
): Promise<ClaimClusterRemoteWorkerOfferResult> {
this.assertCommand(principal, command);
const stats = emptyStats();
const recovered = await this.source.findClusterDispatchRecovery(command.offerId);
if (recovered) {
if (
recovered.lease.status !== 'leased' ||
recovered.lease.expiresAtMs <= recovered.observedAtMs ||
!recovered.workerCurrent ||
recovered.lease.workerId !== principal.workerId ||
recovered.lease.workerSessionId !== command.workerSessionId ||
recovered.lease.workerGeneration !== command.workerGeneration ||
!leaseTokenMatchesDigest(command.leaseToken, recovered.lease.leaseTokenDigest)
) throw new ClusterRemoteWorkerOfferFenceRejectedError();
const revision = await this.resolveRevision(recovered.candidate);
if (!revision) throw new ClusterRemoteWorkerOfferFenceRejectedError();
return Object.freeze({
status: 'offered' as const,
offer: this.offer(
'lease_recovery', command, recovered.candidate,
recovered.lease, revision, 0,
),
stats: Object.freeze(stats),
truncated: false,
});
}
let after: ClusterDispatchCandidateCursor | undefined;
let worker: WorkerSessionRecord | null | undefined;
let sawCandidate = false;
let sawMatch = false;
let sawRace = false;
let lastTruncated = false;
for (let pageIndex = 0; pageIndex < this.maxPages; pageIndex += 1) {
const page = await this.source.listClusterDispatchCandidates({
limit: this.pageSize,
...(after === undefined ? {} : { after }),
});
stats.pages += 1;
lastTruncated = page.truncated;
if (page.candidates.length > this.pageSize) {
throw new RangeError('Remote Worker candidate source exceeded page size');
}
worker ??= await this.workers.findById(principal.workerId);
if (
!worker || worker.sessionId !== command.workerSessionId ||
worker.generation !== command.workerGeneration ||
worker.status !== 'online' || worker.availableSlots < 1 ||
worker.leaseExpiresAtMs <= page.observedAtMs
) return this.idle('worker_unavailable', stats, false);
for (const rawCandidate of page.candidates) {
const candidate = normalizeClusterDispatchCandidate(rawCandidate);
sawCandidate = true;
stats.candidates += 1;
const revision = await this.resolveRevision(candidate);
if (!revision) {
stats.plansUnavailable += 1;
continue;
}
const placement = evaluateRemoteWorkerPlacement(
worker,
revision.placement ?? {},
page.observedAtMs,
);
if (!placement.matches) {
stats.placementMismatches += 1;
continue;
}
sawMatch = true;
if (stats.claimAttempts >= this.maxClaimAttempts) {
return this.idle('claim_budget_exhausted', stats, true);
}
const eventId = this.createEventId();
assertRunDispatchId('eventId', eventId);
stats.claimAttempts += 1;
const claim = await this.leases.claim({
runId: candidate.runId,
attemptId: candidate.attemptId,
workerId: principal.workerId,
workerSessionId: command.workerSessionId,
workerGeneration: command.workerGeneration,
leaseToken: command.leaseToken,
leaseDurationMs: this.leaseDurationMs,
eventId,
offerId: command.offerId,
});
if (claim.status === 'claimed' || claim.status === 'idempotent') {
return Object.freeze({
status: 'offered' as const,
offer: this.offer(
'new_claim', command, candidate, claim.lease, revision,
placement.score,
),
stats: Object.freeze(stats),
truncated: page.truncated,
});
}
if (claim.status === 'worker_unavailable' || claim.status === 'capacity_exhausted') {
return this.idle('worker_unavailable', stats, false);
}
stats.claimRaces += 1;
sawRace = true;
}
if (!page.truncated || page.candidates.length === 0) break;
const last = page.candidates.at(-1);
if (!last) break;
const next = page.next ?? cursor(last);
if (after && next.attemptId === after.attemptId) {
throw new Error('Remote Worker candidate cursor did not advance');
}
after = next;
}
if (lastTruncated) return this.idle('scan_budget_exhausted', stats, true);
if (!sawCandidate) return this.idle('no_candidates', stats, false);
if (stats.plansUnavailable === stats.candidates) return this.idle('plans_unavailable', stats, false);
return this.idle(sawRace ? 'claim_raced' : sawMatch ? 'claim_raced' : 'no_match', stats, false);
}
private async resolveRevision(
candidate: ClusterDispatchCandidate,
): Promise<ClusterTaskExecutionRevision | null> {
let sourceRevision: number;
try {
sourceRevision = parseTaskDefinitionRevisionRef(candidate.taskRevision).revision;
} catch {
return null;
}
const revision = await this.revisions.resolveClusterTaskExecutionRevision({
projectId: candidate.projectId,
taskId: candidate.taskId,
sourceRevision,
});
if (
!revision || revision.projectId !== candidate.projectId ||
revision.taskId !== candidate.taskId ||
revision.taskRevision !== candidate.taskRevision
) return null;
return revision;
}
private offer(
deliveryKind: ClusterRemoteExecutionOffer['deliveryKind'],
command: ClaimClusterRemoteWorkerOfferCommand,
candidate: ClusterDispatchCandidate,
lease: Extract<ClaimRunDispatchLeaseResult, { lease: unknown }>['lease'],
revision: ClusterTaskExecutionRevision,
placementScore: number,
): ClusterRemoteExecutionOffer {
return createClusterRemoteExecutionOffer({
offerId: command.offerId,
deliveryKind,
executionDigest: revision.contentDigest,
candidate,
worker: {
workerId: lease.workerId,
sessionId: lease.workerSessionId,
generation: lease.workerGeneration,
},
lease,
leaseToken: command.leaseToken,
executionRevision: revision,
placementScore,
});
}
private idle(
reason: Extract<ClaimClusterRemoteWorkerOfferResult, { status: 'idle' }>['reason'],
stats: ClusterRemoteWorkerOfferStats,
truncated: boolean,
): ClaimClusterRemoteWorkerOfferResult {
return Object.freeze({
status: 'idle' as const,
reason,
stats: Object.freeze({ ...stats }),
truncated,
});
}
private assertCommand(
principal: ClusterRemoteWorkerOfferPrincipal,
command: ClaimClusterRemoteWorkerOfferCommand,
): void {
if (!principal || typeof principal !== 'object' || Array.isArray(principal)) {
throw new TypeError('Remote Worker offer principal is invalid');
}
assertWorkerId(principal.workerId);
if (!command || typeof command !== 'object' || Array.isArray(command)) {
throw new TypeError('Remote Worker offer command is invalid');
}
const keys = Object.keys(command).sort().join(',');
if (keys !== 'leaseToken,offerId,workerGeneration,workerSessionId') {
throw new TypeError('Remote Worker offer command shape is invalid');
}
assertWorkerSessionId(command.workerSessionId);
if (!Number.isSafeInteger(command.workerGeneration) || command.workerGeneration < 1) {
throw new RangeError('Remote Worker offer generation is invalid');
}
assertRunDispatchId('offerId', command.offerId);
assertRunDispatchLeaseToken(command.leaseToken);
}
}
export function createRemoteWorkerLeaseToken(): string {
return randomBytes(32).toString('base64url');
}
@@ -0,0 +1,58 @@
// Remote execution owns fenced Worker lease renewal, release, and timeout authority.
import { randomUUID } from 'node:crypto';
import {
RemoteWorkerLeaseControlUnavailableError,
assertRemoteWorkerLeaseControlDuration,
normalizeRemoteWorkerLeaseControlCommand,
normalizeRemoteWorkerLeaseControlResult,
type RemoteWorkerLeaseControlCommand,
type RemoteWorkerLeaseControlRepository,
type RemoteWorkerLeaseControlResult,
} from '@qinglong/runtime-core/remote-worker-lease-control';
export interface ClusterRemoteWorkerLeaseControlServiceOptions {
readonly leaseDurationMs?: number;
readonly createEventId?: () => string;
}
function eventId(factory: () => string): string {
const value = factory();
if (
typeof value !== 'string' || value.length < 1 || value.length > 36 ||
/[\u0000-\u001f\u007f]/.test(value)
) throw new RemoteWorkerLeaseControlUnavailableError();
return value;
}
export class ClusterRemoteWorkerLeaseControlService {
private readonly leaseDurationMs: number;
private readonly createEventId: () => string;
constructor(
private readonly repository: RemoteWorkerLeaseControlRepository,
options: ClusterRemoteWorkerLeaseControlServiceOptions = {},
) {
if (
typeof repository?.control !== 'function' ||
(options.createEventId !== undefined &&
typeof options.createEventId !== 'function')
) throw new TypeError('Remote Worker lease control service is invalid');
const leaseDurationMs = options.leaseDurationMs ?? 30_000;
assertRemoteWorkerLeaseControlDuration(leaseDurationMs);
this.leaseDurationMs = leaseDurationMs;
this.createEventId = options.createEventId ?? randomUUID;
}
async control(
value: RemoteWorkerLeaseControlCommand,
): Promise<Readonly<RemoteWorkerLeaseControlResult>> {
const command = normalizeRemoteWorkerLeaseControlCommand(value);
return normalizeRemoteWorkerLeaseControlResult(
await this.repository.control(Object.freeze({
...command,
leaseDurationMs: this.leaseDurationMs,
timeoutEventId: eventId(this.createEventId),
})),
);
}
}
@@ -0,0 +1,122 @@
// Remote execution owns offer-bound Secret delivery without retaining plaintext authority.
import {
InvalidRemoteWorkerSecretDeliveryError,
RemoteWorkerSecretDeliveryFenceRejectedError,
RemoteWorkerSecretDeliveryUnavailableError,
createRemoteWorkerSecretDeliveryResponseBody,
normalizeRemoteWorkerSecretDeliveryAuthority,
normalizeRemoteWorkerSecretDeliveryCommand,
type RemoteWorkerSecretDeliveryAuthorityRepository,
type RemoteWorkerSecretDeliveryCommand,
type RemoteWorkerSecretDeliveryResult,
type RemoteWorkerSecretValueProvider,
} from '@qinglong/runtime-core/remote-secret-delivery';
export interface ClusterRemoteWorkerSecretDeliveryPrincipal {
readonly workerId: string;
}
export type ClusterRemoteWorkerSecretDeliveryCommand = Omit<
RemoteWorkerSecretDeliveryCommand,
'workerId'
>;
export class ClusterRemoteWorkerSecretDeliveryService {
constructor(
private readonly authority: RemoteWorkerSecretDeliveryAuthorityRepository,
private readonly secrets: RemoteWorkerSecretValueProvider,
) {
if (
!authority ||
typeof authority.authorize !== 'function' ||
!secrets ||
typeof secrets.resolve !== 'function'
) throw new TypeError('Remote Worker Secret delivery service is invalid');
}
async deliver(
principal: ClusterRemoteWorkerSecretDeliveryPrincipal,
input: ClusterRemoteWorkerSecretDeliveryCommand,
): Promise<Readonly<RemoteWorkerSecretDeliveryResult>> {
if (
!principal ||
typeof principal !== 'object' ||
Array.isArray(principal) ||
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(principal.workerId)
) throw new TypeError('Remote Worker Secret delivery principal is invalid');
const command = normalizeRemoteWorkerSecretDeliveryCommand({
...input,
workerId: principal.workerId,
});
let authorized;
try {
authorized = normalizeRemoteWorkerSecretDeliveryAuthority(
await this.authority.authorize(command),
);
if (
authorized.workerId !== command.workerId ||
authorized.workerSessionId !== command.workerSessionId ||
authorized.workerGeneration !== command.workerGeneration ||
authorized.runId !== command.runId ||
authorized.attemptId !== command.attemptId ||
authorized.projectId !== command.projectId ||
authorized.taskId !== command.taskId ||
authorized.taskRevision !== command.taskRevision ||
authorized.executionDigest !== command.executionDigest ||
authorized.offerId !== command.offerId ||
authorized.leaseGeneration !== command.leaseGeneration ||
authorized.leaseVersion !== command.expectedLeaseVersion ||
JSON.stringify(authorized.secretRefs) !== JSON.stringify(command.secretRefs)
) throw new InvalidRemoteWorkerSecretDeliveryError(
'repository authority does not match command',
);
} catch (error) {
if (
error instanceof RemoteWorkerSecretDeliveryFenceRejectedError ||
error instanceof RemoteWorkerSecretDeliveryUnavailableError
) throw error;
throw new RemoteWorkerSecretDeliveryUnavailableError();
}
let resolution;
try {
resolution = await this.secrets.resolve(authorized);
} catch {
throw new RemoteWorkerSecretDeliveryUnavailableError();
}
if (!resolution) throw new RemoteWorkerSecretDeliveryUnavailableError();
try {
if (
typeof resolution !== 'object' ||
Array.isArray(resolution) ||
Object.keys(resolution).some((key) => key !== 'values' && key !== 'dispose') ||
(resolution.dispose !== undefined &&
typeof resolution.dispose !== 'function')
) throw new InvalidRemoteWorkerSecretDeliveryError(
'provider response shape is invalid',
);
const body = createRemoteWorkerSecretDeliveryResponseBody({
runId: authorized.runId,
attemptId: authorized.attemptId,
offerId: authorized.offerId,
executionDigest: authorized.executionDigest,
values: resolution.values,
}, authorized.secretRefs);
return Object.freeze({
runId: body.runId,
attemptId: body.attemptId,
offerId: body.offerId,
executionDigest: body.executionDigest,
values: body.values,
...(resolution.dispose === undefined
? {}
: { dispose: resolution.dispose }),
});
} catch (error) {
try { await resolution.dispose?.(); } catch { /* preserve root */ }
if (error instanceof InvalidRemoteWorkerSecretDeliveryError) {
throw new RemoteWorkerSecretDeliveryUnavailableError();
}
throw error;
}
}
}
@@ -0,0 +1,103 @@
// Remote execution owns the least-privilege assembly of Worker-facing runtime capabilities.
import type { PostgresPool } from '@qinglong/runtime-core';
import type { RemoteWorkerSecretValueProvider } from '@qinglong/runtime-core/remote-secret-delivery';
import {
PostgresClusterDispatchSource,
PostgresRemoteRunActivationRepository,
PostgresRemoteWorkerCompletionRepository,
PostgresRemoteWorkerLeaseControlRepository,
PostgresRemoteWorkerSecretDeliveryAuthorityRepository,
PostgresRunDispatchLeaseRepository,
PostgresTaskExecutionRevisionSource,
PostgresWorkerSessionRepository,
} from '@qinglong/cluster-postgres/runtime';
import {
ClusterRemoteWorkerOfferClaimService,
} from './remoteWorkerDispatcher';
import {
ClusterRemoteRunActivationService,
} from './remoteRunActivationService';
import {
ClusterRemoteWorkerSecretDeliveryService,
} from './remoteWorkerSecretDeliveryService';
import {
ClusterRemoteWorkerArtifactService,
ClusterRemoteWorkerCompletionService,
type ClusterRemoteWorkerArtifactStore,
} from './remoteWorkerCompletionService';
import {
ClusterRemoteWorkerLeaseControlService,
} from './remoteWorkerLeaseControlService';
import type { WorkerIngressPipelineOptions } from '../worker-ingress/workerIngressPipeline';
export interface ClusterWorkerRuntimeDependencies {
readonly artifactStore: ClusterRemoteWorkerArtifactStore;
readonly secretProvider?: RemoteWorkerSecretValueProvider;
}
/**
* The in-process capability boundary from the runtime authority to the
* Worker-facing transport. It exposes reviewed operations, never the runtime
* Pool or mutation repositories.
*/
export interface ClusterWorkerRuntimePort {
readonly offers: NonNullable<WorkerIngressPipelineOptions['offers']>;
readonly activation: NonNullable<WorkerIngressPipelineOptions['activation']>;
readonly secrets?: NonNullable<WorkerIngressPipelineOptions['secrets']>;
readonly artifacts: NonNullable<WorkerIngressPipelineOptions['artifacts']>;
readonly completion: NonNullable<WorkerIngressPipelineOptions['completion']>;
readonly leaseControl: NonNullable<
WorkerIngressPipelineOptions['leaseControl']
>;
}
export function createClusterWorkerRuntimePort(
pool: PostgresPool,
dependencies: ClusterWorkerRuntimeDependencies,
): Readonly<ClusterWorkerRuntimePort> {
if (!pool || typeof pool.query !== 'function') {
throw new TypeError('Cluster Worker runtime Pool is invalid');
}
if (
!dependencies ||
typeof dependencies !== 'object' ||
Array.isArray(dependencies)
) {
throw new TypeError('Cluster Worker runtime dependencies are invalid');
}
const workerSessions = new PostgresWorkerSessionRepository(pool);
const completionRepository =
new PostgresRemoteWorkerCompletionRepository(pool);
const secretProvider = dependencies.secretProvider;
return Object.freeze({
offers: new ClusterRemoteWorkerOfferClaimService(
new PostgresClusterDispatchSource(pool),
workerSessions,
new PostgresTaskExecutionRevisionSource(pool),
new PostgresRunDispatchLeaseRepository(pool),
),
activation: new ClusterRemoteRunActivationService(
new PostgresRemoteRunActivationRepository(pool),
),
...(secretProvider === undefined
? {}
: {
secrets: new ClusterRemoteWorkerSecretDeliveryService(
new PostgresRemoteWorkerSecretDeliveryAuthorityRepository(pool),
secretProvider,
),
}),
artifacts: new ClusterRemoteWorkerArtifactService(
completionRepository,
dependencies.artifactStore,
),
completion: new ClusterRemoteWorkerCompletionService(
completionRepository,
dependencies.artifactStore,
),
leaseControl: new ClusterRemoteWorkerLeaseControlService(
new PostgresRemoteWorkerLeaseControlRepository(pool),
),
});
}
@@ -0,0 +1,136 @@
// Run owns bounded convergence of durable cancellation intent to terminal state.
import type {
ClusterRunCancellationConvergenceCoordinator,
ClusterRunCancellationConvergenceCycleResult,
} from '@qinglong/runtime-core/cluster-run-cancellation-convergence';
export interface ClusterRunCancellationConvergenceLifecycleOptions {
readonly intervalMs: number;
readonly stopTimeoutMs: number;
readonly onDiagnostic?: (
error: unknown,
summary?: Readonly<ClusterRunCancellationConvergenceCycleResult>,
) => void | Promise<void>;
}
export interface ClusterRunCancellationConvergenceLifecycleStopSummary {
readonly status: 'stopped' | 'timed_out';
}
/** One constant-cost cadence for all pending non-executing Run cancellations. */
export class ClusterRunCancellationConvergenceLifecycle {
private timer: NodeJS.Timeout | undefined;
private inFlight:
| Promise<Readonly<ClusterRunCancellationConvergenceCycleResult>>
| undefined;
private stopPromise:
| Promise<ClusterRunCancellationConvergenceLifecycleStopSummary>
| undefined;
private running = false;
private stopping = false;
constructor(
private readonly coordinator: Pick<
ClusterRunCancellationConvergenceCoordinator,
'reconcile'
>,
private readonly options: ClusterRunCancellationConvergenceLifecycleOptions,
) {
if (
typeof coordinator?.reconcile !== 'function' ||
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
!Number.isSafeInteger(options.intervalMs) ||
options.intervalMs < 250 ||
options.intervalMs > 60 * 60_000 ||
!Number.isSafeInteger(options.stopTimeoutMs) ||
options.stopTimeoutMs < 100 ||
options.stopTimeoutMs > 30_000 ||
(options.onDiagnostic !== undefined &&
typeof options.onDiagnostic !== 'function')
) {
throw new TypeError('Cluster Run cancellation lifecycle options are invalid');
}
}
start(): 'started' {
if (!this.running && !this.stopping) {
this.running = true;
this.schedule();
}
return 'started';
}
runOnce(): Promise<Readonly<ClusterRunCancellationConvergenceCycleResult>> {
if (this.stopping) {
return Promise.reject(
new Error('Cluster Run cancellation lifecycle is stopping'),
);
}
if (this.inFlight) return this.inFlight;
const work = this.coordinator.reconcile().finally(() => {
if (this.inFlight === work) this.inFlight = undefined;
});
this.inFlight = work;
return work;
}
stopAndDrain(): Promise<ClusterRunCancellationConvergenceLifecycleStopSummary> {
if (this.stopPromise) return this.stopPromise;
this.stopping = true;
this.running = false;
if (this.timer) clearTimeout(this.timer);
this.timer = undefined;
this.stopPromise = (async () => {
const work = this.inFlight;
if (!work) return Object.freeze({ status: 'stopped' as const });
let timeout: NodeJS.Timeout | undefined;
try {
return await Promise.race([
work.then(
() => Object.freeze({ status: 'stopped' as const }),
() => Object.freeze({ status: 'stopped' as const }),
),
new Promise<ClusterRunCancellationConvergenceLifecycleStopSummary>(
(resolve) => {
timeout = setTimeout(
() => resolve(Object.freeze({ status: 'timed_out' as const })),
this.options.stopTimeoutMs,
);
timeout.unref?.();
},
),
]);
} finally {
if (timeout) clearTimeout(timeout);
}
})();
return this.stopPromise;
}
private schedule(): void {
if (!this.running || this.timer) return;
this.timer = setTimeout(() => {
this.timer = undefined;
if (!this.running) return;
void this.runOnce()
.then((summary) => this.diagnostic(undefined, summary))
.catch((error) => this.diagnostic(error))
.finally(() => this.schedule());
}, this.options.intervalMs);
this.timer.unref?.();
}
private async diagnostic(
error: unknown,
summary?: Readonly<ClusterRunCancellationConvergenceCycleResult>,
): Promise<void> {
if (this.stopping) return;
try {
await this.options.onDiagnostic?.(error, summary);
} catch {
// Diagnostics cannot own or stop convergence.
}
}
}
@@ -0,0 +1,116 @@
// Run owns its Policy-fenced durable cancellation mutation route.
import {
CLUSTER_RUN_CANCELLATION_SCHEMA,
ClusterRunCancellationFenceRejectedError,
ClusterRunCancellationNotFoundError,
ClusterRunCancellationUnavailableError,
InvalidClusterRunCancellationError,
createClusterRunCancellationResponseBody,
parseClusterRunCancellationRequestBody,
type ClusterRunCancellationRepository,
} from '@qinglong/runtime-core/cluster-run-cancellation';
import type { ClusterControlAdmissionResponse } from '../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../transport/routeRegistry';
export const CLUSTER_CONTROL_RUN_CANCELLATION_ROUTE = Object.freeze({
method: 'POST' as const,
path: '/api/v3/projects/{projectId}/runs/{runId}/cancellation',
operationId: 'run.cancel',
permission: 'run.stop',
projectParameter: 'projectId',
});
export type ClusterRunCancellationEventIdFactory = () => string;
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
/**
* Publishes a durable cancellation command. Authentication, authorization and
* the first audit complete in admission; the repository revalidates the exact
* policy fence in the same transaction that writes the Run intent and Event.
*/
export function createClusterControlRunCancellationRoute(
repository: ClusterRunCancellationRepository,
createEventId: ClusterRunCancellationEventIdFactory,
): Readonly<ClusterControlRouteDefinition> {
if (
!repository ||
typeof repository.requestUserCancellation !== 'function' ||
typeof createEventId !== 'function'
) {
throw new TypeError('Cluster-control Run cancellation route is invalid');
}
return Object.freeze({
...CLUSTER_CONTROL_RUN_CANCELLATION_ROUTE,
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
let body;
try {
body = parseClusterRunCancellationRequestBody(
authorized.request.body,
);
} catch (error) {
if (error instanceof InvalidClusterRunCancellationError) {
return response(400, {
code: 'invalid_run_cancellation_request',
schema: CLUSTER_RUN_CANCELLATION_SCHEMA,
});
}
return response(503, { code: 'run_cancellation_unavailable' });
}
const projectId = authorized.projectId;
const runId = parameters.runId;
if (
projectId === null ||
typeof runId !== 'string' ||
runId.length < 1 ||
!authorized.policyFence ||
authorized.policyFence.bindingVersion === null
) {
return response(503, { code: 'run_cancellation_unavailable' });
}
try {
const result = await repository.requestUserCancellation({
projectId,
runId,
mutationId: body.mutationId,
eventId: createEventId(),
subject: authorized.principal.subject,
policyFence: authorized.policyFence,
});
return response(
result.status === 'accepted' ? 202 : 200,
createClusterRunCancellationResponseBody(result),
);
} catch (error) {
if (error instanceof ClusterRunCancellationNotFoundError) {
return response(404, { code: 'run_not_found' });
}
if (error instanceof ClusterRunCancellationFenceRejectedError) {
return response(409, {
code: 'run_cancellation_fence_rejected',
reason: error.reason,
});
}
if (
error instanceof InvalidClusterRunCancellationError ||
error instanceof ClusterRunCancellationUnavailableError
) {
return response(503, { code: 'run_cancellation_unavailable' });
}
return response(503, { code: 'run_cancellation_unavailable' });
}
},
});
}
@@ -0,0 +1,124 @@
import {
BoundedRunEventListProjectionUnavailableError,
InvalidBoundedRunEventListProjectionError,
executeBoundedRunEventListProjection,
} from '@qinglong/runtime-core/bounded-run-event-list-projection';
import type { RunRepositoryReader } from '@qinglong/runtime-core/run-repository';
import type { ClusterControlAdmissionResponse } from '../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../transport/routeRegistry';
export const CLUSTER_CONTROL_RUN_EVENT_LIST_ROUTE = Object.freeze({
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/runs/{runId}/events',
operationId: 'run.events.list',
permission: 'run.read',
projectParameter: 'projectId',
allowedQuery: Object.freeze(['after_sequence', 'limit']),
});
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
function parseQuery(
query: Readonly<Record<string, readonly string[]>>,
): Readonly<{ afterSequence?: number; limit?: number }> {
const afterValues = query.after_sequence;
const limitValues = query.limit;
if (
(afterValues !== undefined && afterValues.length !== 1) ||
(limitValues !== undefined && limitValues.length !== 1)
) {
throw new TypeError();
}
const rawAfter = afterValues?.[0];
const afterSequence = rawAfter === undefined ? undefined : Number(rawAfter);
const rawLimit = limitValues?.[0];
const limit = rawLimit === undefined ? undefined : Number(rawLimit);
if (
(rawAfter !== undefined &&
(!Number.isSafeInteger(afterSequence) ||
Number(afterSequence) < 0 ||
Number(afterSequence) > 2_147_483_647 ||
String(afterSequence) !== rawAfter)) ||
(rawLimit !== undefined &&
(!Number.isSafeInteger(limit) ||
Number(limit) < 1 ||
Number(limit) > 64 ||
String(limit) !== rawLimit))
) {
throw new TypeError();
}
return Object.freeze({
...(afterSequence === undefined ? {} : { afterSequence }),
...(limit === undefined ? {} : { limit }),
});
}
function validateRunEventListQuery(
query: Readonly<Record<string, readonly string[]>>,
): void {
parseQuery(query);
}
export function createClusterControlRunEventListRoute(
runs: Pick<RunRepositoryReader, 'findRunById' | 'listEvents'>,
): Readonly<ClusterControlRouteDefinition> {
if (
!runs ||
typeof runs.findRunById !== 'function' ||
typeof runs.listEvents !== 'function'
) {
throw new TypeError('Cluster-control Run event list repository is invalid');
}
return Object.freeze({
...CLUSTER_CONTROL_RUN_EVENT_LIST_ROUTE,
validateQuery: validateRunEventListQuery,
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
if (authorized.request.body !== null) {
return response(400, { code: 'invalid_request_body' });
}
if (authorized.projectId === null) {
return response(503, { code: 'run_event_list_unavailable' });
}
let input;
try {
input = parseQuery(authorized.request.query);
} catch {
return response(400, { code: 'invalid_run_event_list_query' });
}
try {
const result = await executeBoundedRunEventListProjection(
runs,
authorized.projectId,
parameters.runId!,
input,
);
if (!result.found) {
return response(404, { code: 'run_not_found' });
}
const { found: _found, ...timeline } = result;
return response(200, { ...timeline });
} catch (error) {
if (
error instanceof InvalidBoundedRunEventListProjectionError ||
error instanceof BoundedRunEventListProjectionUnavailableError
) {
return response(503, { code: 'run_event_list_unavailable' });
}
throw error;
}
},
});
}
@@ -0,0 +1,130 @@
import {
BoundedRunListProjectionUnavailableError,
InvalidBoundedRunListProjectionError,
executeBoundedRunListProjection,
} from '@qinglong/runtime-core/bounded-run-list-projection';
import type { ProjectRunListReader } from '@qinglong/runtime-core/project-run-list';
import type { ClusterControlAdmissionResponse } from '../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
} from '../transport/routeRegistry';
export const CLUSTER_CONTROL_RUN_LIST_ROUTE = Object.freeze({
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/runs',
operationId: 'run.list',
permission: 'run.read',
projectParameter: 'projectId',
allowedQuery: Object.freeze([
'after_created_at_ms',
'after_run_id',
'limit',
]),
});
const RUN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
function parseQuery(
query: Readonly<Record<string, readonly string[]>>,
): Readonly<{
limit?: number;
after?: Readonly<{ createdAtMs: number; runId: string }>;
}> {
const limitValues = query.limit;
const createdAtValues = query.after_created_at_ms;
const runIdValues = query.after_run_id;
if (
(limitValues !== undefined && limitValues.length !== 1) ||
(createdAtValues !== undefined && createdAtValues.length !== 1) ||
(runIdValues !== undefined && runIdValues.length !== 1) ||
(createdAtValues === undefined) !== (runIdValues === undefined)
) {
throw new TypeError();
}
const rawLimit = limitValues?.[0];
const limit = rawLimit === undefined ? undefined : Number(rawLimit);
if (
rawLimit !== undefined &&
(!Number.isSafeInteger(limit) ||
Number(limit) < 1 ||
Number(limit) > 64 ||
String(limit) !== rawLimit)
) {
throw new TypeError();
}
const rawCreatedAtMs = createdAtValues?.[0];
const runId = runIdValues?.[0];
if (rawCreatedAtMs === undefined || runId === undefined) {
return Object.freeze({ ...(limit === undefined ? {} : { limit }) });
}
const createdAtMs = Number(rawCreatedAtMs);
if (
!Number.isSafeInteger(createdAtMs) ||
createdAtMs < 0 ||
String(createdAtMs) !== rawCreatedAtMs ||
!RUN_ID_PATTERN.test(runId)
) {
throw new TypeError();
}
return Object.freeze({
...(limit === undefined ? {} : { limit }),
after: Object.freeze({ createdAtMs, runId }),
});
}
function validateRunListQuery(
query: Readonly<Record<string, readonly string[]>>,
): void {
parseQuery(query);
}
export function createClusterControlRunListRoute(
runs: ProjectRunListReader,
): Readonly<ClusterControlRouteDefinition> {
if (!runs || typeof runs.listRunsByProject !== 'function') {
throw new TypeError('Cluster-control Run list repository is invalid');
}
return Object.freeze({
...CLUSTER_CONTROL_RUN_LIST_ROUTE,
validateQuery: validateRunListQuery,
async handle(authorized: ClusterControlAuthorizedOperationRequest) {
if (authorized.request.body !== null) {
return response(400, { code: 'invalid_request_body' });
}
if (authorized.projectId === null) {
return response(503, { code: 'run_list_unavailable' });
}
let input;
try {
input = parseQuery(authorized.request.query);
} catch {
return response(400, { code: 'invalid_run_list_query' });
}
try {
const result = await executeBoundedRunListProjection(
runs,
authorized.projectId,
input,
);
return response(200, { ...result });
} catch (error) {
if (
error instanceof InvalidBoundedRunListProjectionError ||
error instanceof BoundedRunListProjectionUnavailableError
) {
return response(503, { code: 'run_list_unavailable' });
}
throw error;
}
},
});
}
@@ -0,0 +1,165 @@
// Run owns its bounded read projection and masks cross-Project storage facts.
import {
EXECUTION_ORIGINS,
RUN_STATUSES,
type ExecutionOrigin,
type ExecutionOwner,
type RunRecord,
type RunRepositoryReader,
type RunStatus,
} from '@qinglong/runtime-core';
import type { ClusterControlAdmissionResponse } from '../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../transport/routeRegistry';
export interface ClusterControlRunReadRepository
extends Pick<RunRepositoryReader, 'findRunById'> {}
export interface ClusterControlRunView {
readonly id: string;
readonly projectId: string;
readonly taskId: string;
readonly taskRevision: string;
readonly status: RunStatus;
readonly version: number;
readonly eventSequence: number;
readonly priority: number;
readonly executionOrigin: ExecutionOrigin;
readonly executionOwner: ExecutionOwner;
readonly createdAtMs: number;
readonly queuedAtMs: number | null;
readonly startedAtMs: number | null;
readonly finishedAtMs: number | null;
}
export const CLUSTER_CONTROL_RUN_READ_ROUTE = Object.freeze({
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/runs/{runId}',
operationId: 'run.get',
permission: 'run.read',
projectParameter: 'projectId',
});
const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/;
function boundedText(value: unknown, maximum: number): value is string {
return (
typeof value === 'string' &&
value.length > 0 &&
value.length <= maximum &&
!CONTROL_CHARACTER_PATTERN.test(value)
);
}
function nonNegativeInteger(value: unknown): value is number {
return Number.isSafeInteger(value) && Number(value) >= 0;
}
function optionalTimestamp(value: unknown): value is number | undefined {
return value === undefined || nonNegativeInteger(value);
}
function projectRunView(
run: RunRecord,
runId: string,
): Readonly<ClusterControlRunView> | null {
if (
!run ||
typeof run !== 'object' ||
Array.isArray(run) ||
run.id !== runId ||
!boundedText(run.id, 128) ||
!boundedText(run.projectId, 128) ||
!boundedText(run.taskId, 255) ||
!boundedText(run.taskRevision, 255) ||
!RUN_STATUSES.includes(run.status) ||
!EXECUTION_ORIGINS.includes(run.executionOrigin) ||
(run.executionOwner !== 'legacy' && run.executionOwner !== 'runtime') ||
!Number.isSafeInteger(run.version) ||
run.version < 0 ||
!nonNegativeInteger(run.eventSequence) ||
!Number.isSafeInteger(run.priority) ||
!nonNegativeInteger(run.createdAtMs) ||
!optionalTimestamp(run.queuedAtMs) ||
!optionalTimestamp(run.startedAtMs) ||
!optionalTimestamp(run.finishedAtMs)
) {
return null;
}
return Object.freeze({
id: run.id,
projectId: run.projectId,
taskId: run.taskId,
taskRevision: run.taskRevision,
status: run.status,
version: run.version,
eventSequence: run.eventSequence,
priority: run.priority,
executionOrigin: run.executionOrigin,
executionOwner: run.executionOwner,
createdAtMs: run.createdAtMs,
queuedAtMs: run.queuedAtMs ?? null,
startedAtMs: run.startedAtMs ?? null,
finishedAtMs: run.finishedAtMs ?? null,
});
}
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
/**
* Defines the first reviewed cluster-control business route. The response is a
* deliberately low-sensitive projection: refs, trigger identity, request IDs,
* executor handles, error summaries and output locations never cross the wire.
*/
export function createClusterControlRunReadRoute(
repository: ClusterControlRunReadRepository,
): Readonly<ClusterControlRouteDefinition> {
if (!repository || typeof repository.findRunById !== 'function') {
throw new TypeError('Cluster-control Run read repository is invalid');
}
return Object.freeze({
...CLUSTER_CONTROL_RUN_READ_ROUTE,
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
if (authorized.request.body !== null) {
return response(400, { code: 'invalid_request_body' });
}
const runId = parameters.runId;
if (!boundedText(runId, 128)) {
return response(503, { code: 'run_query_unavailable' });
}
let run: RunRecord | null;
try {
run = await repository.findRunById(runId);
} catch {
return response(503, { code: 'run_query_unavailable' });
}
if (!run) {
return response(404, { code: 'run_not_found' });
}
const view = projectRunView(run, runId);
if (!view) {
return response(503, { code: 'run_query_unavailable' });
}
if (view.projectId !== authorized.projectId) {
return response(404, { code: 'run_not_found' });
}
return response(200, { run: view });
},
});
}
export * from './runCancellationRoute';
export * from './runListRoute';
export * from './runEventListRoute';
export * from './runStepListRoute';
@@ -0,0 +1,130 @@
import {
BoundedRunStepListProjectionUnavailableError,
InvalidBoundedRunStepListProjectionError,
executeBoundedRunStepListProjection,
} from '@qinglong/runtime-core/bounded-run-step-list-projection';
import type { RunRepositoryReader } from '@qinglong/runtime-core/run-repository';
import type { StepRunRepository } from '@qinglong/runtime-core/step-run';
import type { ClusterControlAdmissionResponse } from '../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../transport/routeRegistry';
export const CLUSTER_CONTROL_RUN_STEP_LIST_ROUTE = Object.freeze({
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/runs/{runId}/steps',
operationId: 'run.steps.list',
permission: 'run.read',
projectParameter: 'projectId',
allowedQuery: Object.freeze(['after_step_key', 'after_step_run_id', 'limit']),
});
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
function parseQuery(query: Readonly<Record<string, readonly string[]>>) {
const stepKeyValues = query.after_step_key;
const stepRunIdValues = query.after_step_run_id;
const limitValues = query.limit;
if (
(stepKeyValues !== undefined && stepKeyValues.length !== 1) ||
(stepRunIdValues !== undefined && stepRunIdValues.length !== 1) ||
(limitValues !== undefined && limitValues.length !== 1) ||
(stepKeyValues === undefined) !== (stepRunIdValues === undefined)
) {
throw new TypeError();
}
const stepKey = stepKeyValues?.[0];
const stepRunId = stepRunIdValues?.[0];
const rawLimit = limitValues?.[0];
const limit = rawLimit === undefined ? undefined : Number(rawLimit);
if (
(stepKey !== undefined && !IDENTITY_PATTERN.test(stepKey)) ||
(stepRunId !== undefined && !IDENTITY_PATTERN.test(stepRunId)) ||
(rawLimit !== undefined &&
(!Number.isSafeInteger(limit) ||
Number(limit) < 1 ||
Number(limit) > 64 ||
String(limit) !== rawLimit))
) {
throw new TypeError();
}
return Object.freeze({
...(limit === undefined ? {} : { limit }),
...(stepKey === undefined || stepRunId === undefined
? {}
: { after: Object.freeze({ stepKey, stepRunId }) }),
});
}
function validateRunStepListQuery(
query: Readonly<Record<string, readonly string[]>>,
): void {
parseQuery(query);
}
export function createClusterControlRunStepListRoute(
runs: Pick<RunRepositoryReader, 'findRunById'>,
stepRuns: Pick<StepRunRepository, 'listByRun'>,
): Readonly<ClusterControlRouteDefinition> {
if (
!runs ||
typeof runs.findRunById !== 'function' ||
!stepRuns ||
typeof stepRuns.listByRun !== 'function'
) {
throw new TypeError('Cluster-control Run Step list repository is invalid');
}
return Object.freeze({
...CLUSTER_CONTROL_RUN_STEP_LIST_ROUTE,
validateQuery: validateRunStepListQuery,
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
if (authorized.request.body !== null) {
return response(400, { code: 'invalid_request_body' });
}
if (authorized.projectId === null) {
return response(503, { code: 'run_step_list_unavailable' });
}
let input;
try {
input = parseQuery(authorized.request.query);
} catch {
return response(400, { code: 'invalid_run_step_list_query' });
}
try {
const result = await executeBoundedRunStepListProjection(
runs,
stepRuns,
authorized.projectId,
parameters.runId!,
input,
);
if (!result.found) {
return response(404, { code: 'run_not_found' });
}
const { found: _found, ...page } = result;
return response(200, { ...page });
} catch (error) {
if (
error instanceof InvalidBoundedRunStepListProjectionError ||
error instanceof BoundedRunStepListProjectionUnavailableError
) {
return response(503, { code: 'run_step_list_unavailable' });
}
throw error;
}
},
});
}
@@ -0,0 +1,45 @@
// Scheduling owns the bounded Cron expression adapter used by the Cluster cadence.
import type {
LocalCronNextOccurrence,
LocalCronSchedule,
} from '@qinglong/runtime-core/local-scheduler';
interface CronerJob {
nextRun(after: Date): Date | null;
stop(): void;
}
interface CronerConstructor {
new (
expression: string,
options: Readonly<{
timezone: string;
paused: true;
unref: true;
}>,
): CronerJob;
}
export const cronerClusterNextOccurrence: LocalCronNextOccurrence = (
schedule: LocalCronSchedule,
afterMs: number,
): number => {
let job: CronerJob | undefined;
try {
const { Cron } = require('croner') as Readonly<{
Cron: CronerConstructor;
}>;
job = new Cron(schedule.expression, {
timezone: schedule.timezone,
paused: true,
unref: true,
});
const next = job.nextRun(new Date(afterMs));
if (!(next instanceof Date)) {
throw new Error('cron has no next occurrence');
}
return next.getTime();
} finally {
job?.stop();
}
};
@@ -0,0 +1,69 @@
// Scheduling owns recovery and lost-retry ordering inside the shared cadence.
import type {
ClusterControlStartupRecoverySummary,
ClusterRunLostRetryPageResult,
} from '@qinglong/runtime-core';
import type {
ClusterSchedulerCoordinator,
ClusterSchedulerCycleSummary,
} from './scheduler';
export interface ClusterRuntimeSchedulerMaintenanceSummary {
readonly recovery: Readonly<ClusterControlStartupRecoverySummary>;
readonly lostRetry: Readonly<ClusterRunLostRetryPageResult>;
}
/**
* Reuses the scheduler's single non-overlapping cadence for runtime recovery
* and lost retry. It owns no timer, connection, cursor or per-Run state.
*/
export class ClusterRuntimeSchedulerCoordinator {
private inFlight: Promise<ClusterSchedulerCycleSummary> | undefined;
private latestMaintenance:
| Readonly<ClusterRuntimeSchedulerMaintenanceSummary>
| undefined;
constructor(
private readonly recovery: Readonly<{
reconcile(): Promise<ClusterControlStartupRecoverySummary>;
}>,
private readonly lostRetry: Readonly<{
reconcile(): Promise<Readonly<ClusterRunLostRetryPageResult>>;
}>,
private readonly scheduler: Pick<
ClusterSchedulerCoordinator,
'scheduleOnce'
>,
) {
if (
typeof recovery?.reconcile !== 'function' ||
typeof lostRetry?.reconcile !== 'function' ||
typeof scheduler?.scheduleOnce !== 'function'
) {
throw new TypeError('Cluster runtime scheduler coordinator is invalid');
}
}
scheduleOnce(): Promise<ClusterSchedulerCycleSummary> {
if (this.inFlight) return this.inFlight;
const work = this.runCycle().finally(() => {
if (this.inFlight === work) this.inFlight = undefined;
});
this.inFlight = work;
return work;
}
latestMaintenanceSummary():
| Readonly<ClusterRuntimeSchedulerMaintenanceSummary>
| undefined {
return this.latestMaintenance;
}
private async runCycle(): Promise<ClusterSchedulerCycleSummary> {
const recovery = await this.recovery.reconcile();
const lostRetry = await this.lostRetry.reconcile();
this.latestMaintenance = Object.freeze({ recovery, lostRetry });
return this.scheduler.scheduleOnce();
}
}
@@ -0,0 +1,294 @@
// Scheduling owns bounded trigger claiming and the single non-overlapping lifecycle timer.
import { randomUUID } from 'node:crypto';
import {
MAX_CLUSTER_SCHEDULE_CLAIM_LEASE_MS,
MIN_CLUSTER_SCHEDULE_CLAIM_LEASE_MS,
resolveClusterScheduleDecision,
type ClusterScheduleStore,
} from '@qinglong/runtime-core/cluster-scheduler';
import type { LocalCronNextOccurrence } from '@qinglong/runtime-core/local-scheduler';
import { cronerClusterNextOccurrence } from './cronerSchedule';
export const MAX_CLUSTER_SCHEDULE_CLAIMS_PER_CYCLE = 256;
export interface ClusterSchedulerCoordinatorOptions {
readonly ownerId: string;
readonly claimLeaseMs?: number;
readonly maxClaimsPerCycle?: number;
readonly misfireGraceMs?: number;
readonly createId?: () => string;
readonly nextOccurrence?: LocalCronNextOccurrence;
readonly onAdmitted?: (
runId: string,
attemptId: string,
) => void | Promise<void>;
}
export interface ClusterSchedulerCycleSummary {
readonly firstClaimAcquiredAtMs: number | null;
readonly lastClaimAcquiredAtMs: number | null;
readonly claimed: number;
readonly initialized: number;
readonly skipped: number;
readonly admitted: number;
readonly raced: number;
readonly saturated: boolean;
}
const OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const COORDINATOR_OPTION_KEYS = new Set([
'claimLeaseMs',
'createId',
'maxClaimsPerCycle',
'misfireGraceMs',
'nextOccurrence',
'onAdmitted',
'ownerId',
]);
export class ClusterSchedulerCoordinator {
private readonly ownerId: string;
private readonly claimLeaseMs: number;
private readonly maxClaimsPerCycle: number;
private readonly misfireGraceMs: number;
private readonly createId: () => string;
private readonly nextOccurrence: LocalCronNextOccurrence;
private readonly onAdmitted?: ClusterSchedulerCoordinatorOptions['onAdmitted'];
constructor(
private readonly schedules: ClusterScheduleStore,
options: ClusterSchedulerCoordinatorOptions,
) {
this.ownerId = options?.ownerId ?? '';
this.claimLeaseMs = options?.claimLeaseMs ?? 30_000;
this.maxClaimsPerCycle = options?.maxClaimsPerCycle ?? 16;
this.misfireGraceMs = options?.misfireGraceMs ?? 30_000;
this.createId = options?.createId ?? randomUUID;
this.nextOccurrence =
options?.nextOccurrence ?? cronerClusterNextOccurrence;
this.onAdmitted = options?.onAdmitted;
if (
!schedules ||
typeof schedules.claimNextClusterSchedule !== 'function' ||
typeof schedules.commitClusterScheduleDecision !== 'function' ||
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some((key) => !COORDINATOR_OPTION_KEYS.has(key)) ||
!OWNER_PATTERN.test(this.ownerId) ||
!Number.isSafeInteger(this.claimLeaseMs) ||
this.claimLeaseMs < MIN_CLUSTER_SCHEDULE_CLAIM_LEASE_MS ||
this.claimLeaseMs > MAX_CLUSTER_SCHEDULE_CLAIM_LEASE_MS ||
!Number.isSafeInteger(this.maxClaimsPerCycle) ||
this.maxClaimsPerCycle < 1 ||
this.maxClaimsPerCycle > MAX_CLUSTER_SCHEDULE_CLAIMS_PER_CYCLE ||
!Number.isSafeInteger(this.misfireGraceMs) ||
this.misfireGraceMs < 0 ||
this.misfireGraceMs > 5 * 60_000 ||
typeof this.createId !== 'function' ||
typeof this.nextOccurrence !== 'function' ||
(this.onAdmitted !== undefined && typeof this.onAdmitted !== 'function')
) {
throw new TypeError('Cluster scheduler coordinator options are invalid');
}
}
async scheduleOnce(): Promise<ClusterSchedulerCycleSummary> {
const stats: {
firstClaimAcquiredAtMs: number | null;
lastClaimAcquiredAtMs: number | null;
claimed: number;
initialized: number;
skipped: number;
admitted: number;
raced: number;
saturated: boolean;
} = {
firstClaimAcquiredAtMs: null,
lastClaimAcquiredAtMs: null,
claimed: 0,
initialized: 0,
skipped: 0,
admitted: 0,
raced: 0,
saturated: false,
};
while (stats.claimed < this.maxClaimsPerCycle) {
const claimToken = this.createId();
const claimed = await this.schedules.claimNextClusterSchedule({
ownerId: this.ownerId,
claimToken,
leaseMs: this.claimLeaseMs,
});
if (!claimed) break;
if (
claimed.claimOwner !== this.ownerId ||
claimed.claimToken !== claimToken ||
claimed.claimExpiresAtMs !==
claimed.claimAcquiredAtMs + this.claimLeaseMs
) {
throw new TypeError('Cluster scheduler store returned a foreign claim');
}
stats.claimed += 1;
stats.firstClaimAcquiredAtMs ??= claimed.claimAcquiredAtMs;
stats.lastClaimAcquiredAtMs = claimed.claimAcquiredAtMs;
const decision = resolveClusterScheduleDecision(
claimed,
this.misfireGraceMs,
this.nextOccurrence,
);
const admitted = decision.disposition === 'admit';
const result = await this.schedules.commitClusterScheduleDecision({
claim: claimed,
decision,
...(admitted
? {
runId: this.createId(),
attemptId: this.createId(),
createdEventId: this.createId(),
queuedEventId: this.createId(),
}
: {}),
});
if (result.status === 'raced') {
stats.raced += 1;
continue;
}
if (result.disposition === 'initialize') stats.initialized += 1;
if (result.disposition === 'skip') stats.skipped += 1;
if (result.status === 'admitted') {
stats.admitted += 1;
await this.onAdmitted?.(result.runId, result.attemptId);
}
}
stats.saturated = stats.claimed === this.maxClaimsPerCycle;
return Object.freeze(stats);
}
}
export interface ClusterSchedulerLifecycleOptions {
readonly intervalMs: number;
readonly stopTimeoutMs: number;
readonly onDiagnostic?: (
error: unknown,
summary?: ClusterSchedulerCycleSummary,
) => void | Promise<void>;
}
export interface ClusterSchedulerLifecycleStopSummary {
readonly status: 'stopped' | 'timed_out';
}
export class ClusterSchedulerLifecycle {
private timer: NodeJS.Timeout | undefined;
private inFlight: Promise<ClusterSchedulerCycleSummary> | undefined;
private stopPromise:
| Promise<ClusterSchedulerLifecycleStopSummary>
| undefined;
private running = false;
private stopping = false;
constructor(
private readonly scheduler: Pick<
ClusterSchedulerCoordinator,
'scheduleOnce'
>,
private readonly options: ClusterSchedulerLifecycleOptions,
) {
if (
!scheduler ||
typeof scheduler.scheduleOnce !== 'function' ||
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
!Number.isSafeInteger(options.intervalMs) ||
options.intervalMs < 250 ||
options.intervalMs > 60 * 60_000 ||
!Number.isSafeInteger(options.stopTimeoutMs) ||
options.stopTimeoutMs < 100 ||
options.stopTimeoutMs > 30_000 ||
(options.onDiagnostic !== undefined &&
typeof options.onDiagnostic !== 'function')
) {
throw new TypeError('Cluster scheduler lifecycle options are invalid');
}
}
start(): 'started' {
if (!this.running && !this.stopping) {
this.running = true;
this.schedule();
}
return 'started';
}
runOnce(): Promise<ClusterSchedulerCycleSummary> {
if (this.stopping) {
return Promise.reject(
new Error('Cluster scheduler lifecycle is stopping'),
);
}
if (this.inFlight) return this.inFlight;
const work = this.scheduler.scheduleOnce().finally(() => {
if (this.inFlight === work) this.inFlight = undefined;
});
this.inFlight = work;
return work;
}
stopAndDrain(): Promise<ClusterSchedulerLifecycleStopSummary> {
if (this.stopPromise) return this.stopPromise;
this.stopping = true;
this.running = false;
if (this.timer) clearTimeout(this.timer);
this.timer = undefined;
this.stopPromise = (async () => {
const work = this.inFlight;
if (!work) return Object.freeze({ status: 'stopped' as const });
let timeout: NodeJS.Timeout | undefined;
try {
return await Promise.race([
work.then(
() => Object.freeze({ status: 'stopped' as const }),
() => Object.freeze({ status: 'stopped' as const }),
),
new Promise<ClusterSchedulerLifecycleStopSummary>((resolve) => {
timeout = setTimeout(
() => resolve(Object.freeze({ status: 'timed_out' as const })),
this.options.stopTimeoutMs,
);
timeout.unref?.();
}),
]);
} finally {
if (timeout) clearTimeout(timeout);
}
})();
return this.stopPromise;
}
private schedule(): void {
if (!this.running || this.timer) return;
this.timer = setTimeout(() => {
this.timer = undefined;
if (!this.running) return;
void this.runOnce()
.then((summary) => this.diagnostic(undefined, summary))
.catch((error) => this.diagnostic(error))
.finally(() => this.schedule());
}, this.options.intervalMs);
this.timer.unref?.();
}
private async diagnostic(
error: unknown,
summary?: ClusterSchedulerCycleSummary,
): Promise<void> {
if (this.stopping) return;
try {
await this.options.onDiagnostic?.(error, summary);
} catch {
// Diagnostics cannot own or stop scheduling.
}
}
}
@@ -0,0 +1,252 @@
// Scheduling owns Workflow frontier and Task Attempt admission on the shared cadence.
import type {
PluginPackageWorkflowFrontierCursor,
PluginPackageWorkflowFrontierRepository,
} from '@qinglong/runtime-core/plugin-package-workflow-frontier';
import type {
PluginPackageWorkflowTaskAttemptAdmissionCursor,
PluginPackageWorkflowTaskAttemptAdmissionRepository,
} from '@qinglong/runtime-core/plugin-package-workflow-task-attempt-admission';
import type {
ClusterSchedulerCoordinator,
ClusterSchedulerCycleSummary,
} from './scheduler';
export interface ClusterWorkflowSchedulerOptions {
readonly frontierPageSize: number;
readonly frontierMaxPages: number;
readonly taskAttemptPageSize: number;
readonly taskAttemptMaxPages: number;
}
export interface ClusterWorkflowSchedulerCycleSummary {
readonly frontierPages: number;
readonly frontierScanned: number;
readonly frontierAdvanced: number;
readonly frontierTruncated: boolean;
readonly taskAttemptPages: number;
readonly taskAttemptsScanned: number;
readonly taskAttemptsCreated: number;
readonly taskAttemptsExisting: number;
readonly taskAttemptsTruncated: boolean;
}
function bounded(
label: string,
value: number,
maximum: number,
): number {
if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
throw new RangeError(`${label} must be between 1 and ${maximum}`);
}
return value;
}
function nextFrontierCursor(
current: PluginPackageWorkflowFrontierCursor | undefined,
next: PluginPackageWorkflowFrontierCursor | undefined,
): PluginPackageWorkflowFrontierCursor {
if (
!next ||
(current !== undefined &&
(next.admittedAtMs < current.admittedAtMs ||
(next.admittedAtMs === current.admittedAtMs &&
next.planDigest <= current.planDigest)))
) {
throw new TypeError(
'Cluster Workflow frontier continuation did not advance',
);
}
return next;
}
function nextTaskAttemptCursor(
current: PluginPackageWorkflowTaskAttemptAdmissionCursor | undefined,
next: PluginPackageWorkflowTaskAttemptAdmissionCursor | undefined,
): PluginPackageWorkflowTaskAttemptAdmissionCursor {
if (
!next ||
(current !== undefined &&
(next.readyAtMs < current.readyAtMs ||
(next.readyAtMs === current.readyAtMs &&
next.stepRunId <= current.stepRunId)))
) {
throw new TypeError(
'Cluster Workflow Task Attempt continuation did not advance',
);
}
return next;
}
/**
* Extends the existing Cluster Scheduler cadence with Workflow frontier and
* Task Attempt admission. It owns no timer, connection, watcher, or
* per-Workflow state.
*/
export class ClusterWorkflowSchedulerCoordinator {
private readonly frontierPageSize: number;
private readonly frontierMaxPages: number;
private readonly taskAttemptPageSize: number;
private readonly taskAttemptMaxPages: number;
private inFlight: Promise<ClusterSchedulerCycleSummary> | undefined;
private latestWorkflow:
| Readonly<ClusterWorkflowSchedulerCycleSummary>
| undefined;
constructor(
private readonly scheduler: Pick<
ClusterSchedulerCoordinator,
'scheduleOnce'
>,
private readonly frontier: PluginPackageWorkflowFrontierRepository,
private readonly taskAttempts: PluginPackageWorkflowTaskAttemptAdmissionRepository,
options: ClusterWorkflowSchedulerOptions,
) {
if (
typeof scheduler?.scheduleOnce !== 'function' ||
typeof frontier?.listCandidates !== 'function' ||
typeof frontier?.advance !== 'function' ||
typeof taskAttempts?.listCandidates !== 'function' ||
typeof taskAttempts?.admit !== 'function' ||
!options ||
typeof options !== 'object' ||
Array.isArray(options)
) {
throw new TypeError('Cluster Workflow scheduler is invalid');
}
this.frontierPageSize = bounded(
'Cluster Workflow frontier page size',
options.frontierPageSize,
64,
);
this.frontierMaxPages = bounded(
'Cluster Workflow frontier page limit',
options.frontierMaxPages,
16,
);
this.taskAttemptPageSize = bounded(
'Cluster Workflow Task Attempt page size',
options.taskAttemptPageSize,
64,
);
this.taskAttemptMaxPages = bounded(
'Cluster Workflow Task Attempt page limit',
options.taskAttemptMaxPages,
16,
);
}
scheduleOnce(): Promise<ClusterSchedulerCycleSummary> {
if (this.inFlight) return this.inFlight;
const work = this.runCycle().finally(() => {
if (this.inFlight === work) this.inFlight = undefined;
});
this.inFlight = work;
return work;
}
latestWorkflowSummary():
| Readonly<ClusterWorkflowSchedulerCycleSummary>
| undefined {
return this.latestWorkflow;
}
private async runCycle(): Promise<ClusterSchedulerCycleSummary> {
const scheduler = await this.scheduler.scheduleOnce();
const frontier = await this.advanceFrontier();
const taskAttempts = await this.admitTaskAttempts();
this.latestWorkflow = Object.freeze({
...frontier,
...taskAttempts,
});
return scheduler;
}
private async advanceFrontier(): Promise<Readonly<{
frontierPages: number;
frontierScanned: number;
frontierAdvanced: number;
frontierTruncated: boolean;
}>> {
let frontierPages = 0;
let frontierScanned = 0;
let frontierAdvanced = 0;
let frontierTruncated = false;
let after: PluginPackageWorkflowFrontierCursor | undefined;
for (let index = 0; index < this.frontierMaxPages; index += 1) {
const page = await this.frontier.listCandidates({
limit: this.frontierPageSize,
...(after === undefined ? {} : { after }),
});
if (page.candidates.length > this.frontierPageSize) {
throw new RangeError(
'Cluster Workflow frontier exceeded its page size',
);
}
frontierPages += 1;
frontierScanned += page.candidates.length;
for (const candidate of page.candidates) {
await this.frontier.advance(candidate.runId);
frontierAdvanced += 1;
}
frontierTruncated = page.truncated;
if (!page.truncated) break;
after = nextFrontierCursor(after, page.next);
}
return Object.freeze({
frontierPages,
frontierScanned,
frontierAdvanced,
frontierTruncated,
});
}
private async admitTaskAttempts(): Promise<Readonly<{
taskAttemptPages: number;
taskAttemptsScanned: number;
taskAttemptsCreated: number;
taskAttemptsExisting: number;
taskAttemptsTruncated: boolean;
}>> {
let taskAttemptPages = 0;
let taskAttemptsScanned = 0;
let taskAttemptsCreated = 0;
let taskAttemptsExisting = 0;
let taskAttemptsTruncated = false;
let after:
| PluginPackageWorkflowTaskAttemptAdmissionCursor
| undefined;
for (let index = 0; index < this.taskAttemptMaxPages; index += 1) {
const page = await this.taskAttempts.listCandidates({
limit: this.taskAttemptPageSize,
...(after === undefined ? {} : { after }),
});
if (page.candidates.length > this.taskAttemptPageSize) {
throw new RangeError(
'Cluster Workflow Task Attempt source exceeded its page size',
);
}
taskAttemptPages += 1;
taskAttemptsScanned += page.candidates.length;
for (const candidate of page.candidates) {
const admitted = await this.taskAttempts.admit(
candidate.runId,
candidate.stepRunId,
);
if (admitted.status === 'created') taskAttemptsCreated += 1;
else taskAttemptsExisting += 1;
}
taskAttemptsTruncated = page.truncated;
if (!page.truncated) break;
after = nextTaskAttemptCursor(after, page.next);
}
return Object.freeze({
taskAttemptPages,
taskAttemptsScanned,
taskAttemptsCreated,
taskAttemptsExisting,
taskAttemptsTruncated,
});
}
}
@@ -0,0 +1,116 @@
import {
BoundedTaskListProjectionUnavailableError,
InvalidBoundedTaskListProjectionError,
executeBoundedTaskListProjection,
} from '@qinglong/runtime-core/bounded-task-list-projection';
import type { TaskDefinitionSource } from '@qinglong/runtime-core/task-definition';
import type { ClusterControlAdmissionResponse } from '../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
} from '../transport/routeRegistry';
export const CLUSTER_CONTROL_TASK_LIST_ROUTE = Object.freeze({
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/tasks',
operationId: 'task.list',
permission: 'task.read',
projectParameter: 'projectId',
allowedQuery: Object.freeze(['after_task_id', 'limit']),
});
const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
function parseQuery(
query: Readonly<Record<string, readonly string[]>>,
): Readonly<{
limit?: number;
after?: Readonly<{ taskId: string }>;
}> {
const limitValues = query.limit;
const taskIdValues = query.after_task_id;
if (
(limitValues !== undefined && limitValues.length !== 1) ||
(taskIdValues !== undefined && taskIdValues.length !== 1)
) {
throw new TypeError();
}
const rawLimit = limitValues?.[0];
const limit = rawLimit === undefined ? undefined : Number(rawLimit);
const taskId = taskIdValues?.[0];
if (
(rawLimit !== undefined &&
(!Number.isSafeInteger(limit) ||
Number(limit) < 1 ||
Number(limit) > 64 ||
String(limit) !== rawLimit)) ||
(taskId !== undefined && !TASK_ID_PATTERN.test(taskId))
) {
throw new TypeError();
}
return Object.freeze({
...(limit === undefined ? {} : { limit }),
...(taskId === undefined
? {}
: { after: Object.freeze({ taskId }) }),
});
}
function validateTaskListQuery(
query: Readonly<Record<string, readonly string[]>>,
): void {
parseQuery(query);
}
export function createClusterControlTaskListRoute(
tasks: Pick<TaskDefinitionSource, 'listTaskDefinitions'>,
): Readonly<ClusterControlRouteDefinition> {
if (!tasks || typeof tasks.listTaskDefinitions !== 'function') {
throw new TypeError('Cluster-control Task list repository is invalid');
}
return Object.freeze({
...CLUSTER_CONTROL_TASK_LIST_ROUTE,
validateQuery: validateTaskListQuery,
async handle(authorized: ClusterControlAuthorizedOperationRequest) {
if (authorized.request.body !== null) {
return response(400, { code: 'invalid_request_body' });
}
if (authorized.projectId === null) {
return response(503, { code: 'task_list_unavailable' });
}
let input;
try {
input = parseQuery(authorized.request.query);
} catch {
return response(400, { code: 'invalid_task_list_query' });
}
try {
const result = await executeBoundedTaskListProjection(
tasks,
authorized.projectId,
input,
);
return response(200, { ...result });
} catch (error) {
if (
error instanceof InvalidBoundedTaskListProjectionError ||
error instanceof BoundedTaskListProjectionUnavailableError
) {
return response(503, { code: 'task_list_unavailable' });
}
throw error;
}
},
});
}
export * from './taskReadRoute';
export * from './taskStartRoute';
@@ -0,0 +1,70 @@
import {
BoundedTaskReadProjectionUnavailableError,
InvalidBoundedTaskReadProjectionError,
executeBoundedTaskReadProjection,
} from '@qinglong/runtime-core/bounded-task-read-projection';
import type { TaskDefinitionSource } from '@qinglong/runtime-core/task-definition';
import type { ClusterControlAdmissionResponse } from '../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../transport/routeRegistry';
export const CLUSTER_CONTROL_TASK_READ_ROUTE = Object.freeze({
method: 'GET' as const,
path: '/api/v3/projects/{projectId}/tasks/{taskId}',
operationId: 'task.get',
permission: 'task.read',
projectParameter: 'projectId',
});
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
export function createClusterControlTaskReadRoute(
tasks: Pick<TaskDefinitionSource, 'findCurrentTaskDefinition'>,
): Readonly<ClusterControlRouteDefinition> {
if (!tasks || typeof tasks.findCurrentTaskDefinition !== 'function') {
throw new TypeError('Cluster-control Task read repository is invalid');
}
return Object.freeze({
...CLUSTER_CONTROL_TASK_READ_ROUTE,
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
if (authorized.request.body !== null) {
return response(400, { code: 'invalid_request_body' });
}
if (authorized.projectId === null) {
return response(503, { code: 'task_query_unavailable' });
}
try {
const projection = await executeBoundedTaskReadProjection(
tasks,
authorized.projectId,
parameters.taskId!,
);
if (projection.found !== true) {
return response(404, { code: 'task_not_found' });
}
const { found: _found, ...task } = projection;
return response(200, { task: Object.freeze(task) });
} catch (error) {
if (
error instanceof InvalidBoundedTaskReadProjectionError ||
error instanceof BoundedTaskReadProjectionUnavailableError
) {
return response(503, { code: 'task_query_unavailable' });
}
throw error;
}
},
});
}
@@ -0,0 +1,114 @@
import {
TASK_START_SCHEMA,
InvalidTaskStartError,
TaskStartFenceRejectedError,
TaskStartNotFoundError,
TaskStartUnavailableError,
createTaskStartResponseBody,
parseTaskStartRequestBody,
type TaskStartRepository,
} from '@qinglong/runtime-core/task-start';
import type { ClusterControlAdmissionResponse } from '../transport/httpSurface';
import type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRouteDefinition,
ClusterControlRouteParameters,
} from '../transport/routeRegistry';
export const CLUSTER_CONTROL_TASK_START_ROUTE = Object.freeze({
method: 'POST' as const,
path: '/api/v3/projects/{projectId}/tasks/{taskId}/runs',
operationId: 'task.start',
permission: 'run.start',
projectParameter: 'projectId',
});
export type ClusterTaskStartIdFactory = () => string;
function response(
statusCode: number,
body: Readonly<Record<string, unknown>>,
): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body: Object.freeze(body) });
}
export function createClusterControlTaskStartRoute(
repository: TaskStartRepository,
createId: ClusterTaskStartIdFactory,
): Readonly<ClusterControlRouteDefinition> {
if (
!repository ||
typeof repository.startTask !== 'function' ||
typeof createId !== 'function'
) {
throw new TypeError('Cluster-control Task start route is invalid');
}
return Object.freeze({
...CLUSTER_CONTROL_TASK_START_ROUTE,
async handle(
authorized: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
) {
let body;
try {
body = parseTaskStartRequestBody(authorized.request.body);
} catch (error) {
if (error instanceof InvalidTaskStartError) {
return response(400, {
code: 'invalid_task_start_request',
schema: TASK_START_SCHEMA,
});
}
return response(503, { code: 'task_start_unavailable' });
}
const projectId = authorized.projectId;
const taskId = parameters.taskId;
if (
projectId === null ||
typeof taskId !== 'string' ||
taskId.length < 1 ||
!authorized.policyFence ||
authorized.policyFence.bindingVersion === null
) {
return response(503, { code: 'task_start_unavailable' });
}
try {
const result = await repository.startTask({
projectId,
taskId,
mutationId: body.mutationId,
expectedRevision: body.expectedRevision,
expectedContentDigest: body.expectedContentDigest,
runId: createId(),
attemptId: createId(),
createdEventId: createId(),
queuedEventId: createId(),
subject: authorized.principal.subject,
policyFence: authorized.policyFence,
});
return response(
result.status === 'accepted' ? 202 : 200,
createTaskStartResponseBody(result),
);
} catch (error) {
if (error instanceof TaskStartNotFoundError) {
return response(404, { code: 'task_not_found' });
}
if (error instanceof TaskStartFenceRejectedError) {
return response(409, {
code: 'task_start_fence_rejected',
reason: error.reason,
});
}
if (
error instanceof InvalidTaskStartError ||
error instanceof TaskStartUnavailableError
) {
return response(503, { code: 'task_start_unavailable' });
}
return response(503, { code: 'task_start_unavailable' });
}
},
});
}
@@ -0,0 +1,341 @@
// Transport owns authenticated, Policy-fenced and synchronously audited admission.
import { randomUUID } from 'node:crypto';
import {
normalizeSecurityPolicyDecision,
normalizeSecurityPrincipal,
type SecurityPolicyDecision,
type SecurityPrincipal,
} from '@qinglong/runtime-core/security';
import {
normalizeSecurityAuditRecord,
type SecurityAuditOutcome,
type SecurityAuditRecord,
type SecurityAuditSink,
} from '@qinglong/runtime-core/security-audit';
import {
ProjectPolicyEngine,
normalizeProjectPermission,
type ProjectPolicyRepository,
} from '@qinglong/runtime-core/project-policy';
import type {
ClusterControlAdmissionMetadata,
ClusterControlAdmissionPipeline,
} from './httpSurface';
import {
ClusterControlRouteResolutionError,
isClusterControlRouteRegistry,
type ClusterControlAuthorizedOperationRequest,
type ClusterControlRoute,
type ClusterControlRouteRegistry,
} from './routeRegistry';
export type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRoute,
ClusterControlRouteRegistry,
} from './routeRegistry';
export interface ClusterControlRequestAuthenticator {
authenticate(
request: ClusterControlAdmissionMetadata,
): SecurityPrincipal | null | Promise<SecurityPrincipal | null>;
}
export interface ClusterControlPolicyRequest {
readonly principal: Readonly<SecurityPrincipal>;
readonly operationId: string;
readonly permission: string;
readonly projectId: string | null;
readonly signal: AbortSignal;
}
export interface ClusterControlPolicyAuthorizer {
authorize(
request: ClusterControlPolicyRequest,
): SecurityPolicyDecision | Promise<SecurityPolicyDecision>;
}
export type ClusterControlSecurityAuditOutcome = SecurityAuditOutcome;
export type ClusterControlSecurityAuditRecord = SecurityAuditRecord;
export type ClusterControlSecurityAuditSink = SecurityAuditSink;
export interface ClusterControlAdmissionPipelineOptions {
readonly routes: ClusterControlRouteRegistry;
readonly authenticator: ClusterControlRequestAuthenticator;
readonly policy: ClusterControlPolicyAuthorizer;
readonly audit: ClusterControlSecurityAuditSink;
readonly now?: () => number;
}
export class ClusterControlAdmissionSecurityError extends Error {
constructor(
readonly statusCode: number,
readonly code: string,
message: string,
) {
super(message);
this.name = 'ClusterControlAdmissionSecurityError';
}
}
/** Adapts the shared Project Policy engine to cluster admission. */
export function createClusterControlProjectPolicyAuthorizer(
repository: ProjectPolicyRepository,
): ClusterControlPolicyAuthorizer {
const engine = new ProjectPolicyEngine(repository);
return Object.freeze({
authorize(request: ClusterControlPolicyRequest) {
if (request.projectId === null) {
return Object.freeze({
effect: 'deny' as const,
reasons: Object.freeze(['project_scope_required']),
fence: null,
});
}
return engine.authorize(
request.principal,
request.projectId,
normalizeProjectPermission(request.permission),
);
},
});
}
function securityError(
statusCode: number,
code: string,
message: string,
): ClusterControlAdmissionSecurityError {
return new ClusterControlAdmissionSecurityError(statusCode, code, message);
}
async function recordSecurityAudit(
audit: ClusterControlSecurityAuditSink,
record: Omit<SecurityAuditRecord, 'eventId' | 'occurredAtMs'>,
now: () => number,
): Promise<void> {
try {
await audit.record(
normalizeSecurityAuditRecord({
...record,
eventId: randomUUID(),
occurredAtMs: now(),
}),
);
} catch {
throw securityError(
503,
'security_audit_unavailable',
'Cluster-control security audit is unavailable',
);
}
}
/**
* Creates a fail-closed, two-phase admission pipeline. Route matching,
* authentication, policy evaluation and durable security audit all complete
* before the returned operation is allowed to receive a request body.
*/
export function createClusterControlAdmissionPipeline(
options: ClusterControlAdmissionPipelineOptions,
): ClusterControlAdmissionPipeline {
if (
!options ||
typeof options !== 'object' ||
!isClusterControlRouteRegistry(options.routes) ||
typeof options.authenticator?.authenticate !== 'function' ||
typeof options.policy?.authorize !== 'function' ||
typeof options.audit?.record !== 'function' ||
(options.now !== undefined && typeof options.now !== 'function')
) {
throw new TypeError(
'Cluster-control admission pipeline options are invalid',
);
}
const now = options.now ?? Date.now;
return Object.freeze({
async prepare(request: ClusterControlAdmissionMetadata) {
let route: ClusterControlRoute;
try {
const resolved = await options.routes.resolve(request);
if (!resolved) {
throw securityError(
404,
'route_not_found',
'Cluster-control route was not found',
);
}
route = resolved;
} catch (error) {
if (
error instanceof ClusterControlAdmissionSecurityError ||
error instanceof ClusterControlRouteResolutionError
) {
throw error;
}
throw securityError(
503,
'route_resolution_unavailable',
'Cluster-control route resolution is unavailable',
);
}
let candidate: SecurityPrincipal | null;
try {
candidate = await options.authenticator.authenticate(request);
} catch {
await recordSecurityAudit(
options.audit,
{
requestId: request.requestId,
operationId: route.operationId,
projectId: route.projectId,
subject: null,
authenticationId: null,
outcome: 'authentication_unavailable',
reasons: Object.freeze(['authentication_unavailable']),
fence: null,
},
now,
);
throw securityError(
503,
'authentication_unavailable',
'Cluster-control authentication is unavailable',
);
}
if (!candidate) {
await recordSecurityAudit(
options.audit,
{
requestId: request.requestId,
operationId: route.operationId,
projectId: route.projectId,
subject: null,
authenticationId: null,
outcome: 'authentication_rejected',
reasons: Object.freeze(['authentication_rejected']),
fence: null,
},
now,
);
throw securityError(
401,
'authentication_required',
'Cluster-control authentication is required',
);
}
let principal: Readonly<SecurityPrincipal>;
try {
principal = normalizeSecurityPrincipal(candidate, now());
} catch {
await recordSecurityAudit(
options.audit,
{
requestId: request.requestId,
operationId: route.operationId,
projectId: route.projectId,
subject: null,
authenticationId: null,
outcome: 'authentication_unavailable',
reasons: Object.freeze(['invalid_principal']),
fence: null,
},
now,
);
throw securityError(
503,
'authentication_unavailable',
'Cluster-control authentication is unavailable',
);
}
let decision: Readonly<SecurityPolicyDecision>;
try {
decision = normalizeSecurityPolicyDecision(
await options.policy.authorize(
Object.freeze({
principal,
operationId: route.operationId,
permission: route.permission,
projectId: route.projectId,
signal: request.signal,
}),
),
);
} catch {
await recordSecurityAudit(
options.audit,
{
requestId: request.requestId,
operationId: route.operationId,
projectId: route.projectId,
subject: principal.subject,
authenticationId: principal.authenticationId,
outcome: 'authorization_unavailable',
reasons: Object.freeze(['authorization_unavailable']),
fence: null,
},
now,
);
throw securityError(
503,
'authorization_unavailable',
'Cluster-control authorization is unavailable',
);
}
const outcome =
decision.effect === 'allow'
? 'allowed'
: decision.effect === 'require_approval'
? 'approval_required'
: 'denied';
await recordSecurityAudit(
options.audit,
{
requestId: request.requestId,
operationId: route.operationId,
projectId: route.projectId,
subject: principal.subject,
authenticationId: principal.authenticationId,
outcome,
reasons: decision.reasons,
fence: decision.fence,
},
now,
);
if (decision.effect === 'deny') {
throw securityError(
403,
'forbidden',
'Cluster-control operation is forbidden',
);
}
if (decision.effect === 'require_approval') {
throw securityError(
403,
'approval_required',
'Cluster-control operation requires approval',
);
}
return Object.freeze({
handle(body: unknown | null) {
return route.handle(
Object.freeze({
request: Object.freeze({ ...request, body }),
principal,
operationId: route.operationId,
permission: route.permission,
projectId: route.projectId,
policyFence: decision.fence,
}),
);
},
});
},
});
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,452 @@
// Transport owns bounded route compilation, resolution and query validation.
import type {
SecurityPolicyFence,
SecurityPrincipal,
} from '@qinglong/runtime-core/security';
import type {
ClusterControlAdmissionMetadata,
ClusterControlAdmissionRequest,
ClusterControlAdmissionResponse,
ClusterControlHttpMethod,
} from './httpSurface';
export const CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS = Object.freeze({
maxRoutes: 256,
maxPathBytes: 1024,
maxPathSegments: 16,
maxPathParameters: 8,
maxQueryParameters: 16,
maxQueryValuesPerParameter: 16,
maxQueryValueBytes: 1024,
});
export type ClusterControlRouteParameters = Readonly<Record<string, string>>;
export interface ClusterControlAuthorizedOperationRequest {
readonly request: ClusterControlAdmissionRequest;
readonly principal: Readonly<SecurityPrincipal>;
readonly operationId: string;
readonly permission: string;
readonly projectId: string | null;
readonly policyFence: Readonly<SecurityPolicyFence> | null;
}
export interface ClusterControlRouteDefinition {
readonly method: ClusterControlHttpMethod;
readonly path: string;
readonly operationId: string;
readonly permission: string;
readonly projectParameter: string | null;
readonly allowedQuery?: readonly string[];
readonly validateQuery?: (
query: Readonly<Record<string, readonly string[]>>,
) => void;
handle(
request: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
): ClusterControlAdmissionResponse | Promise<ClusterControlAdmissionResponse>;
}
export interface ClusterControlRoute {
readonly operationId: string;
readonly permission: string;
readonly projectId: string | null;
handle(
request: ClusterControlAuthorizedOperationRequest,
): ClusterControlAdmissionResponse | Promise<ClusterControlAdmissionResponse>;
}
export interface ClusterControlRouteResolver {
resolve(request: ClusterControlAdmissionMetadata): ClusterControlRoute | null;
}
export interface ClusterControlRouteRegistry
extends ClusterControlRouteResolver {
readonly contractVersion: 1;
readonly size: number;
}
export class ClusterControlRouteRegistryConfigurationError extends TypeError {
constructor(message: string) {
super(`Cluster-control route registry is invalid: ${message}`);
this.name = 'ClusterControlRouteRegistryConfigurationError';
}
}
export class ClusterControlRouteResolutionError extends Error {
constructor(
readonly statusCode: 400,
readonly code: 'invalid_route_path' | 'invalid_route_query',
message: string,
) {
super(message);
this.name = 'ClusterControlRouteResolutionError';
}
}
type CompiledSegment =
| { readonly kind: 'literal'; readonly value: string }
| { readonly kind: 'parameter'; readonly name: string };
interface CompiledRoute {
readonly method: ClusterControlHttpMethod;
readonly operationId: string;
readonly permission: string;
readonly projectParameter: string | null;
readonly segments: readonly CompiledSegment[];
readonly allowedQuery: ReadonlySet<string>;
readonly validateQuery?: ClusterControlRouteDefinition['validateQuery'];
readonly handle: ClusterControlRouteDefinition['handle'];
}
const HTTP_METHODS = new Set<ClusterControlHttpMethod>([
'DELETE',
'GET',
'PATCH',
'POST',
'PUT',
]);
const OPERATION_PATTERN = /^[a-z][a-z0-9_.:-]{0,127}$/;
const PERMISSION_PATTERN = /^[a-z][a-z0-9_.:*:-]{0,127}$/;
const LITERAL_SEGMENT_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
const PARAMETER_NAME_PATTERN = /^[a-z][A-Za-z0-9]{0,63}$/;
const PARAMETER_SEGMENT_PATTERN = /^\{([a-z][A-Za-z0-9]{0,63})\}$/;
const PARAMETER_VALUE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const QUERY_NAME_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/;
const DEFINITION_KEYS = new Set([
'allowedQuery',
'handle',
'method',
'operationId',
'path',
'permission',
'projectParameter',
'validateQuery',
]);
const reviewedRegistries = new WeakSet<object>();
function configurationError(
message: string,
): ClusterControlRouteRegistryConfigurationError {
return new ClusterControlRouteRegistryConfigurationError(message);
}
function exactDefinitionShape(value: ClusterControlRouteDefinition): void {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw configurationError('each route must be an object');
}
const keys = Object.keys(value);
if (
keys.some((key) => !DEFINITION_KEYS.has(key)) ||
!keys.includes('method') ||
!keys.includes('path') ||
!keys.includes('operationId') ||
!keys.includes('permission') ||
!keys.includes('projectParameter') ||
!keys.includes('handle')
) {
throw configurationError('route shape is invalid');
}
}
function compilePath(path: string): readonly CompiledSegment[] {
if (
typeof path !== 'string' ||
Buffer.byteLength(path) >
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxPathBytes ||
!path.startsWith('/api/v3/') ||
path.endsWith('/') ||
path.includes('//') ||
path.includes('%') ||
path.includes('\\') ||
CONTROL_CHARACTER_PATTERN.test(path)
) {
throw configurationError('route path must be a canonical /api/v3 path');
}
const rawSegments = path.slice(1).split('/');
if (
rawSegments.length > CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxPathSegments
) {
throw configurationError('route path has too many segments');
}
const parameterNames = new Set<string>();
const compiled = rawSegments.map((segment): CompiledSegment => {
const parameter = PARAMETER_SEGMENT_PATTERN.exec(segment)?.[1];
if (parameter) {
if (parameterNames.has(parameter)) {
throw configurationError('route path repeats a parameter');
}
parameterNames.add(parameter);
return Object.freeze({ kind: 'parameter', name: parameter });
}
if (!LITERAL_SEGMENT_PATTERN.test(segment)) {
throw configurationError('route path contains an invalid segment');
}
return Object.freeze({ kind: 'literal', value: segment });
});
if (
parameterNames.size >
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxPathParameters
) {
throw configurationError('route path has too many parameters');
}
return Object.freeze(compiled);
}
function compileAllowedQuery(
value: readonly string[] | undefined,
): ReadonlySet<string> {
if (value === undefined) return new Set<string>();
if (
!Array.isArray(value) ||
value.length > CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxQueryParameters
) {
throw configurationError('allowedQuery is invalid');
}
const names = new Set<string>();
for (const name of value) {
if (!QUERY_NAME_PATTERN.test(name) || names.has(name)) {
throw configurationError('allowedQuery contains an invalid name');
}
names.add(name);
}
return names;
}
function compileRoute(
definition: ClusterControlRouteDefinition,
): CompiledRoute {
exactDefinitionShape(definition);
if (!HTTP_METHODS.has(definition.method)) {
throw configurationError('route method is invalid');
}
if (!OPERATION_PATTERN.test(definition.operationId)) {
throw configurationError('route operationId is invalid');
}
if (!PERMISSION_PATTERN.test(definition.permission)) {
throw configurationError('route permission is invalid');
}
if (typeof definition.handle !== 'function') {
throw configurationError('route handler is invalid');
}
if (
definition.validateQuery !== undefined &&
typeof definition.validateQuery !== 'function'
) {
throw configurationError('route query validator is invalid');
}
const segments = compilePath(definition.path);
const parameterNames = new Set(
segments.flatMap((segment) =>
segment.kind === 'parameter' ? [segment.name] : [],
),
);
if (
definition.projectParameter !== null &&
(!PARAMETER_NAME_PATTERN.test(definition.projectParameter) ||
!parameterNames.has(definition.projectParameter))
) {
throw configurationError(
'projectParameter must name one declared path parameter',
);
}
return Object.freeze({
method: definition.method,
operationId: definition.operationId,
permission: definition.permission,
projectParameter: definition.projectParameter,
segments,
allowedQuery: compileAllowedQuery(definition.allowedQuery),
...(definition.validateQuery === undefined
? {}
: { validateQuery: definition.validateQuery }),
handle: definition.handle,
});
}
function routesOverlap(left: CompiledRoute, right: CompiledRoute): boolean {
if (
left.method !== right.method ||
left.segments.length !== right.segments.length
) {
return false;
}
return left.segments.every((segment, index) => {
const other = right.segments[index]!;
return (
segment.kind === 'parameter' ||
other.kind === 'parameter' ||
segment.value === other.value
);
});
}
function validateRequestPath(path: string): readonly string[] {
if (
typeof path !== 'string' ||
Buffer.byteLength(path) >
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxPathBytes ||
!(path === '/api/v3' || path.startsWith('/api/v3/')) ||
path.endsWith('/') ||
path.includes('//') ||
path.includes('%') ||
path.includes('\\') ||
CONTROL_CHARACTER_PATTERN.test(path)
) {
throw new ClusterControlRouteResolutionError(
400,
'invalid_route_path',
'Cluster-control route path is invalid',
);
}
const segments = path.slice(1).split('/');
if (
segments.length > CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxPathSegments ||
segments.some((segment) => !PARAMETER_VALUE_PATTERN.test(segment))
) {
throw new ClusterControlRouteResolutionError(
400,
'invalid_route_path',
'Cluster-control route path is invalid',
);
}
return segments;
}
function validateQuery(
query: Readonly<Record<string, readonly string[]>>,
allowed: ReadonlySet<string>,
): void {
if (!query || typeof query !== 'object' || Array.isArray(query)) {
throw new ClusterControlRouteResolutionError(
400,
'invalid_route_query',
'Cluster-control route query is invalid',
);
}
const names = Object.keys(query);
for (const name of names) {
const values = query[name];
if (
!allowed.has(name) ||
!Array.isArray(values) ||
values.length === 0 ||
values.length >
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxQueryValuesPerParameter ||
values.some(
(value) =>
typeof value !== 'string' ||
Buffer.byteLength(value) >
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxQueryValueBytes ||
CONTROL_CHARACTER_PATTERN.test(value),
)
) {
throw new ClusterControlRouteResolutionError(
400,
'invalid_route_query',
'Cluster-control route query is invalid',
);
}
}
}
function matchRoute(
route: CompiledRoute,
method: ClusterControlHttpMethod,
segments: readonly string[],
): ClusterControlRouteParameters | null {
if (route.method !== method || route.segments.length !== segments.length) {
return null;
}
const parameters = Object.create(null) as Record<string, string>;
for (let index = 0; index < segments.length; index += 1) {
const definition = route.segments[index]!;
const value = segments[index]!;
if (definition.kind === 'literal') {
if (definition.value !== value) return null;
} else {
parameters[definition.name] = value;
}
}
return Object.freeze(parameters);
}
/** Returns true only for an object created by the reviewed registry factory. */
export function isClusterControlRouteRegistry(
value: unknown,
): value is ClusterControlRouteRegistry {
return (
!!value &&
typeof value === 'object' &&
reviewedRegistries.has(value as object)
);
}
/**
* Compiles a bounded, immutable and non-overlapping route table. Route-owned
* operation, permission and Project scope are resolved before authentication.
*/
export function createClusterControlRouteRegistry(
definitions: readonly ClusterControlRouteDefinition[],
): ClusterControlRouteRegistry {
if (
!Array.isArray(definitions) ||
definitions.length > CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxRoutes
) {
throw configurationError('definitions must be a bounded array');
}
const routes = definitions.map(compileRoute);
const operationIds = new Set<string>();
for (let index = 0; index < routes.length; index += 1) {
const route = routes[index]!;
if (operationIds.has(route.operationId)) {
throw configurationError('operationId must be unique');
}
operationIds.add(route.operationId);
for (let otherIndex = 0; otherIndex < index; otherIndex += 1) {
if (routesOverlap(route, routes[otherIndex]!)) {
throw configurationError('route definitions overlap');
}
}
}
const registry: ClusterControlRouteRegistry = {
contractVersion: 1,
size: routes.length,
resolve(request) {
const segments = validateRequestPath(request.path);
for (const route of routes) {
const parameters = matchRoute(route, request.method, segments);
if (!parameters) continue;
validateQuery(request.query, route.allowedQuery);
if (route.validateQuery) {
try {
route.validateQuery(request.query);
} catch {
throw new ClusterControlRouteResolutionError(
400,
'invalid_route_query',
'Cluster-control route query is invalid',
);
}
}
const projectId =
route.projectParameter === null
? null
: parameters[route.projectParameter]!;
return Object.freeze({
operationId: route.operationId,
permission: route.permission,
projectId,
handle(request: ClusterControlAuthorizedOperationRequest) {
return route.handle(request, parameters);
},
});
}
return null;
},
};
reviewedRegistries.add(registry);
return Object.freeze(registry);
}
@@ -0,0 +1,99 @@
// Cluster Control Worker Ingress boundary; keep production PostgreSQL composition explicit.
import {
assertPostgresWorkerIngressSchemaReady,
PostgresSecurityAuditRepository,
PostgresWorkerCredentialRepository,
PostgresWorkerExecutionAttestationRepository,
PostgresWorkerSessionRepository,
} from '@qinglong/cluster-postgres/worker-ingress';
import {
startClusterWorkerIngressApplication,
type ClusterWorkerIngressApplicationResult,
} from './workerIngressApplication';
import {
createClusterWorkerIngressDatabaseOpener,
createClusterWorkerIngressHttpOptions,
type EnabledClusterWorkerIngressConfig,
} from './workerIngressConfig';
import {
createWorkerCredentialAuthenticator,
} from './workerCredentialAuthenticator';
import {
createWorkerIngressAdmissionPipeline,
} from './workerIngressPipeline';
import type { ClusterWorkerRuntimePort } from '../remote-execution/workerRuntimePort';
export interface ProductionClusterWorkerIngressOptions {
readonly config: EnabledClusterWorkerIngressConfig;
readonly runtime: ClusterWorkerRuntimePort;
readonly onPoolError?: (error: Error) => void;
}
/**
* Starts the reviewed Worker-facing listener. The worker-ingress Pool is used
* only for authentication, Session, attestation and audit authority. Every
* Run/Attempt/Lease mutation crosses the injected runtime capability port.
*/
export async function startProductionClusterWorkerIngress(
options: ProductionClusterWorkerIngressOptions,
): Promise<ClusterWorkerIngressApplicationResult> {
if (
!options ||
typeof options !== 'object' ||
!options.config?.enabled ||
!options.runtime
) {
throw new TypeError('Production Worker ingress options are invalid');
}
if (
options.onPoolError !== undefined &&
typeof options.onPoolError !== 'function'
) {
throw new TypeError('Production Worker ingress Pool error sink is invalid');
}
const http = await createClusterWorkerIngressHttpOptions(options.config);
const openDatabase = createClusterWorkerIngressDatabaseOpener(
options.config,
(error) => options.onPoolError?.(error),
);
return startClusterWorkerIngressApplication({
enabled: true,
profile: 'cluster-control',
workerCredentialPepper:
options.config.security.workerCredentialPepper,
openDatabase,
http,
async create({ database, workerCredentialPepper }) {
const report = await assertPostgresWorkerIngressSchemaReady(
database.pool,
);
return Object.freeze({
evidence: Object.freeze({
contractName: report.contractName,
contractVersion: report.contractVersion,
serverMajor: report.serverMajor,
migrationIds: Object.freeze([...report.migrationIds]),
}),
pipeline: createWorkerIngressAdmissionPipeline({
authenticator: createWorkerCredentialAuthenticator(
new PostgresWorkerCredentialRepository(database.pool),
workerCredentialPepper,
),
workers: new PostgresWorkerSessionRepository(database.pool),
attestations: new PostgresWorkerExecutionAttestationRepository(
database.pool,
),
audit: new PostgresSecurityAuditRepository(database.pool),
offers: options.runtime.offers,
activation: options.runtime.activation,
...(options.runtime.secrets === undefined
? {}
: { secrets: options.runtime.secrets }),
artifacts: options.runtime.artifacts,
completion: options.runtime.completion,
leaseControl: options.runtime.leaseControl,
}),
});
},
});
}
@@ -0,0 +1,96 @@
// Cluster Control Worker Ingress boundary; keep Worker credential authentication explicit.
import { timingSafeEqual } from 'node:crypto';
import {
WorkerCredentialUnavailableError,
normalizeWorkerCredentialRecord,
type WorkerCredentialRepository,
} from '@qinglong/runtime-core/worker-credential';
import {
assertWorkerCredentialPepper,
workerCredentialSecretDigest,
} from '@qinglong/runtime-core/worker-credential-token';
import type { ClusterControlAdmissionMetadata } from '../transport/httpSurface';
export interface AuthenticatedWorkerPrincipal {
readonly workerId: string;
readonly credentialId: string;
readonly credentialVersion: number;
readonly authenticationId: string;
readonly authenticatedAtMs: number;
readonly expiresAtMs: number;
}
export interface WorkerCredentialAuthenticator {
authenticate(
metadata: ClusterControlAdmissionMetadata,
): Promise<Readonly<AuthenticatedWorkerPrincipal> | null>;
}
const AUTHORIZATION =
/^Worker ql3w_([A-Za-z0-9][A-Za-z0-9._:-]{0,63})_([A-Za-z0-9_-]{43})$/;
export function createWorkerCredentialAuthenticator(
repository: WorkerCredentialRepository,
pepper: string,
options: Readonly<{ now?: () => number; principalTtlMs?: number }> = {},
): WorkerCredentialAuthenticator {
if (!repository || typeof repository.resolve !== 'function') {
throw new TypeError('Worker credential authenticator repository is invalid');
}
assertWorkerCredentialPepper(pepper);
const now = options.now ?? Date.now;
const principalTtlMs = options.principalTtlMs ?? 60_000;
if (
!Number.isSafeInteger(principalTtlMs) ||
principalTtlMs < 1_000 ||
principalTtlMs > 300_000
) {
throw new RangeError('Worker credential principal TTL is invalid');
}
return Object.freeze({
async authenticate(metadata: ClusterControlAdmissionMetadata) {
const header = metadata.headers.authorization;
if (typeof header !== 'string') return null;
const match = AUTHORIZATION.exec(header);
if (!match) return null;
let presented: Buffer | undefined;
try {
presented = Buffer.from(
workerCredentialSecretDigest(pepper, match[1]!, match[2]!),
'hex',
);
const candidate = await repository.resolve(match[1]!);
if (metadata.signal.aborted) throw new WorkerCredentialUnavailableError();
const record = candidate ? normalizeWorkerCredentialRecord(candidate) : null;
const stored = record
? Buffer.from(record.secretDigest, 'hex')
: Buffer.alloc(32);
const matches = timingSafeEqual(presented, stored);
stored.fill(0);
if (!record || !matches) return null;
const nowMs = now();
if (
!Number.isSafeInteger(nowMs) ||
nowMs < 0 ||
record.state !== 'active' ||
record.notBeforeAtMs > nowMs ||
record.expiresAtMs <= nowMs
) return null;
return Object.freeze({
workerId: record.workerId,
credentialId: record.credentialId,
credentialVersion: record.version,
authenticationId: `worker_credential:${record.credentialId}:${record.version}`,
authenticatedAtMs: nowMs,
expiresAtMs: Math.min(record.expiresAtMs, nowMs + principalTtlMs),
});
} catch (error) {
if (error instanceof WorkerCredentialUnavailableError) throw error;
throw new WorkerCredentialUnavailableError();
} finally {
presented?.fill(0);
}
},
});
}
@@ -0,0 +1,170 @@
// Cluster Control Worker Ingress boundary; keep listener lifecycle authority explicit.
import type {
ClusterControlReadinessEvidence,
ClusterControlAdmissionDisposer,
DeploymentProfile,
OpenPostgresDatabase,
PostgresDatabaseResource,
} from '@qinglong/runtime-core';
import { assertWorkerCredentialPepper } from '@qinglong/runtime-core/worker-credential-token';
import { MAX_REMOTE_SECRET_DELIVERY_RESPONSE_BYTES } from '@qinglong/runtime-core/remote-secret-delivery';
import {
startClusterControlHttpSurface,
type ClusterControlAdmissionPipeline,
type ClusterControlHttpAddress,
type ClusterControlHttpSurfaceOptions,
type ClusterControlMutualTlsOptions,
} from '../transport/httpSurface';
export interface ClusterWorkerIngressAssemblyInput {
readonly database: PostgresDatabaseResource;
readonly workerCredentialPepper: string;
}
export interface ClusterWorkerIngressAssembly {
readonly evidence: ClusterControlReadinessEvidence;
readonly pipeline: ClusterControlAdmissionPipeline;
}
export interface ClusterWorkerIngressApplicationOptions {
readonly enabled?: boolean;
readonly profile: DeploymentProfile;
readonly workerCredentialPepper?: string;
readonly openDatabase: OpenPostgresDatabase;
readonly http: ClusterControlHttpSurfaceOptions;
readonly create: (
input: ClusterWorkerIngressAssemblyInput,
) => ClusterWorkerIngressAssembly | Promise<ClusterWorkerIngressAssembly>;
}
export type ClusterWorkerIngressApplicationResult =
| { readonly status: 'disabled'; stop(): Promise<'stopped'> }
| {
readonly status: 'active';
readonly protocol: 'https';
readonly transport: 'mutual-tls';
readonly address: ClusterControlHttpAddress;
readonly evidence: ClusterControlReadinessEvidence;
reloadTransport(options: ClusterControlMutualTlsOptions): number;
stop(): Promise<'stopped'>;
};
/**
* Separate Worker-facing composition root. It owns a dedicated listener and a
* worker-ingress database resource. Storage readiness and repositories are
* supplied by the outer composition root, so this transport layer cannot
* acquire Project Policy, dispatch, recovery-claim or DDL authority itself.
*/
export async function startClusterWorkerIngressApplication(
options: ClusterWorkerIngressApplicationOptions,
): Promise<ClusterWorkerIngressApplicationResult> {
if (!(options.enabled ?? false)) {
return Object.freeze({
status: 'disabled',
async stop() {
return 'stopped' as const;
},
});
}
if (options.profile !== 'cluster-control') {
throw new TypeError('Worker ingress requires cluster-control profile');
}
if (typeof options.create !== 'function') {
throw new TypeError('Worker ingress assembly factory is required');
}
if (!options.http?.mutualTls) {
throw new TypeError('Worker ingress requires mutual TLS');
}
assertWorkerCredentialPepper(options.workerCredentialPepper ?? '');
const bodyLimit = options.http.maxBodyBytes ?? 64 * 1024;
if (
!Number.isSafeInteger(bodyLimit) ||
bodyLimit < 1024 ||
bodyLimit > 64 * 1024
) {
throw new RangeError(
'Worker ingress body limit must be between 1 KiB and 64 KiB',
);
}
let database: PostgresDatabaseResource | undefined;
const http = await startClusterControlHttpSurface({
...options.http,
maxBodyBytes: bodyLimit,
maxResponseBytes: Math.min(
options.http.maxResponseBytes ?? MAX_REMOTE_SECRET_DELIVERY_RESPONSE_BYTES,
MAX_REMOTE_SECRET_DELIVERY_RESPONSE_BYTES,
),
maxInFlightRequests: Math.min(options.http.maxInFlightRequests ?? 64, 256),
});
let disposeAdmission: ClusterControlAdmissionDisposer | undefined;
try {
database = await options.openDatabase();
const assembly = await options.create({
database,
workerCredentialPepper: options.workerCredentialPepper!,
});
disposeAdmission = http.installAdmission(
assembly.evidence,
assembly.pipeline,
);
let stopPromise: Promise<'stopped'> | undefined;
return Object.freeze({
status: 'active' as const,
protocol: 'https' as const,
transport: 'mutual-tls' as const,
address: http.address,
evidence: assembly.evidence,
reloadTransport(mutualTls: ClusterControlMutualTlsOptions) {
return http.reloadMutualTls(mutualTls);
},
stop() {
stopPromise ??= (async () => {
let primary: unknown;
try {
await disposeAdmission?.();
} catch (error) {
primary = error;
}
try {
await database?.close();
} catch (error) {
primary ??= error;
}
try {
await http.close();
} catch (error) {
primary ??= error;
}
if (primary) throw primary;
return 'stopped' as const;
})();
return stopPromise;
},
});
} catch (error) {
try {
await disposeAdmission?.();
} catch {
/* preserve root */
}
try {
await database?.close();
} catch {
/* preserve root */
}
try {
await http.close();
} catch {
/* preserve root */
}
throw error;
}
}
export * from './workerCredentialAuthenticator';
export * from './workerIngressPipeline';
export * from '../remote-execution/remoteRunActivationService';
export * from '../remote-execution/remoteWorkerSecretDeliveryService';
export * from '../remote-execution/remoteWorkerCompletionService';
export * from '../remote-execution/remoteWorkerLeaseControlService';
@@ -0,0 +1,864 @@
// Cluster Control Worker Ingress boundary; keep fail-closed deployment configuration explicit.
import {
createPrivateKey,
createPublicKey,
timingSafeEqual,
X509Certificate,
type KeyObject,
} from 'node:crypto';
import { constants } from 'node:fs';
import { open } from 'node:fs/promises';
import { isAbsolute } from 'node:path';
import type {
DeploymentProfile,
OpenPostgresDatabase,
} from '@qinglong/runtime-core';
import {
createPostgresDatabaseOpener,
isPostgresTlsDnsServername,
loadPostgresConnectionEnvironment,
loadPostgresCertificateAuthorityFile,
type PostgresConnectionOptions,
type PostgresPoolOptions,
} from '@qinglong/cluster-postgres/runtime';
import type {
ClusterControlHttpSurfaceOptions,
ClusterControlMutualTlsOptions,
} from '../transport/httpSurface';
export type ClusterWorkerIngressEnvironment = Readonly<
Record<string, string | undefined>
>;
export interface DisabledClusterWorkerIngressConfig {
readonly enabled: false;
readonly profile: DeploymentProfile;
}
export interface ClusterWorkerArtifactS3Config {
readonly bucket: string;
readonly region: string;
readonly prefix?: string;
readonly expectedBucketOwner?: string;
readonly endpoint?: string;
readonly forcePathStyle: boolean;
readonly encryption:
| Readonly<{ readonly mode: 's3' }>
| Readonly<{ readonly mode: 'kms'; readonly keyId: string }>;
}
export interface ClusterWorkerMountedSecretConfig {
readonly provider: 'mounted-files';
readonly rootDirectory: string;
}
export interface EnabledClusterWorkerIngressConfig {
readonly enabled: true;
readonly profile: 'cluster-control';
readonly http: Omit<ClusterControlHttpSurfaceOptions, 'mutualTls'>;
readonly transport: Readonly<{
readonly privateKeyFile: string;
readonly certificateFile: string;
readonly clientCertificateAuthorityFile: string;
readonly clientCertificateRevocationListFile?: string;
}>;
readonly database: Readonly<{
readonly connection: PostgresConnectionOptions;
readonly pool: PostgresPoolOptions;
}>;
readonly security: Readonly<{
readonly workerCredentialPepper: string;
}>;
readonly artifact: Readonly<ClusterWorkerArtifactS3Config>;
readonly secret?: Readonly<ClusterWorkerMountedSecretConfig>;
}
export type ClusterWorkerIngressConfig =
| DisabledClusterWorkerIngressConfig
| EnabledClusterWorkerIngressConfig;
export class ClusterWorkerIngressConfigError extends TypeError {
constructor(message: string) {
super(`Worker ingress configuration is invalid: ${message}`);
this.name = 'ClusterWorkerIngressConfigError';
}
}
const PROFILES = new Set<DeploymentProfile>([
'edge',
'standalone',
'cluster-control',
'worker',
]);
const MAX_TLS_FILE_BYTES = 1024 * 1024;
function booleanValue(
environment: ClusterWorkerIngressEnvironment,
name: string,
defaultValue: boolean,
): boolean {
const value = environment[name];
if (value === undefined || value === '') return defaultValue;
if (value === 'true') return true;
if (value === 'false') return false;
throw new ClusterWorkerIngressConfigError(`${name} must be true or false`);
}
function integerValue(
environment: ClusterWorkerIngressEnvironment,
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 ClusterWorkerIngressConfigError(`${name} must be an integer`);
}
const parsed = Number(value);
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
throw new ClusterWorkerIngressConfigError(
`${name} must be between ${minimum} and ${maximum}`,
);
}
return parsed;
}
function boundedValue(
environment: ClusterWorkerIngressEnvironment,
name: string,
maximumLength: number,
required = false,
): string | undefined {
const value = environment[name];
if (value === undefined || value === '') {
if (required) {
throw new ClusterWorkerIngressConfigError(`${name} is required`);
}
return undefined;
}
if (value.length > maximumLength || /[\0\r\n]/.test(value)) {
throw new ClusterWorkerIngressConfigError(`${name} is invalid`);
}
return value;
}
function deploymentProfile(
environment: ClusterWorkerIngressEnvironment,
): DeploymentProfile {
const value = environment.QL_DEPLOYMENT_PROFILE ?? 'standalone';
if (!PROFILES.has(value as DeploymentProfile)) {
throw new ClusterWorkerIngressConfigError(
'QL_DEPLOYMENT_PROFILE is invalid',
);
}
return value as DeploymentProfile;
}
function absoluteFile(
environment: ClusterWorkerIngressEnvironment,
name: string,
): string {
const value = boundedValue(environment, name, 4096, true)!;
if (!isAbsolute(value)) {
throw new ClusterWorkerIngressConfigError(`${name} must be absolute`);
}
return value;
}
function optionalAbsoluteFile(
environment: ClusterWorkerIngressEnvironment,
name: string,
): string | undefined {
const value = boundedValue(environment, name, 4096);
if (value === undefined) return undefined;
if (!isAbsolute(value)) {
throw new ClusterWorkerIngressConfigError(`${name} must be absolute`);
}
return value;
}
function workerCredentialPepper(
environment: ClusterWorkerIngressEnvironment,
): string {
const value = boundedValue(
environment,
'QL3_WORKER_CREDENTIAL_PEPPER',
64,
true,
)!;
if (!/^[A-Za-z0-9_-]{43}$/.test(value)) {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_CREDENTIAL_PEPPER must be canonical base64url for 32 bytes',
);
}
const decoded = Buffer.from(value, 'base64url');
const canonical =
decoded.byteLength === 32 && decoded.toString('base64url') === value;
decoded.fill(0);
if (!canonical) {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_CREDENTIAL_PEPPER must be canonical base64url for 32 bytes',
);
}
return value;
}
function databaseConnection(
environment: ClusterWorkerIngressEnvironment,
): PostgresConnectionOptions {
let connection: PostgresConnectionOptions;
try {
connection = loadPostgresConnectionEnvironment(environment, {
connectionString: 'QL3_POSTGRES_WORKER_INGRESS_URL',
host: 'QL3_POSTGRES_WORKER_INGRESS_HOST',
port: 'QL3_POSTGRES_WORKER_INGRESS_PORT',
database: 'QL3_POSTGRES_WORKER_INGRESS_DATABASE',
user: 'QL3_POSTGRES_WORKER_INGRESS_USER',
password: 'QL3_POSTGRES_WORKER_INGRESS_PASSWORD',
});
} catch (error) {
throw new ClusterWorkerIngressConfigError(
error instanceof Error
? error.message
: 'PostgreSQL Worker ingress connection is invalid',
);
}
const mode =
environment.QL3_WORKER_INGRESS_POSTGRES_TLS_MODE ?? 'verify-full';
if (mode !== 'verify-full' && mode !== 'disable') {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_INGRESS_POSTGRES_TLS_MODE must be verify-full or disable',
);
}
if (
mode === 'disable' &&
!booleanValue(
environment,
'QL3_WORKER_INGRESS_POSTGRES_ALLOW_INSECURE',
false,
)
) {
throw new ClusterWorkerIngressConfigError(
'disabling PostgreSQL TLS requires QL3_WORKER_INGRESS_POSTGRES_ALLOW_INSECURE=true',
);
}
const servername = boundedValue(
environment,
'QL3_WORKER_INGRESS_POSTGRES_TLS_SERVERNAME',
253,
);
if (mode === 'verify-full' && !isPostgresTlsDnsServername(servername)) {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_INGRESS_POSTGRES_TLS_SERVERNAME must be an explicit DNS name for verify-full',
);
}
const certificateAuthorityFile = boundedValue(
environment,
'QL3_WORKER_INGRESS_POSTGRES_TLS_CA_FILE',
4096,
);
if (mode === 'disable' && certificateAuthorityFile !== undefined) {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_INGRESS_POSTGRES_TLS_CA_FILE cannot be used when TLS is disabled',
);
}
let certificateAuthority: string | undefined;
if (certificateAuthorityFile !== undefined) {
try {
certificateAuthority = loadPostgresCertificateAuthorityFile(
certificateAuthorityFile,
);
} catch {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_INGRESS_POSTGRES_TLS_CA_FILE must contain a bounded trusted CA bundle',
);
}
}
return Object.freeze({
...connection,
tls:
mode === 'disable'
? Object.freeze({ mode: 'disable' as const })
: Object.freeze({
mode: 'verify-full' as const,
...(certificateAuthority === undefined
? {}
: { ca: certificateAuthority }),
servername: servername!,
}),
});
}
function workerArtifactS3(
environment: ClusterWorkerIngressEnvironment,
): Readonly<ClusterWorkerArtifactS3Config> {
const bucket = boundedValue(
environment,
'QL3_WORKER_ARTIFACT_S3_BUCKET',
63,
true,
)!;
if (
!/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(bucket) ||
bucket.includes('..') ||
/^\d{1,3}(?:\.\d{1,3}){3}$/.test(bucket)
) {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_ARTIFACT_S3_BUCKET is invalid',
);
}
const region = boundedValue(
environment,
'QL3_WORKER_ARTIFACT_S3_REGION',
63,
true,
)!;
if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(region)) {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_ARTIFACT_S3_REGION is invalid',
);
}
const prefix = boundedValue(
environment,
'QL3_WORKER_ARTIFACT_S3_PREFIX',
255,
);
if (
prefix !== undefined &&
(
!/^[A-Za-z0-9][A-Za-z0-9/_=-]{0,254}$/.test(prefix) ||
prefix.startsWith('/') ||
prefix.endsWith('/') ||
prefix.includes('//') ||
prefix.split('/').some((segment) => segment === '.' || segment === '..')
)
) {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_ARTIFACT_S3_PREFIX is invalid',
);
}
const expectedBucketOwner = boundedValue(
environment,
'QL3_WORKER_ARTIFACT_S3_EXPECTED_BUCKET_OWNER',
12,
);
if (
expectedBucketOwner !== undefined &&
!/^\d{12}$/.test(expectedBucketOwner)
) {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_ARTIFACT_S3_EXPECTED_BUCKET_OWNER must be 12 digits',
);
}
const endpointValue = boundedValue(
environment,
'QL3_WORKER_ARTIFACT_S3_ENDPOINT',
2048,
);
let endpoint: string | undefined;
if (endpointValue !== undefined) {
let parsed: URL;
try {
parsed = new URL(endpointValue);
} catch {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_ARTIFACT_S3_ENDPOINT is invalid',
);
}
const allowInsecure = booleanValue(
environment,
'QL3_WORKER_ARTIFACT_S3_ALLOW_INSECURE',
false,
);
if (
(parsed.protocol !== 'https:' &&
!(parsed.protocol === 'http:' && allowInsecure)) ||
parsed.username !== '' ||
parsed.password !== '' ||
parsed.search !== '' ||
parsed.hash !== '' ||
parsed.pathname !== '/'
) {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_ARTIFACT_S3_ENDPOINT must be an origin URL; HTTP requires explicit insecure opt-in',
);
}
endpoint = parsed.origin;
}
const encryptionMode =
boundedValue(
environment,
'QL3_WORKER_ARTIFACT_S3_ENCRYPTION',
3,
) ?? 's3';
if (encryptionMode !== 's3' && encryptionMode !== 'kms') {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_ARTIFACT_S3_ENCRYPTION must be s3 or kms',
);
}
const keyId = boundedValue(
environment,
'QL3_WORKER_ARTIFACT_S3_KMS_KEY_ID',
2048,
);
if (
(encryptionMode === 'kms' && keyId === undefined) ||
(encryptionMode === 's3' && keyId !== undefined)
) {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_ARTIFACT_S3_KMS_KEY_ID must be present exactly for kms encryption',
);
}
return Object.freeze({
bucket,
region,
...(prefix === undefined ? {} : { prefix }),
...(expectedBucketOwner === undefined
? {}
: { expectedBucketOwner }),
...(endpoint === undefined ? {} : { endpoint }),
forcePathStyle: booleanValue(
environment,
'QL3_WORKER_ARTIFACT_S3_FORCE_PATH_STYLE',
false,
),
encryption:
encryptionMode === 's3'
? Object.freeze({ mode: 's3' as const })
: Object.freeze({ mode: 'kms' as const, keyId: keyId! }),
});
}
function workerSecret(
environment: ClusterWorkerIngressEnvironment,
): Readonly<ClusterWorkerMountedSecretConfig> | undefined {
const provider = boundedValue(
environment,
'QL3_WORKER_SECRET_PROVIDER',
32,
);
if (provider === undefined || provider === 'disabled') return undefined;
if (provider !== 'mounted-files') {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_SECRET_PROVIDER must be disabled or mounted-files',
);
}
return Object.freeze({
provider,
rootDirectory: absoluteFile(
environment,
'QL3_WORKER_SECRET_ROOT_DIRECTORY',
),
});
}
/**
* Applies the Profile gate before reading database, Worker secret or TLS file
* configuration. Disabled edge/standalone installs therefore remain free of
* Worker ingress credential and filesystem requirements.
*/
export function loadClusterWorkerIngressConfig(
environment: ClusterWorkerIngressEnvironment,
): ClusterWorkerIngressConfig {
if (
!environment ||
typeof environment !== 'object' ||
Array.isArray(environment)
) {
throw new ClusterWorkerIngressConfigError('environment must be an object');
}
const profile = deploymentProfile(environment);
const enabled = booleanValue(
environment,
'QL3_WORKER_INGRESS_ENABLED',
false,
);
if (!enabled) return Object.freeze({ enabled: false, profile });
if (profile !== 'cluster-control') {
throw new ClusterWorkerIngressConfigError(
'enabled ingress requires QL_DEPLOYMENT_PROFILE=cluster-control',
);
}
const applicationName =
boundedValue(
environment,
'QL3_WORKER_INGRESS_POSTGRES_APPLICATION_NAME',
63,
) ?? 'qinglong-worker-ingress';
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,62}$/.test(applicationName)) {
throw new ClusterWorkerIngressConfigError(
'QL3_WORKER_INGRESS_POSTGRES_APPLICATION_NAME is invalid',
);
}
const host =
boundedValue(environment, 'QL3_WORKER_INGRESS_HOST', 253) ?? '0.0.0.0';
const secret = workerSecret(environment);
return Object.freeze({
enabled: true,
profile: 'cluster-control',
http: Object.freeze({
host,
port: integerValue(
environment,
'QL3_WORKER_INGRESS_PORT',
5801,
1,
65_535,
),
maxBodyBytes: integerValue(
environment,
'QL3_WORKER_INGRESS_MAX_BODY_BYTES',
64 * 1024,
1024,
64 * 1024,
),
maxResponseBytes: integerValue(
environment,
'QL3_WORKER_INGRESS_MAX_RESPONSE_BYTES',
64 * 1024,
1024,
64 * 1024,
),
maxInFlightRequests: integerValue(
environment,
'QL3_WORKER_INGRESS_MAX_IN_FLIGHT',
64,
1,
256,
),
authenticationRateWindowMs: integerValue(
environment,
'QL3_WORKER_INGRESS_AUTH_RATE_WINDOW_MS',
60_000,
1_000,
60 * 60_000,
),
authenticationRatePerPeer: integerValue(
environment,
'QL3_WORKER_INGRESS_AUTH_RATE_PER_PEER',
120,
1,
1_000_000,
),
authenticationRateGlobal: integerValue(
environment,
'QL3_WORKER_INGRESS_AUTH_RATE_GLOBAL',
1_200,
1,
1_000_000,
),
authenticationRateMaxPeers: integerValue(
environment,
'QL3_WORKER_INGRESS_AUTH_RATE_MAX_PEERS',
4_096,
1,
65_536,
),
requestTimeoutMs: integerValue(
environment,
'QL3_WORKER_INGRESS_REQUEST_TIMEOUT_MS',
15_000,
100,
120_000,
),
drainTimeoutMs: integerValue(
environment,
'QL3_WORKER_INGRESS_DRAIN_TIMEOUT_MS',
10_000,
100,
120_000,
),
}),
transport: Object.freeze({
privateKeyFile: absoluteFile(
environment,
'QL3_WORKER_INGRESS_TLS_PRIVATE_KEY_FILE',
),
certificateFile: absoluteFile(
environment,
'QL3_WORKER_INGRESS_TLS_CERTIFICATE_FILE',
),
clientCertificateAuthorityFile: absoluteFile(
environment,
'QL3_WORKER_INGRESS_TLS_CLIENT_CA_FILE',
),
...(() => {
const clientCertificateRevocationListFile = optionalAbsoluteFile(
environment,
'QL3_WORKER_INGRESS_TLS_CLIENT_CRL_FILE',
);
return clientCertificateRevocationListFile === undefined
? {}
: { clientCertificateRevocationListFile };
})(),
}),
database: Object.freeze({
connection: databaseConnection(environment),
pool: Object.freeze({
applicationName,
maxConnections: integerValue(
environment,
'QL3_WORKER_INGRESS_POSTGRES_MAX_CONNECTIONS',
4,
1,
16,
),
connectionTimeoutMs: integerValue(
environment,
'QL3_WORKER_INGRESS_POSTGRES_CONNECTION_TIMEOUT_MS',
5_000,
100,
60_000,
),
}),
}),
security: Object.freeze({
workerCredentialPepper: workerCredentialPepper(environment),
}),
artifact: workerArtifactS3(environment),
...(secret === undefined ? {} : { secret }),
});
}
async function readTlsFile(
path: string,
privateMaterial: boolean,
): Promise<Buffer> {
let handle;
try {
handle = await open(path, constants.O_RDONLY);
const stat = await handle.stat();
if (
!stat.isFile() ||
stat.size < 1 ||
stat.size > MAX_TLS_FILE_BYTES ||
(privateMaterial && (stat.mode & 0o022) !== 0)
) {
throw new ClusterWorkerIngressConfigError('TLS file metadata is unsafe');
}
const bytes = await handle.readFile();
if (bytes.byteLength < 1 || bytes.byteLength > MAX_TLS_FILE_BYTES) {
bytes.fill(0);
throw new ClusterWorkerIngressConfigError('TLS file size is unsafe');
}
return bytes;
} catch (error) {
if (error instanceof ClusterWorkerIngressConfigError) throw error;
throw new ClusterWorkerIngressConfigError('TLS material is unavailable');
} finally {
await handle?.close().catch(() => undefined);
}
}
function activeCertificate(
name: string,
bytes: Buffer,
now: number,
): X509Certificate {
let certificate: X509Certificate;
try {
certificate = new X509Certificate(bytes);
} catch {
throw new ClusterWorkerIngressConfigError(
`${name} is not an X.509 certificate`,
);
}
const validFrom = Date.parse(certificate.validFrom);
const validTo = Date.parse(certificate.validTo);
if (
!Number.isFinite(validFrom) ||
!Number.isFinite(validTo) ||
now < validFrom ||
now >= validTo
) {
throw new ClusterWorkerIngressConfigError(`${name} is not currently valid`);
}
return certificate;
}
function activeCertificateAuthorities(
bytes: Buffer,
now: number,
): readonly Buffer[] {
const pem = bytes.toString('utf8');
const matches = pem.match(
/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g,
);
if (!matches || matches.length < 1 || matches.length > 16) {
throw new ClusterWorkerIngressConfigError(
'TLS client certificate authority bundle must contain 1 to 16 PEM certificates',
);
}
const remainder = matches.reduce(
(value, certificate) => value.replace(certificate, ''),
pem,
);
if (remainder.trim() !== '') {
throw new ClusterWorkerIngressConfigError(
'TLS client certificate authority bundle contains unsupported data',
);
}
const authorities: Buffer[] = [];
try {
for (const match of matches) {
const authorityBytes = Buffer.from(`${match}\n`, 'utf8');
const authority = activeCertificate(
'TLS client certificate authority',
authorityBytes,
now,
);
if (!authority.ca) {
authorityBytes.fill(0);
throw new ClusterWorkerIngressConfigError(
'TLS client certificate authority is not a CA',
);
}
authorities.push(authorityBytes);
}
return Object.freeze(authorities);
} catch (error) {
for (const authority of authorities) authority.fill(0);
throw error;
}
}
function certificateRevocationList(bytes: Buffer): Buffer {
const value = bytes.toString('utf8').trim();
if (
!value.startsWith('-----BEGIN X509 CRL-----') ||
!value.endsWith('-----END X509 CRL-----')
) {
throw new ClusterWorkerIngressConfigError(
'TLS client certificate revocation list is not a PEM CRL',
);
}
return bytes;
}
function matchingPrivateKey(
privateKey: KeyObject,
certificate: X509Certificate,
): boolean {
const key = createPublicKey(privateKey).export({
type: 'spki',
format: 'der',
});
const certificateKey = certificate.publicKey.export({
type: 'spki',
format: 'der',
});
return (
key.byteLength === certificateKey.byteLength &&
timingSafeEqual(key, certificateKey)
);
}
export async function loadClusterWorkerIngressMutualTls(
config: EnabledClusterWorkerIngressConfig,
now: number = Date.now(),
): Promise<ClusterControlMutualTlsOptions> {
if (!config?.enabled || config.profile !== 'cluster-control') {
throw new ClusterWorkerIngressConfigError(
'TLS material requires an enabled Worker ingress config',
);
}
if (!Number.isSafeInteger(now) || now < 0) {
throw new ClusterWorkerIngressConfigError('observation time is invalid');
}
const keyBytes = await readTlsFile(config.transport.privateKeyFile, true);
let certificateBytes: Buffer | undefined;
let clientAuthorityBundleBytes: Buffer | undefined;
let certificateRevocationListBytes: Buffer | undefined;
let clientCertificateAuthorities: readonly Buffer[] = Object.freeze([]);
try {
let privateKey: KeyObject;
try {
privateKey = createPrivateKey(keyBytes);
} catch {
throw new ClusterWorkerIngressConfigError('TLS private key is invalid');
}
certificateBytes = await readTlsFile(
config.transport.certificateFile,
false,
);
clientAuthorityBundleBytes = await readTlsFile(
config.transport.clientCertificateAuthorityFile,
false,
);
const certificate = activeCertificate(
'TLS server certificate',
certificateBytes,
now,
);
clientCertificateAuthorities = activeCertificateAuthorities(
clientAuthorityBundleBytes,
now,
);
if (!matchingPrivateKey(privateKey, certificate)) {
throw new ClusterWorkerIngressConfigError(
'TLS private key does not match the server certificate',
);
}
if (config.transport.clientCertificateRevocationListFile !== undefined) {
certificateRevocationListBytes = certificateRevocationList(
await readTlsFile(
config.transport.clientCertificateRevocationListFile,
false,
),
);
}
const mutualTls: ClusterControlMutualTlsOptions = Object.freeze({
privateKey: keyBytes,
certificateChain: certificateBytes,
clientCertificateAuthorities,
...(certificateRevocationListBytes === undefined
? {}
: {
certificateRevocationLists: Object.freeze([
certificateRevocationListBytes,
]),
}),
});
clientAuthorityBundleBytes.fill(0);
return mutualTls;
} catch (error) {
keyBytes.fill(0);
certificateBytes?.fill(0);
clientAuthorityBundleBytes?.fill(0);
certificateRevocationListBytes?.fill(0);
for (const authority of clientCertificateAuthorities) authority.fill(0);
throw error;
}
}
export async function createClusterWorkerIngressHttpOptions(
config: EnabledClusterWorkerIngressConfig,
now: number = Date.now(),
): Promise<ClusterControlHttpSurfaceOptions> {
const mutualTls = await loadClusterWorkerIngressMutualTls(config, now);
return Object.freeze({ ...config.http, mutualTls });
}
export function createClusterWorkerIngressDatabaseOpener(
config: EnabledClusterWorkerIngressConfig,
onPoolError: (error: Error) => void,
): OpenPostgresDatabase {
if (!config?.enabled || config.profile !== 'cluster-control') {
throw new ClusterWorkerIngressConfigError(
'database opener requires an enabled Worker ingress config',
);
}
if (typeof onPoolError !== 'function') {
throw new ClusterWorkerIngressConfigError('onPoolError must be a function');
}
return createPostgresDatabaseOpener({
role: 'worker-ingress',
connection: config.database.connection,
pool: config.database.pool,
onPoolError,
});
}
@@ -0,0 +1,584 @@
// Cluster Control Worker Ingress boundary; keep authenticated admission routing explicit.
import { randomUUID } from 'node:crypto';
import {
WorkerSessionConflictError,
WorkerSessionFenceRejectedError,
} from '@qinglong/runtime-core';
import type {
AuthenticatedWorkerSessionRepository,
} from '@qinglong/runtime-core/worker-credential-delivery';
import {
WorkerCredentialDeliveryConflictError,
WorkerCredentialDeliveryUnavailableError,
} from '@qinglong/runtime-core/worker-credential-delivery';
import {
InvalidWorkerSessionTransportError,
createWorkerSessionHeartbeatResponseBody,
createWorkerSessionRegisterResponseBody,
createWorkerSessionTransitionResponseBody,
parseWorkerSessionHeartbeatRequestBody,
parseWorkerSessionRegisterRequestBody,
parseWorkerSessionTransitionRequestBody,
} from '@qinglong/runtime-core/worker-session-transport';
import {
WorkerExecutionAttestationFenceRejectedError,
WorkerExecutionAttestationUnavailableError,
type WorkerExecutionAttestationRepository,
} from '@qinglong/runtime-core/worker-attestation';
import {
WorkerCredentialUnavailableError,
} from '@qinglong/runtime-core/worker-credential';
import {
normalizeSecurityAuditRecord,
type SecurityAuditSink,
} from '@qinglong/runtime-core/security-audit';
import type {
ClusterControlAdmissionMetadata,
ClusterControlAdmissionPipeline,
ClusterControlAdmissionResponse,
ClusterControlStreamingAdmissionBody,
} from '../transport/httpSurface';
import type {
AuthenticatedWorkerPrincipal,
WorkerCredentialAuthenticator,
} from './workerCredentialAuthenticator';
import {
ClusterRemoteWorkerOfferFenceRejectedError,
type ClusterRemoteWorkerOfferClaimService,
} from '../remote-execution/remoteWorkerDispatcher';
import {
RemoteRunActivationFenceRejectedError,
RemoteRunActivationUnavailableError,
} from '@qinglong/runtime-core/remote-activation';
import {
createRemoteRunActivationResponseBody,
InvalidRemoteRunActivationDeliveryError,
} from '@qinglong/runtime-core/remote-activation-delivery';
import type { ClusterRemoteRunActivationService } from '../remote-execution/remoteRunActivationService';
import {
createRemoteExecutionOfferPullBody,
InvalidRemoteExecutionOfferDeliveryError,
} from '@qinglong/runtime-core/remote-offer-delivery';
import {
createRemoteWorkerSecretDeliveryResponseBody,
InvalidRemoteWorkerSecretDeliveryError,
REMOTE_SECRET_DELIVERY_SCHEMA,
RemoteWorkerSecretDeliveryFenceRejectedError,
RemoteWorkerSecretDeliveryUnavailableError,
} from '@qinglong/runtime-core/remote-secret-delivery';
import type { ClusterRemoteWorkerSecretDeliveryService } from '../remote-execution/remoteWorkerSecretDeliveryService';
import {
InvalidRemoteWorkerCompletionError,
MAX_REMOTE_WORKER_ARTIFACT_BYTES,
MAX_REMOTE_WORKER_ARTIFACT_HEADER_BYTES,
REMOTE_WORKER_ARTIFACT_CONTENT_TYPE,
RemoteWorkerCompletionFenceRejectedError,
RemoteWorkerCompletionUnavailableError,
createRemoteWorkerArtifactUploadResponseBody,
createRemoteWorkerCompletionResponseBody,
parseRemoteWorkerCompletionRequestBody,
} from '@qinglong/runtime-core/remote-worker-completion';
import type {
ClusterRemoteWorkerArtifactService,
ClusterRemoteWorkerCompletionService,
} from '../remote-execution/remoteWorkerCompletionService';
import {
InvalidRemoteWorkerLeaseControlError,
RemoteWorkerLeaseControlFenceRejectedError,
RemoteWorkerLeaseControlUnavailableError,
createRemoteWorkerLeaseControlResponseBody,
parseRemoteWorkerLeaseControlRequestBody,
} from '@qinglong/runtime-core/remote-worker-lease-control';
import type { ClusterRemoteWorkerLeaseControlService } from '../remote-execution/remoteWorkerLeaseControlService';
export interface WorkerIngressPipelineOptions {
readonly authenticator: WorkerCredentialAuthenticator;
readonly workers: AuthenticatedWorkerSessionRepository;
readonly attestations: WorkerExecutionAttestationRepository;
readonly audit: SecurityAuditSink;
readonly offers?: Pick<ClusterRemoteWorkerOfferClaimService, 'claimNext'>;
readonly activation?: Pick<
ClusterRemoteRunActivationService,
'acknowledgeStarting' | 'acknowledgeRunning' | 'failStart'
>;
readonly secrets?: Pick<ClusterRemoteWorkerSecretDeliveryService, 'deliver'>;
readonly artifacts?: Pick<ClusterRemoteWorkerArtifactService, 'upload'>;
readonly completion?: Pick<ClusterRemoteWorkerCompletionService, 'complete'>;
readonly leaseControl?: Pick<ClusterRemoteWorkerLeaseControlService, 'control'>;
readonly now?: () => number;
}
type Operation =
| 'register'
| 'heartbeat'
| 'transition'
| 'attestations'
| 'offers'
| 'starting'
| 'running'
| 'start-failure'
| 'secrets'
| 'artifacts'
| 'completion'
| 'lease-control';
interface ResolvedRoute {
readonly workerId: string;
readonly sessionId: string;
readonly operation: Operation;
}
const ROUTE = /^\/api\/v3\/worker-ingress\/workers\/([A-Za-z0-9][A-Za-z0-9._:-]{0,127})\/sessions\/([0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\/(register|heartbeat|transition|attestations|offers|starting|running|start-failure|secrets|artifacts|completion|lease-control)$/;
function failure(statusCode: number, code: string): Error {
return Object.assign(new Error(code), { statusCode, code });
}
function route(metadata: ClusterControlAdmissionMetadata): ResolvedRoute {
if (metadata.method !== 'POST' || Object.keys(metadata.query).length !== 0) {
throw failure(404, 'worker_route_not_found');
}
const match = ROUTE.exec(metadata.path);
if (!match) throw failure(404, 'worker_route_not_found');
return Object.freeze({
workerId: match[1]!,
sessionId: match[2]!,
operation: match[3]! as Operation,
});
}
function objectBody(body: unknown | null, keys: readonly string[]): Record<string, unknown> {
if (!body || typeof body !== 'object' || Array.isArray(body)) {
throw failure(400, 'invalid_worker_request');
}
const actual = Object.keys(body).sort();
const expected = [...keys].sort();
if (
actual.length !== expected.length ||
actual.some((key, index) => key !== expected[index])
) throw failure(400, 'invalid_worker_request');
return body as Record<string, unknown>;
}
async function audit(
sink: SecurityAuditSink,
metadata: ClusterControlAdmissionMetadata,
operation: Operation,
principal: Readonly<AuthenticatedWorkerPrincipal> | null,
outcome: 'authentication_rejected' | 'authentication_unavailable' | 'allowed',
now: () => number,
): Promise<void> {
await sink.record(normalizeSecurityAuditRecord({
eventId: randomUUID(),
requestId: metadata.requestId,
operationId: `worker.${operation}`,
projectId: null,
subject: principal ? { type: 'worker', id: principal.workerId } : null,
authenticationId: principal?.authenticationId ?? null,
outcome,
reasons: [outcome === 'allowed' ? 'worker_credential' : outcome],
fence: null,
occurredAtMs: now(),
}));
}
function response(statusCode: number, body: unknown): ClusterControlAdmissionResponse {
return Object.freeze({ statusCode, body });
}
function mapIngressFailure(error: unknown): never {
if (error && typeof error === 'object' && 'statusCode' in error) throw error;
if (
error instanceof WorkerSessionConflictError ||
error instanceof WorkerSessionFenceRejectedError ||
error instanceof WorkerCredentialDeliveryConflictError
) throw failure(409, 'worker_session_fenced');
if (error instanceof WorkerExecutionAttestationFenceRejectedError) {
throw failure(409, 'worker_attestation_fenced');
}
if (error instanceof ClusterRemoteWorkerOfferFenceRejectedError) {
throw failure(409, 'worker_offer_fenced');
}
if (error instanceof RemoteRunActivationFenceRejectedError) {
throw failure(409, 'worker_activation_fenced');
}
if (error instanceof RemoteWorkerSecretDeliveryFenceRejectedError) {
throw failure(409, 'worker_secret_delivery_fenced');
}
if (error instanceof RemoteWorkerCompletionFenceRejectedError) {
throw failure(409, 'worker_completion_fenced');
}
if (error instanceof RemoteWorkerLeaseControlFenceRejectedError) {
throw failure(409, 'worker_lease_control_fenced');
}
if (error instanceof InvalidRemoteWorkerCompletionError) {
throw failure(400, 'invalid_worker_request');
}
if (error instanceof InvalidRemoteWorkerLeaseControlError) {
throw failure(400, 'invalid_worker_request');
}
if (error instanceof InvalidWorkerSessionTransportError) {
throw failure(400, 'invalid_worker_request');
}
if (
error instanceof InvalidRemoteExecutionOfferDeliveryError ||
error instanceof InvalidRemoteRunActivationDeliveryError ||
error instanceof InvalidRemoteWorkerSecretDeliveryError
) throw failure(503, 'worker_ingress_unavailable');
if (
error instanceof WorkerExecutionAttestationUnavailableError ||
error instanceof WorkerCredentialUnavailableError ||
error instanceof WorkerCredentialDeliveryUnavailableError ||
error instanceof RemoteRunActivationUnavailableError ||
error instanceof RemoteWorkerSecretDeliveryUnavailableError ||
error instanceof RemoteWorkerCompletionUnavailableError ||
error instanceof RemoteWorkerLeaseControlUnavailableError
) throw failure(503, 'worker_ingress_unavailable');
if (error instanceof TypeError || error instanceof RangeError) {
throw failure(400, 'invalid_worker_request');
}
throw failure(503, 'worker_ingress_unavailable');
}
export function createWorkerIngressAdmissionPipeline(
options: WorkerIngressPipelineOptions,
): ClusterControlAdmissionPipeline {
if (
!options ||
typeof options.authenticator?.authenticate !== 'function' ||
typeof options.workers?.register !== 'function' ||
typeof options.workers?.heartbeatAuthenticated !== 'function' ||
typeof options.workers?.transitionAuthenticated !== 'function' ||
typeof options.attestations?.submit !== 'function' ||
typeof options.audit?.record !== 'function' ||
(options.offers !== undefined &&
typeof options.offers.claimNext !== 'function') ||
(options.activation !== undefined &&
(typeof options.activation.acknowledgeStarting !== 'function' ||
typeof options.activation.acknowledgeRunning !== 'function' ||
typeof options.activation.failStart !== 'function')) ||
(options.secrets !== undefined &&
typeof options.secrets.deliver !== 'function') ||
(options.artifacts !== undefined &&
typeof options.artifacts.upload !== 'function') ||
(options.completion !== undefined &&
typeof options.completion.complete !== 'function') ||
(options.leaseControl !== undefined &&
typeof options.leaseControl.control !== 'function')
) throw new TypeError('Worker ingress pipeline options are invalid');
const now = options.now ?? Date.now;
return Object.freeze({
async prepare(metadata: ClusterControlAdmissionMetadata) {
const resolved = route(metadata);
let principal: Readonly<AuthenticatedWorkerPrincipal> | null;
try {
principal = await options.authenticator.authenticate(metadata);
} catch {
try { await audit(options.audit, metadata, resolved.operation, null, 'authentication_unavailable', now); } catch { /* fail below */ }
throw failure(503, 'worker_authentication_unavailable');
}
if (!principal || principal.workerId !== resolved.workerId) {
try { await audit(options.audit, metadata, resolved.operation, null, 'authentication_rejected', now); } catch { throw failure(503, 'worker_audit_unavailable'); }
throw failure(401, 'worker_authentication_required');
}
try {
await audit(options.audit, metadata, resolved.operation, principal, 'allowed', now);
} catch {
throw failure(503, 'worker_audit_unavailable');
}
if (resolved.operation === 'artifacts') {
return Object.freeze({
bodyMode: 'stream' as const,
contentType: REMOTE_WORKER_ARTIFACT_CONTENT_TYPE,
maximumBodyBytes:
4 + MAX_REMOTE_WORKER_ARTIFACT_HEADER_BYTES +
MAX_REMOTE_WORKER_ARTIFACT_BYTES,
async handleStream(body: ClusterControlStreamingAdmissionBody) {
try {
if (!options.artifacts) {
throw failure(503, 'worker_artifact_unavailable');
}
const receipt = await options.artifacts.upload({
workerId: resolved.workerId,
workerSessionId: resolved.sessionId,
contentLength: body.contentLength,
chunks: body.chunks,
signal: metadata.signal,
});
return response(
200,
createRemoteWorkerArtifactUploadResponseBody(receipt),
);
} catch (error) {
return mapIngressFailure(error);
}
},
});
}
return Object.freeze({
async handle(body: unknown | null) {
try {
if (resolved.operation === 'register') {
const command = parseWorkerSessionRegisterRequestBody(body, {
workerId: resolved.workerId,
sessionId: resolved.sessionId,
});
const result = await options.workers.register(command);
return response(
200,
createWorkerSessionRegisterResponseBody(result),
);
}
if (resolved.operation === 'heartbeat') {
const command = parseWorkerSessionHeartbeatRequestBody(body, {
workerId: resolved.workerId,
sessionId: resolved.sessionId,
});
const worker = await options.workers.heartbeatAuthenticated(
command,
{
workerId: principal.workerId,
credentialId: principal.credentialId,
credentialVersion: principal.credentialVersion,
},
);
return response(
200,
createWorkerSessionHeartbeatResponseBody(worker),
);
}
if (resolved.operation === 'transition') {
const command = parseWorkerSessionTransitionRequestBody(body, {
workerId: resolved.workerId,
sessionId: resolved.sessionId,
});
const worker = await options.workers.transitionAuthenticated(
command,
{
workerId: principal.workerId,
credentialId: principal.credentialId,
credentialVersion: principal.credentialVersion,
},
);
return response(
200,
createWorkerSessionTransitionResponseBody(worker),
);
}
if (resolved.operation === 'offers') {
if (!options.offers) {
throw failure(503, 'worker_offer_unavailable');
}
const value = objectBody(body, [
'workerGeneration', 'offerId', 'leaseToken',
]);
const result = await options.offers.claimNext(
{ workerId: resolved.workerId },
{
workerSessionId: resolved.sessionId,
workerGeneration: value.workerGeneration as number,
offerId: value.offerId as string,
leaseToken: value.leaseToken as string,
},
);
return response(200, createRemoteExecutionOfferPullBody(result));
}
if (
resolved.operation === 'starting' ||
resolved.operation === 'start-failure'
) {
if (!options.activation) {
throw failure(503, 'worker_activation_unavailable');
}
const value = objectBody(body, [
'runId', 'attemptId', 'workerGeneration', 'offerId',
'leaseGeneration', 'leaseToken', 'expectedLeaseVersion',
]);
const command = {
runId: value.runId as string,
attemptId: value.attemptId as string,
workerSessionId: resolved.sessionId,
workerGeneration: value.workerGeneration as number,
offerId: value.offerId as string,
leaseGeneration: value.leaseGeneration as number,
leaseToken: value.leaseToken as string,
expectedLeaseVersion: value.expectedLeaseVersion as number,
};
const activation = resolved.operation === 'starting'
? await options.activation.acknowledgeStarting(
{ workerId: resolved.workerId }, command,
)
: await options.activation.failStart(
{ workerId: resolved.workerId }, command,
);
return response(
200,
createRemoteRunActivationResponseBody(activation),
);
}
if (resolved.operation === 'running') {
if (!options.activation) {
throw failure(503, 'worker_activation_unavailable');
}
const value = objectBody(body, [
'runId', 'attemptId', 'workerGeneration', 'offerId',
'leaseGeneration', 'leaseToken', 'expectedLeaseVersion',
'executorHandle', 'logArtifactId', 'callbackSequence',
'callbackTokenDigest',
]);
if (
value.logArtifactId !== null &&
typeof value.logArtifactId !== 'string'
) throw failure(400, 'invalid_worker_request');
const activation = await options.activation.acknowledgeRunning(
{ workerId: resolved.workerId },
{
runId: value.runId as string,
attemptId: value.attemptId as string,
workerSessionId: resolved.sessionId,
workerGeneration: value.workerGeneration as number,
offerId: value.offerId as string,
leaseGeneration: value.leaseGeneration as number,
leaseToken: value.leaseToken as string,
expectedLeaseVersion: value.expectedLeaseVersion as number,
executorHandle: value.executorHandle as string,
callbackSequence: value.callbackSequence as number,
callbackTokenDigest: value.callbackTokenDigest as string,
...(value.logArtifactId === null
? {}
: { logArtifactId: value.logArtifactId }),
},
);
return response(
200,
createRemoteRunActivationResponseBody(activation),
);
}
if (resolved.operation === 'secrets') {
if (!options.secrets) {
throw failure(503, 'worker_secret_delivery_unavailable');
}
const value = objectBody(body, [
'schema', 'runId', 'attemptId', 'projectId', 'taskId',
'taskRevision', 'executionDigest', 'workerGeneration',
'offerId', 'leaseGeneration', 'leaseToken',
'expectedLeaseVersion', 'secretRefs',
]);
if (value.schema !== REMOTE_SECRET_DELIVERY_SCHEMA) {
throw failure(400, 'invalid_worker_request');
}
const delivered = await options.secrets.deliver(
{ workerId: resolved.workerId },
{
workerSessionId: resolved.sessionId,
workerGeneration: value.workerGeneration as number,
runId: value.runId as string,
attemptId: value.attemptId as string,
projectId: value.projectId as string,
taskId: value.taskId as string,
taskRevision: value.taskRevision as string,
executionDigest: value.executionDigest as string,
offerId: value.offerId as string,
leaseGeneration: value.leaseGeneration as number,
leaseToken: value.leaseToken as string,
expectedLeaseVersion: value.expectedLeaseVersion as number,
secretRefs: value.secretRefs as string[],
},
);
try {
const responseBody = createRemoteWorkerSecretDeliveryResponseBody(
delivered,
value.secretRefs as string[],
);
if (
responseBody.runId !== value.runId ||
responseBody.attemptId !== value.attemptId ||
responseBody.offerId !== value.offerId ||
responseBody.executionDigest !== value.executionDigest
) throw new InvalidRemoteWorkerSecretDeliveryError(
'service response authority does not match request',
);
return response(
200,
responseBody,
);
} finally {
try { await delivered.dispose?.(); } catch { /* response remains valid */ }
}
}
if (resolved.operation === 'completion') {
if (!options.completion) {
throw failure(503, 'worker_completion_unavailable');
}
const command = parseRemoteWorkerCompletionRequestBody(body, {
workerId: resolved.workerId,
workerSessionId: resolved.sessionId,
});
const completed = await options.completion.complete(
command,
metadata.signal,
);
return response(
200,
createRemoteWorkerCompletionResponseBody(completed),
);
}
if (resolved.operation === 'lease-control') {
if (!options.leaseControl) {
throw failure(503, 'worker_lease_control_unavailable');
}
const command = parseRemoteWorkerLeaseControlRequestBody(body, {
workerId: resolved.workerId,
workerSessionId: resolved.sessionId,
});
return response(
200,
createRemoteWorkerLeaseControlResponseBody(
await options.leaseControl.control(command),
),
);
}
const value = objectBody(body, [
'attestationId', 'runId', 'attemptId', 'sequence', 'state',
'workerGeneration', 'leaseTokenDigest', 'leaseGeneration',
'leaseVersion', 'offerId', 'callbackSequence', 'executorHandle',
'journalRevision',
]);
if (value.workerGeneration === undefined) {
throw failure(400, 'invalid_worker_request');
}
const result = await options.attestations.submit({
attestationId: value.attestationId as string,
runId: value.runId as string,
attemptId: value.attemptId as string,
sequence: value.sequence as number,
state: value.state as 'running' | 'stopped',
workerId: resolved.workerId,
workerSessionId: resolved.sessionId,
workerGeneration: value.workerGeneration as number,
leaseTokenDigest: value.leaseTokenDigest as string,
leaseGeneration: value.leaseGeneration as number,
leaseVersion: value.leaseVersion as number,
offerId: value.offerId as string,
callbackSequence: value.callbackSequence as number,
executorHandle: value.executorHandle as string,
journalRevision: value.journalRevision as number,
});
return response(result.status === 'created' ? 201 : 200, {
attestationId: result.attestation.attestationId,
sequence: result.attestation.sequence,
state: result.attestation.state,
receivedAtMs: result.attestation.receivedAtMs,
replay: result.status === 'existing',
});
} catch (error) {
return mapIngressFailure(error);
}
},
});
},
});
}