From 2b5990670e23f4663b693e689ca3919b59723923 Mon Sep 17 00:00:00 2001 From: lagrangee Date: Fri, 2 Oct 2026 17:22:25 +0800 Subject: [PATCH] fix(matrix): seal rejected GitHub observations as blocked --- scripts/adaptive-live-matrix.ts | 23 ++++++-------- tests/live-scenario-admission.test.ts | 44 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/scripts/adaptive-live-matrix.ts b/scripts/adaptive-live-matrix.ts index ca453c1..7d05982 100644 --- a/scripts/adaptive-live-matrix.ts +++ b/scripts/adaptive-live-matrix.ts @@ -1165,20 +1165,15 @@ export const verifyAdaptiveTurnObservation = async (input: { pointer: string; expectedCodexCliVersion: string; github: boolean; -}) => - input.github - ? ( - await verifyGitHubJourneyObservation({ - workspaceRoot: input.workspaceRoot, - pointer: input.pointer, - expectedCodexCliVersion: input.expectedCodexCliVersion, - }) - ).base - : verifyLiveJourneyObservation({ - workspaceRoot: input.workspaceRoot, - pointer: input.pointer, - expectedCodexCliVersion: input.expectedCodexCliVersion, - }); +}) => { + const observation = await verifyLiveJourneyObservation(input); + // Mechanical failures retain only synthetic evidence, even for a GitHub Scenario. + // Finalization restricts these verified rejection records to a blocked verdict. + if (!input.github || observation.evidenceRejection?.reason === "mechanical-failure") { + return observation; + } + return (await verifyGitHubJourneyObservation(input)).base; +}; export const finalizeAdaptiveScenario = async (input: { generationPath: string; diff --git a/tests/live-scenario-admission.test.ts b/tests/live-scenario-admission.test.ts index 49e802d..6d6adae 100644 --- a/tests/live-scenario-admission.test.ts +++ b/tests/live-scenario-admission.test.ts @@ -1207,6 +1207,50 @@ exit 0 await expect( startAdaptiveScenario({ generationPath, scenarioId: "test-one" }), ).resolves.toMatchObject({ evidenceOutcome: "rejected", turn: 1 }); + await expect( + verifyAdaptiveTurnObservation({ + workspaceRoot: join(result.workspaceRoot, "scenarios/test-one"), + pointer: "observations/turn-01.json", + expectedCodexCliVersion: "codex-fixture 1", + github: true, + }), + ).resolves.toMatchObject({ + terminalBoundary: "turn.failed", + evidenceRejection: { reason: "mechanical-failure" }, + eventCounts: { "durable-evidence-rejected": 1 }, + }); + const eventsPath = join(result.workspaceRoot, "scenarios/test-one/events/turn-01.jsonl"); + const sealedEvents = await readFile(eventsPath, "utf8"); + await writeFile(eventsPath, `${sealedEvents}\n`); + await expect( + verifyAdaptiveTurnObservation({ + workspaceRoot: join(result.workspaceRoot, "scenarios/test-one"), + pointer: "observations/turn-01.json", + expectedCodexCliVersion: "codex-fixture 1", + github: true, + }), + ).rejects.toThrow("evidence digest mismatch"); + await writeFile(eventsPath, sealedEvents); + const rejectedVerdictPath = join(fixture.root, "rejected-verdict.json"); + for (const outcome of ["pass", "fail"] as const) { + await writeFile( + rejectedVerdictPath, + JSON.stringify({ + outcome, + ...(outcome === "fail" ? { failureCategory: "test-system" } : {}), + rationale: "Rejected evidence cannot support a semantic result.", + }), + ); + await expect( + finalizeAdaptiveScenario({ + generationPath, + scenarioId: "test-one", + verdictPath: rejectedVerdictPath, + }), + ).rejects.toThrow( + outcome === "pass" ? "clean or safely resumed" : "must be finalized as blocked", + ); + } await expect( finalizeAdaptiveScenario({ generationPath, scenarioId: "test-one", verdictPath }), ).resolves.toMatchObject({ outcome: "blocked" });