diff --git a/docs/09-reviews/2026-09-02-m5-2-real-effects-live-attempt.md b/docs/09-reviews/2026-09-02-m5-2-real-effects-live-attempt.md index 34ee6ab..fc7ae5a 100644 --- a/docs/09-reviews/2026-09-02-m5-2-real-effects-live-attempt.md +++ b/docs/09-reviews/2026-09-02-m5-2-real-effects-live-attempt.md @@ -121,3 +121,17 @@ brittle model obligation. The correction makes it a coordinator-bound schema constant instead, while retaining exact path/counterexample comparison against the detached checkout, bounded reason validation, wrong-content negative coverage, and all downstream lifecycle/Git/verifier bindings. + +## Attempts 14 and 15 + +Attempt 14 stopped before business execution with a bootstrap marker mismatch: +3 created roles were deleted and absence-confirmed, all business counts stayed +zero, and cleanup completed. One bounded retry was allowed because the reviewer +fix was not exercised and no business effect occurred. + +Attempt 15 crossed bootstrap but again reported 5 selected and 4 completed +actions at the reviewer callback. This disproves the digest-copy hypothesis: +another exact finding condition is being rejected. The compound validator is +therefore split into fixed source, resource, counterexample, checkout, reason, +and digest branches. The public failure projection may expose only one matching +boundary enum; it still cannot expose model arguments or outputs. diff --git a/docs/09-reviews/2026-09-02-mainline-checkpoint.md b/docs/09-reviews/2026-09-02-mainline-checkpoint.md index c78ac49..72c97a9 100644 --- a/docs/09-reviews/2026-09-02-mainline-checkpoint.md +++ b/docs/09-reviews/2026-09-02-mainline-checkpoint.md @@ -82,6 +82,13 @@ coordinator-derivable digest from tool output with a coordinator-bound schema constant. Exact path and counterexample matching against the detached checkout, negative tamper coverage, and the downstream verifier chain remain mandatory. +Attempt 14 did not exercise the change because role bootstrap failed with zero +business records and exact cleanup. Attempt 15 reached the reviewer but retained +the 5-selected/4-completed result, disproving the digest-copy hypothesis. The +compound callback validator is now split into six fixed, publicly safe boundary +enums so the next failure identifies the exact rejected condition without +publishing any model argument or output. + ## Ordered next gates 1. Run the merged real-effects Codex path once with the new partial-stage diff --git a/src/validation/coordinator-driven-no-plan-scenario.mjs b/src/validation/coordinator-driven-no-plan-scenario.mjs index 7d9999b..200128f 100644 --- a/src/validation/coordinator-driven-no-plan-scenario.mjs +++ b/src/validation/coordinator-driven-no-plan-scenario.mjs @@ -77,6 +77,14 @@ export const COORDINATOR_FAILURE_RECONCILIATION_REASONS = Object.freeze([ "codex-native-turn-still-in-progress", "codex-native-turn-started-id-mismatch", ]); +export const COORDINATOR_FAILURE_BOUNDARIES = Object.freeze({ + threadmesh_real_effect_review_source_invalid: "review-source", + threadmesh_real_effect_review_resource_invalid: "review-resource", + threadmesh_real_effect_review_counterexample_invalid: "review-counterexample", + threadmesh_real_effect_review_checkout_invalid: "review-checkout", + threadmesh_real_effect_review_reason_invalid: "review-reason", + threadmesh_real_effect_review_digest_invalid: "review-digest", +}); export function deriveCoordinatorDrivenFailureStage(counts) { const durableStages = Math.min( @@ -110,6 +118,7 @@ export function captureCoordinatorDrivenFailureProgress(database, error) { ? { state: "ambiguous", reasonCode, + boundary: COORDINATOR_FAILURE_BOUNDARIES[error?.originCode] ?? null, } : null; return Object.freeze({ @@ -1349,15 +1358,25 @@ export async function runCoordinatorDrivenNoPlanScenario({ counterexample: value?.counterexample, }; const candidateDigest = independentGitFindingDigest(candidate); - if ( - value?.sourceEventId !== artifactEvent.messageId || - candidate.resourcePath !== REAL_EFFECT_RESOURCE || - candidate.counterexample !== content.trim() || - !content.includes(candidate.counterexample) || - checkout.subjectSha !== implementationSha || - typeof value?.reason !== "string" || value.reason.length < 1 || - value?.findingDigest !== candidateDigest - ) throw scenarioError("threadmesh_real_effect_review_finding_not_reproduced"); + if (value?.sourceEventId !== artifactEvent.messageId) { + throw scenarioError("threadmesh_real_effect_review_source_invalid"); + } + if (candidate.resourcePath !== REAL_EFFECT_RESOURCE) { + throw scenarioError("threadmesh_real_effect_review_resource_invalid"); + } + if (candidate.counterexample !== content.trim() || + !content.includes(candidate.counterexample)) { + throw scenarioError("threadmesh_real_effect_review_counterexample_invalid"); + } + if (checkout.subjectSha !== implementationSha) { + throw scenarioError("threadmesh_real_effect_review_checkout_invalid"); + } + if (typeof value?.reason !== "string" || value.reason.length < 1) { + throw scenarioError("threadmesh_real_effect_review_reason_invalid"); + } + if (value?.findingDigest !== candidateDigest) { + throw scenarioError("threadmesh_real_effect_review_digest_invalid"); + } finding = Object.freeze(candidate); findingDigest = candidateDigest; payloads["review-failed"].findingDigest = findingDigest; diff --git a/src/validation/m5-2-event-pump-codex-gate.mjs b/src/validation/m5-2-event-pump-codex-gate.mjs index cf6fdb0..5a3e1b4 100644 --- a/src/validation/m5-2-event-pump-codex-gate.mjs +++ b/src/validation/m5-2-event-pump-codex-gate.mjs @@ -2,6 +2,7 @@ import path from "node:path"; import { canonicalJson, sha256Digest } from "../canonical-json.mjs"; import { + COORDINATOR_FAILURE_BOUNDARIES, COORDINATOR_FAILURE_RECONCILIATION_REASONS, deriveCoordinatorDrivenFailureStage, runCoordinatorDrivenNoPlanScenario, @@ -203,17 +204,20 @@ export function projectM52EventPumpFailureProgress(value) { let reconciliation = null; if (value.reconciliation !== null) { const source = value.reconciliation; - const reconciliationKeys = ["state", "reasonCode"]; + const reconciliationKeys = ["state", "reasonCode", "boundary"]; if (!source || typeof source !== "object" || Array.isArray(source) || canonicalJson(Object.keys(source).sort()) !== canonicalJson(reconciliationKeys.sort()) || source.state !== "ambiguous" || - !COORDINATOR_FAILURE_RECONCILIATION_REASONS.includes(source.reasonCode)) { + !COORDINATOR_FAILURE_RECONCILIATION_REASONS.includes(source.reasonCode) || + (source.boundary !== null && + !Object.values(COORDINATOR_FAILURE_BOUNDARIES).includes(source.boundary))) { return null; } reconciliation = Object.freeze({ state: source.state, reasonCode: source.reasonCode, + boundary: source.boundary, }); } return Object.freeze({ diff --git a/test/coordinator-driven-no-plan-scenario.test.mjs b/test/coordinator-driven-no-plan-scenario.test.mjs index 22a5221..3259f14 100644 --- a/test/coordinator-driven-no-plan-scenario.test.mjs +++ b/test/coordinator-driven-no-plan-scenario.test.mjs @@ -298,7 +298,7 @@ test("real-effects path rejects a model-reported finding not present in checkout (error) => { assert.equal(error?.code, "threadmesh_codex_live_context_terminal_reconciled"); assert.equal(error?.originCode, - "threadmesh_real_effect_review_finding_not_reproduced"); + "threadmesh_real_effect_review_counterexample_invalid"); assert.equal(error.cleanup?.complete, true); assert.equal(error.cleanup?.roles.length, 5); assert.equal(error.cleanup?.verifierServiceClosed, true); diff --git a/test/m5-2-event-pump-codex-gate.test.mjs b/test/m5-2-event-pump-codex-gate.test.mjs index 67f67c4..cc5aec2 100644 --- a/test/m5-2-event-pump-codex-gate.test.mjs +++ b/test/m5-2-event-pump-codex-gate.test.mjs @@ -435,6 +435,7 @@ test("failure progress projection exposes only bounded SQLite-derived stage data reconciliation: { state: "ambiguous", reasonCode: "codex-native-turn-completed-observation-only", + boundary: "review-counterexample", }, }; assert.deepEqual(projectM52EventPumpFailureProgress(source), source); @@ -447,6 +448,7 @@ test("failure progress projection exposes only bounded SQLite-derived stage data (value) => { value.stage = "review-published"; }, (value) => { value.reconciliation.reasonCode = "/private/raw/path"; }, (value) => { value.reconciliation.reasonCode = "codex-native-turn-secret-id"; }, + (value) => { value.reconciliation.boundary = "review-secret-id"; }, ]) { const tampered = structuredClone(source); mutate(tampered);