Repository navigation
Version the diagnosis judge's template and drop its false review claim from v2 - #210
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ants render_case_prompt, prepare_audit and audit-prepare now require the template version (keyword-only, no default; --template-version is required). The three past release drivers name JUDGE_TEMPLATE_VERSION = 1, the version their releases were judged on, so re-running one reproduces its prompts; the Haiku 5.5 driver's tests seed on v1. Tests state and check the invariants: every committed prompt sha256 (docs/gpt61sol, docs/haiku55) re-renders from its release's committed board on its recorded version (local, needs the grounding); v2 is v1 with one deletion, the review clause; selection is explicit (signatures, an AST scan of every call, and a Hypothesis property that a prompt on another version is re-opened, never adopted). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Answers review round 1 of #210: - prepare_audit compares prompt.md as UTF-8 bytes, writes prompts as bytes and leaves a kept case's files untouched (read_text folded \r and CRLF, so a correct verdict could be dropped and a CRLF prompt kept). - A verdict without prompt.md stands only when its sidecar's prompt_sha256 is the case rendered on its recorded version; the prompt is restored. A sidecar hash that is not prompt.md's bytes re-opens the case. - run_audit_codex.sh has Codex read a private copy of prompt.md, records that copy's prompt_sha256 and template version, and publishes nothing if prompt.md changed while Codex judged it. - Tests: byte-exact properties with \r and CRLF, the missing-prompt cases (the review's changed-case scenario included), a Codex race test, and an allowlist of the one function that reads a version off a prompt. - Docs: each committed prompt re-renders from the board of the release that judged it; release 20261006 judged nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review, round 1 (GPT-6.1 Sol, at b11a959): REQUEST_CHANGES. It found three blocking issues and two non-blocking ones; all five are fixed in 575f257 and daf038b.
Each fix has tests, including Hypothesis properties that draw 🤖 Generated with Claude Code |
Answers review round 2 of #210: - audit-collect refuses a verdict whose sidecar's prompt_sha256 is not prompt.md's bytes (prompt_hash_problems), such as one published after an audit-prepare that raced its runner rewrote the case. - Both runners publish verdict.meta.json before verdict.json, so an interrupted publish leaves at most a sidecar without a verdict, never a verdict that audit-prepare would read as legacy v1 with no hash. - run_audit_codex.sh traps EXIT, TERM and INT in each worker to remove its private prompt copy. - Tests: the race at collect time, an mv shim that kills each runner's worker between its two moves, a Codex worker terminated mid-judge, and no hash problems after any preparation in both properties. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review, round 2 (GPT-6.1 Sol, at daf038b): REQUEST_CHANGES. It confirmed round 1's fixes and found one more blocking issue, in the publication window. That is fixed in 0770d99.
The new tests are the race at collect time, an 🤖 Generated with Claude Code |
Answers review round 3 of #210: an audit-prepare that dropped a stale or truncated verdict also removed the sidecar a runner had just published, stranding the runner's verdict without its record. - prepare_audit removes a sidecar only together with the verdict it describes (verdict_sha256), or a legacy sidecar that records none, and removes verdict.json only while it holds the bytes it read. - A verdict is kept only when its sidecar, if it records verdict_sha256, describes it. - Both runners remove an invalid verdict.json when they start a case, so no verdict sits beside a new sidecar before the run's verdict is published. - Tests: the review's schedule step by step and end to end on the real Codex runner (an mv shim runs audit-prepare between the two moves), the drop rule, the sidecar-describes-verdict keep rule, a property dimension for it, and interruption tests that start from a truncated verdict. - Docs state what holds when audit-prepare and a runner share a tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review, round 3 (GPT-6.1 Sol, at 0770d99): REQUEST_CHANGES. It found one more blocking issue. A racing
The review's schedule is now a test, both step by step and end to end on the real Codex runner, where an 🤖 Generated with Claude Code |
Answers review round 4 of #210: _drop_verdict can still remove a sidecar a runner has just published (it reads, then unlinks, the path; and a new verdict whose bytes repeat the old one's matches the old digest), and the stranded verdict passed as legacy v1. Every such schedule ends with a runner's verdict without its sidecar, so that state is no longer accepted: recorded_template_version(None) is None, prepare_audit re-opens the verdict, and template_version_problems (so audit-collect) refuses it. A sidecar without the field still means v1; every verdict in releases 20260929 and 20260930 has a sidecar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review, round 4 (GPT-6.1 Sol, at 18595d7): REQUEST_CHANGES. The branch now also merges main at cfd851d (#213 and #214, a clean merge), giving head fd55a1b. All 18 affected test files pass there, main's two new ones included. Round 5 of the review is running. 🤖 Generated with Claude Code |
Answers review round 5 of #210 (APPROVE, one non-blocking note): docs/audit.md said audit-prepare never removes another verdict's sidecar, which round 4's schedule disproves. The guarantee rests on refusing and re-opening a verdict without its sidecar and on the prompt-hash check; the docs and _drop_verdict's docstring now say so. No code change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#210's test_the_committed_prompt_records_agree_and_name_a_version read docs/gpt61sol/seed_digest.csv with its old three-column header, which unit 4 extended with each kept case's sidecar sha256 (meta_sha256). The reader now accepts that column beside the three it compares, and still requires one of the two headers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The diagnosis judge's prompt header now has two versions. v1 is the header every verdict so far was judged on. v2 drops v1's claim that the reference pipeline "has survived an adversarial review program" and that "the few real bugs found were fixed before this run". The claim is false: release dashboard-data-20261006's exclusion record stops scoring 28 outputs for engine defects (
reason_code: reference_engine_defectat 9ce4ade), and the 2026-10-05 reference adversary confirmed four more (reference_audit/2026-10-05-reference-adversary/README.md).This finishes the WIP commit 695dceb (2026-10-09), rebased onto main. It was held so that release dashboard-data-20261010 (#208, 5a8164a) judged every verdict on one template. That release is live, and every one of its verdicts is on v1.
What it does
policybench/judge_template.pyholds the headers, pinned by sha256. v2 is built from v1 by deletingV1_REVIEW_CLAIM.prepare_auditkeeps a verdict only on the exact bytes its judge read. It renders the judged case on the version its sidecar records (judge_template_version; a sidecar without the field means v1). A verdict without a sidecar has no record of what its judge read, so it is re-opened, andaudit-collectrefuses it. That rendering must equalprompt.mdbyte for byte, and must match the sidecar'sprompt_sha256when there is one. Withoutprompt.md, only a matchingprompt_sha256keeps the verdict, and the prompt is restored. A sidecar that recordsverdict_sha256must also describe the verdict. A kept case's files are not rewritten. Anything else is re-opened: its verdict is dropped, and its sidecar too, unless the sidecar describes another verdict when prepare reads it. Prompts are written as UTF-8 bytes.render_case_prompt,prepare_auditandaudit-prepare --template-versionall require it. There is no default.prompt.md, record itsprompt_sha256andjudge_template_versioninverdict.meta.json, and publish nothing ifprompt.mdchanged during the run. That is new for the Codex runner. Both remove an invalidverdict.jsonand any stale sidecar before judging a case, and publish the sidecar before the verdict. With one runner per case and oneaudit-prepareat a time, the two may share a tree: once both finish, no verdict passes collect, or survives the next prepare, on bytes its judge did not read.audit-collectrefuses a tree whose verdicts disagree with their prompts on the version (template_version_problems), or whose sidecarprompt_sha256is notprompt.md's bytes (prompt_hash_problems).JUDGE_TEMPLATE_VERSION = 1:finish_adds0928.py,finish_gpt61sol.pyandfinish_haiku55.py. Their releases were judged on v1, so re-running one reproduces its prompts. A new release's driver names v2.Changes from the WIP
scripts/finish_haiku55.pyreached main after the WIP's base. On the plain cherry-pick it rendered its in-memory audit on v2, re-opened every kept case, and 31 of its tests failed (19 failed, 12 errors;evidence/baseline_haiku.txt). Its tests now seed on v1, as release 20261006's seed was. That includestest_every_seed_prompt_rerenders_except_the_nine_reworded.prompt.mdwas kept unconditionally, both before this PR and in the WIP. Now it needs its sidecar's hash.read_text, which folds\rand CRLF. They now compare as bytes.audit-preparerewrote its case passedaudit-collect. Collect now checks the sidecar's hash.audit-preparere-opens it andaudit-collectrefuses it. Every verdict in releases 20260929 and 20260930 has a sidecar.docs/audit.mdgains an invariants section and the--template-version 2pipeline step. v1 is dated through release 20261010.Invariants
Each holds for every input. They are tested in
tests/test_judge_template.py.Every committed prompt re-renders under its recorded template version. Each committed prompt record re-renders from the board of the release that judged it. Release 20261006 judged nothing: it carried release 20260930's audit while rewording nine explanations, which release 20261010 re-opened. The committed prompt records are:
docs/gpt61sol/seed_digest.csv);docs/gpt61sol/judge_provenance.json);docs/haiku55/seed_digest.csv);docs/haiku55/judge_provenance.json).Tests:
test_every_committed_prompt_rerenders_on_its_recorded_versionis slow and local only, because the audit grounding is not committed. It renders each release's board, read from its release PR's squash commit, on the version each record names. All 674, 674 and 677 sha256s match exactly, and no other case renders.test_the_committed_prompt_records_agree_and_name_a_versionruns in CI. Every record resolves to v1, and release 20261006's seed digest equals release 20260929's digest overlaid with release 20260930's re-judged prompts and verdicts.test_prepare_keeps_each_judged_case_on_its_own_templateandtest_a_verdict_is_kept_on_its_recorded_version_alone(Hypothesis, CI) check that a judged case keeps its files exactly when the case, rendered on its recorded version, is the bytes its judge read:prompt.md, andprompt_sha256when recorded. The second draws\rtext, CRLF copies, a missing prompt and four kinds of sidecar hash.test_prompts_compare_and_keep_exact_bytes,test_a_verdict_without_its_prompt_stands_only_on_its_recorded_hashandtest_a_changed_case_without_its_prompt_is_reopenedare worked examples.test_collect_refuses_a_verdict_published_on_rewritten_bytes,test_codex_publishes_no_verdict_for_a_prompt_rewritten_while_it_judgedandtest_an_interrupted_publish_never_leaves_a_verdict_without_its_sidecar(both runners, also from a truncated verdict) cover publication. So dotest_prepare_racing_a_publish_never_strands_the_verdict,test_an_audit_prepare_between_a_publishs_two_moves_strands_nothing(end to end on the real Codex runner),test_a_dropped_verdict_takes_only_its_own_sidecarandtest_a_verdict_its_sidecar_does_not_describe_is_reopened. Together they show that no verdict reachesaudit-collect, or survivesaudit-prepare, on bytes its judge did not read.test_finish_gpt61sol.pynow prepares releases 20260929 and 20260930 on both versions, byte for byte.v2 differs from v1 only by the dropped clause.
test_the_header_diff_is_one_deletion_of_the_clause: the common prefix and suffix bracket exactlyV1_REVIEW_CLAIM, and difflib's only edit is that one deletion.test_only_the_header_depends_on_the_version(Hypothesis): for any case, the v2 prompt is the v1 prompt without the clause, at the header's offset._PROMPT_HEADER, read from git.Version selection is explicit, never inferred.
test_the_renderers_have_no_default_versionandtest_a_renderer_called_without_a_version_is_refused: the version is keyword-only with no default.test_audit_prepare_requires_a_template_version: without the flag, the CLI exits 2 and writes nothing.test_every_renderer_call_names_its_version: an AST scan ofpolicybench/,scripts/andreference_audit/finds 8 calls. Every one namestemplate_version, and none reads it off a prompt withtemplate_version_of.test_only_the_tree_check_reads_a_version_off_a_prompt:template_version_ofis called only intemplate_version_problems, which renders nothing. A version read into a variable elsewhere fails it.test_each_past_release_driver_renders_on_v1_by_name: each driver passes itsJUDGE_TEMPLATE_VERSIONconstant, which is 1.test_a_verdict_is_kept_on_its_recorded_version_alone(Hypothesis): a verdict is kept exactly whenprompt.mdis the case rendered on the recorded version. A prompt on another version is re-opened, even when the caller names that version.UNRECORDED_TEMPLATE_VERSION). That is a constant, never derived from the prompt.Mutation check
I planted bugs one at a time in a detached worktree, and a test caught each one.
Round 1 (
evidence/mutants.txt,evidence/mutants2.txt):prompt.md;render_case_prompt, and onprepare_audit;Round 2 (
evidence/mutants3.txt, each mutant run against the new tests alone):prompt.mdas text;Round 3 (
evidence/mutants4.txt):Removing only the TERM trap survives, because bash 3.2 still runs the EXIT trap on TERM.
Round 4 (
evidence/mutants5.txt):verdict_sha256;Either layer alone survives the end-to-end test, as intended, because the other layer covers it.
Round 5 (
evidence/mutants6.txt): restoring the legacy reading of a verdict without a sidecar. Its new test and the property catch it.Tests run
At fd55a1b (the PR's commit 80bad56 merged with main at cfd851d), with
--basetempoutside the repo (evidence/round5.txt). The head, e23e33c, changes onlydocs/audit.mdand a docstring after that; the template, provenance and audit tests pass there too (evidence/final_head.txt):test_judge_template.py: 37 passed, slow tests included.test_audit.py: 25 passed.test_judge_provenance.py: 18 passed.test_finish_haiku55.py: 471 passed, 7 skipped. The four seed tests,test_every_seed_prompt_rerenders_except_the_nine_rewordedamong them, ran against release 20260930's local audit tree, which equalsdocs/haiku55/seed_digest.csv.test_finish_gpt61sol.py: 213 passed, 3 skipped. Its frozen-tree tests re-prepare releases 20260929 and 20260930's real verdicts, sidecars and prompts on both versions and keep every byte.test_finish_adds0928.py: 82 passed.test_validate_verdict.py: 5 passed.test_stamp_gpt61sol_prompt_bindings.py: 7 passed.test_restate_gpt61sol_adjudications.py: 33 passed.test_model_response_window.py: 26 passed.test_adjudications.py: 23 passed.test_freeze_haiku55.py: 368 passed, 5 skipped.test_freeze_gpt61sol.py: 155 passed.test_build_references_upgrade.py: 83 passed.test_reference_adversary.py: 107 passed.test_run_audit_claude.py: 100 passed.test_held_references.py: 93 passed, andtest_tax_table_audit.py: 54 passed. These are main's new files, run because the merge touchedcli.py.Free disk was 41 GB at the start but fell to 25–31 GB as other jobs ran, so I did not run the full suite locally; CI runs it. Evidence is in
~/reviews/policybench-judge-template-versions/evidence/.Impact
No published number, prompt or verdict changes. Every committed prompt re-renders byte for byte. The next release's driver should name
JUDGE_TEMPLATE_VERSION = 2.axiom: n/a: benchmark audit tooling, no policy encoded
🤖 Generated with Claude Code