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
@@ -0,0 +1,98 @@
import path from 'path';
import config from '../../../config';
import Logger from '../../../loaders/logger';
import {
activateManualPrimaryRuntime,
type ManualPrimaryActivationAudit,
type ManualPrimaryActivationStack,
} from '../../application/manualPrimaryRuntimeActivation';
import { installManualPrimaryExecutionRouter } from '../../compatibility/manualPrimaryExecutionBridge';
import type { RuntimeRolloutPolicy } from '../../domain/runtimeRollout';
import { parseDeploymentProfile } from '../../domain/deploymentProfile';
import type { RuntimeRolloutLoadResult } from '../../ports/runtimeRolloutLoader';
import { loadRuntimeRolloutManifest } from '../fs/runtimeRolloutManifestLoader';
import type { DefaultManualPrimaryActivationOptions } from './defaultManualPrimaryActivation';
export const DEFAULT_RUNTIME_ROLLOUT_MANIFEST_FILE = 'qinglong3-rollout.json';
interface DefaultManualPrimaryStackModule {
createDefaultManualPrimaryActivationStack(
rollout: RuntimeRolloutPolicy,
options?: DefaultManualPrimaryActivationOptions,
): ManualPrimaryActivationStack;
}
export interface BootstrapDefaultManualPrimaryRuntimeOptions
extends DefaultManualPrimaryActivationOptions {
load?: () => Promise<RuntimeRolloutLoadResult>;
loadStack?: () => Promise<DefaultManualPrimaryStackModule>;
install?: typeof installManualPrimaryExecutionRouter;
audit?: (record: ManualPrimaryActivationAudit) => void | Promise<void>;
}
/**
* Lightweight HTTP-worker bootstrap. Heavy Runtime adapters are imported only
* after an accepted manifest explicitly selects manual Primary ownership.
*/
export async function bootstrapDefaultManualPrimaryRuntime(
options: BootstrapDefaultManualPrimaryRuntimeOptions = {},
) {
const sourcePath = path.join(
config.configPath,
DEFAULT_RUNTIME_ROLLOUT_MANIFEST_FILE,
);
const load = await (
options.load ?? (() => loadRuntimeRolloutManifest(sourcePath))
)();
const selected =
load.status === 'accepted' && load.policy.modeFor('manual') === 'primary';
const audit =
options.audit ??
((record: ManualPrimaryActivationAudit) => {
Logger.info(`[runtime-activation] ${JSON.stringify(record)}`);
});
let stackModule: DefaultManualPrimaryStackModule | undefined;
if (selected) {
try {
stackModule = await (
options.loadStack ?? (() => import('./defaultManualPrimaryActivation'))
)();
} catch (error) {
try {
await audit({ ...load.audit, activation: 'failed' });
} catch {
// Preserve the module load error without exposing manifest contents.
}
throw error;
}
}
const activationOptions: DefaultManualPrimaryActivationOptions = {
...(options.database === undefined ? {} : { database: options.database }),
...(options.owner === undefined ? {} : { owner: options.owner }),
...(options.recovery === undefined ? {} : { recovery: options.recovery }),
...(options.completion === undefined
? {}
: { completion: options.completion }),
...(options.cancellation === undefined
? {}
: { cancellation: options.cancellation }),
...(options.timeout === undefined ? {} : { timeout: options.timeout }),
};
return activateManualPrimaryRuntime({
load: async () => load,
create(rollout) {
if (!stackModule) {
throw new Error('Primary stack was not loaded for the selected policy');
}
return stackModule.createDefaultManualPrimaryActivationStack(rollout, {
...activationOptions,
deploymentProfile:
options.deploymentProfile ??
parseDeploymentProfile(process.env.QL_DEPLOYMENT_PROFILE),
});
},
install: options.install ?? installManualPrimaryExecutionRouter,
audit,
});
}
@@ -0,0 +1,271 @@
import type { Sequelize } from 'sequelize';
import { sequelize } from '../../../data';
import Logger from '../../../loaders/logger';
import {
PrimaryCompletionReceiptLifecycle,
type PrimaryCompletionReceiptLifecycleOptions,
} from '../../application/primaryCompletionReceiptLifecycle';
import { PrimaryCompletionReceiptJournalScanner } from '../../application/primaryCompletionReceiptJournalScanner';
import { PrimaryCompletionReceiptSupervisor } from '../../application/primaryCompletionReceiptSupervisor';
import { PrimaryCompletionReceiptConsumer } from '../../application/primaryCompletionReceiptConsumer';
import { PrimaryRunCompletionService } from '../../application/primaryRunCompletionService';
import {
PrimaryCancellationLifecycle,
type PrimaryCancellationLifecycleOptions,
} from '../../application/primaryCancellationLifecycle';
import { PrimaryCancellationDispatcher } from '../../application/primaryCancellationDispatcher';
import { PrimaryCancellationSupervisor } from '../../application/primaryCancellationSupervisor';
import { PrimaryTimeoutRequester } from '../../application/primaryTimeoutRequester';
import { PrimaryTimeoutSupervisor } from '../../application/primaryTimeoutSupervisor';
import {
PrimaryTimeoutLifecycle,
type PrimaryTimeoutLifecycleOptions,
} from '../../application/primaryTimeoutLifecycle';
import { RunCommandService } from '../../application/runCommandService';
import { PrimaryRunStartupReconciler } from '../../application/primaryRunStartupReconciler';
import {
PrimaryRunStartupSupervisor,
type PrimaryRunStartupOptions,
} from '../../application/primaryRunStartupSupervisor';
import type { RuntimeRolloutPolicy } from '../../domain/runtimeRollout';
import {
localPrimaryResourcePolicy,
type DeploymentProfile,
} from '../../domain/deploymentProfile';
import { LegacySequelizeCancellationDispatchRepository } from '../legacy-sequelize/cancellationDispatchRepository';
import { LegacySequelizePrimaryCancellationSource } from '../legacy-sequelize/primaryCancellationSource';
import { LegacySequelizePrimaryTimeoutSource } from '../legacy-sequelize/primaryTimeoutSource';
import { PrimaryCronProjection } from '../legacy-sequelize/primaryCronProjection';
import { LegacySequelizePrimaryRunRecoverySource } from '../legacy-sequelize/primaryRunRecoverySource';
import { LegacySequelizeCompletionReceiptJournal } from '../legacy-sequelize/completionReceiptJournal';
import { LegacySequelizeProjectedRunRepository } from '../legacy-sequelize/projectedRunRepository';
import { LocalProcessPersistedExecutionInspector } from '../local-process/localProcessIdentity';
import { LocalProcessPersistedExecutionController } from '../local-process/persistedLocalProcessController';
import { LocalProcessExecutor } from '../local-process/localProcessExecutor';
import { CompletionReceiptFileStore } from '../fs/completionReceiptFileStore';
import {
DEFAULT_COMPLETION_RECEIPT_ROOT,
DEFAULT_LOCAL_PROCESS_LAUNCHER_PATH,
LegacyManualPrimaryLogFiles,
} from './defaultManualPrimaryRuntime';
import { ManualPrimaryRuntime } from '../../application/manualPrimaryRuntime';
export interface DefaultManualPrimaryActivationOptions {
database?: Sequelize;
owner?: string;
deploymentProfile?: DeploymentProfile;
recovery?: PrimaryRunStartupOptions;
completion?: Pick<
PrimaryCompletionReceiptLifecycleOptions,
'intervalMs' | 'initialDelayMs' | 'stopTimeoutMs' | 'cycle'
>;
cancellation?: Pick<
PrimaryCancellationLifecycleOptions,
'intervalMs' | 'initialDelayMs' | 'stopTimeoutMs' | 'cycle'
>;
timeout?: Pick<
PrimaryTimeoutLifecycleOptions,
'intervalMs' | 'initialDelayMs' | 'stopTimeoutMs' | 'cycle'
>;
}
function boundedOwner(value: string): string {
if (!value || value.length > 128) {
throw new RangeError(
'Primary activation owner must be 1 to 128 characters',
);
}
return value;
}
export function createDefaultManualPrimaryActivationStack(
rollout: RuntimeRolloutPolicy,
options: DefaultManualPrimaryActivationOptions = {},
) {
const database = options.database ?? sequelize;
const resources = localPrimaryResourcePolicy(
options.deploymentProfile ?? 'standalone',
);
const repository = new LegacySequelizeProjectedRunRepository(database, [
new PrimaryCronProjection(database),
]);
const recoverySource = new LegacySequelizePrimaryRunRecoverySource(database);
const completionReceiptJournal = new LegacySequelizeCompletionReceiptJournal(
database,
);
const completionReceiptStore = new CompletionReceiptFileStore(
DEFAULT_COMPLETION_RECEIPT_ROOT,
);
const completionReceipts = new PrimaryCompletionReceiptConsumer(
completionReceiptStore,
new PrimaryRunCompletionService(repository),
{
journal: completionReceiptJournal,
quarantineRetentionMs: resources.receiptQuarantineRetentionMs,
},
);
const startup = new PrimaryRunStartupSupervisor(
new PrimaryRunStartupReconciler(
repository,
recoverySource,
[new LocalProcessPersistedExecutionInspector()],
{
completionReceipts,
completionReceiptJournal,
receiptPublishGraceMs: resources.receiptPublishGraceMs,
},
),
);
const completion = new PrimaryCompletionReceiptLifecycle(
new PrimaryCompletionReceiptSupervisor(
new PrimaryCompletionReceiptJournalScanner(
completionReceiptJournal,
completionReceiptStore,
completionReceipts,
{
terminalMissingRetentionMs:
resources.receiptTerminalMissingRetentionMs,
},
),
),
{
intervalMs:
options.completion?.intervalMs ?? resources.completion.intervalMs,
initialDelayMs:
options.completion?.initialDelayMs ??
resources.completion.initialDelayMs,
stopTimeoutMs:
options.completion?.stopTimeoutMs ?? resources.completion.stopTimeoutMs,
cycle: options.completion?.cycle ?? {
pageSize: resources.completion.pageSize,
maxPages: resources.completion.maxPages,
},
onCycle(summary) {
Logger.info(
`[runtime-completion] ${JSON.stringify({
profile: resources.profile,
pages: summary.pages,
scanned: summary.scanned,
applied: summary.applied,
alreadyTerminal: summary.alreadyTerminal,
quarantined: summary.quarantined,
purgedQuarantines: summary.purgedQuarantines,
expiredMissing: summary.expiredMissing,
missing: summary.missing,
cleanupPending: summary.cleanupPending,
skipped: summary.skipped,
ambiguous: summary.ambiguous,
failed: summary.failed,
stopReason: summary.stopReason,
remaining: summary.remaining,
})}`,
);
},
onError() {
Logger.error('[runtime-completion] cycle failed');
},
},
);
const cancellation = new PrimaryCancellationLifecycle(
new PrimaryCancellationSupervisor(
new PrimaryCancellationDispatcher(
new LegacySequelizePrimaryCancellationSource(database),
new LegacySequelizeCancellationDispatchRepository(database),
[new LocalProcessPersistedExecutionController()],
{ owner: boundedOwner(options.owner ?? `http:${process.pid}`) },
),
),
{
intervalMs:
options.cancellation?.intervalMs ?? resources.cancellation.intervalMs,
initialDelayMs:
options.cancellation?.initialDelayMs ??
resources.cancellation.initialDelayMs,
stopTimeoutMs:
options.cancellation?.stopTimeoutMs ??
resources.cancellation.stopTimeoutMs,
cycle: options.cancellation?.cycle ?? {
pageSize: resources.cancellation.pageSize,
maxPages: resources.cancellation.maxPages,
},
onCycle(summary) {
Logger.info(
`[runtime-cancellation] ${JSON.stringify({
pages: summary.pages,
scanned: summary.scanned,
claimed: summary.claimed,
pending: summary.pending,
failed: summary.failed,
stopReason: summary.stopReason,
remaining: summary.remaining,
})}`,
);
},
onError() {
Logger.error('[runtime-cancellation] cycle failed');
},
},
);
const timeout = new PrimaryTimeoutLifecycle(
new PrimaryTimeoutSupervisor(
new PrimaryTimeoutRequester(
new LegacySequelizePrimaryTimeoutSource(database),
new RunCommandService(repository),
),
),
{
intervalMs: options.timeout?.intervalMs ?? resources.timeout.intervalMs,
initialDelayMs:
options.timeout?.initialDelayMs ?? resources.timeout.initialDelayMs,
stopTimeoutMs:
options.timeout?.stopTimeoutMs ?? resources.timeout.stopTimeoutMs,
cycle: options.timeout?.cycle ?? {
pageSize: resources.timeout.pageSize,
maxPages: resources.timeout.maxPages,
},
onCycle(summary) {
Logger.info(
`[runtime-timeout] ${JSON.stringify({
profile: resources.profile,
pages: summary.pages,
scanned: summary.scanned,
accepted: summary.accepted,
alreadyRequested: summary.alreadyRequested,
alreadyTerminal: summary.alreadyTerminal,
failed: summary.failed,
stopReason: summary.stopReason,
remaining: summary.remaining,
})}`,
);
},
onError() {
Logger.error('[runtime-timeout] cycle failed');
},
},
);
return {
router: new ManualPrimaryRuntime(
repository,
new LocalProcessExecutor({
durableLauncherPath: DEFAULT_LOCAL_PROCESS_LAUNCHER_PATH,
}),
rollout,
new LegacyManualPrimaryLogFiles(
undefined,
undefined,
completionReceiptJournal,
),
{
orchestrator: { completionReceiptJournal },
},
),
reconcile: () => startup.run(options.recovery),
startCompletion: () => completion.start(),
stopCompletion: () => completion.stop(),
startTimeout: () => timeout.start(),
stopTimeout: () => timeout.stop(),
startCancellation: () => cancellation.start(),
stopCancellation: () => cancellation.stop(),
};
}
@@ -0,0 +1,132 @@
import fs from 'fs/promises';
import path from 'path';
import dayjs from 'dayjs';
import config from '../../../config';
import { getUniqPath } from '../../../config/util';
import { sequelize } from '../../../data';
import { logStreamManager } from '../../../shared/logStreamManager';
import {
ManualPrimaryRuntime,
type ManualPrimaryLogFiles,
type PreparedManualPrimaryLog,
} from '../../application/manualPrimaryRuntime';
import type { ManualPrimaryStartInput } from '../../compatibility/manualPrimaryExecutionBridge';
import { createLegacyLogOutputRef } from '../../compatibility/legacyLogOutputRef';
import type { RuntimeRolloutPolicy } from '../../domain/runtimeRollout';
import { PrimaryCronProjection } from '../legacy-sequelize/primaryCronProjection';
import { LegacySequelizeProjectedRunRepository } from '../legacy-sequelize/projectedRunRepository';
import { LocalProcessExecutor } from '../local-process/localProcessExecutor';
import { enableDurableLocalProcessOutput } from '../local-process/durableLocalProcessOutput';
import { CompletionReceiptFileStore } from '../fs/completionReceiptFileStore';
import type { CompletionReceiptJournal } from '../../ports/completionReceiptJournal';
export const DEFAULT_COMPLETION_RECEIPT_ROOT = path.join(
config.dataPath,
'runtime',
'completion-receipts',
);
export const DEFAULT_LOCAL_PROCESS_LAUNCHER_PATH = path.join(
config.rootPath,
'shell',
'ql3-launcher.sh',
);
function isWithin(root: string, candidate: string): boolean {
return candidate === root || candidate.startsWith(root + path.sep);
}
function relativeLogDirectory(root: string, value: string): string {
const candidate = path.isAbsolute(value) ? value : path.resolve(root, value);
if (!isWithin(root, candidate)) {
throw new Error('Manual Primary log directory escapes the configured root');
}
const relative = path.relative(root, candidate).split(path.sep).join('/');
if (!relative || relative === '.') {
throw new Error('Manual Primary log directory must be below the log root');
}
return relative;
}
export class LegacyManualPrimaryLogFiles implements ManualPrimaryLogFiles {
private readonly completionReceipts: CompletionReceiptFileStore;
private readonly completionReceiptRoot: string;
constructor(
private readonly logRoot = path.resolve(config.logPath),
completionReceiptRoot = DEFAULT_COMPLETION_RECEIPT_ROOT,
private readonly completionReceiptJournal?: Pick<
CompletionReceiptJournal,
'resolve'
>,
) {
this.completionReceiptRoot = path.resolve(completionReceiptRoot);
this.completionReceipts = new CompletionReceiptFileStore(
this.completionReceiptRoot,
);
}
async prepare(
input: ManualPrimaryStartInput,
): Promise<PreparedManualPrimaryLog> {
const configured =
!input.cron.logName || input.cron.logName === '/dev/null'
? await getUniqPath(input.cron.command, String(input.cron.id))
: input.cron.logName;
const directory = relativeLogDirectory(this.logRoot, configured);
const logPath = path.posix.join(
directory,
dayjs(input.acceptedAtMs).format('YYYY-MM-DD-HH-mm-ss-SSS') + '.log',
);
createLegacyLogOutputRef(logPath);
const absolutePath = path.resolve(this.logRoot, ...logPath.split('/'));
if (!isWithin(this.logRoot, absolutePath)) {
throw new Error('Manual Primary log file escapes the configured root');
}
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
const output = enableDurableLocalProcessOutput(
{
async write(output) {
await logStreamManager.write(
absolutePath,
Buffer.from(output.chunk).toString('utf8'),
);
},
},
{
outputFilePath: absolutePath,
completionReceiptRoot: this.completionReceiptRoot,
},
);
const completionReceipts = this.completionReceipts;
const completionReceiptJournal = this.completionReceiptJournal;
return {
logPath,
output,
async completionCommitted(attemptId) {
await completionReceipts.remove(attemptId);
await completionReceiptJournal?.resolve(attemptId);
},
async close() {
await logStreamManager.closeStream(absolutePath);
},
};
}
}
/** Legacy factory retained for focused tests; production activation uses the
* shared lifecycle stack in defaultManualPrimaryActivation.ts. */
export function createDefaultManualPrimaryRuntime(
rollout: RuntimeRolloutPolicy,
): ManualPrimaryRuntime {
const repository = new LegacySequelizeProjectedRunRepository(sequelize, [
new PrimaryCronProjection(sequelize),
]);
return new ManualPrimaryRuntime(
repository,
new LocalProcessExecutor({
durableLauncherPath: DEFAULT_LOCAL_PROCESS_LAUNCHER_PATH,
}),
rollout,
new LegacyManualPrimaryLogFiles(),
);
}
@@ -0,0 +1,110 @@
import type {
ExecutionResourcePolicy,
ExecutionSpec,
} from '../../domain/execution';
import { InvalidExecutionSpecError } from '../../domain/executorErrors';
export interface LegacyCronSnapshot {
id: number;
command: string;
taskBefore?: string;
taskAfter?: string;
workDirectory?: string;
logName?: string;
}
export interface LegacyCronExecutionInput {
runId: string;
attemptId: string;
projectId: string;
taskRevision: string;
cron: LegacyCronSnapshot;
realTime: boolean;
realLogPath?: string;
noDelay?: boolean;
timeoutMs?: number;
terminationGraceMs?: number;
resourcePolicy?: ExecutionResourcePolicy;
}
export const DEFAULT_LEGACY_TERMINATION_GRACE_MS = 10_000;
function quoteShellValue(value: string): string {
return `'${value.replace(/'/g, `'\\''`)}'`;
}
function normalizeHook(value: string): string {
return value.replace(/;? *\r?\n/g, ';').trim();
}
function assignment(name: string, value: string | number | boolean): string {
return `${name}=${quoteShellValue(String(value))}`;
}
function legacyTaskCommand(command: string): string {
const trimmed = command.trim();
if (!trimmed) {
throw new InvalidExecutionSpecError(
'Legacy Cron command must not be empty',
);
}
if (trimmed.startsWith('task ') || trimmed.startsWith('ql ')) return trimmed;
return `task ${trimmed}`;
}
export function buildLegacyCronExecutionSpec(
input: LegacyCronExecutionInput,
): ExecutionSpec {
if (!Number.isSafeInteger(input.cron.id) || input.cron.id < 1) {
throw new InvalidExecutionSpecError(
'Legacy Cron id must be a positive safe integer',
);
}
const variables: string[] = [];
if (input.realLogPath) {
variables.push(assignment('real_log_path', input.realLogPath));
}
if (input.noDelay) variables.push(assignment('no_delay', true));
variables.push(assignment('real_time', input.realTime));
variables.push(assignment('no_tee', true));
variables.push(assignment('ID', input.cron.id));
if (input.cron.logName) {
variables.push(assignment('log_name', input.cron.logName));
}
if (input.cron.taskBefore) {
variables.push(
assignment('task_before', normalizeHook(input.cron.taskBefore)),
);
}
if (input.cron.taskAfter) {
variables.push(
assignment('task_after', normalizeHook(input.cron.taskAfter)),
);
}
if (input.cron.workDirectory) {
variables.push(assignment('work_dir', input.cron.workDirectory));
}
return {
runId: input.runId,
attemptId: input.attemptId,
projectId: input.projectId,
taskId: `legacy-cron:${input.cron.id}`,
taskRevision: input.taskRevision,
command: {
kind: 'shell',
command: `${variables.join(' ')} ${legacyTaskCommand(
input.cron.command,
)}`,
shell: '/bin/bash',
},
environmentPolicy: 'inherit',
...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
terminationGraceMs:
input.terminationGraceMs ?? DEFAULT_LEGACY_TERMINATION_GRACE_MS,
...(input.resourcePolicy === undefined
? {}
: { resourcePolicy: input.resourcePolicy }),
};
}