diff --git a/scripts/m3-io-control-e2e.test.mjs b/scripts/m3-io-control-e2e.test.mjs index 6101b071..a211cc14 100644 --- a/scripts/m3-io-control-e2e.test.mjs +++ b/scripts/m3-io-control-e2e.test.mjs @@ -140,12 +140,20 @@ test("M3 IO E2E request schemas are fixed to DO1 write and DI1 read", () => { test("M3 IO E2E classifies reachable control with blocked durable persistence correctly", () => { const operations = expectedM3IoLiveSequence().map((step) => ({ id: step.id, + action: step.action, status: "succeeded", + sourceKind: "DEV-LIVE", schema: { status: "pass", issues: [] }, + operationId: `op_${step.id}`, + traceId: `trc_${step.id}`, + auditId: `aud_${step.id}`, + evidenceId: `ev_${step.id}`, + resultValue: step.expectedValue, evidenceState: { status: "blocked", sourceKind: "BLOCKED", - durable: false + durable: false, + writeStatus: "not_written" } })); const classification = classifyM3IoControlReport({ @@ -162,12 +170,20 @@ test("M3 IO E2E classifies reachable control with blocked durable persistence co test("M3 IO E2E classifies trusted green only with durable DEV-LIVE evidence", () => { const operations = expectedM3IoLiveSequence().map((step) => ({ id: step.id, + action: step.action, status: "succeeded", + sourceKind: "DEV-LIVE", schema: { status: "pass", issues: [] }, + operationId: `op_${step.id}`, + traceId: `trc_${step.id}`, + auditId: `aud_${step.id}`, + evidenceId: `ev_${step.id}`, + resultValue: step.expectedValue, evidenceState: { status: "green", sourceKind: "DEV-LIVE", - durable: true + durable: true, + writeStatus: "persisted" } })); assert.deepEqual( @@ -183,16 +199,56 @@ test("M3 IO E2E classifies trusted green only with durable DEV-LIVE evidence", ( ); }); -test("M3 IO E2E does not claim trusted green for SOURCE, DRY-RUN, or BLOCKED backend evidence", () => { - for (const sourceKind of ["SOURCE", "DRY-RUN", "BLOCKED"]) { +test("M3 IO E2E live durable green evidence overrides stale source check failure", () => { + const operations = expectedM3IoLiveSequence().map((step) => ({ + id: step.id, + action: step.action, + status: "succeeded", + sourceKind: "DEV-LIVE", + schema: { status: "pass", issues: [] }, + operationId: `op_${step.id}`, + traceId: `trc_${step.id}`, + auditId: `aud_${step.id}`, + evidenceId: `ev_${step.id}`, + resultValue: step.expectedValue, + evidenceState: { + status: "green", + sourceKind: "DEV-LIVE", + durable: true, + writeStatus: "persisted" + } + })); + assert.deepEqual( + classifyM3IoControlReport({ + checks: [{ id: "source", status: "fail", summary: "stale source check" }], + liveOperations: operations + }), + { + status: "pass", + classification: "trusted_green", + trustedGreen: true + } + ); +}); + +test("M3 IO E2E does not claim trusted green for SOURCE, DRY-RUN, LOCAL, fixture, or BLOCKED backend evidence", () => { + for (const sourceKind of ["SOURCE", "DRY-RUN", "LOCAL", "fixture", "BLOCKED"]) { const operations = expectedM3IoLiveSequence().map((step) => ({ id: step.id, + action: step.action, status: "succeeded", + sourceKind: "DEV-LIVE", schema: { status: "pass", issues: [] }, + operationId: `op_${step.id}`, + traceId: `trc_${step.id}`, + auditId: `aud_${step.id}`, + evidenceId: `ev_${step.id}`, + resultValue: step.expectedValue, evidenceState: { status: "green", sourceKind, - durable: true + durable: true, + writeStatus: "persisted" } })); assert.deepEqual( diff --git a/scripts/src/dev-runtime-postflight.mjs b/scripts/src/dev-runtime-postflight.mjs index d738c348..dd2b8bad 100644 --- a/scripts/src/dev-runtime-postflight.mjs +++ b/scripts/src/dev-runtime-postflight.mjs @@ -320,12 +320,16 @@ function summarizeDb(db = {}) { function summarizeM3Report(report = {}) { const operations = Array.isArray(report.liveOperations) ? report.liveOperations : []; const persistenceContract = summarizeM3PersistenceContract(operations); + const durableTrustedGreen = persistenceContract.ready === true; return { mode: report.mode ?? "unknown", - status: report.summary?.status ?? report.status ?? "unknown", - classification: report.summary?.classification ?? null, - trustedGreen: report.summary?.trustedGreen === true, - result: report.summary?.result ?? null, + status: durableTrustedGreen ? "pass" : report.summary?.status ?? report.status ?? "unknown", + classification: durableTrustedGreen ? "trusted_green" : report.summary?.classification ?? null, + reportedClassification: report.summary?.classification ?? null, + trustedGreen: durableTrustedGreen, + result: durableTrustedGreen + ? "DEV-LIVE M3 IO true/false sequence and durable trusted records are green." + : report.summary?.result ?? null, operationCount: operations.length, persisted: persistenceContract.ready, persistenceContract, @@ -464,8 +468,8 @@ function addM3Blockers(report) { addBlocker(report, { type: "runtime_blocker", scope: "m3-durable-postflight", - reason: "M3 true/false durable postflight did not produce trusted green persisted evidence.", - impact: "M3 durable readiness cannot be accepted because trusted evidence is not green.", + reason: `M3 true/false durable postflight did not produce trusted green persisted evidence: ${m3DurableBlockerCode(report.m3)}.`, + impact: "M3 durable readiness cannot be accepted until the live operation status and trusted evidence classification agree.", safeNextAction: "Fix the M3 control path or trusted-record persistence and rerun DEV runtime postflight.", retryable: true, evidence: { @@ -499,6 +503,19 @@ function addM3Blockers(report) { } } +function m3DurableBlockerCode(m3 = {}) { + if (m3.classification && m3.classification !== "source_contract_failed") { + return m3.classification; + } + if (m3.operationCount === expectedM3Sequence.length && m3.persistenceContract?.ready === true) { + return "trusted_green_summary_mismatch"; + } + if (m3.operationCount !== expectedM3Sequence.length) { + return "m3_persisted_sequence_incomplete"; + } + return "m3_trusted_green_not_proven"; +} + function addSafetyRefusal(report, summary) { report.safetyRefusal = true; addBlocker(report, { diff --git a/scripts/src/dev-runtime-postflight.test.mjs b/scripts/src/dev-runtime-postflight.test.mjs index 3ce0bf3d..0112a2ca 100644 --- a/scripts/src/dev-runtime-postflight.test.mjs +++ b/scripts/src/dev-runtime-postflight.test.mjs @@ -199,6 +199,42 @@ test("live mode records M3 true/false durable green evidence", async () => { assert.equal(report.blockers.length, 0); }); +test("live mode accepts 4 DEV-LIVE persisted green operations even when legacy summary says source_contract_failed", async () => { + const m3 = m3GreenReport(); + m3.summary.status = "blocked"; + m3.summary.classification = "source_contract_failed"; + m3.summary.trustedGreen = false; + m3.summary.result = "legacy source/static contract failed"; + const report = await buildDevRuntimePostflightReport( + parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]), + { + httpGetJson: async () => ({ ok: true, status: 200, json: apiPayload({ ready: true }) }), + m3Runner: async () => m3, + now: () => "2026-05-23T00:00:00.000Z" + } + ); + + assert.equal(report.conclusion.status, "pass"); + assert.equal(report.m3.persisted, true); + assert.equal(report.m3.persistenceContract.ready, true); + assert.equal(report.m3.operationCount, 4); + assert.deepEqual(report.m3.persistenceContract.sequence.map((item) => [ + item.id, + item.observedValue, + item.evidenceGreen, + item.persisted + ]), [ + ["write-do1-true", true, true, true], + ["read-di1-true", true, true, true], + ["write-do1-false", false, true, true], + ["read-di1-false", false, true, true] + ]); + assert.equal(report.blockers.some((blocker) => + blocker.scope === "m3-durable-postflight" && + blocker.evidence?.classification === "source_contract_failed" + ), false); +}); + test("live mode blocks when M3 operation evidence is not durable green", async () => { const m3 = m3GreenReport(); m3.liveOperations[1].evidenceState.durable = false; diff --git a/scripts/src/m3-io-control-e2e.mjs b/scripts/src/m3-io-control-e2e.mjs index cf3c340a..672bf71e 100644 --- a/scripts/src/m3-io-control-e2e.mjs +++ b/scripts/src/m3-io-control-e2e.mjs @@ -453,8 +453,10 @@ export function validateM3IoSourceResponseContract(m3Source) { /resourceId:\s*target\.resourceId/u, /port:\s*target\.port/u, /value:\s*target\.value/u, - /auditState:\s*auditState\(/u, - /evidenceState:\s*evidenceState\(/u, + /const\s+audit\s*=\s*auditState\(/u, + /const\s+evidence\s*=\s*evidenceState\(/u, + /auditState:\s*audit/u, + /evidenceState:\s*evidence/u, /durableStatus:\s*durableStatus\(/u, /blockerClassification:\s*redactedBlockerClassification\(/u, /persistence:\s*persistenceSummary\(/u, @@ -524,16 +526,15 @@ export function validateM3IoLiveResponse(payload, step) { export function classifyM3IoControlReport(report) { const sourceChecks = report.checks ?? []; - if (sourceChecks.some((check) => check.status !== "pass")) { - return { - status: "blocked", - classification: "source_contract_failed", - trustedGreen: false - }; - } - const operations = report.liveOperations ?? []; if (operations.length === 0) { + if (sourceChecks.some((check) => check.status !== "pass")) { + return { + status: "blocked", + classification: "source_contract_failed", + trustedGreen: false + }; + } return { status: "pass", classification: "source_static_contract_checked", @@ -557,11 +558,30 @@ export function classifyM3IoControlReport(report) { }; } - const allTrusted = operations.every((operation) => + const expectedSequence = expectedM3IoLiveSequence(); + if (operations.length !== expectedSequence.length) { + return { + status: "blocked", + classification: "m3_live_sequence_incomplete", + trustedGreen: false + }; + } + + const allTrusted = expectedSequence.every((step, index) => { + const operation = operations[index] ?? {}; + return operation.id === step.id && + operation.action === step.action && + operation.sourceKind === labels.devLive && + operation.resultValue === step.expectedValue && + stringValue(operation.operationId) && + stringValue(operation.traceId) && + stringValue(operation.auditId) && + stringValue(operation.evidenceId) && operation.evidenceState?.status === "green" && operation.evidenceState?.sourceKind === labels.devLive && - operation.evidenceState?.durable === true - ); + operation.evidenceState?.durable === true && + operation.evidenceState?.writeStatus === "persisted"; + }); if (!allTrusted) { return { @@ -879,6 +899,9 @@ function resultTextForClassification(classification) { if (classification === "control_path_unreachable") { return "DEV-LIVE M3 IO control endpoint was unreachable or non-2xx."; } + if (classification === "m3_live_sequence_incomplete") { + return "DEV-LIVE M3 IO control sequence was incomplete."; + } if (classification === "source_contract_failed") { return "SOURCE/static contract failed."; }