fix(evals): the deduction lane hashes the prose it serves #133
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/lane-pins-see-surfaces"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes the gap #132 uncovered. The pinned artifact carried verdict counts only, so CORE's user-visible output was unguarded by its own hash pins.
The runner's docstring justified the exclusion: prose is "presentation, not decision", so the pinned bytes "stay stable against wording-only changes; wording is covered by
tests/test_deduction_surface.py."Measured, that justification was false. With
render._display_nounsabotaged so every clause readsall SABOTAGE_dogs are SABOTAGE_animals:test_deduction_serve_lane+_license(20)tests/test_deduction_surface.py(41)evals/grammar_roundtripThat is how the ratified v1b band served
all dog are mammalfor the entire arc withwrong=0intact.The fix
build_reportnow emitssurface_sha256+ per-casesurfacesfrom the realdeduction_grounded_surface— the same call chat serving makes, so what gets hashed is what a user reads.One trap worth naming:
build_combined_reportre-projects five named fields per split, so a field added tobuild_reportalone never reaches the pinned bytes. My first attempt did exactly that and the pin stayed green. The payload is not the report — both had to change.Surfaces are recorded, not just digested, so a moved pin shows the exact sentence that changed in review rather than an opaque hash someone has to go re-derive. 2,766 → 37,280 bytes.
deduction_serve_v1under sabotage52370b73vsc855d55cRe-pinned surgically — one line, old hash recorded in a comment beside it, never
--update. Verdicts are untouched (166/166 correct,wrong=0); the hash moved because the payload grew.test_surface_hash_moves_when_the_renderer_is_sabotagedmakes it permanent: it corrupts the renderer, requires a digest to move, and asserts the aggregate counts are unchanged — proving the digest tracks prose rather than smuggling in a decision change. A pin that cannot fail guards nothing.Accepted cost: wording-only changes now move this pin. That is the intent — a wording change is a user-visible change and should require a deliberate re-pin, which is precisely the discipline whose absence let
all dog are mammalship.The other 10 lanes are untouched.
deduction_servewas fixed because it is the one demonstrably serving prose to users.[Verification]: in-worktree on CPython 3.12.13 with
uv sync --locked—uv run core test --suite smoke -q621 passed (unchanged);uv run core test --suite deductive -q405 passed (403 + 2 new);scripts/verify_lane_shas.py11/11 with the new pin, and 10/11 (RED ondeduction_serve_v1) under the sabotage it previously could not see.The pinned artifact carried verdict counts only. The runner's docstring justified that: prose is "presentation, not decision", so the pinned bytes "stay stable against wording-only changes; wording is covered by tests/test_deduction_surface.py." Measured, that justification was false. With render._display_noun sabotaged so every clause reads "all SABOTAGE_dogs are SABOTAGE_animals": 11 lane SHA pins -> 11/11 byte-identical, blind test_deduction_serve_lane + _license -> 20 passed, blind tests/test_deduction_surface.py -> 41 passed, blind (the named wording guard) evals/grammar_roundtrip -> RED, the only witness So CORE's user-visible output was unguarded by its own hash pins, which is how the ratified v1b band served "all dog are mammal" for the entire arc with wrong=0 intact. build_report now emits surface_sha256 + per-case surfaces from the real deduction_grounded_surface — the same call chat serving makes, so what is hashed is what a user reads. Note build_combined_report re-projects five named fields per split, so a field added to build_report alone never reaches the pinned bytes; both had to change. The payload is not the report. Surfaces are recorded, not just digested, so a moved pin shows the exact sentence that changed in review instead of an opaque hash to go re-derive. 2,766 -> 37,280 bytes. deduction_serve_v1 under sabotage: BEFORE byte-identical (blind) AFTER 52370b73 vs c855d55c (RED) Re-pinned surgically, one line, old hash recorded beside it, never --update. Verdicts untouched: 166/166 correct, wrong=0. The hash moved because the payload grew. test_surface_hash_moves_when_the_renderer_is_sabotaged makes it permanent: it corrupts the renderer, requires a digest to move, AND asserts the aggregate counts are unchanged — proving the digest tracks PROSE rather than smuggling in a decision change. A pin that cannot fail guards nothing. Accepted cost: wording-only changes now move this pin. That is the intent — a wording change IS a user-visible change and should require a deliberate re-pin. The other 10 lanes are untouched; deduction_serve was fixed because it is the one demonstrably serving prose to users. [Verification]: in-worktree on CPython 3.12.13, uv sync --locked — smoke 621 unchanged; deductive 405 (403 + 2 new); scripts/verify_lane_shas.py 11/11 with the new pin, and 10/11 (RED on deduction_serve_v1) under the sabotage it previously could not see.