/users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:21:09.469Z","ordinal":8,"type":"response_item","payload":{"type":"message","id":"msg_01a02ec8-3b5d-7fe2-b784-9f5adbcb8c75","role":"user","content":[{"type":"input_text","text":"You are continuing the authorized NO-ENGINE hardening of the Sage4-vs-GNU3\nmatch producer.\n\nAnother lane owns post-match GNU4 analysis. Do not touch or run GNU4 analysis.\n\nCURRENT LOCAL HEAD\n\n6f24bd9ad470a0b7af1cc99c6692354f7d55047f\n\nPreserve the entire existing local recovery history and all failed-smoke\nevidence.\n\nDO NOT:\n\n- reset, rebase, amend, or squash existing commits;\n- discard local recovery commits;\n- pull/reset this branch back to remote HEAD;\n- push;\n- launch gnubg;\n- launch bgsage;\n- run real Sage/GNU analysis;\n- run a one-pair smoke;\n- run the 10-pair campaign;\n- run post-match GNU4;\n- perform Canonical writes;\n- modify the frozen campaign settings.\n\nThe campaign configuration SHA-256 must remain exactly:\n\nb5614f359236c78efd27aa48c544f807fee26619fce215565e1cbb930abfc2df\n\nAll ten deterministic pair IDs/seeds must remain unchanged.\n\nBefore editing run:\n\ngit status --short\ngit log --oneline -30\n\nThe worktree must start clean.\n\nGIT POLICY\n\nMake small coherent LOCAL commits.\n\nPrefer one commit per independent review finding and include the corresponding\nnegative regression tests in that commit.\n\nSuggested subjects:\n\n1. fix: derive SGF application identity from frozen GNU runtime\n2. fix: require state-connected publication decision history\n3. fix: make analysis journals publication authority\n4. fix: preserve checker dice seat identity at publication\n5. fix: require complete deterministic dice stream inventory\n6. fix: validate state-bearing GNU SGF properties\n\nDo not push.\n\n======================================================================\n1. DERIVE SGF AP VERSION FROM THE FROZEN GNU RUNTIME\n======================================================================\n\nCurrent defect:\n\nThe SGF parser requires:\n\nAP[GNU Backgammon:1.06.002]\n\nbut the frozen production GNU executable identifies as:\n\n1.08.003 20260710\n\nGNU emits its build VERSION in SGF AP, so normal frozen production output\nshould use GNU Backgammon:1.08.003 and is currently rejected.\n\nDo not simply replace one hard-coded version with another.\n\nDerive the accepted AP identity from the same verified frozen GNU runtime\nidentity already established by environment/preflight authority.\n\nRequirements:\n\n- application must be GNU Backgammon;\n- version must match the canonical frozen runtime version token;\n- current frozen runtime should accept 1.08.003;\n- 1.06.002 must fail for this runtime;\n- wrong application fails;\n- wrong version fails;\n- missing/malformed AP fails closed;\n- campaign.json remains unchanged.\n\nUse one source of runtime-version truth.\n\nAdd a realistic/golden GNU 1.08.003 SGF fixture rather than continuing to\ngenerate all fixtures with the obsolete AP string.\n\n======================================================================\n2. REQUIRE A STATE-CONNECTED PUBLICATION DECISION TRANSCRIPT\n======================================================================\n\nCurrent defect:\n\nPublication verifies individual pre/post GNUIDs but does not require the\ncomplete decision transcript to form one connected state history.\n\nRequire decisions.jsonl to be an exact ordered state-machine transcript.\n\nFor adjacent records require the preceding post-state to equal the following\npre-state unless an explicitly represented and validated automatic transition\noccurs.\n\nAny automatic transition must itself have durable evidence. Do not silently\nallow discontinuities.\n\nAt publication:\n\n- decode relevant pre/post GNUIDs;\n- re-run the same command-specific transition semantics used during live\n execution;\n- reuse/factor common pure validators rather than maintaining a weaker\n publication-specific policy.\n\nValidate exact semantics for:\n\n- roll;\n- checker move;\n- double;\n- take;\n- pass/drop;\n- resignation;\n- normal/bearoff completion;\n- automatic next roll;\n- automatic next-game opening.\n\nCheck:\n\n- game ordinal;\n- decision ordinal;\n- command type;\n- acting physical seat;\n- acting engine;\n- board state;\n- player on roll;\n- decision player;\n- score;\n- cube value;\n- cube owner;\n- pending action;\n- offerer/responder;\n- resignation state;\n- terminal semantics.\n\nROLL DECISIONS MUST NOT BE OPTIONAL.\n\nRequire the exact ordered roll/checker/cube/terminal command sequence.\n\nReject at publication:\n\n- disconnected adjacent GNUIDs;\n- swapped decisions with rewritten ordinals;\n- fabricated but individually plausible GNUIDs;\n- missing roll record;\n- extra roll record;\n- roll moved to the wrong point;\n- wrong checker state;\n- wrong cube state;\n- wrong terminal state;\n- wrong automatic opening;\n- wrong seat/engine.\n\nReplace synthetic fixtures that use unrelated adjacent pre/post GNUIDs with\ngenuinely state-connected fixtures.\n\n======================================================================\n3. MAKE RAW ANALYSIS JOURNALS PUBLICATION AUTHORITY\n======================================================================\n\nProduction creates:\n\nanalysis_requests.jsonl\nanalysis_results.jsonl\n\nand corresponding Engine Kit result evidence.\n\nPublication must REQUIRE and independently validate these artifacts.\n\nRequire:\n\n- both journals exist when analysis occurred;\n- manifest paths exist and are correct;\n- manifest SHA-256 values match actual files;\n- match_manifest output hashes are independently rechecked;\n- journals are structurally valid;\n- records are complete and monotonically ordered.\n\nReconcile one-to-one and in exact order:\n\nanalysis request\n↔ raw analysis result\n↔ engine_kit_result forensic evidence\n↔ decision record\n\nValidate:\n\n- pair ID;\n- match side;\n- game ordinal;\n- request/decision ordinal;\n- engine;\n- physical seat;\n- decision type;\n- GNUID/requested position;\n- dice/context where applicable;\n- configured depth;\n- actual depth;\n- result/candidate semantics;\n- raw_source evidence where represented.\n\nReject:\n\n- missing request/result;\n- duplicate;\n- reordering;\n- wrong decision association;\n- wrong engine/seat/GNUID/type;\n- malformed result;\n- wrong configured depth;\n- invalid actual depth;\n- cross-side/game/pair result;\n- manifest/file hash mismatch.\n\nIMPORTANT DEPTH POLICY:\n\nDo not alter frozen configured depths.\n\nSage checker/cube remains 4ply/3ply.\nGNU checker/cube remains 3ply/2ply.\n\nReuse EXACTLY the already validated live depth policy.\n\nIn particular, do not incorrectly require GNU checker actual ply to equal\nconfigured 3ply if the existing live policy permits a shallower nonnegative\nactual checker depth.\n\nGNU cube actual-depth policy must remain consistent with the existing live\nvalidator.\n\nA valid publication fixture may no longer omit analysis journals.\n\n======================================================================\n4. PRESERVE PHYSICAL-SEAT AND ENGINE IDENTITY IN CHECKER-DICE AUTHORITY\n======================================================================\n\nCurrent defect:\n\nFinal checker/native reconciliation can reduce dice to just:\n\n[die1, die2]\n\nafter separately validating the claimed stream.\n\nThat loses physical-seat and engine identity and permits a self-consistent\ncross-seat substitution if two deterministic streams happen to produce the\nsame dice values.\n\nFor every checker action reconcile an ordered semantic identity such as:\n\n(\n game ordinal,\n action ordinal,\n physical seat,\n engine,\n die1,\n die2,\n stream identity,\n roll index\n)\n\nsimultaneously against:\n\n- decision record;\n- deterministic dice record;\n- native checker action;\n- frozen side A/B physical-seat mapping.\n\nWhere native evidence does not encode engine directly, derive the ONLY legal\nengine from the physical seat and mirrored side and verify all other evidence\nagrees.\n\nAdd an explicit collision regression:\n\nUse equal dice values from two different streams and prove publication still\nrejects substitution of the wrong:\n\n- physical seat;\n- engine;\n- stream.\n\nAlso reject cross-side substitutions.\n\n======================================================================\n5. REQUIRE THE COMPLETE DETERMINISTIC DICE STREAM INVENTORY\n======================================================================\n\nPublication must authenticate the entire frozen input envelope, not only\nstreams consumed by games that happened to be played.\n\nFrozen authority expects:\n\n25 game slots × 2 physical seats = 50 streams\n\nDerive this from the frozen dice protocol where possible rather than adding a\nsecond unrelated magic constant.\n\nRequire exactly:\n\n- every expected stream manifest entry;\n- every expected stream CSV file;\n- no missing stream;\n- no extra stream;\n- no duplicate stream identity.\n\nFor ALL streams, including unused ones:\n\n- regenerate expected stream authority from pair seed;\n- verify stream ID/namespace;\n- verify game slot;\n- verify physical seat;\n- verify side namespace where applicable;\n- verify complete CSV content;\n- verify file SHA-256;\n- verify deterministic stream hash.\n\nReject:\n\n- missing active CSV;\n- missing inactive CSV;\n- missing manifest entry;\n- extra stream;\n- forged inactive hash;\n- modified inactive CSV;\n- wrong slot;\n- wrong seat;\n- wrong namespace;\n- duplicate stream ID.\n\n======================================================================\n6. VALIDATE ALL STATE-BEARING GNU SGF PROPERTIES\n======================================================================\n\nCurrent defect:\n\nSGF parsing sees node properties that affect backgammon state, but\ncanonicalization currently ignores some of them.\n\nFor example, adding/changing:\n\nCV[2]\n\ncan leave the same canonical game despite changing cube state.\n\nInspect the frozen GNU 1.08.003 SGF grammar/property definitions.\n\nEnumerate every state-bearing property GNU can emit for normal match records.\n\nFor each such property:\n\nEITHER\n\nA. canonicalize and reconcile its semantic state;\n\nOR\n\nB. fail closed when it appears in a context the validator does not explicitly\nsupport.\n\nDo not silently discard state-bearing properties.\n\nCover properties capable of affecting:\n\n- cube value;\n- cube owner/position;\n- dice;\n- player to move;\n- checker/setup position;\n- game/match state;\n- resignation semantics;\n- cube semantics;\n- any setup state capable of changing the position.\n\nExplicitly classify harmless descriptive metadata separately so safe metadata\ndoes not create false mismatches.\n\nRequired regressions include:\n\n- CV changes 1 → 2;\n- cube owner changes;\n- player-to-move changes;\n- dice property changes;\n- checker setup changes;\n- unexpected setup property mid-game;\n- inconsistent setup properties;\n- state-bearing SGF inconsistent with text/decision evidence;\n- the exact CV[2] counterexample from review.\n\n======================================================================\nPRESERVE ALL PREVIOUSLY CLEARED PROTECTIONS\n======================================================================\n\nDo not regress:\n\n- complete SGF/text action ordering;\n- structural native-game completeness;\n- exact terminal kind/level semantics;\n- actor seat/engine terminal validation;\n- immediate subsequent-opening validation;\n- artifact_root → campaign_root → pairs → staging → final durability;\n- immutable post-rename parent fsync;\n- runtime-root analysis-journal durability;\n- decisions.jsonl crash durability;\n- failure-bundle durability;\n- primary-error preservation;\n- complete opening semantics;\n- checker/roll/double/take/pass/resignation postconditions;\n- deterministic physical-seat dice streams;\n- automatic/manual dice reconciliation;\n- exact multi-game player mappings;\n- raw Engine Kit result/raw_source forensics;\n- malformed primitive-result preservation;\n- GNU manual RNG verification;\n- GNU command errors fail closed;\n- isolated GNU HOME;\n- native-path safety;\n- failure/CLI/interrupt semantics;\n- environment integrity;\n- authoritative published-pair reconciliation;\n- failed-smoke evidence.\n\n======================================================================\nNO-ENGINE TEST / IDENTITY GATE\n======================================================================\n\nRun the complete no-engine campaign test suite.\n\nDo NOT launch engines.\n\nIf necessary use only an existing repo-local/scratch-local test venv. Do not\nchange project dependency metadata simply to run pytest.\n\nAfter implementation prove:\n\ncampaign SHA-256:\n\nb5614f359236c78efd27aa48c544f807fee26619fce215565e1cbb930abfc2df\n\nAlso prove:\n\n- campaign.json unchanged;\n- config.py frozen semantics unchanged;\n- identity.py unchanged;\n- all ten deterministic pair IDs and seeds unchanged;\n- Sage checker/cube still 4ply/3ply;\n- GNU checker/cube still 3ply/2ply;\n- normal-v1 unchanged;\n- match length still 7;\n- threads still 1/1;\n- mirrored A/B mapping unchanged;\n- pgrep -x gnubg finds no process;\n- pgrep -x bgsage finds no process;\n- worktree is clean;\n- nothing was pushed.\n\nShow:\n\ngit log --oneline --reverse 6f24bd9..HEAD\n\nFINAL RESPONSE\n\nReport:\n\n- starting HEAD;\n- final HEAD;\n- every new commit SHA and subject;\n- files changed in each commit;\n- remediation for all six findings;\n- negative tests added;\n- complete no-engine test count/result;\n- campaign SHA;\n- all ten pair identity/seed verification;\n- no-engine-process result;\n- final clean git status;\n- confirmation no push occurred.\n\nEnd exactly:\n\nIMPLEMENTATION_DONE\n\nIf blocked, preserve safely completed fixes as local commits, do not push,\nleave the repository clean where possible, and end:\n\nIMPLEMENTATION_BLOCKED"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787491269.469218}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:21:09.470Z","ordinal":9,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"UserMessage","id":"01a02ec8-3b5e-7ff2-aa26-9e643972a0ff","content":[{"type":"text","text":"You are continuing the authorized NO-ENGINE hardening of the Sage4-vs-GNU3\nmatch producer.\n\nAnother lane owns post-match GNU4 analysis. Do not touch or run GNU4 analysis.\n\nCURRENT LOCAL HEAD\n\n6f24bd9ad470a0b7af1cc99c6692354f7d55047f\n\nPreserve the entire existing local recovery history and all failed-smoke\nevidence.\n\nDO NOT:\n\n- reset, rebase, amend, or squash existing commits;\n- discard local recovery commits;\n- pull/reset this branch back to remote HEAD;\n- push;\n- launch gnubg;\n- launch bgsage;\n- run real Sage/GNU analysis;\n- run a one-pair smoke;\n- run the 10-pair campaign;\n- run post-match GNU4;\n- perform Canonical writes;\n- modify the frozen campaign settings.\n\nThe campaign configuration SHA-256 must remain exactly:\n\nb5614f359236c78efd27aa48c544f807fee26619fce215565e1cbb930abfc2df\n\nAll ten deterministic pair IDs/seeds must remain unchanged.\n\nBefore editing run:\n\ngit status --short\ngit log --oneline -30\n\nThe worktree must start clean.\n\nGIT POLICY\n\nMake small coherent LOCAL commits.\n\nPrefer one commit per independent review finding and include the corresponding\nnegative regression tests in that commit.\n\nSuggested subjects:\n\n1. fix: derive SGF application identity from frozen GNU runtime\n2. fix: require state-connected publication decision history\n3. fix: make analysis journals publication authority\n4. fix: preserve checker dice seat identity at publication\n5. fix: require complete deterministic dice stream inventory\n6. fix: validate state-bearing GNU SGF properties\n\nDo not push.\n\n======================================================================\n1. DERIVE SGF AP VERSION FROM THE FROZEN GNU RUNTIME\n======================================================================\n\nCurrent defect:\n\nThe SGF parser requires:\n\nAP[GNU Backgammon:1.06.002]\n\nbut the frozen production GNU executable identifies as:\n\n1.08.003 20260710\n\nGNU emits its build VERSION in SGF AP, so normal frozen production output\nshould use GNU Backgammon:1.08.003 and is currently rejected.\n\nDo not simply replace one hard-coded version with another.\n\nDerive the accepted AP identity from the same verified frozen GNU runtime\nidentity already established by environment/preflight authority.\n\nRequirements:\n\n- application must be GNU Backgammon;\n- version must match the canonical frozen runtime version token;\n- current frozen runtime should accept 1.08.003;\n- 1.06.002 must fail for this runtime;\n- wrong application fails;\n- wrong version fails;\n- missing/malformed AP fails closed;\n- campaign.json remains unchanged.\n\nUse one source of runtime-version truth.\n\nAdd a realistic/golden GNU 1.08.003 SGF fixture rather than continuing to\ngenerate all fixtures with the obsolete AP string.\n\n======================================================================\n2. REQUIRE A STATE-CONNECTED PUBLICATION DECISION TRANSCRIPT\n======================================================================\n\nCurrent defect:\n\nPublication verifies individual pre/post GNUIDs but does not require the\ncomplete decision transcript to form one connected state history.\n\nRequire decisions.jsonl to be an exact ordered state-machine transcript.\n\nFor adjacent records require the preceding post-state to equal the following\npre-state unless an explicitly represented and validated automatic transition\noccurs.\n\nAny automatic transition must itself have durable evidence. Do not silently\nallow discontinuities.\n\nAt publication:\n\n- decode relevant pre/post GNUIDs;\n- re-run the same command-specific transition semantics used during live\n execution;\n- reuse/factor common pure validators rather than maintaining a weaker\n publication-specific policy.\n\nValidate exact semantics for:\n\n- roll;\n- checker move;\n- double;\n- take;\n- pass/drop;\n- resignation;\n- normal/bearoff completion;\n- automatic next roll;\n- automatic next-game opening.\n\nCheck:\n\n- game ordinal;\n- decision ordinal;\n- command type;\n- acting physical seat;\n- acting engine;\n- board state;\n- player on roll;\n- decision player;\n- score;\n- cube value;\n- cube owner;\n- pending action;\n- offerer/responder;\n- resignation state;\n- terminal semantics.\n\nROLL DECISIONS MUST NOT BE OPTIONAL.\n\nRequire the exact ordered roll/checker/cube/terminal command sequence.\n\nReject at publication:\n\n- disconnected adjacent GNUIDs;\n- swapped decisions with rewritten ordinals;\n- fabricated but individually plausible GNUIDs;\n- missing roll record;\n- extra roll record;\n- roll moved to the wrong point;\n- wrong checker state;\n- wrong cube state;\n- wrong terminal state;\n- wrong automatic opening;\n- wrong seat/engine.\n\nReplace synthetic fixtures that use unrelated adjacent pre/post GNUIDs with\ngenuinely state-connected fixtures.\n\n======================================================================\n3. MAKE RAW ANALYSIS JOURNALS PUBLICATION AUTHORITY\n======================================================================\n\nProduction creates:\n\nanalysis_requests.jsonl\nanalysis_results.jsonl\n\nand corresponding Engine Kit result evidence.\n\nPublication must REQUIRE and independently validate these artifacts.\n\nRequire:\n\n- both journals exist when analysis occurred;\n- manifest paths exist and are correct;\n- manifest SHA-256 values match actual files;\n- match_manifest output hashes are independently rechecked;\n- journals are structurally valid;\n- records are complete and monotonically ordered.\n\nReconcile one-to-one and in exact order:\n\nanalysis request\n↔ raw analysis result\n↔ engine_kit_result forensic evidence\n↔ decision record\n\nValidate:\n\n- pair ID;\n- match side;\n- game ordinal;\n- request/decision ordinal;\n- engine;\n- physical seat;\n- decision type;\n- GNUID/requested position;\n- dice/context where applicable;\n- configured depth;\n- actual depth;\n- result/candidate semantics;\n- raw_source evidence where represented.\n\nReject:\n\n- missing request/result;\n- duplicate;\n- reordering;\n- wrong decision association;\n- wrong engine/seat/GNUID/type;\n- malformed result;\n- wrong configured depth;\n- invalid actual depth;\n- cross-side/game/pair result;\n- manifest/file hash mismatch.\n\nIMPORTANT DEPTH POLICY:\n\nDo not alter frozen configured depths.\n\nSage checker/cube remains 4ply/3ply.\nGNU checker/cube remains 3ply/2ply.\n\nReuse EXACTLY the already validated live depth policy.\n\nIn particular, do not incorrectly require GNU checker actual ply to equal\nconfigured 3ply if the existing live policy permits a shallower nonnegative\nactual checker depth.\n\nGNU cube actual-depth policy must remain consistent with the existing live\nvalidator.\n\nA valid publication fixture may no longer omit analysis journals.\n\n======================================================================\n4. PRESERVE PHYSICAL-SEAT AND ENGINE IDENTITY IN CHECKER-DICE AUTHORITY\n======================================================================\n\nCurrent defect:\n\nFinal checker/native reconciliation can reduce dice to just:\n\n[die1, die2]\n\nafter separately validating the claimed stream.\n\nThat loses physical-seat and engine identity and permits a self-consistent\ncross-seat substitution if two deterministic streams happen to produce the\nsame dice values.\n\nFor every checker action reconcile an ordered semantic identity such as:\n\n(\n game ordinal,\n action ordinal,\n physical seat,\n engine,\n die1,\n die2,\n stream identity,\n roll index\n)\n\nsimultaneously against:\n\n- decision record;\n- deterministic dice record;\n- native checker action;\n- frozen side A/B physical-seat mapping.\n\nWhere native evidence does not encode engine directly, derive the ONLY legal\nengine from the physical seat and mirrored side and verify all other evidence\nagrees.\n\nAdd an explicit collision regression:\n\nUse equal dice values from two different streams and prove publication still\nrejects substitution of the wrong:\n\n- physical seat;\n- engine;\n- stream.\n\nAlso reject cross-side substitutions.\n\n======================================================================\n5. REQUIRE THE COMPLETE DETERMINISTIC DICE STREAM INVENTORY\n======================================================================\n\nPublication must authenticate the entire frozen input envelope, not only\nstreams consumed by games that happened to be played.\n\nFrozen authority expects:\n\n25 game slots × 2 physical seats = 50 streams\n\nDerive this from the frozen dice protocol where possible rather than adding a\nsecond unrelated magic constant.\n\nRequire exactly:\n\n- every expected stream manifest entry;\n- every expected stream CSV file;\n- no missing stream;\n- no extra stream;\n- no duplicate stream identity.\n\nFor ALL streams, including unused ones:\n\n- regenerate expected stream authority from pair seed;\n- verify stream ID/namespace;\n- verify game slot;\n- verify physical seat;\n- verify side namespace where applicable;\n- verify complete CSV content;\n- verify file SHA-256;\n- verify deterministic stream hash.\n\nReject:\n\n- missing active CSV;\n- missing inactive CSV;\n- missing manifest entry;\n- extra stream;\n- forged inactive hash;\n- modified inactive CSV;\n- wrong slot;\n- wrong seat;\n- wrong namespace;\n- duplicate stream ID.\n\n======================================================================\n6. VALIDATE ALL STATE-BEARING GNU SGF PROPERTIES\n======================================================================\n\nCurrent defect:\n\nSGF parsing sees node properties that affect backgammon state, but\ncanonicalization currently ignores some of them.\n\nFor example, adding/changing:\n\nCV[2]\n\ncan leave the same canonical game despite changing cube state.\n\nInspect the frozen GNU 1.08.003 SGF grammar/property definitions.\n\nEnumerate every state-bearing property GNU can emit for normal match records.\n\nFor each such property:\n\nEITHER\n\nA. canonicalize and reconcile its semantic state;\n\nOR\n\nB. fail closed when it appears in a context the validator does not explicitly\nsupport.\n\nDo not silently discard state-bearing properties.\n\nCover properties capable of affecting:\n\n- cube value;\n- cube owner/position;\n- dice;\n- player to move;\n- checker/setup position;\n- game/match state;\n- resignation semantics;\n- cube semantics;\n- any setup state capable of changing the position.\n\nExplicitly classify harmless descriptive metadata separately so safe metadata\ndoes not create false mismatches.\n\nRequired regressions include:\n\n- CV changes 1 → 2;\n- cube owner changes;\n- player-to-move changes;\n- dice property changes;\n- checker setup changes;\n- unexpected setup property mid-game;\n- inconsistent setup properties;\n- state-bearing SGF inconsistent with text/decision evidence;\n- the exact CV[2] counterexample from review.\n\n======================================================================\nPRESERVE ALL PREVIOUSLY CLEARED PROTECTIONS\n======================================================================\n\nDo not regress:\n\n- complete SGF/text action ordering;\n- structural native-game completeness;\n- exact terminal kind/level semantics;\n- actor seat/engine terminal validation;\n- immediate subsequent-opening validation;\n- artifact_root → campaign_root → pairs → staging → final durability;\n- immutable post-rename parent fsync;\n- runtime-root analysis-journal durability;\n- decisions.jsonl crash durability;\n- failure-bundle durability;\n- primary-error preservation;\n- complete opening semantics;\n- checker/roll/double/take/pass/resignation postconditions;\n- deterministic physical-seat dice streams;\n- automatic/manual dice reconciliation;\n- exact multi-game player mappings;\n- raw Engine Kit result/raw_source forensics;\n- malformed primitive-result preservation;\n- GNU manual RNG verification;\n- GNU command errors fail closed;\n- isolated GNU HOME;\n- native-path safety;\n- failure/CLI/interrupt semantics;\n- environment integrity;\n- authoritative published-pair reconciliation;\n- failed-smoke evidence.\n\n======================================================================\nNO-ENGINE TEST / IDENTITY GATE\n======================================================================\n\nRun the complete no-engine campaign test suite.\n\nDo NOT launch engines.\n\nIf necessary use only an existing repo-local/scratch-local test venv. Do not\nchange project dependency metadata simply to run pytest.\n\nAfter implementation prove:\n\ncampaign SHA-256:\n\nb5614f359236c78efd27aa48c544f807fee26619fce215565e1cbb930abfc2df\n\nAlso prove:\n\n- campaign.json unchanged;\n- config.py frozen semantics unchanged;\n- identity.py unchanged;\n- all ten deterministic pair IDs and seeds unchanged;\n- Sage checker/cube still 4ply/3ply;\n- GNU checker/cube still 3ply/2ply;\n- normal-v1 unchanged;\n- match length still 7;\n- threads still 1/1;\n- mirrored A/B mapping unchanged;\n- pgrep -x gnubg finds no process;\n- pgrep -x bgsage finds no process;\n- worktree is clean;\n- nothing was pushed.\n\nShow:\n\ngit log --oneline --reverse 6f24bd9..HEAD\n\nFINAL RESPONSE\n\nReport:\n\n- starting HEAD;\n- final HEAD;\n- every new commit SHA and subject;\n- files changed in each commit;\n- remediation for all six findings;\n- negative tests added;\n- complete no-engine test count/result;\n- campaign SHA;\n- all ten pair identity/seed verification;\n- no-engine-process result;\n- final clean git status;\n- confirmation no push occurred.\n\nEnd exactly:\n\nIMPLEMENTATION_DONE\n\nIf blocked, preserve safely completed fixes as local commits, do not push,\nleave the repository clean where possible, and end:\n\nIMPLEMENTATION_BLOCKED","text_elements":[]}]},"started_at_ms":1787491269470,"completed_at_ms":1787491269470}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:24:35.059Z","ordinal":155,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-d06ae1de-edf7-4793-af5f-36605fadc1b5","process_id":"55441","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_match_policy.py -k 'native_outputs or frozen_gnu_108003 or nonruntime_sgf_application' --disable-warnings --maxfail=1"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_match_policy.py -k 'native_outputs or frozen_gnu_108003 or nonruntime_sgf_application' --disable-warnings --maxfail=1"}],"source":"unified_exec_startup","status":"completed","stdout":".................. [100%]\n18 passed, 60 deselected in 0.21s\n","stderr":"","aggregated_output":".................. [100%]\n18 passed, 60 deselected in 0.21s\n","exit_code":0,"duration":{"secs":0,"nanos":551756366},"formatted_output":".................. [100%]\n18 passed, 60 deselected in 0.21s\n"},"started_at_ms":1787491474507,"completed_at_ms":1787491475059}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:24:35.068Z","ordinal":156,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ecb-5e7c-7f33-aac2-079734d31e06","call_id":"call_Gh1zmvtQlt3lntCWbJs73vJa","output":[{"type":"input_text","text":"Script completed\nWall time 0.8 seconds\nOutput:\n"},{"type":"input_text","text":".................. [100%]\n18 passed, 60 deselected in 0.21s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787491475.0685499}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:24:56.994Z","ordinal":161,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-91564eb1-0710-4975-99e1-00e19078e532","process_id":"54524","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=1"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=1"}],"source":"unified_exec_startup","status":"completed","stdout":"........................................................................ [ 37%]\n........................................................................ [ 75%]\n............................................... [100%]\n191 passed in 16.99s\n","stderr":"","aggregated_output":"........................................................................ [ 37%]\n........................................................................ [ 75%]\n............................................... [100%]\n191 passed in 16.99s\n","exit_code":0,"duration":{"secs":17,"nanos":167899355},"formatted_output":"........................................................................ [ 37%]\n........................................................................ [ 75%]\n............................................... [100%]\n191 passed in 16.99s\n"},"started_at_ms":1787491479826,"completed_at_ms":1787491496994}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:24:57.005Z","ordinal":162,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ecb-b42d-7572-9540-8cf046004413","call_id":"call_a4c92Nvv4BD58cS6wulJAJp2","output":[{"type":"input_text","text":"Script completed\nWall time 17.4 seconds\nOutput:\n"},{"type":"input_text","text":"........................................................................ [ 37%]\n........................................................................ [ 75%]\n............................................... [100%]\n191 passed in 16.99s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787491497.0053802}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:36:31.246Z","ordinal":303,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-0bd770f8-0076-4622-9494-ef518407fe52","process_id":"47594","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_committed_pair_is_verified_and_never_regenerated --disable-warnings --maxfail=1"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_committed_pair_is_verified_and_never_regenerated --disable-warnings --maxfail=1"}],"source":"unified_exec_startup","status":"completed","stdout":". [100%]\n1 passed in 2.59s\n","stderr":"","aggregated_output":". [100%]\n1 passed in 2.59s\n","exit_code":0,"duration":{"secs":2,"nanos":947982092},"formatted_output":". [100%]\n1 passed in 2.59s\n"},"started_at_ms":1787492188298,"completed_at_ms":1787492191246}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:36:31.255Z","ordinal":304,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ed6-4c17-7ee1-b0f3-4dd0387bf1c3","call_id":"call_CmRp4o44oI7htYDhzlufqH04","output":[{"type":"input_text","text":"Script completed\nWall time 3.2 seconds\nOutput:\n"},{"type":"input_text","text":". [100%]\n1 passed in 2.59s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492191.2551079}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:37:30.692Z","ordinal":323,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-a2e3e573-0b00-4837-b83d-a17f0085c3ba","process_id":"74046","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_accepts_complete_real_shaped_multi_game_native_evidence tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_accepts_exact_drop_and_resignation_semantics --disable-warnings --maxfail=1"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_accepts_complete_real_shaped_multi_game_native_evidence tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_accepts_exact_drop_and_resignation_semantics --disable-warnings --maxfail=1"}],"source":"unified_exec_startup","status":"completed","stdout":"... [100%]\n3 passed in 7.48s\n","stderr":"","aggregated_output":"... [100%]\n3 passed in 7.48s\n","exit_code":0,"duration":{"secs":7,"nanos":671469564},"formatted_output":"... [100%]\n3 passed in 7.48s\n"},"started_at_ms":1787492243020,"completed_at_ms":1787492250691}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:37:30.702Z","ordinal":324,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ed7-344e-71a2-bbf3-60859fc47951","call_id":"call_aiwunKixsOvuMR7AGxl1qxNx","output":[{"type":"input_text","text":"Script completed\nWall time 7.9 seconds\nOutput:\n"},{"type":"input_text","text":"... [100%]\n3 passed in 7.48s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492250.7024288}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:38:01.367Z","ordinal":329,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-b4a941bf-7373-4ac2-84ef-7cbfe1b71b75","process_id":"66082","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py --disable-warnings --maxfail=5"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py --disable-warnings --maxfail=5"}],"source":"unified_exec_startup","status":"failed","stdout":".........F..F........................F...........F...F\n=================================== FAILURES ===================================\n_ test_publish_pair_rejects_terminal_and_ordered_journal_corruption[normal-encoded-as-resignation] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te..._pair_rejects_terminal_and_ordered_journal_corruption_normal-encoded-as-resignation_-dcd741e94dbe4ac4a56183a558a627a8')\ncase = 'normal-encoded-as-resignation'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"resignation-encoded-as-normal\",\n \"normal-encoded-as-resignation\",\n \"pass-encoded-as-resignation\",\n \"wrong-resignation-level\",\n \"wrong-command-type\",\n \"wrong-acting-seat\",\n \"wrong-acting-engine\",\n \"wrong-pre-command-score\",\n \"wrong-post-command-score\",\n \"missing-subsequent-opening\",\n \"subsequent-opening-wrong-game\",\n \"reordered-decision-records\",\n \"reordered-dice-records\",\n \"duplicated-dice-record\",\n \"missing-dice-record\",\n \"wrong-roll-index\",\n \"correct-dice-wrong-physical-seat\",\n \"correct-dice-wrong-engine\",\n \"wrong-stream-identity\",\n \"wrong-pair-seed\",\n \"wrong-match-side\",\n ],\n )\n def test_publish_pair_rejects_terminal_and_ordered_journal_corruption(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n terminal_kinds = None\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n if case in {\"resignation-encoded-as-normal\", \"wrong-resignation-level\"}:\n games = [(\"O\", 2), (\"O\", 6)]\n terminal_kinds = [\"resignation\", \"ordinary_game_over\"]\n elif case == \"pass-encoded-as-resignation\":\n games = [(\"O\", 1), (\"O\", 6)]\n terminal_kinds = [\"drop\", \"ordinary_game_over\"]\n write_execution_fixture(execution, identity, games, terminal_kinds)\n match = execution / \"matches/A\"\n decision_path = match / \"decisions.jsonl\"\n decisions = [json.loads(line) for line in decision_path.read_text(encoding=\"utf-8\").splitlines()]\n terminal_index = next(\n index for index, record in enumerate(decisions)\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal = decisions[terminal_index][\"transition_evidence\"]\n \n if case == \"resignation-encoded-as-normal\":\n terminal[\"terminal_event\"][\"kind\"] = \"ordinary_game_over\"\n terminal[\"terminal_event\"].pop(\"resignation_level\")\n elif case == \"normal-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = terminal[\"terminal_event\"][\"result_level\"]\n elif case == \"pass-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = 1\n terminal[\"terminal_event\"].pop(\"loser_physical_seat\")\n terminal[\"terminal_event\"].pop(\"loser_engine\")\n elif case == \"wrong-resignation-level\":\n terminal[\"terminal_event\"][\"resignation_level\"] = 3\n elif case == \"wrong-command-type\":\n terminal[\"command_type\"] = \"accepted_resignation\"\n elif case == \"wrong-acting-seat\":\n terminal[\"acting_physical_seat\"] = \"X\" if terminal[\"acting_physical_seat\"] == \"O\" else \"O\"\n elif case == \"wrong-acting-engine\":\n terminal[\"acting_engine\"] = \"gnu\" if terminal[\"acting_engine\"] == \"sage\" else \"sage\"\n elif case == \"wrong-pre-command-score\":\n terminal[\"pre_command\"][\"score\"][0] += 1\n elif case == \"wrong-post-command-score\":\n terminal[\"post_command\"][\"score\"][0] += 1\n elif case == \"missing-subsequent-opening\":\n terminal[\"subsequent_opening_state\"] = None\n elif case == \"subsequent-opening-wrong-game\":\n terminal[\"subsequent_opening_state\"][\"game_number\"] += 1\n elif case == \"reordered-decision-records\":\n decisions[0], decisions[1] = decisions[1], decisions[0]\n \n dice_path = match / \"dice/seat_dice_consumption.jsonl\"\n dice_records = [json.loads(line) for line in dice_path.read_text(encoding=\"utf-8\").splitlines()]\n if case == \"reordered-dice-records\":\n dice_records[0], dice_records[1] = dice_records[1], dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"duplicated-dice-record\":\n dice_records.insert(1, dict(dice_records[0]))\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"missing-dice-record\":\n del dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"wrong-roll-index\":\n dice_records[0][\"roll_index\"] += 1\n elif case == \"correct-dice-wrong-physical-seat\":\n dice_records[0][\"physical_seat\"] = \"X\"\n elif case == \"correct-dice-wrong-engine\":\n dice_records[0][\"engine\"] = \"gnu\"\n elif case == \"wrong-stream-identity\":\n dice_records[0][\"stream_id\"] = \"stream-\" + \"0\" * 64\n elif case == \"wrong-pair-seed\":\n dice_records[0][\"base_seed\"] = \"sha256:\" + \"0\" * 64\n elif case == \"wrong-match-side\":\n dice_records[0][\"match_side\"] = \"B\"\n \n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n dice_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in dice_records),\n encoding=\"utf-8\",\n )\n dice_manifest_path = match / \"dice/seat_dice_manifest.json\"\n dice_manifest = json.loads(dice_manifest_path.read_text(encoding=\"utf-8\"))\n dice_manifest[\"consumption\"][\"entries\"] = len(dice_records)\n dice_manifest[\"consumption\"][\"sha256\"] = hashlib.sha256(dice_path.read_bytes()).hexdigest()\n write_json(dice_manifest_path, dice_manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:328: Failed\n_ test_publish_pair_rejects_terminal_and_ordered_journal_corruption[wrong-command-type] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...est_publish_pair_rejects_terminal_and_ordered_journal_corruption_wrong-command-type_-78b23d92fb0440cf84d6344c5eb9f085')\ncase = 'wrong-command-type'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"resignation-encoded-as-normal\",\n \"normal-encoded-as-resignation\",\n \"pass-encoded-as-resignation\",\n \"wrong-resignation-level\",\n \"wrong-command-type\",\n \"wrong-acting-seat\",\n \"wrong-acting-engine\",\n \"wrong-pre-command-score\",\n \"wrong-post-command-score\",\n \"missing-subsequent-opening\",\n \"subsequent-opening-wrong-game\",\n \"reordered-decision-records\",\n \"reordered-dice-records\",\n \"duplicated-dice-record\",\n \"missing-dice-record\",\n \"wrong-roll-index\",\n \"correct-dice-wrong-physical-seat\",\n \"correct-dice-wrong-engine\",\n \"wrong-stream-identity\",\n \"wrong-pair-seed\",\n \"wrong-match-side\",\n ],\n )\n def test_publish_pair_rejects_terminal_and_ordered_journal_corruption(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n terminal_kinds = None\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n if case in {\"resignation-encoded-as-normal\", \"wrong-resignation-level\"}:\n games = [(\"O\", 2), (\"O\", 6)]\n terminal_kinds = [\"resignation\", \"ordinary_game_over\"]\n elif case == \"pass-encoded-as-resignation\":\n games = [(\"O\", 1), (\"O\", 6)]\n terminal_kinds = [\"drop\", \"ordinary_game_over\"]\n write_execution_fixture(execution, identity, games, terminal_kinds)\n match = execution / \"matches/A\"\n decision_path = match / \"decisions.jsonl\"\n decisions = [json.loads(line) for line in decision_path.read_text(encoding=\"utf-8\").splitlines()]\n terminal_index = next(\n index for index, record in enumerate(decisions)\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal = decisions[terminal_index][\"transition_evidence\"]\n \n if case == \"resignation-encoded-as-normal\":\n terminal[\"terminal_event\"][\"kind\"] = \"ordinary_game_over\"\n terminal[\"terminal_event\"].pop(\"resignation_level\")\n elif case == \"normal-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = terminal[\"terminal_event\"][\"result_level\"]\n elif case == \"pass-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = 1\n terminal[\"terminal_event\"].pop(\"loser_physical_seat\")\n terminal[\"terminal_event\"].pop(\"loser_engine\")\n elif case == \"wrong-resignation-level\":\n terminal[\"terminal_event\"][\"resignation_level\"] = 3\n elif case == \"wrong-command-type\":\n terminal[\"command_type\"] = \"accepted_resignation\"\n elif case == \"wrong-acting-seat\":\n terminal[\"acting_physical_seat\"] = \"X\" if terminal[\"acting_physical_seat\"] == \"O\" else \"O\"\n elif case == \"wrong-acting-engine\":\n terminal[\"acting_engine\"] = \"gnu\" if terminal[\"acting_engine\"] == \"sage\" else \"sage\"\n elif case == \"wrong-pre-command-score\":\n terminal[\"pre_command\"][\"score\"][0] += 1\n elif case == \"wrong-post-command-score\":\n terminal[\"post_command\"][\"score\"][0] += 1\n elif case == \"missing-subsequent-opening\":\n terminal[\"subsequent_opening_state\"] = None\n elif case == \"subsequent-opening-wrong-game\":\n terminal[\"subsequent_opening_state\"][\"game_number\"] += 1\n elif case == \"reordered-decision-records\":\n decisions[0], decisions[1] = decisions[1], decisions[0]\n \n dice_path = match / \"dice/seat_dice_consumption.jsonl\"\n dice_records = [json.loads(line) for line in dice_path.read_text(encoding=\"utf-8\").splitlines()]\n if case == \"reordered-dice-records\":\n dice_records[0], dice_records[1] = dice_records[1], dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"duplicated-dice-record\":\n dice_records.insert(1, dict(dice_records[0]))\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"missing-dice-record\":\n del dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"wrong-roll-index\":\n dice_records[0][\"roll_index\"] += 1\n elif case == \"correct-dice-wrong-physical-seat\":\n dice_records[0][\"physical_seat\"] = \"X\"\n elif case == \"correct-dice-wrong-engine\":\n dice_records[0][\"engine\"] = \"gnu\"\n elif case == \"wrong-stream-identity\":\n dice_records[0][\"stream_id\"] = \"stream-\" + \"0\" * 64\n elif case == \"wrong-pair-seed\":\n dice_records[0][\"base_seed\"] = \"sha256:\" + \"0\" * 64\n elif case == \"wrong-match-side\":\n dice_records[0][\"match_side\"] = \"B\"\n \n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n dice_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in dice_records),\n encoding=\"utf-8\",\n )\n dice_manifest_path = match / \"dice/seat_dice_manifest.json\"\n dice_manifest = json.loads(dice_manifest_path.read_text(encoding=\"utf-8\"))\n dice_manifest[\"consumption\"][\"entries\"] = len(dice_records)\n dice_manifest[\"consumption\"][\"sha256\"] = hashlib.sha256(dice_path.read_bytes()).hexdigest()\n write_json(dice_manifest_path, dice_manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:328: Failed\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[sgf-text-result-mismatch] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...on_rejects_incomplete_or_cross_format_native_game_evidence_sgf-text-result-mismatch_-0637b9087bfe48d7907c3c9c7aaad360')\ncase = 'sgf-text-result-mismatch'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n assert count == 1\n text_path.write_text(changed, encoding=\"utf-8\")\n elif case == \"root-only-sgf-with-result\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n trees[0] = re.sub(r\";[WB]\\[[^\\n]*\", \")\\n\", trees[0], count=1)\n sgf_path.write_text(\"\".join(trees), encoding=\"utf-8\")\n elif case == \"result-only-text-with-result\":\n first_result = text.index(\" Wins 2 points\")\n first_actions = text.index(\" 1)\", text.index(\" Game 1\"))\n text_path.write_text(text[:first_actions] + text[first_result:], encoding=\"utf-8\")\n elif case == \"reordered-actions\":\n row = re.search(r\"(?m)^(\\s*1\\) )(.{27}) (.+)$\", text)\n assert row is not None\n swapped = row.group(1) + f\"{row.group(3):<27} \" + row.group(2).strip()\n text_path.write_text(text[:row.start()] + swapped + text[row.end():], encoding=\"utf-8\")\n elif case == \"missing-move\":\n move = re.search(r\"([1-6]{2}: \\d+/\\d+) \\d+/\\d+\", text)\n assert move is not None\n text_path.write_text(text[:move.start()] + move.group(1) + text[move.end():], encoding=\"utf-8\")\n elif case == \"extra-move\":\n move = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text)\n assert move is not None\n text_path.write_text(text[:move.end()] + \" 13/10\" + text[move.end():], encoding=\"utf-8\")\n elif case == \"action-belongs-to-wrong-game\":\n game_two_action = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text[game_2:])\n game_one_action = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text)\n assert game_two_action is not None and game_one_action is not None\n transplanted = game_two_action.group(0)\n text_path.write_text(\n text[:game_one_action.start()] + transplanted + text[game_one_action.end():],\n encoding=\"utf-8\",\n )\n else: # pragma: no cover\n raise AssertionError(case)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:502: Failed\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[same-result-different-terminal-action] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...complete_or_cross_format_native_game_evidence_same-result-different-terminal-action_-5922da79947b4bd08d0a278dd9ad40e3')\ncase = 'same-result-different-terminal-action'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n> assert count == 1\nE assert 0 == 1\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:465: AssertionError\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[missing-move] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...st_publication_rejects_incomplete_or_cross_format_native_game_evidence_missing-move_-d5126c9c6e884b9c958af04ebfd1a132')\ncase = 'missing-move'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n assert count == 1\n text_path.write_text(changed, encoding=\"utf-8\")\n elif case == \"root-only-sgf-with-result\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n trees[0] = re.sub(r\";[WB]\\[[^\\n]*\", \")\\n\", trees[0], count=1)\n sgf_path.write_text(\"\".join(trees), encoding=\"utf-8\")\n elif case == \"result-only-text-with-result\":\n first_result = text.index(\" Wins 2 points\")\n first_actions = text.index(\" 1)\", text.index(\" Game 1\"))\n text_path.write_text(text[:first_actions] + text[first_result:], encoding=\"utf-8\")\n elif case == \"reordered-actions\":\n row = re.search(r\"(?m)^(\\s*1\\) )(.{27}) (.+)$\", text)\n assert row is not None\n swapped = row.group(1) + f\"{row.group(3):<27} \" + row.group(2).strip()\n text_path.write_text(text[:row.start()] + swapped + text[row.end():], encoding=\"utf-8\")\n elif case == \"missing-move\":\n move = re.search(r\"([1-6]{2}: \\d+/\\d+) \\d+/\\d+\", text)\n> assert move is not None\nE assert None is not None\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:482: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publish_pair_rejects_terminal_and_ordered_journal_corruption[normal-encoded-as-resignation]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publish_pair_rejects_terminal_and_ordered_journal_corruption[wrong-command-type]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[sgf-text-result-mismatch]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[same-result-different-terminal-action]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[missing-move]\n!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 5 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!\n5 failed, 49 passed in 13.47s\n","stderr":"","aggregated_output":".........F..F........................F...........F...F\n=================================== FAILURES ===================================\n_ test_publish_pair_rejects_terminal_and_ordered_journal_corruption[normal-encoded-as-resignation] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te..._pair_rejects_terminal_and_ordered_journal_corruption_normal-encoded-as-resignation_-dcd741e94dbe4ac4a56183a558a627a8')\ncase = 'normal-encoded-as-resignation'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"resignation-encoded-as-normal\",\n \"normal-encoded-as-resignation\",\n \"pass-encoded-as-resignation\",\n \"wrong-resignation-level\",\n \"wrong-command-type\",\n \"wrong-acting-seat\",\n \"wrong-acting-engine\",\n \"wrong-pre-command-score\",\n \"wrong-post-command-score\",\n \"missing-subsequent-opening\",\n \"subsequent-opening-wrong-game\",\n \"reordered-decision-records\",\n \"reordered-dice-records\",\n \"duplicated-dice-record\",\n \"missing-dice-record\",\n \"wrong-roll-index\",\n \"correct-dice-wrong-physical-seat\",\n \"correct-dice-wrong-engine\",\n \"wrong-stream-identity\",\n \"wrong-pair-seed\",\n \"wrong-match-side\",\n ],\n )\n def test_publish_pair_rejects_terminal_and_ordered_journal_corruption(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n terminal_kinds = None\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n if case in {\"resignation-encoded-as-normal\", \"wrong-resignation-level\"}:\n games = [(\"O\", 2), (\"O\", 6)]\n terminal_kinds = [\"resignation\", \"ordinary_game_over\"]\n elif case == \"pass-encoded-as-resignation\":\n games = [(\"O\", 1), (\"O\", 6)]\n terminal_kinds = [\"drop\", \"ordinary_game_over\"]\n write_execution_fixture(execution, identity, games, terminal_kinds)\n match = execution / \"matches/A\"\n decision_path = match / \"decisions.jsonl\"\n decisions = [json.loads(line) for line in decision_path.read_text(encoding=\"utf-8\").splitlines()]\n terminal_index = next(\n index for index, record in enumerate(decisions)\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal = decisions[terminal_index][\"transition_evidence\"]\n \n if case == \"resignation-encoded-as-normal\":\n terminal[\"terminal_event\"][\"kind\"] = \"ordinary_game_over\"\n terminal[\"terminal_event\"].pop(\"resignation_level\")\n elif case == \"normal-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = terminal[\"terminal_event\"][\"result_level\"]\n elif case == \"pass-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = 1\n terminal[\"terminal_event\"].pop(\"loser_physical_seat\")\n terminal[\"terminal_event\"].pop(\"loser_engine\")\n elif case == \"wrong-resignation-level\":\n terminal[\"terminal_event\"][\"resignation_level\"] = 3\n elif case == \"wrong-command-type\":\n terminal[\"command_type\"] = \"accepted_resignation\"\n elif case == \"wrong-acting-seat\":\n terminal[\"acting_physical_seat\"] = \"X\" if terminal[\"acting_physical_seat\"] == \"O\" else \"O\"\n elif case == \"wrong-acting-engine\":\n terminal[\"acting_engine\"] = \"gnu\" if terminal[\"acting_engine\"] == \"sage\" else \"sage\"\n elif case == \"wrong-pre-command-score\":\n terminal[\"pre_command\"][\"score\"][0] += 1\n elif case == \"wrong-post-command-score\":\n terminal[\"post_command\"][\"score\"][0] += 1\n elif case == \"missing-subsequent-opening\":\n terminal[\"subsequent_opening_state\"] = None\n elif case == \"subsequent-opening-wrong-game\":\n terminal[\"subsequent_opening_state\"][\"game_number\"] += 1\n elif case == \"reordered-decision-records\":\n decisions[0], decisions[1] = decisions[1], decisions[0]\n \n dice_path = match / \"dice/seat_dice_consumption.jsonl\"\n dice_records = [json.loads(line) for line in dice_path.read_text(encoding=\"utf-8\").splitlines()]\n if case == \"reordered-dice-records\":\n dice_records[0], dice_records[1] = dice_records[1], dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"duplicated-dice-record\":\n dice_records.insert(1, dict(dice_records[0]))\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"missing-dice-record\":\n del dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"wrong-roll-index\":\n dice_records[0][\"roll_index\"] += 1\n elif case == \"correct-dice-wrong-physical-seat\":\n dice_records[0][\"physical_seat\"] = \"X\"\n elif case == \"correct-dice-wrong-engine\":\n dice_records[0][\"engine\"] = \"gnu\"\n elif case == \"wrong-stream-identity\":\n dice_records[0][\"stream_id\"] = \"stream-\" + \"0\" * 64\n elif case == \"wrong-pair-seed\":\n dice_records[0][\"base_seed\"] = \"sha256:\" + \"0\" * 64\n elif case == \"wrong-match-side\":\n dice_records[0][\"match_side\"] = \"B\"\n \n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n dice_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in dice_records),\n encoding=\"utf-8\",\n )\n dice_manifest_path = match / \"dice/seat_dice_manifest.json\"\n dice_manifest = json.loads(dice_manifest_path.read_text(encoding=\"utf-8\"))\n dice_manifest[\"consumption\"][\"entries\"] = len(dice_records)\n dice_manifest[\"consumption\"][\"sha256\"] = hashlib.sha256(dice_path.read_bytes()).hexdigest()\n write_json(dice_manifest_path, dice_manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:328: Failed\n_ test_publish_pair_rejects_terminal_and_ordered_journal_corruption[wrong-command-type] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...est_publish_pair_rejects_terminal_and_ordered_journal_corruption_wrong-command-type_-78b23d92fb0440cf84d6344c5eb9f085')\ncase = 'wrong-command-type'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"resignation-encoded-as-normal\",\n \"normal-encoded-as-resignation\",\n \"pass-encoded-as-resignation\",\n \"wrong-resignation-level\",\n \"wrong-command-type\",\n \"wrong-acting-seat\",\n \"wrong-acting-engine\",\n \"wrong-pre-command-score\",\n \"wrong-post-command-score\",\n \"missing-subsequent-opening\",\n \"subsequent-opening-wrong-game\",\n \"reordered-decision-records\",\n \"reordered-dice-records\",\n \"duplicated-dice-record\",\n \"missing-dice-record\",\n \"wrong-roll-index\",\n \"correct-dice-wrong-physical-seat\",\n \"correct-dice-wrong-engine\",\n \"wrong-stream-identity\",\n \"wrong-pair-seed\",\n \"wrong-match-side\",\n ],\n )\n def test_publish_pair_rejects_terminal_and_ordered_journal_corruption(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n terminal_kinds = None\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n if case in {\"resignation-encoded-as-normal\", \"wrong-resignation-level\"}:\n games = [(\"O\", 2), (\"O\", 6)]\n terminal_kinds = [\"resignation\", \"ordinary_game_over\"]\n elif case == \"pass-encoded-as-resignation\":\n games = [(\"O\", 1), (\"O\", 6)]\n terminal_kinds = [\"drop\", \"ordinary_game_over\"]\n write_execution_fixture(execution, identity, games, terminal_kinds)\n match = execution / \"matches/A\"\n decision_path = match / \"decisions.jsonl\"\n decisions = [json.loads(line) for line in decision_path.read_text(encoding=\"utf-8\").splitlines()]\n terminal_index = next(\n index for index, record in enumerate(decisions)\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal = decisions[terminal_index][\"transition_evidence\"]\n \n if case == \"resignation-encoded-as-normal\":\n terminal[\"terminal_event\"][\"kind\"] = \"ordinary_game_over\"\n terminal[\"terminal_event\"].pop(\"resignation_level\")\n elif case == \"normal-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = terminal[\"terminal_event\"][\"result_level\"]\n elif case == \"pass-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = 1\n terminal[\"terminal_event\"].pop(\"loser_physical_seat\")\n terminal[\"terminal_event\"].pop(\"loser_engine\")\n elif case == \"wrong-resignation-level\":\n terminal[\"terminal_event\"][\"resignation_level\"] = 3\n elif case == \"wrong-command-type\":\n terminal[\"command_type\"] = \"accepted_resignation\"\n elif case == \"wrong-acting-seat\":\n terminal[\"acting_physical_seat\"] = \"X\" if terminal[\"acting_physical_seat\"] == \"O\" else \"O\"\n elif case == \"wrong-acting-engine\":\n terminal[\"acting_engine\"] = \"gnu\" if terminal[\"acting_engine\"] == \"sage\" else \"sage\"\n elif case == \"wrong-pre-command-score\":\n terminal[\"pre_command\"][\"score\"][0] += 1\n elif case == \"wrong-post-command-score\":\n terminal[\"post_command\"][\"score\"][0] += 1\n elif case == \"missing-subsequent-opening\":\n terminal[\"subsequent_opening_state\"] = None\n elif case == \"subsequent-opening-wrong-game\":\n terminal[\"subsequent_opening_state\"][\"game_number\"] += 1\n elif case == \"reordered-decision-records\":\n decisions[0], decisions[1] = decisions[1], decisions[0]\n \n dice_path = match / \"dice/seat_dice_consumption.jsonl\"\n dice_records = [json.loads(line) for line in dice_path.read_text(encoding=\"utf-8\").splitlines()]\n if case == \"reordered-dice-records\":\n dice_records[0], dice_records[1] = dice_records[1], dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"duplicated-dice-record\":\n dice_records.insert(1, dict(dice_records[0]))\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"missing-dice-record\":\n del dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"wrong-roll-index\":\n dice_records[0][\"roll_index\"] += 1\n elif case == \"correct-dice-wrong-physical-seat\":\n dice_records[0][\"physical_seat\"] = \"X\"\n elif case == \"correct-dice-wrong-engine\":\n dice_records[0][\"engine\"] = \"gnu\"\n elif case == \"wrong-stream-identity\":\n dice_records[0][\"stream_id\"] = \"stream-\" + \"0\" * 64\n elif case == \"wrong-pair-seed\":\n dice_records[0][\"base_seed\"] = \"sha256:\" + \"0\" * 64\n elif case == \"wrong-match-side\":\n dice_records[0][\"match_side\"] = \"B\"\n \n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n dice_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in dice_records),\n encoding=\"utf-8\",\n )\n dice_manifest_path = match / \"dice/seat_dice_manifest.json\"\n dice_manifest = json.loads(dice_manifest_path.read_text(encoding=\"utf-8\"))\n dice_manifest[\"consumption\"][\"entries\"] = len(dice_records)\n dice_manifest[\"consumption\"][\"sha256\"] = hashlib.sha256(dice_path.read_bytes()).hexdigest()\n write_json(dice_manifest_path, dice_manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:328: Failed\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[sgf-text-result-mismatch] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...on_rejects_incomplete_or_cross_format_native_game_evidence_sgf-text-result-mismatch_-0637b9087bfe48d7907c3c9c7aaad360')\ncase = 'sgf-text-result-mismatch'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n assert count == 1\n text_path.write_text(changed, encoding=\"utf-8\")\n elif case == \"root-only-sgf-with-result\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n trees[0] = re.sub(r\";[WB]\\[[^\\n]*\", \")\\n\", trees[0], count=1)\n sgf_path.write_text(\"\".join(trees), encoding=\"utf-8\")\n elif case == \"result-only-text-with-result\":\n first_result = text.index(\" Wins 2 points\")\n first_actions = text.index(\" 1)\", text.index(\" Game 1\"))\n text_path.write_text(text[:first_actions] + text[first_result:], encoding=\"utf-8\")\n elif case == \"reordered-actions\":\n row = re.search(r\"(?m)^(\\s*1\\) )(.{27}) (.+)$\", text)\n assert row is not None\n swapped = row.group(1) + f\"{row.group(3):<27} \" + row.group(2).strip()\n text_path.write_text(text[:row.start()] + swapped + text[row.end():], encoding=\"utf-8\")\n elif case == \"missing-move\":\n move = re.search(r\"([1-6]{2}: \\d+/\\d+) \\d+/\\d+\", text)\n assert move is not None\n text_path.write_text(text[:move.start()] + move.group(1) + text[move.end():], encoding=\"utf-8\")\n elif case == \"extra-move\":\n move = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text)\n assert move is not None\n text_path.write_text(text[:move.end()] + \" 13/10\" + text[move.end():], encoding=\"utf-8\")\n elif case == \"action-belongs-to-wrong-game\":\n game_two_action = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text[game_2:])\n game_one_action = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text)\n assert game_two_action is not None and game_one_action is not None\n transplanted = game_two_action.group(0)\n text_path.write_text(\n text[:game_one_action.start()] + transplanted + text[game_one_action.end():],\n encoding=\"utf-8\",\n )\n else: # pragma: no cover\n raise AssertionError(case)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:502: Failed\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[same-result-different-terminal-action] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...complete_or_cross_format_native_game_evidence_same-result-different-terminal-action_-5922da79947b4bd08d0a278dd9ad40e3')\ncase = 'same-result-different-terminal-action'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n> assert count == 1\nE assert 0 == 1\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:465: AssertionError\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[missing-move] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...st_publication_rejects_incomplete_or_cross_format_native_game_evidence_missing-move_-d5126c9c6e884b9c958af04ebfd1a132')\ncase = 'missing-move'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n assert count == 1\n text_path.write_text(changed, encoding=\"utf-8\")\n elif case == \"root-only-sgf-with-result\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n trees[0] = re.sub(r\";[WB]\\[[^\\n]*\", \")\\n\", trees[0], count=1)\n sgf_path.write_text(\"\".join(trees), encoding=\"utf-8\")\n elif case == \"result-only-text-with-result\":\n first_result = text.index(\" Wins 2 points\")\n first_actions = text.index(\" 1)\", text.index(\" Game 1\"))\n text_path.write_text(text[:first_actions] + text[first_result:], encoding=\"utf-8\")\n elif case == \"reordered-actions\":\n row = re.search(r\"(?m)^(\\s*1\\) )(.{27}) (.+)$\", text)\n assert row is not None\n swapped = row.group(1) + f\"{row.group(3):<27} \" + row.group(2).strip()\n text_path.write_text(text[:row.start()] + swapped + text[row.end():], encoding=\"utf-8\")\n elif case == \"missing-move\":\n move = re.search(r\"([1-6]{2}: \\d+/\\d+) \\d+/\\d+\", text)\n> assert move is not None\nE assert None is not None\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:482: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publish_pair_rejects_terminal_and_ordered_journal_corruption[normal-encoded-as-resignation]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publish_pair_rejects_terminal_and_ordered_journal_corruption[wrong-command-type]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[sgf-text-result-mismatch]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[same-result-different-terminal-action]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[missing-move]\n!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 5 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!\n5 failed, 49 passed in 13.47s\n","exit_code":1,"duration":{"secs":13,"nanos":727801903},"formatted_output":".........F..F........................F...........F...F\n=================================== FAILURES ===================================\n_ test_publish_pair_rejects_terminal_and_ordered_journal_corruption[normal-encoded-as-resignation] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te..._pair_rejects_terminal_and_ordered_journal_corruption_normal-encoded-as-resignation_-dcd741e94dbe4ac4a56183a558a627a8')\ncase = 'normal-encoded-as-resignation'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"resignation-encoded-as-normal\",\n \"normal-encoded-as-resignation\",\n \"pass-encoded-as-resignation\",\n \"wrong-resignation-level\",\n \"wrong-command-type\",\n \"wrong-acting-seat\",\n \"wrong-acting-engine\",\n \"wrong-pre-command-score\",\n \"wrong-post-command-score\",\n \"missing-subsequent-opening\",\n \"subsequent-opening-wrong-game\",\n \"reordered-decision-records\",\n \"reordered-dice-records\",\n \"duplicated-dice-record\",\n \"missing-dice-record\",\n \"wrong-roll-index\",\n \"correct-dice-wrong-physical-seat\",\n \"correct-dice-wrong-engine\",\n \"wrong-stream-identity\",\n \"wrong-pair-seed\",\n \"wrong-match-side\",\n ],\n )\n def test_publish_pair_rejects_terminal_and_ordered_journal_corruption(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n terminal_kinds = None\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n if case in {\"resignation-encoded-as-normal\", \"wrong-resignation-level\"}:\n games = [(\"O\", 2), (\"O\", 6)]\n terminal_kinds = [\"resignation\", \"ordinary_game_over\"]\n elif case == \"pass-encoded-as-resignation\":\n games = [(\"O\", 1), (\"O\", 6)]\n terminal_kinds = [\"drop\", \"ordinary_game_over\"]\n write_execution_fixture(execution, identity, games, terminal_kinds)\n match = execution / \"matches/A\"\n decision_path = match / \"decisions.jsonl\"\n decisions = [json.loads(line) for line in decision_path.read_text(encoding=\"utf-8\").splitlines()]\n terminal_index = next(\n index for index, record in enumerate(decisions)\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal = decisions[terminal_index][\"transition_evidence\"]\n \n if case == \"resignation-encoded-as-normal\":\n terminal[\"terminal_event\"][\"kind\"] = \"ordinary_game_over\"\n terminal[\"terminal_event\"].pop(\"resignation_level\")\n elif case == \"normal-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = terminal[\"terminal_event\"][\"result_level\"]\n elif case == \"pass-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = 1\n terminal[\"terminal_event\"].pop(\"loser_physical_seat\")\n terminal[\"terminal_event\"].pop(\"loser_engine\")\n elif case == \"wrong-resignation-level\":\n terminal[\"terminal_event\"][\"resignation_level\"] = 3\n elif case == \"wrong-command-type\":\n terminal[\"command_type\"] = \"accepted_resignation\"\n elif case == \"wrong-acting-seat\":\n terminal[\"acting_physical_seat\"] = \"X\" if terminal[\"acting_physical_seat\"] == \"O\" else \"O\"\n elif case == \"wrong-acting-engine\":\n terminal[\"acting_engine\"] = \"gnu\" if terminal[\"acting_engine\"] == \"sage\" else \"sage\"\n elif case == \"wrong-pre-command-score\":\n terminal[\"pre_command\"][\"score\"][0] += 1\n elif case == \"wrong-post-command-score\":\n terminal[\"post_command\"][\"score\"][0] += 1\n elif case == \"missing-subsequent-opening\":\n terminal[\"subsequent_opening_state\"] = None\n elif case == \"subsequent-opening-wrong-game\":\n terminal[\"subsequent_opening_state\"][\"game_number\"] += 1\n elif case == \"reordered-decision-records\":\n decisions[0], decisions[1] = decisions[1], decisions[0]\n \n dice_path = match / \"dice/seat_dice_consumption.jsonl\"\n dice_records = [json.loads(line) for line in dice_path.read_text(encoding=\"utf-8\").splitlines()]\n if case == \"reordered-dice-records\":\n dice_records[0], dice_records[1] = dice_records[1], dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"duplicated-dice-record\":\n dice_records.insert(1, dict(dice_records[0]))\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"missing-dice-record\":\n del dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"wrong-roll-index\":\n dice_records[0][\"roll_index\"] += 1\n elif case == \"correct-dice-wrong-physical-seat\":\n dice_records[0][\"physical_seat\"] = \"X\"\n elif case == \"correct-dice-wrong-engine\":\n dice_records[0][\"engine\"] = \"gnu\"\n elif case == \"wrong-stream-identity\":\n dice_records[0][\"stream_id\"] = \"stream-\" + \"0\" * 64\n elif case == \"wrong-pair-seed\":\n dice_records[0][\"base_seed\"] = \"sha256:\" + \"0\" * 64\n elif case == \"wrong-match-side\":\n dice_records[0][\"match_side\"] = \"B\"\n \n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n dice_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in dice_records),\n encoding=\"utf-8\",\n )\n dice_manifest_path = match / \"dice/seat_dice_manifest.json\"\n dice_manifest = json.loads(dice_manifest_path.read_text(encoding=\"utf-8\"))\n dice_manifest[\"consumption\"][\"entries\"] = len(dice_records)\n dice_manifest[\"consumption\"][\"sha256\"] = hashlib.sha256(dice_path.read_bytes()).hexdigest()\n write_json(dice_manifest_path, dice_manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:328: Failed\n_ test_publish_pair_rejects_terminal_and_ordered_journal_corruption[wrong-command-type] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...est_publish_pair_rejects_terminal_and_ordered_journal_corruption_wrong-command-type_-78b23d92fb0440cf84d6344c5eb9f085')\ncase = 'wrong-command-type'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"resignation-encoded-as-normal\",\n \"normal-encoded-as-resignation\",\n \"pass-encoded-as-resignation\",\n \"wrong-resignation-level\",\n \"wrong-command-type\",\n \"wrong-acting-seat\",\n \"wrong-acting-engine\",\n \"wrong-pre-command-score\",\n \"wrong-post-command-score\",\n \"missing-subsequent-opening\",\n \"subsequent-opening-wrong-game\",\n \"reordered-decision-records\",\n \"reordered-dice-records\",\n \"duplicated-dice-record\",\n \"missing-dice-record\",\n \"wrong-roll-index\",\n \"correct-dice-wrong-physical-seat\",\n \"correct-dice-wrong-engine\",\n \"wrong-stream-identity\",\n \"wrong-pair-seed\",\n \"wrong-match-side\",\n ],\n )\n def test_publish_pair_rejects_terminal_and_ordered_journal_corruption(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n terminal_kinds = None\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n if case in {\"resignation-encoded-as-normal\", \"wrong-resignation-level\"}:\n games = [(\"O\", 2), (\"O\", 6)]\n terminal_kinds = [\"resignation\", \"ordinary_game_over\"]\n elif case == \"pass-encoded-as-resignation\":\n games = [(\"O\", 1), (\"O\", 6)]\n terminal_kinds = [\"drop\", \"ordinary_game_over\"]\n write_execution_fixture(execution, identity, games, terminal_kinds)\n match = execution / \"matches/A\"\n decision_path = match / \"decisions.jsonl\"\n decisions = [json.loads(line) for line in decision_path.read_text(encoding=\"utf-8\").splitlines()]\n terminal_index = next(\n index for index, record in enumerate(decisions)\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal = decisions[terminal_index][\"transition_evidence\"]\n \n if case == \"resignation-encoded-as-normal\":\n terminal[\"terminal_event\"][\"kind\"] = \"ordinary_game_over\"\n terminal[\"terminal_event\"].pop(\"resignation_level\")\n elif case == \"normal-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = terminal[\"terminal_event\"][\"result_level\"]\n elif case == \"pass-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = 1\n terminal[\"terminal_event\"].pop(\"loser_physical_seat\")\n terminal[\"terminal_event\"].pop(\"loser_engine\")\n elif case == \"wrong-resignation-level\":\n terminal[\"terminal_event\"][\"resignation_level\"] = 3\n elif case == \"wrong-command-type\":\n terminal[\"command_type\"] = \"accepted_resignation\"\n elif case == \"wrong-acting-seat\":\n terminal[\"acting_physical_seat\"] = \"X\" if terminal[\"acting_physical_seat\"] == \"O\" else \"O\"\n elif case == \"wrong-acting-engine\":\n terminal[\"acting_engine\"] = \"gnu\" if terminal[\"acting_engine\"] == \"sage\" else \"sage\"\n elif case == \"wrong-pre-command-score\":\n terminal[\"pre_command\"][\"score\"][0] += 1\n elif case == \"wrong-post-command-score\":\n terminal[\"post_command\"][\"score\"][0] += 1\n elif case == \"missing-subsequent-opening\":\n terminal[\"subsequent_opening_state\"] = None\n elif case == \"subsequent-opening-wrong-game\":\n terminal[\"subsequent_opening_state\"][\"game_number\"] += 1\n elif case == \"reordered-decision-records\":\n decisions[0], decisions[1] = decisions[1], decisions[0]\n \n dice_path = match / \"dice/seat_dice_consumption.jsonl\"\n dice_records = [json.loads(line) for line in dice_path.read_text(encoding=\"utf-8\").splitlines()]\n if case == \"reordered-dice-records\":\n dice_records[0], dice_records[1] = dice_records[1], dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"duplicated-dice-record\":\n dice_records.insert(1, dict(dice_records[0]))\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"missing-dice-record\":\n del dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"wrong-roll-index\":\n dice_records[0][\"roll_index\"] += 1\n elif case == \"correct-dice-wrong-physical-seat\":\n dice_records[0][\"physical_seat\"] = \"X\"\n elif case == \"correct-dice-wrong-engine\":\n dice_records[0][\"engine\"] = \"gnu\"\n elif case == \"wrong-stream-identity\":\n dice_records[0][\"stream_id\"] = \"stream-\" + \"0\" * 64\n elif case == \"wrong-pair-seed\":\n dice_records[0][\"base_seed\"] = \"sha256:\" + \"0\" * 64\n elif case == \"wrong-match-side\":\n dice_records[0][\"match_side\"] = \"B\"\n \n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n dice_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in dice_records),\n encoding=\"utf-8\",\n )\n dice_manifest_path = match / \"dice/seat_dice_manifest.json\"\n dice_manifest = json.loads(dice_manifest_path.read_text(encoding=\"utf-8\"))\n dice_manifest[\"consumption\"][\"entries\"] = len(dice_records)\n dice_manifest[\"consumption\"][\"sha256\"] = hashlib.sha256(dice_path.read_bytes()).hexdigest()\n write_json(dice_manifest_path, dice_manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:328: Failed\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[sgf-text-result-mismatch] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...on_rejects_incomplete_or_cross_format_native_game_evidence_sgf-text-result-mismatch_-0637b9087bfe48d7907c3c9c7aaad360')\ncase = 'sgf-text-result-mismatch'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n assert count == 1\n text_path.write_text(changed, encoding=\"utf-8\")\n elif case == \"root-only-sgf-with-result\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n trees[0] = re.sub(r\";[WB]\\[[^\\n]*\", \")\\n\", trees[0], count=1)\n sgf_path.write_text(\"\".join(trees), encoding=\"utf-8\")\n elif case == \"result-only-text-with-result\":\n first_result = text.index(\" Wins 2 points\")\n first_actions = text.index(\" 1)\", text.index(\" Game 1\"))\n text_path.write_text(text[:first_actions] + text[first_result:], encoding=\"utf-8\")\n elif case == \"reordered-actions\":\n row = re.search(r\"(?m)^(\\s*1\\) )(.{27}) (.+)$\", text)\n assert row is not None\n swapped = row.group(1) + f\"{row.group(3):<27} \" + row.group(2).strip()\n text_path.write_text(text[:row.start()] + swapped + text[row.end():], encoding=\"utf-8\")\n elif case == \"missing-move\":\n move = re.search(r\"([1-6]{2}: \\d+/\\d+) \\d+/\\d+\", text)\n assert move is not None\n text_path.write_text(text[:move.start()] + move.group(1) + text[move.end():], encoding=\"utf-8\")\n elif case == \"extra-move\":\n move = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text)\n assert move is not None\n text_path.write_text(text[:move.end()] + \" 13/10\" + text[move.end():], encoding=\"utf-8\")\n elif case == \"action-belongs-to-wrong-game\":\n game_two_action = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text[game_2:])\n game_one_action = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text)\n assert game_two_action is not None and game_one_action is not None\n transplanted = game_two_action.group(0)\n text_path.write_text(\n text[:game_one_action.start()] + transplanted + text[game_one_action.end():],\n encoding=\"utf-8\",\n )\n else: # pragma: no cover\n raise AssertionError(case)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:502: Failed\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[same-result-different-terminal-action] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...complete_or_cross_format_native_game_evidence_same-result-different-terminal-action_-5922da79947b4bd08d0a278dd9ad40e3')\ncase = 'same-result-different-terminal-action'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n> assert count == 1\nE assert 0 == 1\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:465: AssertionError\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[missing-move] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...st_publication_rejects_incomplete_or_cross_format_native_game_evidence_missing-move_-d5126c9c6e884b9c958af04ebfd1a132')\ncase = 'missing-move'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n assert count == 1\n text_path.write_text(changed, encoding=\"utf-8\")\n elif case == \"root-only-sgf-with-result\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n trees[0] = re.sub(r\";[WB]\\[[^\\n]*\", \")\\n\", trees[0], count=1)\n sgf_path.write_text(\"\".join(trees), encoding=\"utf-8\")\n elif case == \"result-only-text-with-result\":\n first_result = text.index(\" Wins 2 points\")\n first_actions = text.index(\" 1)\", text.index(\" Game 1\"))\n text_path.write_text(text[:first_actions] + text[first_result:], encoding=\"utf-8\")\n elif case == \"reordered-actions\":\n row = re.search(r\"(?m)^(\\s*1\\) )(.{27}) (.+)$\", text)\n assert row is not None\n swapped = row.group(1) + f\"{row.group(3):<27} \" + row.group(2).strip()\n text_path.write_text(text[:row.start()] + swapped + text[row.end():], encoding=\"utf-8\")\n elif case == \"missing-move\":\n move = re.search(r\"([1-6]{2}: \\d+/\\d+) \\d+/\\d+\", text)\n> assert move is not None\nE assert None is not None\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:482: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publish_pair_rejects_terminal_and_ordered_journal_corruption[normal-encoded-as-resignation]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publish_pair_rejects_terminal_and_ordered_journal_corruption[wrong-command-type]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[sgf-text-result-mismatch]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[same-result-different-terminal-action]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[missing-move]\n!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 5 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!\n5 failed, 49 passed in 13.47s\n"},"started_at_ms":1787492267637,"completed_at_ms":1787492281366}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:38:01.386Z","ordinal":330,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ed7-ac29-7802-b89e-0603b4bffcdc","call_id":"call_eyV7aCysXqTfWcBGtlTcItS7","output":[{"type":"input_text","text":"Script completed\nWall time 13.9 seconds\nOutput:\n"},{"type":"input_text","text":".........F..F........................F...........F...F\n=================================== FAILURES ===================================\n_ test_publish_pair_rejects_terminal_and_ordered_journal_corruption[normal-encoded-as-resignation] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te..._pair_rejects_terminal_and_ordered_journal_corruption_normal-encoded-as-resignation_-dcd741e94dbe4ac4a56183a558a627a8')\ncase = 'normal-encoded-as-resignation'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"resignation-encoded-as-normal\",\n \"normal-encoded-as-resignation\",\n \"pass-encoded-as-resignation\",\n \"wrong-resignation-level\",\n \"wrong-command-type\",\n \"wrong-acting-seat\",\n \"wrong-acting-engine\",\n \"wrong-pre-command-score\",\n \"wrong-post-command-score\",\n \"missing-subsequent-opening\",\n \"subsequent-opening-wrong-game\",\n \"reordered-decision-records\",\n \"reordered-dice-records\",\n \"duplicated-dice-record\",\n \"missing-dice-record\",\n \"wrong-roll-index\",\n \"correct-dice-wrong-physical-seat\",\n \"correct-dice-wrong-engine\",\n \"wrong-stream-identity\",\n \"wrong-pair-seed\",\n \"wrong-match-side\",\n ],\n )\n def test_publish_pair_rejects_terminal_and_ordered_journal_corruption(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n terminal_kinds = None\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n if case in {\"resignation-encoded-as-normal\", \"wrong-resignation-level\"}:\n games = [(\"O\", 2), (\"O\", 6)]\n terminal_kinds = [\"resignation\", \"ordinary_game_over\"]\n elif case == \"pass-encoded-as-resignation\":\n games = [(\"O\", 1), (\"O\", 6)]\n terminal_kinds = [\"drop\", \"ordinary_game_over\"]\n write_execution_fixture(execution, identity, games, terminal_kinds)\n match = execution / \"matches/A\"\n decision_path = match / \"decisions.jsonl\"\n decisions = [json.loads(line) for line in decision_path.read_text(encoding=\"utf-8\").splitlines()]\n terminal_index = next(\n index for index, record in enumerate(decisions)\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal = decisions[terminal_index][\"transition_evidence\"]\n \n if case == \"resignation-encoded-as-normal\":\n terminal[\"terminal_event\"][\"kind\"] = \"ordinary_game_over\"\n terminal[\"terminal_event\"].pop(\"resignation_level\")\n elif case == \"normal-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = terminal[\"terminal_event\"][\"result_level\"]\n elif case == \"pass-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = 1\n terminal[\"terminal_event\"].pop(\"loser_physical_seat\")\n terminal[\"terminal_event\"].pop(\"loser_engine\")\n elif case == \"wrong-resignation-level\":\n terminal[\"terminal_event\"][\"resignation_level\"] = 3\n elif case == \"wrong-command-type\":\n terminal[\"command_type\"] = \"accepted_resignation\"\n elif case == \"wrong-acting-seat\":\n terminal[\"acting_physical_seat\"] = \"X\" if terminal[\"acting_physical_seat\"] == \"O\" else \"O\"\n elif case == \"wrong-acting-engine\":\n terminal[\"acting_engine\"] = \"gnu\" if terminal[\"acting_engine\"] == \"sage\" else \"sage\"\n elif case == \"wrong-pre-command-score\":\n terminal[\"pre_command\"][\"score\"][0] += 1\n elif case == \"wrong-post-command-score\":\n terminal[\"post_command\"][\"score\"][0] += 1\n elif case == \"missing-subsequent-opening\":\n terminal[\"subsequent_opening_state\"] = None\n elif case == \"subsequent-opening-wrong-game\":\n terminal[\"subsequent_opening_state\"][\"game_number\"] += 1\n elif case == \"reordered-decision-records\":\n decisions[0], decisions[1] = decisions[1], decisions[0]\n \n dice_path = match / \"dice/seat_dice_consumption.jsonl\"\n dice_records = [json.loads(line) for line in dice_path.read_text(encoding=\"utf-8\").splitlines()]\n if case == \"reordered-dice-records\":\n dice_records[0], dice_records[1] = dice_records[1], dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"duplicated-dice-record\":\n dice_records.insert(1, dict(dice_records[0]))\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"missing-dice-record\":\n del dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"wrong-roll-index\":\n dice_records[0][\"roll_index\"] += 1\n elif case == \"correct-dice-wrong-physical-seat\":\n dice_records[0][\"physical_seat\"] = \"X\"\n elif case == \"correct-dice-wrong-engine\":\n dice_records[0][\"engine\"] = \"gnu\"\n elif case == \"wrong-stream-identity\":\n dice_records[0][\"stream_id\"] = \"stream-\" + \"0\" * 64\n elif case == \"wrong-pair-seed\":\n dice_records[0][\"base_seed\"] = \"sha256:\" + \"0\" * 64\n elif case == \"wrong-match-side\":\n dice_records[0][\"match_side\"] = \"B\"\n \n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n dice_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in dice_records),\n encoding=\"utf-8\",\n )\n dice_manifest_path = match / \"dice/seat_dice_manifest.json\"\n dice_manifest = json.loads(dice_manifest_path.read_text(encoding=\"utf-8\"))\n dice_manifest[\"consumption\"][\"entries\"] = len(dice_records)\n dice_manifest[\"consumption\"][\"sha256\"] = hashlib.sha256(dice_path.read_bytes()).hexdigest()\n write_json(dice_manifest_path, dice_manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:328: Failed\n_ test_publish_pair_rejects_terminal_and_ordered_journal_corruption[wrong-command-type] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...est_publish_pair_rejects_terminal_and_ordered_journal_corruption_wrong-command-type_-78b23d92fb0440cf84d6344c5eb9f085')\ncase = 'wrong-command-type'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"resignation-encoded-as-normal\",\n \"normal-encoded-as-resignation\",\n \"pass-encoded-as-resignation\",\n \"wrong-resignation-level\",\n \"wrong-command-type\",\n \"wrong-acting-seat\",\n \"wrong-acting-engine\",\n \"wrong-pre-command-score\",\n \"wrong-post-command-score\",\n \"missing-subsequent-opening\",\n \"subsequent-opening-wrong-game\",\n \"reordered-decision-records\",\n \"reordered-dice-records\",\n \"duplicated-dice-record\",\n \"missing-dice-record\",\n \"wrong-roll-index\",\n \"correct-dice-wrong-physical-seat\",\n \"correct-dice-wrong-engine\",\n \"wrong-stream-identity\",\n \"wrong-pair-seed\",\n \"wrong-match-side\",\n ],\n )\n def test_publish_pair_rejects_terminal_and_ordered_journal_corruption(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n terminal_kinds = None\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n if case in {\"resignation-encoded-as-normal\", \"wrong-resignation-level\"}:\n games = [(\"O\", 2), (\"O\", 6)]\n terminal_kinds = [\"resignation\", \"ordinary_game_over\"]\n elif case == \"pass-encoded-as-resignation\":\n games = [(\"O\", 1), (\"O\", 6)]\n terminal_kinds = [\"drop\", \"ordinary_game_over\"]\n write_execution_fixture(execution, identity, games, terminal_kinds)\n match = execution / \"matches/A\"\n decision_path = match / \"decisions.jsonl\"\n decisions = [json.loads(line) for line in decision_path.read_text(encoding=\"utf-8\").splitlines()]\n terminal_index = next(\n index for index, record in enumerate(decisions)\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal = decisions[terminal_index][\"transition_evidence\"]\n \n if case == \"resignation-encoded-as-normal\":\n terminal[\"terminal_event\"][\"kind\"] = \"ordinary_game_over\"\n terminal[\"terminal_event\"].pop(\"resignation_level\")\n elif case == \"normal-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = terminal[\"terminal_event\"][\"result_level\"]\n elif case == \"pass-encoded-as-resignation\":\n terminal[\"terminal_event\"][\"kind\"] = \"resignation\"\n terminal[\"terminal_event\"][\"resignation_level\"] = 1\n terminal[\"terminal_event\"].pop(\"loser_physical_seat\")\n terminal[\"terminal_event\"].pop(\"loser_engine\")\n elif case == \"wrong-resignation-level\":\n terminal[\"terminal_event\"][\"resignation_level\"] = 3\n elif case == \"wrong-command-type\":\n terminal[\"command_type\"] = \"accepted_resignation\"\n elif case == \"wrong-acting-seat\":\n terminal[\"acting_physical_seat\"] = \"X\" if terminal[\"acting_physical_seat\"] == \"O\" else \"O\"\n elif case == \"wrong-acting-engine\":\n terminal[\"acting_engine\"] = \"gnu\" if terminal[\"acting_engine\"] == \"sage\" else \"sage\"\n elif case == \"wrong-pre-command-score\":\n terminal[\"pre_command\"][\"score\"][0] += 1\n elif case == \"wrong-post-command-score\":\n terminal[\"post_command\"][\"score\"][0] += 1\n elif case == \"missing-subsequent-opening\":\n terminal[\"subsequent_opening_state\"] = None\n elif case == \"subsequent-opening-wrong-game\":\n terminal[\"subsequent_opening_state\"][\"game_number\"] += 1\n elif case == \"reordered-decision-records\":\n decisions[0], decisions[1] = decisions[1], decisions[0]\n \n dice_path = match / \"dice/seat_dice_consumption.jsonl\"\n dice_records = [json.loads(line) for line in dice_path.read_text(encoding=\"utf-8\").splitlines()]\n if case == \"reordered-dice-records\":\n dice_records[0], dice_records[1] = dice_records[1], dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"duplicated-dice-record\":\n dice_records.insert(1, dict(dice_records[0]))\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"missing-dice-record\":\n del dice_records[0]\n for ordinal, record in enumerate(dice_records, 1):\n record[\"consumption_ordinal\"] = ordinal\n elif case == \"wrong-roll-index\":\n dice_records[0][\"roll_index\"] += 1\n elif case == \"correct-dice-wrong-physical-seat\":\n dice_records[0][\"physical_seat\"] = \"X\"\n elif case == \"correct-dice-wrong-engine\":\n dice_records[0][\"engine\"] = \"gnu\"\n elif case == \"wrong-stream-identity\":\n dice_records[0][\"stream_id\"] = \"stream-\" + \"0\" * 64\n elif case == \"wrong-pair-seed\":\n dice_records[0][\"base_seed\"] = \"sha256:\" + \"0\" * 64\n elif case == \"wrong-match-side\":\n dice_records[0][\"match_side\"] = \"B\"\n \n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n dice_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in dice_records),\n encoding=\"utf-8\",\n )\n dice_manifest_path = match / \"dice/seat_dice_manifest.json\"\n dice_manifest = json.loads(dice_manifest_path.read_text(encoding=\"utf-8\"))\n dice_manifest[\"consumption\"][\"entries\"] = len(dice_records)\n dice_manifest[\"consumption\"][\"sha256\"] = hashlib.sha256(dice_path.read_bytes()).hexdigest()\n write_json(dice_manifest_path, dice_manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:328: Failed\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[sgf-text-result-mismatch] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...on_rejects_incomplete_or_cross_format_native_game_evidence_sgf-text-result-mismatch_-0637b9087bfe48d7907c3c9c7aaad360')\ncase = 'sgf-text-result-mismatch'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n assert count == 1\n text_path.write_text(changed, encoding=\"utf-8\")\n elif case == \"root-only-sgf-with-result\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n trees[0] = re.sub(r\";[WB]\\[[^\\n]*\", \")\\n\", trees[0], count=1)\n sgf_path.write_text(\"\".join(trees), encoding=\"utf-8\")\n elif case == \"result-only-text-with-result\":\n first_result = text.index(\" Wins 2 points\")\n first_actions = text.index(\" 1)\", text.index(\" Game 1\"))\n text_path.write_text(text[:first_actions] + text[first_result:], encoding=\"utf-8\")\n elif case == \"reordered-actions\":\n row = re.search(r\"(?m)^(\\s*1\\) )(.{27}) (.+)$\", text)\n assert row is not None\n swapped = row.group(1) + f\"{row.group(3):<27} \" + row.group(2).strip()\n text_path.write_text(text[:row.start()] + swapped + text[row.end():], encoding=\"utf-8\")\n elif case == \"missing-move\":\n move = re.search(r\"([1-6]{2}: \\d+/\\d+) \\d+/\\d+\", text)\n assert move is not None\n text_path.write_text(text[:move.start()] + move.group(1) + text[move.end():], encoding=\"utf-8\")\n elif case == \"extra-move\":\n move = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text)\n assert move is not None\n text_path.write_text(text[:move.end()] + \" 13/10\" + text[move.end():], encoding=\"utf-8\")\n elif case == \"action-belongs-to-wrong-game\":\n game_two_action = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text[game_2:])\n game_one_action = re.search(r\"[1-6]{2}: \\d+/\\d+ \\d+/\\d+\", text)\n assert game_two_action is not None and game_one_action is not None\n transplanted = game_two_action.group(0)\n text_path.write_text(\n text[:game_one_action.start()] + transplanted + text[game_one_action.end():],\n encoding=\"utf-8\",\n )\n else: # pragma: no cover\n raise AssertionError(case)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError):\nE Failed: DID NOT RAISE MatchExecutionError\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:502: Failed\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[same-result-different-terminal-action] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...complete_or_cross_format_native_game_evidence_same-result-different-terminal-action_-5922da79947b4bd08d0a278dd9ad40e3')\ncase = 'same-result-different-terminal-action'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n> assert count == 1\nE assert 0 == 1\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:465: AssertionError\n_ test_publication_rejects_incomplete_or_cross_format_native_game_evidence[missing-move] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...st_publication_rejects_incomplete_or_cross_format_native_game_evidence_missing-move_-d5126c9c6e884b9c958af04ebfd1a132')\ncase = 'missing-move'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"text-truncated-after-game-1\",\n \"text-missing-final-game\",\n \"text-missing-middle-game\",\n \"sgf-one-text-multi\",\n \"sgf-multi-text-one\",\n \"sgf-missing-middle-game\",\n \"sgf-ordering-gap\",\n \"sgf-duplicated-game\",\n \"sgf-text-result-mismatch\",\n \"sgf-text-score-progression-mismatch\",\n \"duplicated-game-block\",\n \"preamble-identity\",\n \"trailer-identity\",\n \"dice-game-count-mismatch\",\n \"decision-game-count-mismatch\",\n \"decision-result-mismatch\",\n \"match-manifest-result-mismatch\",\n \"same-result-different-checker-move\",\n \"same-result-different-dice-sequence\",\n \"same-result-different-cube-action\",\n \"same-result-different-terminal-action\",\n \"root-only-sgf-with-result\",\n \"result-only-text-with-result\",\n \"reordered-actions\",\n \"missing-move\",\n \"extra-move\",\n \"action-belongs-to-wrong-game\",\n ],\n )\n def test_publication_rejects_incomplete_or_cross_format_native_game_evidence(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n games = [(\"O\", 2), (\"X\", 1), (\"O\", 6)]\n write_execution_fixture(execution, identity, games)\n match = execution / \"matches/A\"\n sgf_path = match / \"native/match.sgf\"\n text_path = match / \"native/match.txt\"\n text = text_path.read_text(encoding=\"utf-8\")\n mapping = {\"O\": \"sage\", \"X\": \"gnu\"}\n one_sgf, one_text, _ = native_documents(mapping, [(\"O\", 8)])\n \n game_2 = text.index(\"\\n Game 2\")\n game_3 = text.index(\"\\n Game 3\")\n if case == \"text-truncated-after-game-1\":\n text_path.write_text(text[:game_2], encoding=\"utf-8\")\n elif case == \"text-missing-final-game\":\n text_path.write_text(text[:game_3], encoding=\"utf-8\")\n elif case == \"text-missing-middle-game\":\n text_path.write_text(text[:game_2] + text[game_3:], encoding=\"utf-8\")\n elif case == \"sgf-one-text-multi\":\n sgf_path.write_text(one_sgf, encoding=\"utf-8\")\n elif case == \"sgf-multi-text-one\":\n text_path.write_text(one_text, encoding=\"utf-8\")\n elif case == \"sgf-missing-middle-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-ordering-gap\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"[game:1]\", \"[game:3]\", 1),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-duplicated-game\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n sgf_path.write_text(trees[0] + trees[1] + trees[1] + trees[2], encoding=\"utf-8\")\n elif case == \"sgf-text-result-mismatch\":\n sgf_path.write_text(\n sgf_path.read_text(encoding=\"utf-8\").replace(\"RE[W+6]\", \"RE[W+5]\"),\n encoding=\"utf-8\",\n )\n elif case == \"sgf-text-score-progression-mismatch\":\n text_path.write_text(text.replace(\"sage_seat_O : 2\", \"sage_seat_O : 3\", 1), encoding=\"utf-8\")\n elif case == \"duplicated-game-block\":\n text_path.write_text(text[:game_3] + text[game_2:game_3] + text[game_3:], encoding=\"utf-8\")\n elif case == \"preamble-identity\":\n text_path.write_text(\"sage_seat_O : 0\\n\" + text, encoding=\"utf-8\")\n elif case == \"trailer-identity\":\n text_path.write_text(text + \"\\ngnu_seat_X : 1\\n\", encoding=\"utf-8\")\n elif case == \"dice-game-count-mismatch\":\n consumption = match / \"dice/seat_dice_consumption.jsonl\"\n records = [json.loads(line) for line in consumption.read_text(encoding=\"utf-8\").splitlines()]\n consumption.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 3),\n encoding=\"utf-8\",\n )\n elif case == \"decision-game-count-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records if record[\"game_number\"] != 2),\n encoding=\"utf-8\",\n )\n elif case == \"decision-result-mismatch\":\n decisions = match / \"decisions.jsonl\"\n records = [json.loads(line) for line in decisions.read_text(encoding=\"utf-8\").splitlines()]\n terminal_record = next(\n record for record in records\n if record[\"transition_evidence\"][\"terminal_event\"] is not None\n )\n terminal_record[\"transition_evidence\"][\"terminal_event\"][\"points\"] += 1\n decisions.write_text(\n \"\".join(json.dumps(record) + \"\\n\" for record in records), encoding=\"utf-8\"\n )\n elif case == \"match-manifest-result-mismatch\":\n manifest_path = match / \"match_manifest.json\"\n manifest = json.loads(manifest_path.read_text(encoding=\"utf-8\"))\n manifest[\"native_evidence\"][\"games\"][0][\"points\"] = 3\n write_json(manifest_path, manifest)\n elif case == \"same-result-different-checker-move\":\n move = re.search(r\"[1-6]{2}: (\\d+)/(\\d+)\", text)\n assert move is not None\n replacement = f\"{move.group(1)}/{1 if move.group(2) != '1' else 2}\"\n text_path.write_text(text[:move.start(1)] + replacement + text[move.end(2):], encoding=\"utf-8\")\n elif case == \"same-result-different-dice-sequence\":\n dice = re.search(r\"([1-6])([1-6]):\", text)\n assert dice is not None\n changed = \"1\" if dice.group(1) != \"1\" and dice.group(2) != \"1\" else \"6\"\n text_path.write_text(text[:dice.start(1)] + changed + text[dice.end(1):], encoding=\"utf-8\")\n elif case == \"same-result-different-cube-action\":\n text_path.write_text(text.replace(\"Doubles => 2 Takes\", \"Doubles => 2 Drops\", 1), encoding=\"utf-8\")\n elif case == \"same-result-different-terminal-action\":\n changed, count = re.subn(\n r\"[1-6]{2}: [^\\n]+(?=\\n\\s+Wins 2 points)\", \"Drops\", text, count=1\n )\n assert count == 1\n text_path.write_text(changed, encoding=\"utf-8\")\n elif case == \"root-only-sgf-with-result\":\n trees = sgf_path.read_text(encoding=\"utf-8\").splitlines(keepends=True)\n trees[0] = re.sub(r\";[WB]\\[[^\\n]*\", \")\\n\", trees[0], count=1)\n sgf_path.write_text(\"\".join(trees), encoding=\"utf-8\")\n elif case == \"result-only-text-with-result\":\n first_result = text.index(\" Wins 2 points\")\n first_actions = text.index(\" 1)\", text.index(\" Game 1\"))\n text_path.write_text(text[:first_actions] + text[first_result:], encoding=\"utf-8\")\n elif case == \"reordered-actions\":\n row = re.search(r\"(?m)^(\\s*1\\) )(.{27}) (.+)$\", text)\n assert row is not None\n swapped = row.group(1) + f\"{row.group(3):<27} \" + row.group(2).strip()\n text_path.write_text(text[:row.start()] + swapped + text[row.end():], encoding=\"utf-8\")\n elif case == \"missing-move\":\n move = re.search(r\"([1-6]{2}: \\d+/\\d+) \\d+/\\d+\", text)\n> assert move is not None\nE assert None is not None\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:482: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publish_pair_rejects_terminal_and_ordered_journal_corruption[normal-encoded-as-resignation]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publish_pair_rejects_terminal_and_ordered_journal_corruption[wrong-command-type]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[sgf-text-result-mismatch]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[same-result-different-terminal-action]\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_incomplete_or_cross_format_native_game_evidence[missing-move]\n!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 5 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!\n5 failed, 49 passed in 13.47s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492281.3856323}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:39:36.081Z","ordinal":353,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-018841b7-436d-4a86-9897-7074abbfbb98","process_id":"73348","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py -k 'connected_decision_state_machine or terminal_and_ordered_journal_corruption or incomplete_or_cross_format' --disable-warnings --maxfail=5"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py -k 'connected_decision_state_machine or terminal_and_ordered_journal_corruption or incomplete_or_cross_format' --disable-warnings --maxfail=5"}],"source":"unified_exec_startup","status":"completed","stdout":"............................................................ [100%]\n60 passed, 15 deselected in 14.77s\n","stderr":"","aggregated_output":"............................................................ [100%]\n60 passed, 15 deselected in 14.77s\n","exit_code":0,"duration":{"secs":15,"nanos":161725110},"formatted_output":"............................................................ [100%]\n60 passed, 15 deselected in 14.77s\n"},"started_at_ms":1787492360919,"completed_at_ms":1787492376081}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:39:36.092Z","ordinal":354,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ed9-1e1c-7522-8e8a-ffafba04d839","call_id":"call_qur4mvyDqmHL7aslNJv72iyE","output":[{"type":"input_text","text":"Script completed\nWall time 15.4 seconds\nOutput:\n"},{"type":"input_text","text":"............................................................ [100%]\n60 passed, 15 deselected in 14.77s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492376.0925415}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:39:59.225Z","ordinal":359,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-95d0fd07-3c4f-4413-8a00-d8714b1a967c","process_id":"52157","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=3"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=3"}],"source":"unified_exec_startup","status":"completed","stdout":"........................................................................ [ 35%]\n........................................................................ [ 70%]\n........................................................... [100%]\n203 passed in 17.56s\n","stderr":"","aggregated_output":"........................................................................ [ 35%]\n........................................................................ [ 70%]\n........................................................... [100%]\n203 passed in 17.56s\n","exit_code":0,"duration":{"secs":17,"nanos":775677633},"formatted_output":"........................................................................ [ 35%]\n........................................................................ [ 70%]\n........................................................... [100%]\n203 passed in 17.56s\n"},"started_at_ms":1787492381449,"completed_at_ms":1787492399225}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:39:59.235Z","ordinal":360,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ed9-7883-74e2-9086-1c7c24763745","call_id":"call_DQIRcYnOXYuHraHSSnhDwjv1","output":[{"type":"input_text","text":"Script completed\nWall time 18.0 seconds\nOutput:\n"},{"type":"input_text","text":"........................................................................ [ 35%]\n........................................................................ [ 70%]\n........................................................... [100%]\n203 passed in 17.56s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492399.235731}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:43:43.780Z","ordinal":465,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-96486d4a-7bc9-493b-b2a5-f84d5912cce4","process_id":"78245","command":["/usr/bin/bash","-lc","python3 -m py_compile runner/sage_gnu_campaign/match.py tests/sage_gnu_campaign/native_fixtures.py\n/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_committed_pair_is_verified_and_never_regenerated tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_accepts_complete_real_shaped_multi_game_native_evidence --disable-warnings --maxfail=1"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"python3 -m py_compile runner/sage_gnu_campaign/match.py tests/sage_gnu_campaign/native_fixtures.py\n/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_committed_pair_is_verified_and_never_regenerated tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_accepts_complete_real_shaped_multi_game_native_evidence --disable-warnings --maxfail=1"}],"source":"unified_exec_startup","status":"completed","stdout":".. [100%]\n2 passed in 7.44s\n","stderr":"","aggregated_output":".. [100%]\n2 passed in 7.44s\n","exit_code":0,"duration":{"secs":7,"nanos":801810642},"formatted_output":".. [100%]\n2 passed in 7.44s\n"},"started_at_ms":1787492615978,"completed_at_ms":1787492623780}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:43:43.790Z","ordinal":466,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02edc-e5ae-7f02-8a0b-5488d109e032","call_id":"call_Y0vKORsMzOeWYYHBcxDaQGts","output":[{"type":"input_text","text":"Script completed\nWall time 8.0 seconds\nOutput:\n"},{"type":"input_text","text":".. [100%]\n2 passed in 7.44s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492623.790641}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:44:46.073Z","ordinal":479,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-d0f3cc07-28f8-43d3-ac2a-7ff07bee1903","process_id":"66527","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_engine_kit_boundary.py tests/sage_gnu_campaign/test_ledger_and_publication.py -k 'authoritative_analysis_journals or committed_pair_is_verified or real_session_boundary or primitive_malformed' --disable-warnings --maxfail=3"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_engine_kit_boundary.py tests/sage_gnu_campaign/test_ledger_and_publication.py -k 'authoritative_analysis_journals or committed_pair_is_verified or real_session_boundary or primitive_malformed' --disable-warnings --maxfail=3"}],"source":"unified_exec_startup","status":"failed","stdout":"....................F........ [100%]\n=================================== FAILURES ===================================\n_ test_publication_requires_authoritative_analysis_journals[wrong-configured-depth] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...py_test_publication_requires_authoritative_analysis_journals_wrong-configured-depth_-87399d0648d242bc900cd9df90c1bd73')\ncase = 'wrong-configured-depth'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"missing-request\", \"missing-result\", \"duplicate\", \"reordered\",\n \"wrong-decision-association\", \"wrong-engine\", \"wrong-seat\", \"wrong-gnuid\",\n \"wrong-type\", \"malformed-result\", \"wrong-configured-depth\",\n \"invalid-actual-depth\", \"cross-side\", \"cross-game\", \"cross-pair\",\n \"missing-raw-source\", \"malformed-candidates\", \"wrong-manifest-path\",\n \"manifest-file-hash-mismatch\",\n ],\n )\n def test_publication_requires_authoritative_analysis_journals(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n write_execution_fixture(execution, identity)\n match = execution / \"matches/A\"\n request_path = match / \"analysis_requests.jsonl\"\n result_path = match / \"analysis_results.jsonl\"\n decision_path = match / \"decisions.jsonl\"\n manifest_path = match / \"match_manifest.json\"\n requests = [json.loads(line) for line in request_path.read_text().splitlines()]\n results = [json.loads(line) for line in result_path.read_text().splitlines()]\n decisions = [json.loads(line) for line in decision_path.read_text().splitlines()]\n \n if case == \"missing-request\":\n requests.pop()\n elif case == \"missing-result\":\n results.pop()\n elif case == \"duplicate\":\n requests.insert(1, dict(requests[0]))\n results.insert(1, dict(results[0]))\n elif case == \"reordered\":\n requests[0], requests[1] = requests[1], requests[0]\n results[0], results[1] = results[1], results[0]\n elif case == \"wrong-decision-association\":\n requests[0][\"decision_ordinal\"] = requests[1][\"decision_ordinal\"]\n results[0][\"decision_ordinal\"] = results[1][\"decision_ordinal\"]\n elif case in {\"wrong-engine\", \"wrong-seat\", \"wrong-gnuid\", \"wrong-type\"}:\n key, value = {\n \"wrong-engine\": (\"engine\", \"gnu\"),\n \"wrong-seat\": (\"physical_seat\", \"X\"),\n \"wrong-gnuid\": (\"gnuid\", decisions[-1][\"gnuid\"]),\n \"wrong-type\": (\"decision_type\", \"cube\"),\n }[case]\n requests[0][key] = value\n results[0][key] = value\n elif case == \"malformed-result\":\n results[0][\"returned_result\"] = \"malformed\"\n elif case == \"wrong-configured-depth\":\n results[0][\"returned_result\"][\"engine\"][\"analysis_setting\"] = \"2ply\"\n elif case == \"invalid-actual-depth\":\n raw = results[0][\"returned_result\"]\n raw[\"checker_decision\"][\"actual_ply\"] = -1\n raw[\"checker_decision\"][\"candidates\"][0][\"actual_ply\"] = -1\n validated = decisions[0][\"engine_kit_result\"]\n validated[\"checker_decision\"] = raw[\"checker_decision\"]\n validated[\"campaign_depth_evidence\"][\"recommended_actual_ply\"] = -1\n validated[\"campaign_depth_evidence\"][\"candidate_actual_plies\"] = [-1]\n elif case in {\"cross-side\", \"cross-game\", \"cross-pair\"}:\n key, value = {\n \"cross-side\": (\"match_side\", \"B\"),\n \"cross-game\": (\"game_number\", 2),\n \"cross-pair\": (\"pair_id\", \"pair-\" + \"0\" * 24),\n }[case]\n requests[0][key] = value\n results[0][key] = value\n elif case == \"missing-raw-source\":\n results[0][\"returned_result\"].pop(\"raw_source\")\n decisions[0][\"engine_kit_result\"].pop(\"raw_source\")\n elif case == \"malformed-candidates\":\n raw = results[0][\"returned_result\"]\n raw[\"checker_decision\"][\"candidates\"].append(\n dict(raw[\"checker_decision\"][\"candidates\"][0])\n )\n decisions[0][\"engine_kit_result\"][\"checker_decision\"] = raw[\"checker_decision\"]\n decisions[0][\"engine_kit_result\"][\"campaign_depth_evidence\"][\n \"candidate_actual_plies\"\n ].append(raw[\"checker_decision\"][\"actual_ply\"])\n elif case == \"wrong-manifest-path\":\n manifest = json.loads(manifest_path.read_text())\n manifest[\"analysis_request_evidence\"] = \"wrong.jsonl\"\n write_json(manifest_path, manifest)\n elif case == \"manifest-file-hash-mismatch\":\n requests[0][\"engine\"] = \"gnu\"\n \n request_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in requests),\n encoding=\"utf-8\",\n )\n result_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in results),\n encoding=\"utf-8\",\n )\n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n if case not in {\"wrong-manifest-path\", \"manifest-file-hash-mismatch\"}:\n manifest = json.loads(manifest_path.read_text())\n for path in (request_path, result_path, decision_path):\n manifest[\"output_sha256\"][str(path.relative_to(match))] = hashlib.sha256(\n path.read_bytes()\n ).hexdigest()\n write_json(manifest_path, manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError, match=\"analysis|manifest\"):\nE AssertionError: Regex pattern did not match.\nE Expected regex: 'analysis|manifest'\nE Actual message: 'raw and validated Engine Kit result evidence conflict'\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:538: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_requires_authoritative_analysis_journals[wrong-configured-depth]\n1 failed, 28 passed, 77 deselected in 6.18s\n","stderr":"","aggregated_output":"....................F........ [100%]\n=================================== FAILURES ===================================\n_ test_publication_requires_authoritative_analysis_journals[wrong-configured-depth] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...py_test_publication_requires_authoritative_analysis_journals_wrong-configured-depth_-87399d0648d242bc900cd9df90c1bd73')\ncase = 'wrong-configured-depth'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"missing-request\", \"missing-result\", \"duplicate\", \"reordered\",\n \"wrong-decision-association\", \"wrong-engine\", \"wrong-seat\", \"wrong-gnuid\",\n \"wrong-type\", \"malformed-result\", \"wrong-configured-depth\",\n \"invalid-actual-depth\", \"cross-side\", \"cross-game\", \"cross-pair\",\n \"missing-raw-source\", \"malformed-candidates\", \"wrong-manifest-path\",\n \"manifest-file-hash-mismatch\",\n ],\n )\n def test_publication_requires_authoritative_analysis_journals(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n write_execution_fixture(execution, identity)\n match = execution / \"matches/A\"\n request_path = match / \"analysis_requests.jsonl\"\n result_path = match / \"analysis_results.jsonl\"\n decision_path = match / \"decisions.jsonl\"\n manifest_path = match / \"match_manifest.json\"\n requests = [json.loads(line) for line in request_path.read_text().splitlines()]\n results = [json.loads(line) for line in result_path.read_text().splitlines()]\n decisions = [json.loads(line) for line in decision_path.read_text().splitlines()]\n \n if case == \"missing-request\":\n requests.pop()\n elif case == \"missing-result\":\n results.pop()\n elif case == \"duplicate\":\n requests.insert(1, dict(requests[0]))\n results.insert(1, dict(results[0]))\n elif case == \"reordered\":\n requests[0], requests[1] = requests[1], requests[0]\n results[0], results[1] = results[1], results[0]\n elif case == \"wrong-decision-association\":\n requests[0][\"decision_ordinal\"] = requests[1][\"decision_ordinal\"]\n results[0][\"decision_ordinal\"] = results[1][\"decision_ordinal\"]\n elif case in {\"wrong-engine\", \"wrong-seat\", \"wrong-gnuid\", \"wrong-type\"}:\n key, value = {\n \"wrong-engine\": (\"engine\", \"gnu\"),\n \"wrong-seat\": (\"physical_seat\", \"X\"),\n \"wrong-gnuid\": (\"gnuid\", decisions[-1][\"gnuid\"]),\n \"wrong-type\": (\"decision_type\", \"cube\"),\n }[case]\n requests[0][key] = value\n results[0][key] = value\n elif case == \"malformed-result\":\n results[0][\"returned_result\"] = \"malformed\"\n elif case == \"wrong-configured-depth\":\n results[0][\"returned_result\"][\"engine\"][\"analysis_setting\"] = \"2ply\"\n elif case == \"invalid-actual-depth\":\n raw = results[0][\"returned_result\"]\n raw[\"checker_decision\"][\"actual_ply\"] = -1\n raw[\"checker_decision\"][\"candidates\"][0][\"actual_ply\"] = -1\n validated = decisions[0][\"engine_kit_result\"]\n validated[\"checker_decision\"] = raw[\"checker_decision\"]\n validated[\"campaign_depth_evidence\"][\"recommended_actual_ply\"] = -1\n validated[\"campaign_depth_evidence\"][\"candidate_actual_plies\"] = [-1]\n elif case in {\"cross-side\", \"cross-game\", \"cross-pair\"}:\n key, value = {\n \"cross-side\": (\"match_side\", \"B\"),\n \"cross-game\": (\"game_number\", 2),\n \"cross-pair\": (\"pair_id\", \"pair-\" + \"0\" * 24),\n }[case]\n requests[0][key] = value\n results[0][key] = value\n elif case == \"missing-raw-source\":\n results[0][\"returned_result\"].pop(\"raw_source\")\n decisions[0][\"engine_kit_result\"].pop(\"raw_source\")\n elif case == \"malformed-candidates\":\n raw = results[0][\"returned_result\"]\n raw[\"checker_decision\"][\"candidates\"].append(\n dict(raw[\"checker_decision\"][\"candidates\"][0])\n )\n decisions[0][\"engine_kit_result\"][\"checker_decision\"] = raw[\"checker_decision\"]\n decisions[0][\"engine_kit_result\"][\"campaign_depth_evidence\"][\n \"candidate_actual_plies\"\n ].append(raw[\"checker_decision\"][\"actual_ply\"])\n elif case == \"wrong-manifest-path\":\n manifest = json.loads(manifest_path.read_text())\n manifest[\"analysis_request_evidence\"] = \"wrong.jsonl\"\n write_json(manifest_path, manifest)\n elif case == \"manifest-file-hash-mismatch\":\n requests[0][\"engine\"] = \"gnu\"\n \n request_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in requests),\n encoding=\"utf-8\",\n )\n result_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in results),\n encoding=\"utf-8\",\n )\n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n if case not in {\"wrong-manifest-path\", \"manifest-file-hash-mismatch\"}:\n manifest = json.loads(manifest_path.read_text())\n for path in (request_path, result_path, decision_path):\n manifest[\"output_sha256\"][str(path.relative_to(match))] = hashlib.sha256(\n path.read_bytes()\n ).hexdigest()\n write_json(manifest_path, manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError, match=\"analysis|manifest\"):\nE AssertionError: Regex pattern did not match.\nE Expected regex: 'analysis|manifest'\nE Actual message: 'raw and validated Engine Kit result evidence conflict'\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:538: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_requires_authoritative_analysis_journals[wrong-configured-depth]\n1 failed, 28 passed, 77 deselected in 6.18s\n","exit_code":1,"duration":{"secs":6,"nanos":447623955},"formatted_output":"....................F........ [100%]\n=================================== FAILURES ===================================\n_ test_publication_requires_authoritative_analysis_journals[wrong-configured-depth] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...py_test_publication_requires_authoritative_analysis_journals_wrong-configured-depth_-87399d0648d242bc900cd9df90c1bd73')\ncase = 'wrong-configured-depth'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"missing-request\", \"missing-result\", \"duplicate\", \"reordered\",\n \"wrong-decision-association\", \"wrong-engine\", \"wrong-seat\", \"wrong-gnuid\",\n \"wrong-type\", \"malformed-result\", \"wrong-configured-depth\",\n \"invalid-actual-depth\", \"cross-side\", \"cross-game\", \"cross-pair\",\n \"missing-raw-source\", \"malformed-candidates\", \"wrong-manifest-path\",\n \"manifest-file-hash-mismatch\",\n ],\n )\n def test_publication_requires_authoritative_analysis_journals(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n write_execution_fixture(execution, identity)\n match = execution / \"matches/A\"\n request_path = match / \"analysis_requests.jsonl\"\n result_path = match / \"analysis_results.jsonl\"\n decision_path = match / \"decisions.jsonl\"\n manifest_path = match / \"match_manifest.json\"\n requests = [json.loads(line) for line in request_path.read_text().splitlines()]\n results = [json.loads(line) for line in result_path.read_text().splitlines()]\n decisions = [json.loads(line) for line in decision_path.read_text().splitlines()]\n \n if case == \"missing-request\":\n requests.pop()\n elif case == \"missing-result\":\n results.pop()\n elif case == \"duplicate\":\n requests.insert(1, dict(requests[0]))\n results.insert(1, dict(results[0]))\n elif case == \"reordered\":\n requests[0], requests[1] = requests[1], requests[0]\n results[0], results[1] = results[1], results[0]\n elif case == \"wrong-decision-association\":\n requests[0][\"decision_ordinal\"] = requests[1][\"decision_ordinal\"]\n results[0][\"decision_ordinal\"] = results[1][\"decision_ordinal\"]\n elif case in {\"wrong-engine\", \"wrong-seat\", \"wrong-gnuid\", \"wrong-type\"}:\n key, value = {\n \"wrong-engine\": (\"engine\", \"gnu\"),\n \"wrong-seat\": (\"physical_seat\", \"X\"),\n \"wrong-gnuid\": (\"gnuid\", decisions[-1][\"gnuid\"]),\n \"wrong-type\": (\"decision_type\", \"cube\"),\n }[case]\n requests[0][key] = value\n results[0][key] = value\n elif case == \"malformed-result\":\n results[0][\"returned_result\"] = \"malformed\"\n elif case == \"wrong-configured-depth\":\n results[0][\"returned_result\"][\"engine\"][\"analysis_setting\"] = \"2ply\"\n elif case == \"invalid-actual-depth\":\n raw = results[0][\"returned_result\"]\n raw[\"checker_decision\"][\"actual_ply\"] = -1\n raw[\"checker_decision\"][\"candidates\"][0][\"actual_ply\"] = -1\n validated = decisions[0][\"engine_kit_result\"]\n validated[\"checker_decision\"] = raw[\"checker_decision\"]\n validated[\"campaign_depth_evidence\"][\"recommended_actual_ply\"] = -1\n validated[\"campaign_depth_evidence\"][\"candidate_actual_plies\"] = [-1]\n elif case in {\"cross-side\", \"cross-game\", \"cross-pair\"}:\n key, value = {\n \"cross-side\": (\"match_side\", \"B\"),\n \"cross-game\": (\"game_number\", 2),\n \"cross-pair\": (\"pair_id\", \"pair-\" + \"0\" * 24),\n }[case]\n requests[0][key] = value\n results[0][key] = value\n elif case == \"missing-raw-source\":\n results[0][\"returned_result\"].pop(\"raw_source\")\n decisions[0][\"engine_kit_result\"].pop(\"raw_source\")\n elif case == \"malformed-candidates\":\n raw = results[0][\"returned_result\"]\n raw[\"checker_decision\"][\"candidates\"].append(\n dict(raw[\"checker_decision\"][\"candidates\"][0])\n )\n decisions[0][\"engine_kit_result\"][\"checker_decision\"] = raw[\"checker_decision\"]\n decisions[0][\"engine_kit_result\"][\"campaign_depth_evidence\"][\n \"candidate_actual_plies\"\n ].append(raw[\"checker_decision\"][\"actual_ply\"])\n elif case == \"wrong-manifest-path\":\n manifest = json.loads(manifest_path.read_text())\n manifest[\"analysis_request_evidence\"] = \"wrong.jsonl\"\n write_json(manifest_path, manifest)\n elif case == \"manifest-file-hash-mismatch\":\n requests[0][\"engine\"] = \"gnu\"\n \n request_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in requests),\n encoding=\"utf-8\",\n )\n result_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in results),\n encoding=\"utf-8\",\n )\n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n if case not in {\"wrong-manifest-path\", \"manifest-file-hash-mismatch\"}:\n manifest = json.loads(manifest_path.read_text())\n for path in (request_path, result_path, decision_path):\n manifest[\"output_sha256\"][str(path.relative_to(match))] = hashlib.sha256(\n path.read_bytes()\n ).hexdigest()\n write_json(manifest_path, manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError, match=\"analysis|manifest\"):\nE AssertionError: Regex pattern did not match.\nE Expected regex: 'analysis|manifest'\nE Actual message: 'raw and validated Engine Kit result evidence conflict'\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:538: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_requires_authoritative_analysis_journals[wrong-configured-depth]\n1 failed, 28 passed, 77 deselected in 6.18s\n"},"started_at_ms":1787492679624,"completed_at_ms":1787492686073}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:44:46.084Z","ordinal":480,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02edd-d904-78a1-a0f1-537b5c2f5323","call_id":"call_WWhgYI0mD7IJ7W2eZaSOWMDB","output":[{"type":"input_text","text":"Script completed\nWall time 6.7 seconds\nOutput:\n"},{"type":"input_text","text":"....................F........ [100%]\n=================================== FAILURES ===================================\n_ test_publication_requires_authoritative_analysis_journals[wrong-configured-depth] _\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te...py_test_publication_requires_authoritative_analysis_journals_wrong-configured-depth_-87399d0648d242bc900cd9df90c1bd73')\ncase = 'wrong-configured-depth'\n\n @pytest.mark.parametrize(\n \"case\",\n [\n \"missing-request\", \"missing-result\", \"duplicate\", \"reordered\",\n \"wrong-decision-association\", \"wrong-engine\", \"wrong-seat\", \"wrong-gnuid\",\n \"wrong-type\", \"malformed-result\", \"wrong-configured-depth\",\n \"invalid-actual-depth\", \"cross-side\", \"cross-game\", \"cross-pair\",\n \"missing-raw-source\", \"malformed-candidates\", \"wrong-manifest-path\",\n \"manifest-file-hash-mismatch\",\n ],\n )\n def test_publication_requires_authoritative_analysis_journals(\n tmp_path: Path, case: str,\n ) -> None:\n config = load_campaign_config(CONFIG)\n identity = pair_identity(config, 1)\n execution = tmp_path / \"execution\"\n write_execution_fixture(execution, identity)\n match = execution / \"matches/A\"\n request_path = match / \"analysis_requests.jsonl\"\n result_path = match / \"analysis_results.jsonl\"\n decision_path = match / \"decisions.jsonl\"\n manifest_path = match / \"match_manifest.json\"\n requests = [json.loads(line) for line in request_path.read_text().splitlines()]\n results = [json.loads(line) for line in result_path.read_text().splitlines()]\n decisions = [json.loads(line) for line in decision_path.read_text().splitlines()]\n \n if case == \"missing-request\":\n requests.pop()\n elif case == \"missing-result\":\n results.pop()\n elif case == \"duplicate\":\n requests.insert(1, dict(requests[0]))\n results.insert(1, dict(results[0]))\n elif case == \"reordered\":\n requests[0], requests[1] = requests[1], requests[0]\n results[0], results[1] = results[1], results[0]\n elif case == \"wrong-decision-association\":\n requests[0][\"decision_ordinal\"] = requests[1][\"decision_ordinal\"]\n results[0][\"decision_ordinal\"] = results[1][\"decision_ordinal\"]\n elif case in {\"wrong-engine\", \"wrong-seat\", \"wrong-gnuid\", \"wrong-type\"}:\n key, value = {\n \"wrong-engine\": (\"engine\", \"gnu\"),\n \"wrong-seat\": (\"physical_seat\", \"X\"),\n \"wrong-gnuid\": (\"gnuid\", decisions[-1][\"gnuid\"]),\n \"wrong-type\": (\"decision_type\", \"cube\"),\n }[case]\n requests[0][key] = value\n results[0][key] = value\n elif case == \"malformed-result\":\n results[0][\"returned_result\"] = \"malformed\"\n elif case == \"wrong-configured-depth\":\n results[0][\"returned_result\"][\"engine\"][\"analysis_setting\"] = \"2ply\"\n elif case == \"invalid-actual-depth\":\n raw = results[0][\"returned_result\"]\n raw[\"checker_decision\"][\"actual_ply\"] = -1\n raw[\"checker_decision\"][\"candidates\"][0][\"actual_ply\"] = -1\n validated = decisions[0][\"engine_kit_result\"]\n validated[\"checker_decision\"] = raw[\"checker_decision\"]\n validated[\"campaign_depth_evidence\"][\"recommended_actual_ply\"] = -1\n validated[\"campaign_depth_evidence\"][\"candidate_actual_plies\"] = [-1]\n elif case in {\"cross-side\", \"cross-game\", \"cross-pair\"}:\n key, value = {\n \"cross-side\": (\"match_side\", \"B\"),\n \"cross-game\": (\"game_number\", 2),\n \"cross-pair\": (\"pair_id\", \"pair-\" + \"0\" * 24),\n }[case]\n requests[0][key] = value\n results[0][key] = value\n elif case == \"missing-raw-source\":\n results[0][\"returned_result\"].pop(\"raw_source\")\n decisions[0][\"engine_kit_result\"].pop(\"raw_source\")\n elif case == \"malformed-candidates\":\n raw = results[0][\"returned_result\"]\n raw[\"checker_decision\"][\"candidates\"].append(\n dict(raw[\"checker_decision\"][\"candidates\"][0])\n )\n decisions[0][\"engine_kit_result\"][\"checker_decision\"] = raw[\"checker_decision\"]\n decisions[0][\"engine_kit_result\"][\"campaign_depth_evidence\"][\n \"candidate_actual_plies\"\n ].append(raw[\"checker_decision\"][\"actual_ply\"])\n elif case == \"wrong-manifest-path\":\n manifest = json.loads(manifest_path.read_text())\n manifest[\"analysis_request_evidence\"] = \"wrong.jsonl\"\n write_json(manifest_path, manifest)\n elif case == \"manifest-file-hash-mismatch\":\n requests[0][\"engine\"] = \"gnu\"\n \n request_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in requests),\n encoding=\"utf-8\",\n )\n result_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in results),\n encoding=\"utf-8\",\n )\n decision_path.write_text(\n \"\".join(json.dumps(record, sort_keys=True) + \"\\n\" for record in decisions),\n encoding=\"utf-8\",\n )\n if case not in {\"wrong-manifest-path\", \"manifest-file-hash-mismatch\"}:\n manifest = json.loads(manifest_path.read_text())\n for path in (request_path, result_path, decision_path):\n manifest[\"output_sha256\"][str(path.relative_to(match))] = hashlib.sha256(\n path.read_bytes()\n ).hexdigest()\n write_json(manifest_path, manifest)\n \n artifact_root = tmp_path / \"artifacts\"\n artifact_root.mkdir()\n> with pytest.raises(MatchExecutionError, match=\"analysis|manifest\"):\nE AssertionError: Regex pattern did not match.\nE Expected regex: 'analysis|manifest'\nE Actual message: 'raw and validated Engine Kit result evidence conflict'\n\ntests/sage_gnu_campaign/test_ledger_and_publication.py:538: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_requires_authoritative_analysis_journals[wrong-configured-depth]\n1 failed, 28 passed, 77 deselected in 6.18s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492686.0842762}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:45:02.302Z","ordinal":491,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-f4ee1c81-2489-418e-9be6-98a3860157f8","process_id":"77750","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_engine_kit_boundary.py tests/sage_gnu_campaign/test_ledger_and_publication.py -k 'authoritative_analysis_journals or committed_pair_is_verified or real_session_boundary or primitive_malformed' --disable-warnings --maxfail=3"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_engine_kit_boundary.py tests/sage_gnu_campaign/test_ledger_and_publication.py -k 'authoritative_analysis_journals or committed_pair_is_verified or real_session_boundary or primitive_malformed' --disable-warnings --maxfail=3"}],"source":"unified_exec_startup","status":"completed","stdout":"............................. [100%]\n29 passed, 77 deselected in 5.90s\n","stderr":"","aggregated_output":"............................. [100%]\n29 passed, 77 deselected in 5.90s\n","exit_code":0,"duration":{"secs":6,"nanos":95696469},"formatted_output":"............................. [100%]\n29 passed, 77 deselected in 5.90s\n"},"started_at_ms":1787492696205,"completed_at_ms":1787492702302}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:45:02.313Z","ordinal":492,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ede-1869-7290-8940-6424d67e1213","call_id":"call_yU4QNhSO5W6DLeGL68iOZJnm","output":[{"type":"input_text","text":"Script completed\nWall time 6.3 seconds\nOutput:\n"},{"type":"input_text","text":"............................. [100%]\n29 passed, 77 deselected in 5.90s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492702.3135004}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:45:28.853Z","ordinal":497,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-b738e5cd-ac56-4ebe-ab9d-692001fee602","process_id":"12788","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=5"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=5"}],"source":"unified_exec_startup","status":"completed","stdout":"........................................................................ [ 32%]\n........................................................................ [ 64%]\n........................................................................ [ 97%]\n...... [100%]\n222 passed in 21.96s\n","stderr":"","aggregated_output":"........................................................................ [ 32%]\n........................................................................ [ 64%]\n........................................................................ [ 97%]\n...... [100%]\n222 passed in 21.96s\n","exit_code":0,"duration":{"secs":22,"nanos":166833087},"formatted_output":"........................................................................ [ 32%]\n........................................................................ [ 64%]\n........................................................................ [ 97%]\n...... [100%]\n222 passed in 21.96s\n"},"started_at_ms":1787492706685,"completed_at_ms":1787492728853}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:45:28.865Z","ordinal":498,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ede-8020-7881-be82-7b33df809554","call_id":"call_IVf5gYNQxvSfiJFkd6eSzaQu","output":[{"type":"input_text","text":"Script completed\nWall time 22.4 seconds\nOutput:\n"},{"type":"input_text","text":"........................................................................ [ 32%]\n........................................................................ [ 64%]\n........................................................................ [ 97%]\n...... [100%]\n222 passed in 21.96s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492728.8648787}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:47:54.321Z","ordinal":541,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-375df6d1-55b9-47a7-bfa4-3663730a8885","process_id":"12187","command":["/usr/bin/bash","-lc","python3 -m py_compile runner/sage_gnu_campaign/match.py\n/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_committed_pair_is_verified_and_never_regenerated tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_accepts_complete_real_shaped_multi_game_native_evidence --disable-warnings --maxfail=1"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"python3 -m py_compile runner/sage_gnu_campaign/match.py\n/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_committed_pair_is_verified_and_never_regenerated tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_accepts_complete_real_shaped_multi_game_native_evidence --disable-warnings --maxfail=1"}],"source":"unified_exec_startup","status":"completed","stdout":".. [100%]\n2 passed in 7.32s\n","stderr":"","aggregated_output":".. [100%]\n2 passed in 7.32s\n","exit_code":0,"duration":{"secs":7,"nanos":696069090},"formatted_output":".. [100%]\n2 passed in 7.32s\n"},"started_at_ms":1787492866625,"completed_at_ms":1787492874321}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:48:02.134Z","ordinal":542,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ee0-d6d6-7552-a85e-55a2e9e4618e","call_id":"call_5dsSoIELXjwvaGDok3K08JEq","output":[{"type":"input_text","text":"Script completed\nWall time 15.7 seconds\nOutput:\n"},{"type":"input_text","text":".. [100%]\n2 passed in 7.32s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492882.1341076}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:49:16.146Z","ordinal":559,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-7f2c076e-5ce5-4bbd-906b-017d03463257","process_id":"21154","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py -k 'equal_dice_collision or committed_pair_is_verified or complete_real_shaped_multi' --disable-warnings --maxfail=3"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py -k 'equal_dice_collision or committed_pair_is_verified or complete_real_shaped_multi' --disable-warnings --maxfail=3"}],"source":"unified_exec_startup","status":"completed","stdout":"...... [100%]\n6 passed, 92 deselected in 8.02s\n","stderr":"","aggregated_output":"...... [100%]\n6 passed, 92 deselected in 8.02s\n","exit_code":0,"duration":{"secs":8,"nanos":291043383},"formatted_output":"...... [100%]\n6 passed, 92 deselected in 8.02s\n"},"started_at_ms":1787492947855,"completed_at_ms":1787492956146}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:49:16.161Z","ordinal":560,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ee1-f801-74d3-9629-a73d49ab06da","call_id":"call_qLMQpi3Ih6QwamxuxMOavkEY","output":[{"type":"input_text","text":"Script completed\nWall time 8.5 seconds\nOutput:\n"},{"type":"input_text","text":"...... [100%]\n6 passed, 92 deselected in 8.02s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492956.1610918}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:49:44.136Z","ordinal":565,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-bb2f798f-4f48-4633-bb45-649743b57493","process_id":"80977","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=5"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=5"}],"source":"unified_exec_startup","status":"completed","stdout":"........................................................................ [ 31%]\n........................................................................ [ 63%]\n........................................................................ [ 95%]\n.......... [100%]\n226 passed in 23.46s\n","stderr":"","aggregated_output":"........................................................................ [ 31%]\n........................................................................ [ 63%]\n........................................................................ [ 95%]\n.......... [100%]\n226 passed in 23.46s\n","exit_code":0,"duration":{"secs":23,"nanos":674843636},"formatted_output":"........................................................................ [ 31%]\n........................................................................ [ 63%]\n........................................................................ [ 95%]\n.......... [100%]\n226 passed in 23.46s\n"},"started_at_ms":1787492960460,"completed_at_ms":1787492984136}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:49:44.145Z","ordinal":566,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ee2-6551-76a0-b671-65d143be5b46","call_id":"call_BxwlO89Y3JN917zIKa2pkcZ3","output":[{"type":"input_text","text":"Script completed\nWall time 23.9 seconds\nOutput:\n"},{"type":"input_text","text":"........................................................................ [ 31%]\n........................................................................ [ 63%]\n........................................................................ [ 95%]\n.......... [100%]\n226 passed in 23.46s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787492984.1455932}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:54:27.073Z","ordinal":648,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-e88d84cd-8e3d-4b9a-988c-14fccbb2892c","process_id":"18088","command":["/usr/bin/bash","-lc","time /users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_committed_pair_is_verified_and_never_regenerated --disable-warnings --maxfail=1"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"time /users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_committed_pair_is_verified_and_never_regenerated --disable-warnings --maxfail=1"}],"source":"unified_exec_startup","status":"completed","stdout":". [100%]\n1 passed in 61.05s (0:01:01)\n\nreal\t1m1.453s\nuser\t0m59.621s\nsys\t0m0.828s\n","stderr":"","aggregated_output":". [100%]\n1 passed in 61.05s (0:01:01)\n\nreal\t1m1.453s\nuser\t0m59.621s\nsys\t0m0.828s\n","exit_code":0,"duration":{"secs":61,"nanos":376640476},"formatted_output":". [100%]\n1 passed in 61.05s (0:01:01)\n\nreal\t1m1.453s\nuser\t0m59.621s\nsys\t0m0.828s\n"},"started_at_ms":1787493205696,"completed_at_ms":1787493267073}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:54:45.365Z","ordinal":658,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ee6-fdf5-7a72-95be-f7efa4706c5d","call_id":"call_Xs5QLTVprissxojSsu9E4boz","output":[{"type":"input_text","text":"Script completed\nWall time 0.0 seconds\nOutput:\n"},{"type":"input_text","text":". [100%]\n1 passed in 61.05s (0:01:01)\n\nreal\t1m1.453s\nuser\t0m59.621s\nsys\t0m0.828s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787493285.3656273}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:55:45.746Z","ordinal":675,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-3ee94b24-be96-4cb3-aafc-4851ad3b4421","process_id":"3704","command":["/usr/bin/bash","-lc","time /users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py -k 'complete_frozen_dice_stream_inventory or committed_pair_is_verified' --disable-warnings --maxfail=3"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"time /users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py -k 'complete_frozen_dice_stream_inventory or committed_pair_is_verified' --disable-warnings --maxfail=3"}],"source":"unified_exec_startup","status":"completed","stdout":"............ [100%]\n12 passed, 97 deselected in 6.35s\n\nreal\t0m6.705s\nuser\t0m1.117s\nsys\t0m1.105s\n","stderr":"","aggregated_output":"............ [100%]\n12 passed, 97 deselected in 6.35s\n\nreal\t0m6.705s\nuser\t0m1.117s\nsys\t0m1.105s\n","exit_code":0,"duration":{"secs":6,"nanos":619454936},"formatted_output":"............ [100%]\n12 passed, 97 deselected in 6.35s\n\nreal\t0m6.705s\nuser\t0m1.117s\nsys\t0m1.105s\n"},"started_at_ms":1787493339126,"completed_at_ms":1787493345745}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:55:48.388Z","ordinal":676,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ee7-f424-7b30-97fd-2910427a45da","call_id":"call_E8gV5zG6kieLpQY5za3hhwDO","output":[{"type":"input_text","text":"Script completed\nWall time 9.5 seconds\nOutput:\n"},{"type":"input_text","text":"............ [100%]\n12 passed, 97 deselected in 6.35s\n\nreal\t0m6.705s\nuser\t0m1.117s\nsys\t0m1.105s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787493348.3887587}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:56:50.273Z","ordinal":686,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-e24f608e-b9b9-45d6-ba1f-858f51f4e454","process_id":"93550","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=5"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=5"}],"source":"unified_exec_startup","status":"completed","stdout":"........................................................................ [ 30%]\n........................................................................ [ 60%]\n........................................................................ [ 91%]\n..................... [100%]\n237 passed in 56.04s\n","stderr":"","aggregated_output":"........................................................................ [ 30%]\n........................................................................ [ 60%]\n........................................................................ [ 91%]\n..................... [100%]\n237 passed in 56.04s\n","exit_code":0,"duration":{"secs":56,"nanos":250074247},"formatted_output":"........................................................................ [ 30%]\n........................................................................ [ 60%]\n........................................................................ [ 91%]\n..................... [100%]\n237 passed in 56.04s\n"},"started_at_ms":1787493354018,"completed_at_ms":1787493410269}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T13:56:50.285Z","ordinal":687,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ee8-e5ed-7822-813f-941926b754bc","call_id":"call_XpJKYowDBSh8IZsK5x16F8JH","output":[{"type":"input_text","text":"Script completed\nWall time 23.9 seconds\nOutput:\n"},{"type":"input_text","text":"..................................................... [ 60%]\n........................................................................ [ 91%]\n..................... [100%]\n237 passed in 56.04s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787493410.285354}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:02:38.332Z","ordinal":828,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-7ba48f2c-f902-4c86-942c-aa1019ac5f18","process_id":"96498","command":["/usr/bin/bash","-lc","python3 -m py_compile runner/sage_gnu_campaign/match.py tests/sage_gnu_campaign/native_fixtures.py\n/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_match_policy.py -k 'native_outputs' --disable-warnings --maxfail=2"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"python3 -m py_compile runner/sage_gnu_campaign/match.py tests/sage_gnu_campaign/native_fixtures.py\n/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_match_policy.py -k 'native_outputs' --disable-warnings --maxfail=2"}],"source":"unified_exec_startup","status":"failed","stdout":".........F........ [100%]\n=================================== FAILURES ===================================\n________ test_native_outputs_reject_nonruntime_sgf_application[missing] ________\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te..._test_match_policy.py_test_native_outputs_reject_nonruntime_sgf_application_missing_-c26cda5bbf2b4fe5ac511670d92a70a8')\nbad_application = ''\n\n @pytest.mark.parametrize(\n \"bad_application\",\n [\n \"GNU Backgammon:1.06.002\",\n \"Other Backgammon:1.08.003\",\n \"GNU Backgammon:1.08.004\",\n \"GNU Backgammon 1.08.003\",\n \"\",\n ],\n ids=[\"obsolete-version\", \"wrong-application\", \"wrong-version\", \"malformed\", \"missing\"],\n )\n def test_native_outputs_reject_nonruntime_sgf_application(\n tmp_path: Path, bad_application: str,\n ) -> None:\n source = (NATIVE_FIXTURES / \"gnu-1.08.003-match.sgf\").read_text(encoding=\"utf-8\")\n replacement = f\"AP[{bad_application}]\" if bad_application else \"\"\n sgf_path = tmp_path / \"match.sgf\"\n sgf_path.write_text(\n source.replace(\"AP[GNU Backgammon:1.08.003]\", replacement), encoding=\"utf-8\"\n )\n> with pytest.raises(MatchExecutionError, match=\"AP|format\"):\nE AssertionError: Regex pattern did not match.\nE Expected regex: 'AP|format'\nE Actual message: 'GNU saved SGF root properties are incomplete or unsupported'\n\ntests/sage_gnu_campaign/test_match_policy.py:1273: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_match_policy.py::test_native_outputs_reject_nonruntime_sgf_application[missing]\n1 failed, 17 passed, 60 deselected in 0.27s\n","stderr":"","aggregated_output":".........F........ [100%]\n=================================== FAILURES ===================================\n________ test_native_outputs_reject_nonruntime_sgf_application[missing] ________\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te..._test_match_policy.py_test_native_outputs_reject_nonruntime_sgf_application_missing_-c26cda5bbf2b4fe5ac511670d92a70a8')\nbad_application = ''\n\n @pytest.mark.parametrize(\n \"bad_application\",\n [\n \"GNU Backgammon:1.06.002\",\n \"Other Backgammon:1.08.003\",\n \"GNU Backgammon:1.08.004\",\n \"GNU Backgammon 1.08.003\",\n \"\",\n ],\n ids=[\"obsolete-version\", \"wrong-application\", \"wrong-version\", \"malformed\", \"missing\"],\n )\n def test_native_outputs_reject_nonruntime_sgf_application(\n tmp_path: Path, bad_application: str,\n ) -> None:\n source = (NATIVE_FIXTURES / \"gnu-1.08.003-match.sgf\").read_text(encoding=\"utf-8\")\n replacement = f\"AP[{bad_application}]\" if bad_application else \"\"\n sgf_path = tmp_path / \"match.sgf\"\n sgf_path.write_text(\n source.replace(\"AP[GNU Backgammon:1.08.003]\", replacement), encoding=\"utf-8\"\n )\n> with pytest.raises(MatchExecutionError, match=\"AP|format\"):\nE AssertionError: Regex pattern did not match.\nE Expected regex: 'AP|format'\nE Actual message: 'GNU saved SGF root properties are incomplete or unsupported'\n\ntests/sage_gnu_campaign/test_match_policy.py:1273: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_match_policy.py::test_native_outputs_reject_nonruntime_sgf_application[missing]\n1 failed, 17 passed, 60 deselected in 0.27s\n","exit_code":1,"duration":{"secs":1,"nanos":369517539},"formatted_output":".........F........ [100%]\n=================================== FAILURES ===================================\n________ test_native_outputs_reject_nonruntime_sgf_application[missing] ________\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te..._test_match_policy.py_test_native_outputs_reject_nonruntime_sgf_application_missing_-c26cda5bbf2b4fe5ac511670d92a70a8')\nbad_application = ''\n\n @pytest.mark.parametrize(\n \"bad_application\",\n [\n \"GNU Backgammon:1.06.002\",\n \"Other Backgammon:1.08.003\",\n \"GNU Backgammon:1.08.004\",\n \"GNU Backgammon 1.08.003\",\n \"\",\n ],\n ids=[\"obsolete-version\", \"wrong-application\", \"wrong-version\", \"malformed\", \"missing\"],\n )\n def test_native_outputs_reject_nonruntime_sgf_application(\n tmp_path: Path, bad_application: str,\n ) -> None:\n source = (NATIVE_FIXTURES / \"gnu-1.08.003-match.sgf\").read_text(encoding=\"utf-8\")\n replacement = f\"AP[{bad_application}]\" if bad_application else \"\"\n sgf_path = tmp_path / \"match.sgf\"\n sgf_path.write_text(\n source.replace(\"AP[GNU Backgammon:1.08.003]\", replacement), encoding=\"utf-8\"\n )\n> with pytest.raises(MatchExecutionError, match=\"AP|format\"):\nE AssertionError: Regex pattern did not match.\nE Expected regex: 'AP|format'\nE Actual message: 'GNU saved SGF root properties are incomplete or unsupported'\n\ntests/sage_gnu_campaign/test_match_policy.py:1273: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_match_policy.py::test_native_outputs_reject_nonruntime_sgf_application[missing]\n1 failed, 17 passed, 60 deselected in 0.27s\n"},"started_at_ms":1787493756960,"completed_at_ms":1787493758332}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:02:38.351Z","ordinal":829,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02eee-358e-70f1-9acd-4e930bc6c5d3","call_id":"call_sX2N1LpMLSr7tZY3M44PYuMr","output":[{"type":"input_text","text":"Script completed\nWall time 1.6 seconds\nOutput:\n"},{"type":"input_text","text":".........F........ [100%]\n=================================== FAILURES ===================================\n________ test_native_outputs_reject_nonruntime_sgf_application[missing] ________\n\ntmp_path = PosixPath('/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1/.test-tmp/te..._test_match_policy.py_test_native_outputs_reject_nonruntime_sgf_application_missing_-c26cda5bbf2b4fe5ac511670d92a70a8')\nbad_application = ''\n\n @pytest.mark.parametrize(\n \"bad_application\",\n [\n \"GNU Backgammon:1.06.002\",\n \"Other Backgammon:1.08.003\",\n \"GNU Backgammon:1.08.004\",\n \"GNU Backgammon 1.08.003\",\n \"\",\n ],\n ids=[\"obsolete-version\", \"wrong-application\", \"wrong-version\", \"malformed\", \"missing\"],\n )\n def test_native_outputs_reject_nonruntime_sgf_application(\n tmp_path: Path, bad_application: str,\n ) -> None:\n source = (NATIVE_FIXTURES / \"gnu-1.08.003-match.sgf\").read_text(encoding=\"utf-8\")\n replacement = f\"AP[{bad_application}]\" if bad_application else \"\"\n sgf_path = tmp_path / \"match.sgf\"\n sgf_path.write_text(\n source.replace(\"AP[GNU Backgammon:1.08.003]\", replacement), encoding=\"utf-8\"\n )\n> with pytest.raises(MatchExecutionError, match=\"AP|format\"):\nE AssertionError: Regex pattern did not match.\nE Expected regex: 'AP|format'\nE Actual message: 'GNU saved SGF root properties are incomplete or unsupported'\n\ntests/sage_gnu_campaign/test_match_policy.py:1273: AssertionError\n=========================== short test summary info ============================\nFAILED tests/sage_gnu_campaign/test_match_policy.py::test_native_outputs_reject_nonruntime_sgf_application[missing]\n1 failed, 17 passed, 60 deselected in 0.27s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787493758.3509302}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:02:54.169Z","ordinal":840,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-0b79e6d8-88e8-4e3a-b7bf-f593347bcab0","process_id":"21888","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_committed_pair_is_verified_and_never_regenerated tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_accepts_complete_real_shaped_multi_game_native_evidence --disable-warnings --maxfail=2"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_ledger_and_publication.py::test_committed_pair_is_verified_and_never_regenerated tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_accepts_complete_real_shaped_multi_game_native_evidence --disable-warnings --maxfail=2"}],"source":"unified_exec_startup","status":"completed","stdout":".. [100%]\n2 passed in 1.61s\n","stderr":"","aggregated_output":".. [100%]\n2 passed in 1.61s\n","exit_code":0,"duration":{"secs":1,"nanos":868026757},"formatted_output":".. [100%]\n2 passed in 1.61s\n"},"started_at_ms":1787493772301,"completed_at_ms":1787493774169}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:02:54.189Z","ordinal":841,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02eee-736c-7592-8e89-6fa94965dee1","call_id":"call_PNmXrQiHVMWqaPgVJzMkxthn","output":[{"type":"input_text","text":"Script completed\nWall time 2.1 seconds\nOutput:\n"},{"type":"input_text","text":".. [100%]\n2 passed in 1.61s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787493774.1889586}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:04:12.528Z","ordinal":860,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-4e8f9ca6-15a1-4890-8cf2-c3253f2265b6","process_id":"57475","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_match_policy.py -k 'state_bearing_sgf or harmless_gnu_metadata or native_outputs' tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_sgf_setup_inconsistent_with_decision_gnuid --disable-warnings --maxfail=3"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q tests/sage_gnu_campaign/test_match_policy.py -k 'state_bearing_sgf or harmless_gnu_metadata or native_outputs' tests/sage_gnu_campaign/test_ledger_and_publication.py::test_publication_rejects_sgf_setup_inconsistent_with_decision_gnuid --disable-warnings --maxfail=3"}],"source":"unified_exec_startup","status":"completed","stdout":".......................... [100%]\n26 passed, 61 deselected in 0.31s\n","stderr":"","aggregated_output":".......................... [100%]\n26 passed, 61 deselected in 0.31s\n","exit_code":0,"duration":{"secs":0,"nanos":518145385},"formatted_output":".......................... [100%]\n26 passed, 61 deselected in 0.31s\n"},"started_at_ms":1787493852009,"completed_at_ms":1787493852527}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:04:12.547Z","ordinal":861,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02eef-a583-7121-b6f0-9bbf3cff0054","call_id":"call_pIXAzDxlaeKqPhyhKAvBoYqN","output":[{"type":"input_text","text":"Script completed\nWall time 0.8 seconds\nOutput:\n"},{"type":"input_text","text":".......................... [100%]\n26 passed, 61 deselected in 0.31s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787493852.5477958}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:05:16.525Z","ordinal":871,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-2b7620e9-112b-4890-831f-8a956521baa5","process_id":"99817","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=5"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings --maxfail=5"}],"source":"unified_exec_startup","status":"completed","stdout":"........................................................................ [ 29%]\n........................................................................ [ 58%]\n........................................................................ [ 87%]\n.............................. [100%]\n246 passed in 56.25s\n","stderr":"","aggregated_output":"........................................................................ [ 29%]\n........................................................................ [ 58%]\n........................................................................ [ 87%]\n.............................. [100%]\n246 passed in 56.25s\n","exit_code":0,"duration":{"secs":56,"nanos":476066117},"formatted_output":"........................................................................ [ 29%]\n........................................................................ [ 58%]\n........................................................................ [ 87%]\n.............................. [100%]\n246 passed in 56.25s\n"},"started_at_ms":1787493860041,"completed_at_ms":1787493916517}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:05:16.535Z","ordinal":872,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ef0-9f76-74b3-af49-96a6c9ec3297","call_id":"call_zOO3bk1BLgcDbEkDuh6PoAxF","output":[{"type":"input_text","text":"Script completed\nWall time 23.0 seconds\nOutput:\n"},{"type":"input_text","text":"...................................................... [ 58%]\n........................................................................ [ 87%]\n.............................. [100%]\n246 passed in 56.25s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787493916.5349584}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:06:17.062Z","ordinal":895,"type":"response_item","payload":{"type":"custom_tool_call","id":"ctc_04340d676abbd611016a8afe4d9d3487d1ad107870dcfd2cbc","status":"completed","call_id":"call_uQKABQHv6IaSFsYKdlIKRCJR","name":"exec","input":"const r = await tools.exec_command({\n cmd: \"sha256sum experiments/sage-gnu-campaign-v1/campaign.json\\ngit diff --exit-code 6f24bd9ad470a0b7af1cc99c6692354f7d55047f..HEAD -- experiments/sage-gnu-campaign-v1/campaign.json runner/sage_gnu_campaign/config.py runner/sage_gnu_campaign/identity.py\\n/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/python - <<'PY'\\nfrom pathlib import Path\\nfrom runner.sage_gnu_campaign.config import GNU_NORMAL_V1, load_campaign_config\\nfrom runner.sage_gnu_campaign.identity import all_pair_identities\\nconfig = load_campaign_config(Path('experiments/sage-gnu-campaign-v1/campaign.json'))\\ndata = config.data\\nassert config.content_sha256 == 'b5614f359236c78efd27aa48c544f807fee26619fce215565e1cbb930abfc2df'\\nassert data['match']['length_points'] == 7\\nassert data['engines']['sage']['checker_configured_target'] == '4ply'\\nassert data['engines']['sage']['cube_configured_target'] == '3ply'\\nassert data['engines']['gnu']['checker_configured_target'] == '3ply'\\nassert data['engines']['gnu']['cube_configured_target'] == '2ply'\\nassert data['engines']['gnu']['checker_move_filter_identity'] == GNU_NORMAL_V1\\nassert data['engines']['sage']['threads'] == data['engines']['gnu']['threads'] == 1\\nassert data['match']['members'] == {\\n 'A': {'gnu_physical_seat': 'X', 'sage_physical_seat': 'O'},\\n 'B': {'gnu_physical_seat': 'O', 'sage_physical_seat': 'X'},\\n}\\nprint('frozen-semantics: verified')\\nfor identity in all_pair_identities(config):\\n print(identity.pair_index, identity.pair_id, identity.base_seed)\\nPY\\nif pgrep -x gnubg >/dev/null; then echo 'gnubg: FOUND'; else echo 'gnubg: none'; fi\\nif pgrep -x bgsage >/dev/null; then echo 'bgsage: FOUND'; else echo 'bgsage: none'; fi\\ngit status --short\",\n workdir: \"/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1\",\n yield_time_ms: 30000,\n max_output_tokens: 30000\n});\ntext(r.output);\n","internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787493962.379427}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:06:17.383Z","ordinal":896,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-28fbb57d-dbce-4d63-a5fa-088573793c0d","process_id":"4517","command":["/usr/bin/bash","-lc","sha256sum experiments/sage-gnu-campaign-v1/campaign.json\ngit diff --exit-code 6f24bd9ad470a0b7af1cc99c6692354f7d55047f..HEAD -- experiments/sage-gnu-campaign-v1/campaign.json runner/sage_gnu_campaign/config.py runner/sage_gnu_campaign/identity.py\n/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/python - <<'PY'\nfrom pathlib import Path\nfrom runner.sage_gnu_campaign.config import GNU_NORMAL_V1, load_campaign_config\nfrom runner.sage_gnu_campaign.identity import all_pair_identities\nconfig = load_campaign_config(Path('experiments/sage-gnu-campaign-v1/campaign.json'))\ndata = config.data\nassert config.content_sha256 == 'b5614f359236c78efd27aa48c544f807fee26619fce215565e1cbb930abfc2df'\nassert data['match']['length_points'] == 7\nassert data['engines']['sage']['checker_configured_target'] == '4ply'\nassert data['engines']['sage']['cube_configured_target'] == '3ply'\nassert data['engines']['gnu']['checker_configured_target'] == '3ply'\nassert data['engines']['gnu']['cube_configured_target'] == '2ply'\nassert data['engines']['gnu']['checker_move_filter_identity'] == GNU_NORMAL_V1\nassert data['engines']['sage']['threads'] == data['engines']['gnu']['threads'] == 1\nassert data['match']['members'] == {\n 'A': {'gnu_physical_seat': 'X', 'sage_physical_seat': 'O'},\n 'B': {'gnu_physical_seat': 'O', 'sage_physical_seat': 'X'},\n}\nprint('frozen-semantics: verified')\nfor identity in all_pair_identities(config):\n print(identity.pair_index, identity.pair_id, identity.base_seed)\nPY\nif pgrep -x gnubg >/dev/null; then echo 'gnubg: FOUND'; else echo 'gnubg: none'; fi\nif pgrep -x bgsage >/dev/null; then echo 'bgsage: FOUND'; else echo 'bgsage: none'; fi\ngit status --short"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"sha256sum experiments/sage-gnu-campaign-v1/campaign.json\ngit diff --exit-code 6f24bd9ad470a0b7af1cc99c6692354f7d55047f..HEAD -- experiments/sage-gnu-campaign-v1/campaign.json runner/sage_gnu_campaign/config.py runner/sage_gnu_campaign/identity.py\n/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/python - <<'PY'\nfrom pathlib import Path\nfrom runner.sage_gnu_campaign.config import GNU_NORMAL_V1, load_campaign_config\nfrom runner.sage_gnu_campaign.identity import all_pair_identities\nconfig = load_campaign_config(Path('experiments/sage-gnu-campaign-v1/campaign.json'))\ndata = config.data\nassert config.content_sha256 == 'b5614f359236c78efd27aa48c544f807fee26619fce215565e1cbb930abfc2df'\nassert data['match']['length_points'] == 7\nassert data['engines']['sage']['checker_configured_target'] == '4ply'\nassert data['engines']['sage']['cube_configured_target'] == '3ply'\nassert data['engines']['gnu']['checker_configured_target'] == '3ply'\nassert data['engines']['gnu']['cube_configured_target'] == '2ply'\nassert data['engines']['gnu']['checker_move_filter_identity'] == GNU_NORMAL_V1\nassert data['engines']['sage']['threads'] == data['engines']['gnu']['threads'] == 1\nassert data['match']['members'] == {\n 'A': {'gnu_physical_seat': 'X', 'sage_physical_seat': 'O'},\n 'B': {'gnu_physical_seat': 'O', 'sage_physical_seat': 'X'},\n}\nprint('frozen-semantics: verified')\nfor identity in all_pair_identities(config):\n print(identity.pair_index, identity.pair_id, identity.base_seed)\nPY\nif pgrep -x gnubg >/dev/null; then echo 'gnubg: FOUND'; else echo 'gnubg: none'; fi\nif pgrep -x bgsage >/dev/null; then echo 'bgsage: FOUND'; else echo 'bgsage: none'; fi\ngit status --short"}],"source":"unified_exec_startup","status":"completed","stdout":"gnubg: none\nbgsage: none\n","stderr":"","aggregated_output":"gnubg: none\nbgsage: none\n","exit_code":0,"duration":{"secs":0,"nanos":101167725},"formatted_output":"gnubg: none\nbgsage: none\n"},"started_at_ms":1787493977281,"completed_at_ms":1787493977382}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:06:17.397Z","ordinal":897,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ef1-8d34-7bd2-9d81-75fa6f90ea84","call_id":"call_uQKABQHv6IaSFsYKdlIKRCJR","output":[{"type":"input_text","text":"Script completed\nWall time 0.3 seconds\nOutput:\n"},{"type":"input_text","text":"b5614f359236c78efd27aa48c544f807fee26619fce215565e1cbb930abfc2df experiments/sage-gnu-campaign-v1/campaign.json\nfrozen-semantics: verified\n1 pair-000001-191bd73684228b92 sha256:4ecb9ad6b75ca0e48096bad7538fd8fb6a038ebdd9ad3cc8fe27619cfec89741\n2 pair-000002-b7d496198d08e6d6 sha256:3698e327c52f2829e1bfbb37856e2bdab9545aedc77620ffbbb70ba3501a8bea\n3 pair-000003-4b5d30f11044e588 sha256:4cba25c8fb34350cc20c7c94d40ee43dad44c58b90b06dbc542f53983d16a379\n4 pair-000004-22331c1a00a5cb91 sha256:76a6196480d833e6a9bab1115263698b0b6a24cdc415e8f3a04b83e1c4a2a5b5\n5 pair-000005-b6075e04ac48502e sha256:136b1e4b3867a90e715d6b8146d9116cc72f5784cf9017624cd31448ff4d2093\n6 pair-000006-32e23df6c2166691 sha256:6418147a5caf63ea29e820d0d5eeb59eb20f0d1d7ee2c4e1fe155bdbff5c64ca\n7 pair-000007-7a9cb7680d223643 sha256:7065090f23281039162b228008ef21f029943345afc02d83c9686afd8c4b3228\n8 pair-000008-57a6631b5b227e4b sha256:b4f5f3414c2a623192bba481125cb333dafa2d505b00902ded7fe3b2116d5b1b\n9 pair-000009-2eb4f7617c57ab2f sha256:afac3bae6345d82748a287fcca7c8050de059e22ce0c338ab6e7c84df95c438c\n10 pair-000010-ee7ead7b9a0380dd sha256:33368eb22c6559e08a1f88206c33602d45bb9b407e35aaa23cb3882bccc1a490\ngnubg: none\nbgsage: none\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787493977.3969936}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:07:18.764Z","ordinal":907,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-2274d257-4d0c-4007-8228-41c066198c93","process_id":"75801","command":["/usr/bin/bash","-lc","/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"/users/a2andrad/scratch/retcorpus-k001-sage-gnu-hardening-test-venv-v1/bin/pytest -q --disable-warnings"}],"source":"unified_exec_startup","status":"completed","stdout":"........................................................................ [ 29%]\n........................................................................ [ 58%]\n........................................................................ [ 87%]\n.............................. [100%]\n246 passed in 55.27s\n","stderr":"","aggregated_output":"........................................................................ [ 29%]\n........................................................................ [ 58%]\n........................................................................ [ 87%]\n.............................. [100%]\n246 passed in 55.27s\n","exit_code":0,"duration":{"secs":55,"nanos":558770231},"formatted_output":"........................................................................ [ 29%]\n........................................................................ [ 58%]\n........................................................................ [ 87%]\n.............................. [100%]\n246 passed in 55.27s\n"},"started_at_ms":1787493983197,"completed_at_ms":1787494038756}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:07:18.773Z","ordinal":908,"type":"response_item","payload":{"type":"custom_tool_call_output","id":"ctco_01a02ef2-7cf5-7763-821f-ae803b57e698","call_id":"call_jeoeh3j8c2VqjhjuDXsh8GPA","output":[{"type":"input_text","text":"Script completed\nWall time 23.0 seconds\nOutput:\n"},{"type":"input_text","text":".................................................... [ 58%]\n........................................................................ [ 87%]\n.............................. [100%]\n246 passed in 55.27s\n"}],"internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787494038.7732954}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:07:27.505Z","ordinal":912,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"AgentMessage","id":"msg_04340d676abbd611016a8afe9caf9c87d1bfa302378571735b","content":[{"type":"Text","text":"The committed-tree gate passed: `246 passed in 55.27s`; campaign SHA and frozen semantics are exact; all ten pair IDs/seeds printed and unchanged; neither `gnubg` nor `bgsage` is running. I’m collecting the final commit/file inventory and one last clean/local-only check."}],"phase":"commentary"},"started_at_ms":1787494044753,"completed_at_ms":1787494047505}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:07:27.508Z","ordinal":913,"type":"response_item","payload":{"type":"message","id":"msg_04340d676abbd611016a8afe9caf9c87d1bfa302378571735b","role":"assistant","content":[{"type":"output_text","text":"The committed-tree gate passed: `246 passed in 55.27s`; campaign SHA and frozen semantics are exact; all ten pair IDs/seeds printed and unchanged; neither `gnubg` nor `bgsage` is running. I’m collecting the final commit/file inventory and one last clean/local-only check."}],"phase":"commentary","internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787494039.209291}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:07:32.757Z","ordinal":914,"type":"response_item","payload":{"type":"custom_tool_call","id":"ctc_04340d676abbd611016a8afe9f745487d1a3ae943855a225b8","status":"completed","call_id":"call_AEQPIxEyDWQtFVk3dTdr8AKl","name":"exec","input":"const r = await tools.exec_command({\n cmd: \"git rev-parse HEAD\\ngit log --oneline --reverse 6f24bd9ad470a0b7af1cc99c6692354f7d55047f..HEAD\\ngit show --format='COMMIT %H %s' --name-only --no-renames 250eb12 adb81a3 acfb170 b68f70b 0222bb0 3623c70\\nif pgrep -x gnubg >/dev/null; then echo 'gnubg: FOUND'; else echo 'gnubg: none'; fi\\nif pgrep -x bgsage >/dev/null; then echo 'bgsage: FOUND'; else echo 'bgsage: none'; fi\\ngit status --short\\ngit status --branch --short\\nupstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null || true); if [ -n \\\"$upstream\\\" ]; then echo \\\"upstream: $upstream\\\"; git rev-list --left-right --count \\\"$upstream\\\"...HEAD; else echo 'upstream: none'; fi\\ngit reflog -20 --format='%h %gs' | sed -n '1,20p'\",\n workdir: \"/users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1\",\n yield_time_ms: 30000,\n max_output_tokens: 40000\n});\ntext(r.output);\n","internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787494039.209291}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:07:33.060Z","ordinal":915,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"CommandExecution","id":"exec-08c9732b-cdbb-4388-81e3-97fa7cef2f81","process_id":"25100","command":["/usr/bin/bash","-lc","git rev-parse HEAD\ngit log --oneline --reverse 6f24bd9ad470a0b7af1cc99c6692354f7d55047f..HEAD\ngit show --format='COMMIT %H %s' --name-only --no-renames 250eb12 adb81a3 acfb170 b68f70b 0222bb0 3623c70\nif pgrep -x gnubg >/dev/null; then echo 'gnubg: FOUND'; else echo 'gnubg: none'; fi\nif pgrep -x bgsage >/dev/null; then echo 'bgsage: FOUND'; else echo 'bgsage: none'; fi\ngit status --short\ngit status --branch --short\nupstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null || true); if [ -n \"$upstream\" ]; then echo \"upstream: $upstream\"; git rev-list --left-right --count \"$upstream\"...HEAD; else echo 'upstream: none'; fi\ngit reflog -20 --format='%h %gs' | sed -n '1,20p'"],"cwd":"file:///users/a2andrad/workspaces/retcorpus-k001-sage3-gnu4-benchmark/public-runner-reviewed-fixes-v1","parsed_cmd":[{"type":"unknown","cmd":"git rev-parse HEAD\ngit log --oneline --reverse 6f24bd9ad470a0b7af1cc99c6692354f7d55047f..HEAD\ngit show --format='COMMIT %H %s' --name-only --no-renames 250eb12 adb81a3 acfb170 b68f70b 0222bb0 3623c70\nif pgrep -x gnubg >/dev/null; then echo 'gnubg: FOUND'; else echo 'gnubg: none'; fi\nif pgrep -x bgsage >/dev/null; then echo 'bgsage: FOUND'; else echo 'bgsage: none'; fi\ngit status --short\ngit status --branch --short\nupstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null || true); if [ -n \"$upstream\" ]; then echo \"upstream: $upstream\"; git rev-list --left-right --count \"$upstream\"...HEAD; else echo 'upstream: none'; fi\ngit reflog -20 --format='%h %gs' | sed -n '1,20p'"}],"source":"unified_exec_startup","status":"completed","stdout":"bgsage: none\n## benchmark/sage4-gnu3-public-v1...origin/benchmark/sage4-gnu3-public-v1 [ahead 24]\nupstream: origin/benchmark/sage4-gnu3-public-v1\n0\t24\n3623c70 commit: fix: validate state-bearing GNU SGF properties\n0222bb0 commit: fix: require complete deterministic dice stream inventory\nb68f70b commit: fix: preserve checker dice seat identity at publication\nacfb170 commit: fix: make analysis journals publication authority\nadb81a3 commit: fix: require state-connected publication decision history\n250eb12 commit: fix: derive SGF application identity from frozen GNU runtime\n6f24bd9 commit: fix: durably preserve match failure forensics\nd93e37e commit: fix: validate publication terminal and journal semantics\n276c8b9 commit: fix: compare complete native GNU game sequences\ncb69a52 commit: fix: reconcile complete native multi-game evidence\nffb62e9 commit: fix: preserve command-specific terminal events across games\nd81106c commit: fix: durably anchor published pair hierarchy\nc42fe7d commit: fix: preserve primitive malformed Engine Kit results\nd7b9c8a commit: fix: anchor journal durability at runtime root\n4308fec commit: fix: enforce complete GNU action postconditions\naf7b0b7 commit: fix: enforce complete GNU opening state semantics\nc36da80 commit: fix: validate multi-game GNU native player mappings\nc46380d commit: fix: preserve GNU board startup exception during cleanup\n3821cda commit: fix: durably create analysis journal hierarchy\n718d516 commit: fix: enforce command-specific GNU board postconditions\n","stderr":"","aggregated_output":"bgsage: none\n## benchmark/sage4-gnu3-public-v1...origin/benchmark/sage4-gnu3-public-v1 [ahead 24]\nupstream: origin/benchmark/sage4-gnu3-public-v1\n0\t24\n3623c70 commit: fix: validate state-bearing GNU SGF properties\n0222bb0 commit: fix: require complete deterministic dice stream inventory\nb68f70b commit: fix: preserve checker dice seat identity at publication\nacfb170 commit: fix: make analysis journals publication authority\nadb81a3 commit: fix: require state-connected publication decision history\n250eb12 commit: fix: derive SGF application identity from frozen GNU runtime\n6f24bd9 commit: fix: durably preserve match failure forensics\nd93e37e commit: fix: validate publication terminal and journal semantics\n276c8b9 commit: fix: compare complete native GNU game sequences\ncb69a52 commit: fix: reconcile complete native multi-game evidence\nffb62e9 commit: fix: preserve command-specific terminal events across games\nd81106c commit: fix: durably anchor published pair hierarchy\nc42fe7d commit: fix: preserve primitive malformed Engine Kit results\nd7b9c8a commit: fix: anchor journal durability at runtime root\n4308fec commit: fix: enforce complete GNU action postconditions\naf7b0b7 commit: fix: enforce complete GNU opening state semantics\nc36da80 commit: fix: validate multi-game GNU native player mappings\nc46380d commit: fix: preserve GNU board startup exception during cleanup\n3821cda commit: fix: durably create analysis journal hierarchy\n718d516 commit: fix: enforce command-specific GNU board postconditions\n","exit_code":0,"duration":{"secs":0,"nanos":69982972},"formatted_output":"bgsage: none\n## benchmark/sage4-gnu3-public-v1...origin/benchmark/sage4-gnu3-public-v1 [ahead 24]\nupstream: origin/benchmark/sage4-gnu3-public-v1\n0\t24\n3623c70 commit: fix: validate state-bearing GNU SGF properties\n0222bb0 commit: fix: require complete deterministic dice stream inventory\nb68f70b commit: fix: preserve checker dice seat identity at publication\nacfb170 commit: fix: make analysis journals publication authority\nadb81a3 commit: fix: require state-connected publication decision history\n250eb12 commit: fix: derive SGF application identity from frozen GNU runtime\n6f24bd9 commit: fix: durably preserve match failure forensics\nd93e37e commit: fix: validate publication terminal and journal semantics\n276c8b9 commit: fix: compare complete native GNU game sequences\ncb69a52 commit: fix: reconcile complete native multi-game evidence\nffb62e9 commit: fix: preserve command-specific terminal events across games\nd81106c commit: fix: durably anchor published pair hierarchy\nc42fe7d commit: fix: preserve primitive malformed Engine Kit results\nd7b9c8a commit: fix: anchor journal durability at runtime root\n4308fec commit: fix: enforce complete GNU action postconditions\naf7b0b7 commit: fix: enforce complete GNU opening state semantics\nc36da80 commit: fix: validate multi-game GNU native player mappings\nc46380d commit: fix: preserve GNU board startup exception during cleanup\n3821cda commit: fix: durably create analysis journal hierarchy\n718d516 commit: fix: enforce command-specific GNU board postconditions\n"},"started_at_ms":1787494052989,"completed_at_ms":1787494053060}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:08:26.112Z","ordinal":925,"type":"event_msg","payload":{"type":"item_completed","thread_id":"01a02ec8-0da7-7023-8f0e-10fa2d37d56e","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","item":{"type":"AgentMessage","id":"msg_04340d676abbd611016a8afebba79087d1855fd265136128ba","content":[{"type":"Text","text":"Starting HEAD: `6f24bd9ad470a0b7af1cc99c6692354f7d55047f` \nFinal HEAD: `3623c70fa56fcf81f3126a64abb0b762154ad545`\n\n### Local commits\n\n1. `250eb125470623eb5cecf8e633cfbfac0de9b086` — `fix: derive SGF application identity from frozen GNU runtime`\n - `runner/sage_gnu_campaign/campaign.py`\n - `runner/sage_gnu_campaign/match.py`\n - GNU 1.08.003 golden SGF/text fixtures\n - native fixture and match-policy tests\n\n2. `adb81a327d8f4052211dcc31e7b6fa020ce92ae6` — `fix: require state-connected publication decision history`\n - `runner/sage_gnu_campaign/match.py`\n - native fixtures\n - publication tests\n\n3. `acfb170000106c4a34ee8e81bb30ab0c5f1792ec` — `fix: make analysis journals publication authority`\n - `runner/sage_gnu_campaign/campaign.py`\n - `runner/sage_gnu_campaign/match.py`\n - native fixtures\n - Engine Kit boundary and publication tests\n\n4. `b68f70b2679317021c228bbe7077fea866c71542` — `fix: preserve checker dice seat identity at publication`\n - `runner/sage_gnu_campaign/match.py`\n - native fixtures\n - publication tests\n\n5. `0222bb02481f31e71e9d69e72e5b58fbad3c1686` — `fix: require complete deterministic dice stream inventory`\n - `runner/sage_gnu_campaign/dice.py`\n - `runner/sage_gnu_campaign/match.py`\n - `tests/conftest.py`\n - native fixtures\n - publication tests\n\n6. `3623c70fa56fcf81f3126a64abb0b762154ad545` — `fix: validate state-bearing GNU SGF properties`\n - `runner/sage_gnu_campaign/match.py`\n - GNU 1.08.003 golden SGF fixture\n - native fixtures\n - match-policy and publication tests\n\n### Remediation\n\n- SGF `AP` now derives from frozen GNU runtime identity and accepts `GNU Backgammon:1.08.003`; obsolete, missing, malformed, wrong-app, and wrong-version values reject.\n- Publication strictly decodes GNUIDs, requires connected decision states, preserves explicit automatic transitions, requires roll records, and replays live roll/checker/cube/resignation/terminal semantics.\n- Analysis request/result journals, raw Engine Kit evidence, decision evidence, configured/actual depths, ordering, associations, paths, and hashes are independently reconciled. GNU checker’s permitted shallower actual depth remains valid.\n- Checker dice reconciliation now binds game/action ordinal, seat, engine, dice, stream ID/path, and roll index. Equal-dice cross-seat, cross-engine, cross-stream, and cross-side substitutions reject.\n- Exactly 50 deterministic streams—25 slots × O/X—are required and byte-regenerated, including inactive streams. Missing, extra, duplicated, forged, or misidentified entries/files reject.\n- Frozen GNU state-bearing SGF properties are explicitly classified. Supported initial setup is canonicalized and reconciled; unsupported `CV`, `CP`, `DI`, or mid-game setup fails closed. The exact implicit cube-1 → `CV[2]` counterexample rejects, while harmless metadata remains accepted.\n\nNegative coverage includes disconnected/fabricated/swapped states; missing/extra/moved rolls; wrong checker/cube/terminal/opening states; journal omission, duplication, reordering, cross-association and depth corruption; equal-dice identity collisions; full active/inactive inventory corruption; and cube, owner, player, dice, checker-setup, inconsistent-setup, and metadata SGF cases.\n\n### No-engine and identity gate\n\n- Complete no-engine suite: `246 passed in 55.27s`\n- Campaign SHA-256: `b5614f359236c78efd27aa48c544f807fee26619fce215565e1cbb930abfc2df`\n- `campaign.json`, `config.py`, and `identity.py`: unchanged from starting HEAD\n- Sage checker/cube: `4ply/3ply`\n- GNU checker/cube: `3ply/2ply`\n- `normal-v1`, match length 7, threads 1/1, and mirrored A/B mapping: unchanged\n\nAll pair identities remain unchanged:\n\n```text\n1 pair-000001-191bd73684228b92 sha256:4ecb9ad6b75ca0e48096bad7538fd8fb6a038ebdd9ad3cc8fe27619cfec89741\n2 pair-000002-b7d496198d08e6d6 sha256:3698e327c52f2829e1bfbb37856e2bdab9545aedc77620ffbbb70ba3501a8bea\n3 pair-000003-4b5d30f11044e588 sha256:4cba25c8fb34350cc20c7c94d40ee43dad44c58b90b06dbc542f53983d16a379\n4 pair-000004-22331c1a00a5cb91 sha256:76a6196480d833e6a9bab1115263698b0b6a24cdc415e8f3a04b83e1c4a2a5b5\n5 pair-000005-b6075e04ac48502e sha256:136b1e4b3867a90e715d6b8146d9116cc72f5784cf9017624cd31448ff4d2093\n6 pair-000006-32e23df6c2166691 sha256:6418147a5caf63ea29e820d0d5eeb59eb20f0d1d7ee2c4e1fe155bdbff5c64ca\n7 pair-000007-7a9cb7680d223643 sha256:7065090f23281039162b228008ef21f029943345afc02d83c9686afd8c4b3228\n8 pair-000008-57a6631b5b227e4b sha256:b4f5f3414c2a623192bba481125cb333dafa2d505b00902ded7fe3b2116d5b1b\n9 pair-000009-2eb4f7617c57ab2f sha256:afac3bae6345d82748a287fcca7c8050de059e22ce0c338ab6e7c84df95c438c\n10 pair-000010-ee7ead7b9a0380dd sha256:33368eb22c6559e08a1f88206c33602d45bb9b407e35aaa23cb3882bccc1a490\n```\n\n`pgrep -x gnubg`: none \n`pgrep -x bgsage`: none \nFinal `git status --short`: clean \nNo push occurred; branch remains local and 24 commits ahead of upstream, preserving the complete recovery history and failed-smoke evidence.\n\nIMPLEMENTATION_DONE"}],"phase":"final_answer"},"started_at_ms":1787494075720,"completed_at_ms":1787494106112}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:08:26.239Z","ordinal":926,"type":"response_item","payload":{"type":"message","id":"msg_04340d676abbd611016a8afebba79087d1855fd265136128ba","role":"assistant","content":[{"type":"output_text","text":"Starting HEAD: `6f24bd9ad470a0b7af1cc99c6692354f7d55047f` \nFinal HEAD: `3623c70fa56fcf81f3126a64abb0b762154ad545`\n\n### Local commits\n\n1. `250eb125470623eb5cecf8e633cfbfac0de9b086` — `fix: derive SGF application identity from frozen GNU runtime`\n - `runner/sage_gnu_campaign/campaign.py`\n - `runner/sage_gnu_campaign/match.py`\n - GNU 1.08.003 golden SGF/text fixtures\n - native fixture and match-policy tests\n\n2. `adb81a327d8f4052211dcc31e7b6fa020ce92ae6` — `fix: require state-connected publication decision history`\n - `runner/sage_gnu_campaign/match.py`\n - native fixtures\n - publication tests\n\n3. `acfb170000106c4a34ee8e81bb30ab0c5f1792ec` — `fix: make analysis journals publication authority`\n - `runner/sage_gnu_campaign/campaign.py`\n - `runner/sage_gnu_campaign/match.py`\n - native fixtures\n - Engine Kit boundary and publication tests\n\n4. `b68f70b2679317021c228bbe7077fea866c71542` — `fix: preserve checker dice seat identity at publication`\n - `runner/sage_gnu_campaign/match.py`\n - native fixtures\n - publication tests\n\n5. `0222bb02481f31e71e9d69e72e5b58fbad3c1686` — `fix: require complete deterministic dice stream inventory`\n - `runner/sage_gnu_campaign/dice.py`\n - `runner/sage_gnu_campaign/match.py`\n - `tests/conftest.py`\n - native fixtures\n - publication tests\n\n6. `3623c70fa56fcf81f3126a64abb0b762154ad545` — `fix: validate state-bearing GNU SGF properties`\n - `runner/sage_gnu_campaign/match.py`\n - GNU 1.08.003 golden SGF fixture\n - native fixtures\n - match-policy and publication tests\n\n### Remediation\n\n- SGF `AP` now derives from frozen GNU runtime identity and accepts `GNU Backgammon:1.08.003`; obsolete, missing, malformed, wrong-app, and wrong-version values reject.\n- Publication strictly decodes GNUIDs, requires connected decision states, preserves explicit automatic transitions, requires roll records, and replays live roll/checker/cube/resignation/terminal semantics.\n- Analysis request/result journals, raw Engine Kit evidence, decision evidence, configured/actual depths, ordering, associations, paths, and hashes are independently reconciled. GNU checker’s permitted shallower actual depth remains valid.\n- Checker dice reconciliation now binds game/action ordinal, seat, engine, dice, stream ID/path, and roll index. Equal-dice cross-seat, cross-engine, cross-stream, and cross-side substitutions reject.\n- Exactly 50 deterministic streams—25 slots × O/X—are required and byte-regenerated, including inactive streams. Missing, extra, duplicated, forged, or misidentified entries/files reject.\n- Frozen GNU state-bearing SGF properties are explicitly classified. Supported initial setup is canonicalized and reconciled; unsupported `CV`, `CP`, `DI`, or mid-game setup fails closed. The exact implicit cube-1 → `CV[2]` counterexample rejects, while harmless metadata remains accepted.\n\nNegative coverage includes disconnected/fabricated/swapped states; missing/extra/moved rolls; wrong checker/cube/terminal/opening states; journal omission, duplication, reordering, cross-association and depth corruption; equal-dice identity collisions; full active/inactive inventory corruption; and cube, owner, player, dice, checker-setup, inconsistent-setup, and metadata SGF cases.\n\n### No-engine and identity gate\n\n- Complete no-engine suite: `246 passed in 55.27s`\n- Campaign SHA-256: `b5614f359236c78efd27aa48c544f807fee26619fce215565e1cbb930abfc2df`\n- `campaign.json`, `config.py`, and `identity.py`: unchanged from starting HEAD\n- Sage checker/cube: `4ply/3ply`\n- GNU checker/cube: `3ply/2ply`\n- `normal-v1`, match length 7, threads 1/1, and mirrored A/B mapping: unchanged\n\nAll pair identities remain unchanged:\n\n```text\n1 pair-000001-191bd73684228b92 sha256:4ecb9ad6b75ca0e48096bad7538fd8fb6a038ebdd9ad3cc8fe27619cfec89741\n2 pair-000002-b7d496198d08e6d6 sha256:3698e327c52f2829e1bfbb37856e2bdab9545aedc77620ffbbb70ba3501a8bea\n3 pair-000003-4b5d30f11044e588 sha256:4cba25c8fb34350cc20c7c94d40ee43dad44c58b90b06dbc542f53983d16a379\n4 pair-000004-22331c1a00a5cb91 sha256:76a6196480d833e6a9bab1115263698b0b6a24cdc415e8f3a04b83e1c4a2a5b5\n5 pair-000005-b6075e04ac48502e sha256:136b1e4b3867a90e715d6b8146d9116cc72f5784cf9017624cd31448ff4d2093\n6 pair-000006-32e23df6c2166691 sha256:6418147a5caf63ea29e820d0d5eeb59eb20f0d1d7ee2c4e1fe155bdbff5c64ca\n7 pair-000007-7a9cb7680d223643 sha256:7065090f23281039162b228008ef21f029943345afc02d83c9686afd8c4b3228\n8 pair-000008-57a6631b5b227e4b sha256:b4f5f3414c2a623192bba481125cb333dafa2d505b00902ded7fe3b2116d5b1b\n9 pair-000009-2eb4f7617c57ab2f sha256:afac3bae6345d82748a287fcca7c8050de059e22ce0c338ab6e7c84df95c438c\n10 pair-000010-ee7ead7b9a0380dd sha256:33368eb22c6559e08a1f88206c33602d45bb9b407e35aaa23cb3882bccc1a490\n```\n\n`pgrep -x gnubg`: none \n`pgrep -x bgsage`: none \nFinal `git status --short`: clean \nNo push occurred; branch remains local and 24 commits ahead of upstream, preserving the complete recovery history and failed-smoke evidence.\n\nIMPLEMENTATION_DONE"}],"phase":"final_answer","internal_chat_message_metadata_passthrough":{"turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","create_time":1787494065.051827}}} /users/a2andrad/.codex/sessions/2026/08/23/rollout-2026-08-23T09-20-57-01a02ec8-0da7-7023-8f0e-10fa2d37d56e.jsonl:{"timestamp":"2026-08-23T14:08:26.283Z","ordinal":928,"type":"event_msg","payload":{"type":"task_complete","turn_id":"01a02ec8-38eb-71c1-8d95-c5c55b8df3a0","last_agent_message":"Starting HEAD: `6f24bd9ad470a0b7af1cc99c6692354f7d55047f` \nFinal HEAD: `3623c70fa56fcf81f3126a64abb0b762154ad545`\n\n### Local commits\n\n1. `250eb125470623eb5cecf8e633cfbfac0de9b086` — `fix: derive SGF application identity from frozen GNU runtime`\n - `runner/sage_gnu_campaign/campaign.py`\n - `runner/sage_gnu_campaign/match.py`\n - GNU 1.08.003 golden SGF/text fixtures\n - native fixture and match-policy tests\n\n2. `adb81a327d8f4052211dcc31e7b6fa020ce92ae6` — `fix: require state-connected publication decision history`\n - `runner/sage_gnu_campaign/match.py`\n - native fixtures\n - publication tests\n\n3. `acfb170000106c4a34ee8e81bb30ab0c5f1792ec` — `fix: make analysis journals publication authority`\n - `runner/sage_gnu_campaign/campaign.py`\n - `runner/sage_gnu_campaign/match.py`\n - native fixtures\n - Engine Kit boundary and publication tests\n\n4. `b68f70b2679317021c228bbe7077fea866c71542` — `fix: preserve checker dice seat identity at publication`\n - `runner/sage_gnu_campaign/match.py`\n - native fixtures\n - publication tests\n\n5. `0222bb02481f31e71e9d69e72e5b58fbad3c1686` — `fix: require complete deterministic dice stream inventory`\n - `runner/sage_gnu_campaign/dice.py`\n - `runner/sage_gnu_campaign/match.py`\n - `tests/conftest.py`\n - native fixtures\n - publication tests\n\n6. `3623c70fa56fcf81f3126a64abb0b762154ad545` — `fix: validate state-bearing GNU SGF properties`\n - `runner/sage_gnu_campaign/match.py`\n - GNU 1.08.003 golden SGF fixture\n - native fixtures\n - match-policy and publication tests\n\n### Remediation\n\n- SGF `AP` now derives from frozen GNU runtime identity and accepts `GNU Backgammon:1.08.003`; obsolete, missing, malformed, wrong-app, and wrong-version values reject.\n- Publication strictly decodes GNUIDs, requires connected decision states, preserves explicit automatic transitions, requires roll records, and replays live roll/checker/cube/resignation/terminal semantics.\n- Analysis request/result journals, raw Engine Kit evidence, decision evidence, configured/actual depths, ordering, associations, paths, and hashes are independently reconciled. GNU checker’s permitted shallower actual depth remains valid.\n- Checker dice reconciliation now binds game/action ordinal, seat, engine, dice, stream ID/path, and roll index. Equal-dice cross-seat, cross-engine, cross-stream, and cross-side substitutions reject.\n- Exactly 50 deterministic streams—25 slots × O/X—are required and byte-regenerated, including inactive streams. Missing, extra, duplicated, forged, or misidentified entries/files reject.\n- Frozen GNU state-bearing SGF properties are explicitly classified. Supported initial setup is canonicalized and reconciled; unsupported `CV`, `CP`, `DI`, or mid-game setup fails closed. The exact implicit cube-1 → `CV[2]` counterexample rejects, while harmless metadata remains accepted.\n\nNegative coverage includes disconnected/fabricated/swapped states; missing/extra/moved rolls; wrong checker/cube/terminal/opening states; journal omission, duplication, reordering, cross-association and depth corruption; equal-dice identity collisions; full active/inactive inventory corruption; and cube, owner, player, dice, checker-setup, inconsistent-setup, and metadata SGF cases.\n\n### No-engine and identity gate\n\n- Complete no-engine suite: `246 passed in 55.27s`\n- Campaign SHA-256: `b5614f359236c78efd27aa48c544f807fee26619fce215565e1cbb930abfc2df`\n- `campaign.json`, `config.py`, and `identity.py`: unchanged from starting HEAD\n- Sage checker/cube: `4ply/3ply`\n- GNU checker/cube: `3ply/2ply`\n- `normal-v1`, match length 7, threads 1/1, and mirrored A/B mapping: unchanged\n\nAll pair identities remain unchanged:\n\n```text\n1 pair-000001-191bd73684228b92 sha256:4ecb9ad6b75ca0e48096bad7538fd8fb6a038ebdd9ad3cc8fe27619cfec89741\n2 pair-000002-b7d496198d08e6d6 sha256:3698e327c52f2829e1bfbb37856e2bdab9545aedc77620ffbbb70ba3501a8bea\n3 pair-000003-4b5d30f11044e588 sha256:4cba25c8fb34350cc20c7c94d40ee43dad44c58b90b06dbc542f53983d16a379\n4 pair-000004-22331c1a00a5cb91 sha256:76a6196480d833e6a9bab1115263698b0b6a24cdc415e8f3a04b83e1c4a2a5b5\n5 pair-000005-b6075e04ac48502e sha256:136b1e4b3867a90e715d6b8146d9116cc72f5784cf9017624cd31448ff4d2093\n6 pair-000006-32e23df6c2166691 sha256:6418147a5caf63ea29e820d0d5eeb59eb20f0d1d7ee2c4e1fe155bdbff5c64ca\n7 pair-000007-7a9cb7680d223643 sha256:7065090f23281039162b228008ef21f029943345afc02d83c9686afd8c4b3228\n8 pair-000008-57a6631b5b227e4b sha256:b4f5f3414c2a623192bba481125cb333dafa2d505b00902ded7fe3b2116d5b1b\n9 pair-000009-2eb4f7617c57ab2f sha256:afac3bae6345d82748a287fcca7c8050de059e22ce0c338ab6e7c84df95c438c\n10 pair-000010-ee7ead7b9a0380dd sha256:33368eb22c6559e08a1f88206c33602d45bb9b407e35aaa23cb3882bccc1a490\n```\n\n`pgrep -x gnubg`: none \n`pgrep -x bgsage`: none \nFinal `git status --short`: clean \nNo push occurred; branch remains local and 24 commits ahead of upstream, preserving the complete recovery history and failed-smoke evidence.\n\nIMPLEMENTATION_DONE","started_at":1787491268,"completed_at":1787494106,"duration_ms":2837390,"time_to_first_token_ms":6613}}