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 6a7c50a..34ee6ab 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 @@ -105,3 +105,19 @@ the completed-only persisted turn cannot by itself prove whether the second callback result was durably completed. The next diagnostic adds only one aggregate—completed tool actions—to distinguish callback rejection from an adapter-finalization failure. It does not expose tool arguments or outputs. + +## Attempt 13 result and correction + +Attempt 13 ran the merged completed-action projection on `3609ce3` under the +same live conditions. It reported the same stage and reconciliation reason, +but distinguished 5 selected actions from 4 completed actions. The first +reviewer read callback completed; the second finding callback was selected and +rejected before completion. Cleanup again completed exactly. + +The live finding tool required the reviewer to copy the exact path, +counterexample, and a digest that the trusted coordinator can deterministically +derive from the first two values. Copying that digest from tool output is a +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. diff --git a/docs/09-reviews/2026-09-02-mainline-checkpoint.md b/docs/09-reviews/2026-09-02-mainline-checkpoint.md index 67883a8..c78ac49 100644 --- a/docs/09-reviews/2026-09-02-mainline-checkpoint.md +++ b/docs/09-reviews/2026-09-02-mainline-checkpoint.md @@ -75,6 +75,13 @@ not distinguish a rejected second callback from post-callback adapter failure, the projection now also includes the bounded completed-action aggregate before the next run. +Attempt 13 then reported 5 selected versus 4 completed actions, proving that +the second reviewer callback—not adapter finalization—was the failing boundary. +The correction replaces the redundant requirement for the model to copy a +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. + ## 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 d0b0d4b..7d9999b 100644 --- a/src/validation/coordinator-driven-no-plan-scenario.mjs +++ b/src/validation/coordinator-driven-no-plan-scenario.mjs @@ -821,9 +821,13 @@ export async function runCoordinatorDrivenNoPlanScenario({ required: Object.freeze([...Object.keys(staticArguments), "commitSha"]), }), }); + const realReviewFindingDigest = independentGitFindingDigest({ + resourcePath: REAL_EFFECT_RESOURCE, + counterexample: REAL_EFFECT_IMPLEMENTATION.trim(), + }); const realReviewPublish = Object.freeze({ ...TOOLS.review, - description: `${TOOLS.review.description} Use only the artifact content returned by the preceding read tool. Copy its candidateFindingDigest and report the exact resourcePath, counterexample, and a bounded reason.`, + description: `${TOOLS.review.description} Use only the artifact content returned by the preceding read tool. Report its exact resourcePath and counterexample with a bounded reason; use the coordinator-bound findingDigest constant from this schema.`, inputSchema: Object.freeze({ type: "object", additionalProperties: false, properties: Object.freeze({ @@ -832,7 +836,7 @@ export async function runCoordinatorDrivenNoPlanScenario({ resourcePath: Object.freeze({ type: "string", minLength: 1, maxLength: 200 }), counterexample: Object.freeze({ type: "string", minLength: 1, maxLength: 256 }), reason: Object.freeze({ type: "string", minLength: 1, maxLength: 1000 }), - findingDigest: Object.freeze({ type: "string", pattern: "^sha256:[a-f0-9]{64}$" }), + findingDigest: Object.freeze({ const: realReviewFindingDigest }), }), required: Object.freeze([ "sourceEventId", "event", "resourcePath", "counterexample", "reason", @@ -1086,7 +1090,7 @@ export async function runCoordinatorDrivenNoPlanScenario({ counterexample: injectRealReviewFindingTamper ? "WRONG_COUNTEREXAMPLE" : read.content.trim(), reason: "The detached artifact contains the exact blocking counterexample.", - findingDigest: read.candidateFindingDigest, + findingDigest: realReviewFindingDigest, }; } return value; @@ -1135,7 +1139,7 @@ export async function runCoordinatorDrivenNoPlanScenario({ "receiver-decision": "receiver-decision", "r-review": "admitted-tool", }, instructions: realEffects - ? "Review only coordinator-admitted context. In the admitted review turn, call every offered tool exactly once and in order: read the detached-checkout artifact, then report the exact counterexample found in the returned content and copy candidateFindingDigest. Do not stop after the read result." + ? "Review only coordinator-admitted context. In the admitted review turn, call every offered tool exactly once and in order: read the detached-checkout artifact, then report the exact resourcePath and counterexample found in the returned content. Use the coordinator-bound findingDigest constant from the report tool schema; do not derive or copy it from tool output. Do not stop after the read result." : "Review only coordinator-admitted context.", scenarioId: "coordinator_driven_no_plan", }); @@ -1333,7 +1337,6 @@ export async function runCoordinatorDrivenNoPlanScenario({ resourcePath: REAL_EFFECT_RESOURCE, content, commitSha: checkout.subjectSha, - candidateFindingDigest: independentGitFindingDigest(candidate), }; } if (realEffects && selectedTool === TOOLS.review.name) { diff --git a/test/coordinator-driven-no-plan-scenario.test.mjs b/test/coordinator-driven-no-plan-scenario.test.mjs index 90b07b9..22a5221 100644 --- a/test/coordinator-driven-no-plan-scenario.test.mjs +++ b/test/coordinator-driven-no-plan-scenario.test.mjs @@ -231,6 +231,12 @@ test("real-effects path keeps reviewer context blind and binds model finding", a assert.equal(result.verification.signatureVerified, true); assert.equal(result.verification.resultDigestBound, true); assert.equal(result.runtime.modelSelectedToolCalls, 14); + const liveReviewSchema = result.routeHandlerConfigs[0].businessTools[1].inputSchema; + assert.match(liveReviewSchema.properties.findingDigest.const, /^sha256:[a-f0-9]{64}$/u); + assert.deepEqual(liveReviewSchema.required, [ + "sourceEventId", "event", "resourcePath", "counterexample", "reason", + "findingDigest", + ]); assert.deepEqual(result.nativeTurnManifest.records.map(({ actions }) => actions.map(({ tool }) => tool)), [ ["threadmesh_commit_candidate", "threadmesh_publish_artifact"],