feat(ql3): observe system crond runs idempotently

This commit is contained in:
whyour
2026-08-18 06:33:23 +08:00
parent 6831ea3de5
commit 0ad96d38a6
16 changed files with 858 additions and 73 deletions
@@ -22,6 +22,7 @@ const SHADOW_ORIGINS_ENV = 'QL3_SHADOW_ORIGINS';
const SUPPORTED_SHADOW_ORIGINS = new Set<ExecutionOrigin>([
'manual',
'scheduled_node',
'scheduled_system',
'script',
'subscription',
'system',
@@ -84,7 +85,7 @@ function readConfiguredOrigins(): ReadonlySet<ExecutionOrigin> {
incrementFailure('configuration:unsupported_origin');
try {
Logger.warn(
'[ql3-shadow] ignored unsupported origin; this slice supports manual,scheduled_node,script,subscription,system',
'[ql3-shadow] ignored unsupported origin; this slice supports manual,scheduled_node,scheduled_system,script,subscription,system',
);
} catch {
// Invalid compatibility configuration must not affect legacy paths.
@@ -269,6 +270,57 @@ export function observeLegacyExecution(
: NOOP_OBSERVATION;
}
export interface LegacyShellExecutionCallbackInput {
pid?: number;
logPath?: string;
atMs: number;
phase: 'running' | 'finished';
exitCode?: number;
}
/**
* Observes one callback-owned execution without registering an in-memory
* ChildProcess correlation entry. The accepted fact must carry a durable
* requestId so callback replay can converge in the Shadow writer.
*/
export function observeLegacyShellExecutionCallback(
createFact: LegacyExecutionAcceptedFactFactory,
input: LegacyShellExecutionCallbackInput,
): LegacyExecutionObservation | undefined {
const origin: ExecutionOrigin = 'scheduled_system';
let observation: LegacyExecutionObservation;
if (override) {
if (!override.origins.has(origin)) return undefined;
const fact = createAcceptedFactFailOpen(origin, createFact);
observation = fact
? beginFailOpen(override.observer, fact)
: NOOP_OBSERVATION;
} else {
if (!readConfiguredOrigins().has(origin)) return undefined;
const fact = createAcceptedFactFailOpen(origin, createFact);
observation = fact
? deferredObservation(getDefaultObserver(), fact)
: NOOP_OBSERVATION;
}
observation.spawned({
atMs: input.atMs,
...(input.pid === undefined ? {} : { pid: input.pid }),
...(input.logPath === undefined
? {}
: { logArtifactId: createLegacyLogArtifactId(input.logPath) }),
});
if (input.phase === 'running') {
observation.running({ atMs: input.atMs });
} else {
observation.exited({
atMs: input.atMs,
exitCode: input.exitCode ?? 0,
});
}
return observation;
}
export interface LegacyExecutionCancellationInput {
legacyCronId: number;
pid?: number;
@@ -0,0 +1,87 @@
import type { Crontab } from '../../data/cron';
import type { LegacyExecutionObservation } from '../ports/legacyExecutionObserver';
import { observeLegacyShellExecutionCallback } from './legacyExecutionBridge';
import { createLegacyTaskRevision } from './legacyTaskRevision';
const EXECUTION_ID_PATTERN =
/^legacy-system:([1-9][0-9]{0,12}):([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/u;
export interface LegacyScheduledSystemCallbackInput {
executionId: string;
phase: 'running' | 'finished';
observedAtMs: number;
pid?: number;
logPath?: string;
exitCode?: number;
}
export function decorateScheduledSystemCronCommand(
command: string,
systemScheduler: boolean,
): string {
return systemScheduler
? `QL_EXECUTION_ORIGIN=scheduled_system ${command}`
: command;
}
export function parseLegacyScheduledSystemExecutionId(
value: string,
): { requestId: string; acceptedAtMs: number } | undefined {
const match = EXECUTION_ID_PATTERN.exec(value);
if (!match) return undefined;
const acceptedAtMs = Number(match[1]) * 1000;
if (!Number.isSafeInteger(acceptedAtMs) || acceptedAtMs < 1) return undefined;
return Object.freeze({ requestId: value, acceptedAtMs });
}
export function observeLegacyScheduledSystemExecution(
cron: Crontab,
input: LegacyScheduledSystemCallbackInput,
): LegacyExecutionObservation | undefined {
const identity = parseLegacyScheduledSystemExecutionId(input.executionId);
if (
!identity ||
cron.id === undefined ||
!Number.isSafeInteger(input.observedAtMs) ||
input.observedAtMs < identity.acceptedAtMs
) {
return undefined;
}
return observeLegacyShellExecutionCallback(
() => ({
origin: 'scheduled_system',
projectId: 'default',
taskId: `legacy-cron:${cron.id}`,
taskRevision: createLegacyTaskRevision({
command: cron.command,
...(cron.schedule === undefined ? {} : { schedule: cron.schedule }),
extraSchedules:
cron.extra_schedules?.map((item) => item.schedule) ?? [],
...(cron.task_before === undefined
? {}
: { taskBefore: cron.task_before }),
...(cron.task_after === undefined
? {}
: { taskAfter: cron.task_after }),
...(cron.work_dir === undefined
? {}
: { workDirectory: cron.work_dir }),
...(cron.log_name === undefined ? {} : { logName: cron.log_name }),
}),
...(cron.name === undefined ? {} : { taskName: cron.name }),
legacyCronId: cron.id,
triggerType: 'scheduled_system',
triggeredBy: 'legacy:system-crond',
requestId: identity.requestId,
scheduledForMs: identity.acceptedAtMs,
acceptedAtMs: identity.acceptedAtMs,
}),
{
phase: input.phase,
atMs: input.observedAtMs,
...(input.pid === undefined ? {} : { pid: input.pid }),
...(input.logPath === undefined ? {} : { logPath: input.logPath }),
...(input.exitCode === undefined ? {} : { exitCode: input.exitCode }),
},
);
}