edfddd2445
* docs: specify cicd yaml target governance * fix: resolve cicd targets from yaml --------- Co-authored-by: Codex <codex@noreply.local>
350 lines
18 KiB
TypeScript
350 lines
18 KiB
TypeScript
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
|
import { buildExecutionDiagnostics, buildSchedulerHeartbeat, schedulerHeartbeatStaleMs, staleRecoveryCandidate, taskHasTraceGapButFreshHeartbeat } from "../../src/components/microservices/code-queue/src/execution-diagnostics";
|
|
import type { ActiveRun } from "../../src/components/microservices/code-queue/src/code-agent/common";
|
|
import type { CodeQueueExecutionDiagnostics, QueueTask, SchedulerActiveRunHeartbeat, TaskStatus } from "../../src/components/microservices/code-queue/src/types";
|
|
|
|
export const CODE_QUEUE_LIVENESS_CHECK_NAMES = [
|
|
"code-queue:active-run-heartbeat-visible",
|
|
"code-queue:trace-gap-not-stale",
|
|
"code-queue:heartbeat-threshold-normalized",
|
|
"code-queue:stale-active-owner-expired",
|
|
"code-queue:stale-active-recovery-transition",
|
|
"code-queue:control-plane-split-brain-diagnostics",
|
|
"code-queue:oa-publisher-degraded-visible",
|
|
] as const;
|
|
|
|
type CodeQueueLivenessCheckName = typeof CODE_QUEUE_LIVENESS_CHECK_NAMES[number];
|
|
|
|
interface FixtureCheck {
|
|
name: CodeQueueLivenessCheckName;
|
|
ok: boolean;
|
|
detail: Record<string, unknown>;
|
|
}
|
|
|
|
const now = "2026-05-19T00:10:00.000Z";
|
|
const freshAt = "2026-05-19T00:09:50.000Z";
|
|
const oldTraceAt = "2026-05-18T23:40:00.000Z";
|
|
const expiredAt = "2026-05-18T23:50:00.000Z";
|
|
const fixtureProviderId = "D601";
|
|
|
|
function assertCondition(condition: unknown, message: string, detail: Record<string, unknown> = {}): void {
|
|
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
|
}
|
|
|
|
function fixtureTask(id: string, status: TaskStatus, heartbeat: SchedulerActiveRunHeartbeat | null = null): QueueTask {
|
|
return {
|
|
id,
|
|
queueId: "default",
|
|
queueEnteredAt: "2026-05-19T00:00:00.000Z",
|
|
prompt: `${id} prompt`,
|
|
basePrompt: `${id} prompt`,
|
|
referenceTaskIds: [],
|
|
referenceInjection: null,
|
|
providerId: fixtureProviderId,
|
|
cwd: "/workspace",
|
|
model: "gpt-5.5",
|
|
reasoningEffort: null,
|
|
executionMode: "default",
|
|
maxAttempts: 99,
|
|
status,
|
|
createdAt: "2026-05-19T00:00:00.000Z",
|
|
updatedAt: "2026-05-19T00:00:00.000Z",
|
|
startedAt: status === "running" || status === "judging" ? "2026-05-19T00:00:00.000Z" : null,
|
|
finishedAt: null,
|
|
readAt: null,
|
|
currentAttempt: status === "queued" ? 0 : 1,
|
|
currentMode: status === "queued" ? null : "initial",
|
|
codexThreadId: heartbeat?.codexThreadId ?? null,
|
|
activeTurnId: heartbeat?.activeTurnId ?? null,
|
|
schedulerHeartbeat: heartbeat,
|
|
finalResponse: "",
|
|
outputMaxSeq: heartbeat?.outputMaxSeq ?? 0,
|
|
lastError: null,
|
|
lastJudge: null,
|
|
judgeFailCount: 0,
|
|
promptHistory: [],
|
|
output: [],
|
|
events: [],
|
|
attempts: [],
|
|
cancelRequested: false,
|
|
nextPrompt: null,
|
|
nextMode: null,
|
|
};
|
|
}
|
|
|
|
function heartbeat(taskId: string, at: string, overrides: Partial<SchedulerActiveRunHeartbeat> = {}): SchedulerActiveRunHeartbeat {
|
|
return {
|
|
taskId,
|
|
queueId: "default",
|
|
attempt: 1,
|
|
activeTurnId: "turn_fixture",
|
|
codexThreadId: "thread_fixture",
|
|
owner: "D601",
|
|
schedulerInstance: "code-queue-scheduler-fixture",
|
|
executionPlane: "scheduler-execution-plane",
|
|
agentPort: "codex",
|
|
status: "running",
|
|
lastLocalHeartbeatAt: at,
|
|
lastObservedAgentEventAt: at,
|
|
lastPersistedTraceAt: at,
|
|
outputMaxSeq: 10,
|
|
source: "scheduler",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function activeRun(taskId: string, queueId = "default"): ActiveRun {
|
|
return {
|
|
taskId,
|
|
queueId,
|
|
app: { stop: () => undefined },
|
|
port: "codex",
|
|
threadId: "thread_fixture",
|
|
turnId: "turn_fixture",
|
|
startedAt: "2026-05-19T00:00:00.000Z",
|
|
lastLocalHeartbeatAt: freshAt,
|
|
lastObservedAgentEventAt: freshAt,
|
|
lastPersistedTraceAt: freshAt,
|
|
};
|
|
}
|
|
|
|
function schedulerDiagnostics(tasks: QueueTask[], activeRuns: ActiveRun[] = [], oaPublisher: unknown = null, heartbeatStaleMs?: number): CodeQueueExecutionDiagnostics {
|
|
return buildExecutionDiagnostics({
|
|
now,
|
|
heartbeatStaleMs,
|
|
controlPlane: "D601-code-queue-scheduler",
|
|
executionStateSource: "scheduler-execution-plane",
|
|
tasks,
|
|
activeRuns,
|
|
activeRunSlotCount: activeRuns.length,
|
|
activeQueueIds: activeRuns.map((run) => run.queueId),
|
|
processingQueueIds: [],
|
|
orphanedActiveTaskIds: tasks.filter((task) => (task.status === "running" || task.status === "judging") && !activeRuns.some((run) => run.taskId === task.id)).map((task) => task.id),
|
|
oaPublisher: oaPublisher as never,
|
|
});
|
|
}
|
|
|
|
function checkHeartbeatThresholdNormalized(): FixtureCheck {
|
|
const task = fixtureTask("codex_fixture_threshold_fresh_1", "running", heartbeat("codex_fixture_threshold_fresh_1", freshAt));
|
|
const run = activeRun(task.id);
|
|
const zeroDecision = staleRecoveryCandidate({ task, localActive: false, now, heartbeatStaleMs: 0 });
|
|
const tooLowDecision = staleRecoveryCandidate({ task, localActive: false, now, heartbeatStaleMs: 1 });
|
|
const zeroDiagnostics = schedulerDiagnostics([task], [run], null, 0);
|
|
const tooLowDiagnostics = schedulerDiagnostics([task], [run], null, 1);
|
|
assertCondition(zeroDecision.allowed === false && zeroDecision.reason === "owner-heartbeat-fresh", "zero heartbeat threshold must normalize before stale recovery", { zeroDecision });
|
|
assertCondition(tooLowDecision.allowed === false && tooLowDecision.reason === "owner-heartbeat-fresh", "too-low heartbeat threshold must normalize before stale recovery", { tooLowDecision });
|
|
assertCondition(zeroDiagnostics.schedulerHeartbeatStaleMs === schedulerHeartbeatStaleMs, "zero diagnostics threshold should normalize to default stale window", zeroDiagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(tooLowDiagnostics.schedulerHeartbeatStaleMs === schedulerHeartbeatStaleMs, "too-low diagnostics threshold should normalize to default stale window", tooLowDiagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(zeroDiagnostics.state === "healthy" && zeroDiagnostics.effectiveLiveness === "healthy", "fresh heartbeat with zero requested threshold must stay healthy", zeroDiagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(tooLowDiagnostics.state === "healthy" && tooLowDiagnostics.effectiveLiveness === "healthy", "fresh heartbeat with too-low requested threshold must stay healthy", tooLowDiagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(zeroDiagnostics.heartbeatFreshTaskIds.includes(task.id), "fresh heartbeat must remain in fresh task ids", zeroDiagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(zeroDiagnostics.heartbeatExpiredTaskIds.length === 0 && zeroDiagnostics.staleRecoveryCandidateTaskIds.length === 0 && zeroDiagnostics.heartbeatRiskTaskIds.length === 0, "fresh heartbeat must not be reported stale or risky", zeroDiagnostics as unknown as Record<string, unknown>);
|
|
return {
|
|
name: "code-queue:heartbeat-threshold-normalized",
|
|
ok: true,
|
|
detail: {
|
|
zeroDecision,
|
|
tooLowDecision,
|
|
normalizedStaleMs: zeroDiagnostics.schedulerHeartbeatStaleMs,
|
|
state: zeroDiagnostics.state,
|
|
effectiveLiveness: zeroDiagnostics.effectiveLiveness,
|
|
heartbeatFreshTaskIds: zeroDiagnostics.heartbeatFreshTaskIds,
|
|
heartbeatExpiredTaskIds: zeroDiagnostics.heartbeatExpiredTaskIds,
|
|
staleRecoveryCandidateTaskIds: zeroDiagnostics.staleRecoveryCandidateTaskIds,
|
|
},
|
|
};
|
|
}
|
|
|
|
function checkActiveRunHeartbeatVisible(): FixtureCheck {
|
|
const task = fixtureTask("codex_fixture_active_1", "running");
|
|
const run = activeRun(task.id);
|
|
task.schedulerHeartbeat = buildSchedulerHeartbeat(task, run, {
|
|
now: freshAt,
|
|
owner: "D601",
|
|
schedulerInstance: "code-queue-scheduler-fixture",
|
|
agentPort: "codex",
|
|
lastObservedAgentEventAt: freshAt,
|
|
lastPersistedTraceAt: freshAt,
|
|
});
|
|
const diagnostics = schedulerDiagnostics([task], [run]);
|
|
assertCondition(diagnostics.activeHeartbeatTaskIds.includes(task.id), "active heartbeat task id must be visible", diagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(diagnostics.schedulerActiveTaskIds.includes(task.id), "scheduler active task id must be visible", diagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(diagnostics.lastSchedulerHeartbeatAt === freshAt, "last scheduler heartbeat must be surfaced", diagnostics as unknown as Record<string, unknown>);
|
|
return {
|
|
name: "code-queue:active-run-heartbeat-visible",
|
|
ok: true,
|
|
detail: {
|
|
state: diagnostics.state,
|
|
schedulerActiveTaskIds: diagnostics.schedulerActiveTaskIds,
|
|
activeHeartbeatTaskIds: diagnostics.activeHeartbeatTaskIds,
|
|
lastSchedulerHeartbeatAt: diagnostics.lastSchedulerHeartbeatAt,
|
|
heartbeat: task.schedulerHeartbeat,
|
|
},
|
|
};
|
|
}
|
|
|
|
function checkTraceGapNotStale(): FixtureCheck {
|
|
const task = fixtureTask("codex_fixture_trace_gap_1", "running", heartbeat("codex_fixture_trace_gap_1", freshAt, {
|
|
lastPersistedTraceAt: oldTraceAt,
|
|
outputMaxSeq: 89112,
|
|
}));
|
|
const decision = staleRecoveryCandidate({ task, localActive: false, now });
|
|
const hasTraceGap = taskHasTraceGapButFreshHeartbeat(task, now);
|
|
const diagnostics = schedulerDiagnostics([task], []);
|
|
assertCondition(hasTraceGap, "trace gap with fresh owner heartbeat should be classified", { decision, diagnostics });
|
|
assertCondition(decision.allowed === false && decision.reason === "owner-heartbeat-fresh", "fresh heartbeat must block stale retry", { decision });
|
|
assertCondition(diagnostics.traceGapNotStaleTaskIds.includes(task.id), "diagnostics must expose trace gap as not stale", diagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(!diagnostics.staleRecoveryCandidateTaskIds.includes(task.id), "trace gap must not enter stale recovery candidates", diagnostics as unknown as Record<string, unknown>);
|
|
return {
|
|
name: "code-queue:trace-gap-not-stale",
|
|
ok: true,
|
|
detail: {
|
|
decision,
|
|
traceGapNotStaleTaskIds: diagnostics.traceGapNotStaleTaskIds,
|
|
staleRecoveryCandidateTaskIds: diagnostics.staleRecoveryCandidateTaskIds,
|
|
},
|
|
};
|
|
}
|
|
|
|
function checkStaleActiveOwnerExpired(): FixtureCheck {
|
|
const task = fixtureTask("codex_fixture_stale_1", "running", heartbeat("codex_fixture_stale_1", expiredAt, {
|
|
lastObservedAgentEventAt: expiredAt,
|
|
lastPersistedTraceAt: expiredAt,
|
|
}));
|
|
const decision = staleRecoveryCandidate({ task, localActive: false, now });
|
|
const diagnostics = schedulerDiagnostics([task], []);
|
|
assertCondition(decision.allowed === true && decision.reason === "owner-heartbeat-expired", "expired owner heartbeat should be the stale recovery gate", { decision });
|
|
assertCondition(diagnostics.state === "stale-active", "diagnostics must mark stale active only after owner heartbeat expiry", diagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(diagnostics.effectiveLiveness === "at-risk" && diagnostics.recommendedAction === "investigate-heartbeat-risk", "expired heartbeat diagnostics must surface at-risk liveness", diagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(diagnostics.staleRecoveryCandidateTaskIds.includes(task.id), "expired owner heartbeat should create a stale candidate", diagnostics as unknown as Record<string, unknown>);
|
|
return {
|
|
name: "code-queue:stale-active-owner-expired",
|
|
ok: true,
|
|
detail: {
|
|
decision,
|
|
state: diagnostics.state,
|
|
effectiveLiveness: diagnostics.effectiveLiveness,
|
|
recommendedAction: diagnostics.recommendedAction,
|
|
staleRecoveryCandidateTaskIds: diagnostics.staleRecoveryCandidateTaskIds,
|
|
},
|
|
};
|
|
}
|
|
|
|
function recoverStaleActiveTaskForFixture(task: QueueTask): number {
|
|
const decision = staleRecoveryCandidate({ task, localActive: false, now });
|
|
if (!decision.allowed) return 0;
|
|
task.status = "retry_wait";
|
|
task.finishedAt = null;
|
|
task.readAt = null;
|
|
task.activeTurnId = null;
|
|
task.lastError = "fixture stale-active recovery";
|
|
task.nextMode = "retry";
|
|
task.nextPrompt = "fixture recovery prompt";
|
|
task.updatedAt = now;
|
|
return 1;
|
|
}
|
|
|
|
function checkStaleActiveRecoveryTransition(): FixtureCheck {
|
|
const task = fixtureTask("codex_fixture_stale_recovery_1", "running", heartbeat("codex_fixture_stale_recovery_1", expiredAt, {
|
|
lastObservedAgentEventAt: expiredAt,
|
|
lastPersistedTraceAt: expiredAt,
|
|
}));
|
|
const freshTask = fixtureTask("codex_fixture_fresh_no_recovery_1", "running", heartbeat("codex_fixture_fresh_no_recovery_1", freshAt));
|
|
const diagnosticsBefore = schedulerDiagnostics([task], []);
|
|
const recovered = recoverStaleActiveTaskForFixture(task);
|
|
const freshRecovered = recoverStaleActiveTaskForFixture(freshTask);
|
|
const diagnosticsAfter = schedulerDiagnostics([task], []);
|
|
assertCondition(recovered === 1, "expired active task should be recovered exactly once", { recovered, task });
|
|
assertCondition(freshRecovered === 0 && freshTask.status === "running", "fresh owner heartbeat must block recovery", { freshRecovered, freshStatus: freshTask.status });
|
|
assertCondition(task.status === "retry_wait" && task.activeTurnId === null && task.nextMode === "retry", "recovered task should re-enter retry_wait with active turn cleared", { task });
|
|
assertCondition(diagnosticsBefore.staleRecoveryCandidateTaskIds.includes("codex_fixture_stale_recovery_1"), "before recovery should show stale candidate", diagnosticsBefore as unknown as Record<string, unknown>);
|
|
assertCondition(!diagnosticsAfter.staleRecoveryCandidateTaskIds.includes("codex_fixture_stale_recovery_1"), "after recovery should remove stale candidate", diagnosticsAfter as unknown as Record<string, unknown>);
|
|
return {
|
|
name: "code-queue:stale-active-recovery-transition",
|
|
ok: true,
|
|
detail: {
|
|
recovered,
|
|
freshRecovered,
|
|
status: task.status,
|
|
activeTurnId: task.activeTurnId,
|
|
nextMode: task.nextMode,
|
|
candidatesBefore: diagnosticsBefore.staleRecoveryCandidateTaskIds,
|
|
candidatesAfter: diagnosticsAfter.staleRecoveryCandidateTaskIds,
|
|
},
|
|
};
|
|
}
|
|
|
|
function checkControlPlaneSplitBrainDiagnostics(): FixtureCheck {
|
|
const task = fixtureTask("codex_fixture_split_1", "running", heartbeat("codex_fixture_split_1", freshAt));
|
|
const diagnostics = buildExecutionDiagnostics({
|
|
now,
|
|
controlPlane: "master-code-queue-mgr",
|
|
executionStateSource: "postgres-control-plane",
|
|
tasks: [task],
|
|
activeRuns: [],
|
|
activeRunSlotCount: 0,
|
|
oaPublisher: null,
|
|
});
|
|
assertCondition(diagnostics.state === "split-brain" && diagnostics.splitBrain === true, "master postgres-control-plane must report split-brain when DB active has fresh scheduler heartbeat", diagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(diagnostics.splitBrainLive === true && diagnostics.effectiveLiveness === "live", "fresh split-brain diagnostics must be degraded but live", diagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(diagnostics.recommendedAction === "continue-supervision", "fresh split-brain diagnostics must recommend continued supervision", diagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(diagnostics.schedulerActiveRunSlotCount === 0 && diagnostics.databaseActiveTaskCount === 1, "split-brain fixture should preserve the exact control-plane divergence", diagnostics as unknown as Record<string, unknown>);
|
|
return {
|
|
name: "code-queue:control-plane-split-brain-diagnostics",
|
|
ok: true,
|
|
detail: {
|
|
state: diagnostics.state,
|
|
splitBrain: diagnostics.splitBrain,
|
|
splitBrainLive: diagnostics.splitBrainLive,
|
|
effectiveLiveness: diagnostics.effectiveLiveness,
|
|
recommendedAction: diagnostics.recommendedAction,
|
|
executionStateSource: diagnostics.executionStateSource,
|
|
databaseActiveTaskIds: diagnostics.databaseActiveTaskIds,
|
|
schedulerActiveRunSlotCount: diagnostics.schedulerActiveRunSlotCount,
|
|
heartbeatFreshTaskIds: diagnostics.heartbeatFreshTaskIds,
|
|
},
|
|
};
|
|
}
|
|
|
|
function checkOaPublisherDegradedVisible(): FixtureCheck {
|
|
const oaPublisher = { pending: 3, lastError: "fixture OA publish retry", lastPublishedAt: null };
|
|
const diagnostics = schedulerDiagnostics([], [], oaPublisher);
|
|
assertCondition(diagnostics.state === "degraded", "OA publisher pending/lastError must degrade diagnostics", diagnostics as unknown as Record<string, unknown>);
|
|
assertCondition(diagnostics.oaPublisher === oaPublisher, "OA publisher detail must remain visible", diagnostics as unknown as Record<string, unknown>);
|
|
return {
|
|
name: "code-queue:oa-publisher-degraded-visible",
|
|
ok: true,
|
|
detail: {
|
|
state: diagnostics.state,
|
|
degraded: diagnostics.degraded,
|
|
oaPublisher: diagnostics.oaPublisher as Record<string, unknown>,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function runCodeQueueLivenessFixtureChecks(only: string[] = []): { ok: boolean; checks: FixtureCheck[]; failures: Array<{ name: string; error: string }> } {
|
|
const selected = new Set(only.filter((name) => name.trim().length > 0));
|
|
const runners: Array<[CodeQueueLivenessCheckName, () => FixtureCheck]> = [
|
|
["code-queue:active-run-heartbeat-visible", checkActiveRunHeartbeatVisible],
|
|
["code-queue:trace-gap-not-stale", checkTraceGapNotStale],
|
|
["code-queue:heartbeat-threshold-normalized", checkHeartbeatThresholdNormalized],
|
|
["code-queue:stale-active-owner-expired", checkStaleActiveOwnerExpired],
|
|
["code-queue:stale-active-recovery-transition", checkStaleActiveRecoveryTransition],
|
|
["code-queue:control-plane-split-brain-diagnostics", checkControlPlaneSplitBrainDiagnostics],
|
|
["code-queue:oa-publisher-degraded-visible", checkOaPublisherDegradedVisible],
|
|
];
|
|
const checks: FixtureCheck[] = [];
|
|
const failures: Array<{ name: string; error: string }> = [];
|
|
for (const [name, run] of runners) {
|
|
if (selected.size > 0 && !selected.has(name)) continue;
|
|
try {
|
|
checks.push(run());
|
|
} catch (error) {
|
|
failures.push({ name, error: error instanceof Error ? error.message : String(error) });
|
|
checks.push({ name, ok: false, detail: { error: error instanceof Error ? error.message : String(error) } });
|
|
}
|
|
}
|
|
if (checks.length === 0) throw new Error(`no Code Queue liveness fixture checks matched: ${Array.from(selected).join(", ")}`);
|
|
return { ok: failures.length === 0, checks, failures };
|
|
}
|