fix: the BOM guard was fail-open wherever xxd is not installed #798

Merged
timothy merged 2 commits from fix/bom-guard-xxd into main 2026-08-14 18:51:00 +02:00
2 Commits
Author SHA1 Message Date
timothyandClaude Opus 5 6a6ce36b2c fix(797): the twin recipe was still broken, and the mutation proof was green over a dead guard
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 9s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 18s
PR Gates / Docs update reminder (pull_request) Successful in 18s
PR Gates / decisions lifecycle (pull_request) Successful in 28s
Review verdict / Set review-verdict status (pull_request_target) Successful in 17s
PR Gates / Script tests (pytest) (pull_request) Successful in 2m6s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m19s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m42s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m19s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 7s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 5s
review-verdict/h10 Review-verdict: MERGEABLE @ 6a6ce36 (base: main)
Two independent reviews of #798. No Blocker; the functional fix is confirmed
sound — one reviewer ran a 17-input matrix on BSD od, GNU od, and inside the
actual CI runner image, identical in every cell, with `xxd` confirmed absent
there. Everything below is what they found around it.

THE FIX STOPPED AT THE HOOK. `docs/decisions/records/process/bom-format-detection-recipe.md`
(active) and `docs/contributing.md:120` both still prescribed
`head -c3 | xxd -p | grep -q '^efbbbf'` as the standing manual pre-push check —
same absent xxd, same silent all-clean, on the path a human runs PRECISELY when
the hook is being bypassed with --no-verify. Worse, that record's "Verify your
detector" paragraph rejected an `od` form by name, so the active record argued
against the hook this PR ships.

Both corrected. The record keeps its original warning, which is still right and
was never about `od` as a tool: the form that reported all-clean over 19 dirty
files was `od -An -c | grep '357 273 277'` — octal character output, whose
spacing and escaping vary. The replacement is `od -A n -t x1 -N 3`, hexadecimal
bytes, measured across the matrix above. The record now says its own prescribed
detector had been an all-clean detector for three weeks, which is the thing it
was written to prevent.

THE MUTATION PROOF WAS GREEN OVER A DEAD GUARD. Run against the pre-fix hook on
Linux, `test_DISARMING_the_BOM_comparison_stops_detection` PASSED while the guard
detected nothing at all — "the mutant is silent" is trivially true when the
original is silent too. The inventory row cites that function by name. It now
asserts the unmutated guard DOES detect, first.

THE STAND-IN PATH STARVED THE HOOK. `_path_without_xxd` hand-listed six tools the
hook never calls and omitted `tail`, which it does call for cd-detection — so a
payload whose cwd differs from the repo failed open for a reason unrelated to
xxd. The list is derived from the hook's own source now, with a required-set
assertion so a starved PATH fails loudly instead of passing quietly.

Also: the inventory narrative restated the NONE count and drifted the moment this
guard was regraded, invisibly to the summary parser. The duplicate count is
removed rather than corrected.

Refs: #797

Decisions-Edit: yes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 17:30:01 +02:00
timothyandClaude Opus 5 4873870727 fix: the BOM guard was fail-open wherever xxd is not installed
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 9s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 20s
PR Gates / Docs update reminder (pull_request) Successful in 12s
PR Gates / decisions lifecycle (pull_request) Successful in 25s
review-verdict/h10 Awaiting review verdict for 4873870
Review verdict / Set review-verdict status (pull_request_target) Successful in 12s
PR Gates / Script tests (pytest) (pull_request) Successful in 2m9s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Canceled after 21s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Canceled after 0s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Canceled after 0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Canceled after 0s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Canceled after 0s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Canceled after 0s
`pretooluse-bom-guard.sh` detected a BOM with `head -c3 | xxd -p`. `xxd` ships
with vim and is ABSENT on this repo's Linux CI runner — verified on the runner
host — so the command substitution yielded empty, never equalled `efbbbf`, and
the guard allowed every BOM in silence. It has been fail-open on any machine
without vim since it was written.

`od -A n -t x1 -N 3` is POSIX and byte-identical on macOS and Linux.

Why nobody noticed: `docs/guard-inventory.md` graded this guard NONE — no proof
it could go red — and every review of it ran on macOS, where xxd exists. The
environment was a sampled population and the sample was unanimous. It surfaced
only because an unrelated change (#776) added an assertion that this hook must
reach a real decision, and that assertion ran on Linux.

So it ships with the proof it never had: the real hook driven end to end, driven
again with xxd removed from PATH (the regression), a negative control on a clean
file, an anti-vacuity check that the fixture really stages a BOM, and a
clause-level mutation — disarm the comparison in an isolated copy and detection
must stop. Verified RED against the pre-fix hook and green against this one.

The inventory row moves NONE -> MUTATION, and the "gaps ranked by what a silent
failure would let through" list records that its first entry was ranked correctly
for a worse reason than intended.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 17:14:39 +02:00