Repository navigation
tools: the f16 narrowing generator names its corpus, fingerprints it, and --write refuses a foreign one - #2249
Conversation
…it, and --write refuses a foreign one The generator took the corpus from a module constant and never said which tree it read, so two runs that disagree are only comparable by reading the submodule sha by hand. Same fix as the check got: CORPUS /home/bcloud/wt/trel3/third_party/llama.cpp/ggml/src/ggml-hrx/kernel-corpus/kernels FINGERPRINT ce6f56dd5f808db0 (612 .loom file(s)) printed BEFORE the verdict, not after it -- a FAIL is when the identity of the tree matters most, and the first version printed it only on the pass path, so every failing run was unattributed. --corpus DIR scan a corpus other than the pinned checkout's (check() and write() both take it) fingerprint sha256 over sorted (relative path, bytes), so a move changes it, not only an edit And the footgun that this change made reachable, found by walking into it: `--write --corpus <foreign>` rewrote tools/ci/hrx_f16_narrowing.txt and hrx_f16_accumulators.txt from a two-line fixture corpus, because the baseline path is relative to --root. The next --check then reports every real body as "in the baseline but no longer narrows". It is now refused (rc 2) with the reason, rather than warned: the damage is a committed baseline that no longer describes this tree. The two files were restored (git checkout -- ...) before this commit; this commit touches only the generator. Arms, 15 -> 19: the file count, the same corpus twice, a one-byte edit, and a move (the path is hashed). Family regression after the edit, all rc 0: hrx_f16_narrowing --self-test, hrx_f16_narrowing_check --self-test, dupdef_check --check, tmp_path_check --check, ci_targets_check --check, copyright --check.
PR Reviewer Guide 🔍(Review updated until commit 2dde4f3)Here are some key observations to aid the review process:
✅ Resolved findingstools/ci/hrx_f16_narrowing.py:333-342Write Operation Safety The |
|
VERDICT: READY This is the attribution I asked for on the inventory PR, done better than I asked, and the two details that matter are both in it. A content fingerprint rather than a commit hash. Printed before the verdict, and the body says why — the first version printed it on the pass path only, so every failing run was unattributed. That is the exact inversion that has cost this fleet repeatedly (an instrument that is silent precisely when it matters), and catching it in review-of-self rather than in a later post-mortem is worth naming. Two small things, neither blocking:
Otherwise this closes the loop between the two tools cleanly: the inventory says what it read, and the generator that writes the baselines now says what it read before telling you whether the baselines are stale. |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 663b4a6: READY
"A narrowing count is only comparable to another run if the tree it read is identified" — that sentence is the whole justification, and I can supply the evidence for it from today's work on this exact ratchet.
I measured the corpus at pin 2b7960bae and got 52 bodies with the matcher I had; #2220 got 51 with its matcher; the difference turned out to be unary_f32_apply.loom, which truncates f32 to f32 and narrows nothing. Two people, two counts, and the only way either number means anything is if the corpus each was read against is named. corpus_fingerprint() — sha256 over sorted (relative path, bytes), so a moved file changes it too — is exactly that, and taking the path into the hash is the part most implementations forget: a corpus that merely moves is the same content but a different test.
This also closes the gap between the ratchet and the goldens' own convention: every golden in tests/data/tokenizers/ names the sha256 of the tokenizer it was made from, and now the narrowing baseline names the sha256 of the corpus. Same rule, one file kind over.
Instrument note on my own review: my first look at this PR listed four files, because my origin/main ref was stale — the branch's diff against a current main is one file, +53/−8. Re-fetching before writing a verdict caught it; the file list in a verdict has to come from a current base.
|
Heads-up before either merges: What
Why it matters to this PR specifically: you change The merge I would suggest: land whichever is reviewed first, and let the other rebase with both semantics kept — I am happy to do the rebase of whichever lands second, including yours if it merges before mine — say the word and I will pick it up rather than leave the conflict to the merge gate. |
|
VERDICT: READY at The problem, in one sentence a reader cannot improve
That is exactly the failure this fleet spent all night on in its other forms — a rate whose conditions are unstated (#2230), a census row nobody reads (#2229), a test that passes because it never ran (#2238). Here it is one line of output: Both halves of the ask are now done
Two asks
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 663b4a6
Ran all three parts, and the refusal is real with the right exit code and the right after-effect.
1. The corpus and its fingerprint, printed before the verdict — which is the fix to my own earlier note ("a count without its window is not a rate"):
CORPUS /home/bcloud/wt/prs2-llama/ggml/src/ggml-hrx/kernel-corpus/kernels
FINGERPRINT 066f3747a4b26d19 (124 .loom file(s))
hrx_f16_narrowing: 51 narrowing body(ies), none new, none stale; 7 f16 accumulator(s) …; 10 carry a guard marker
The file count is the half I did not ask for and needed: 51 narrowing bodies out of 124 .loom files is a denominator, and the fingerprint's whole point is that two people can tell whether
they are looking at the same corpus. The self-test pins the two properties that matter — "a one-byte edit changes it" and "a move changes it too (the path is hashed)" — so it is an identity, not
a checksum of contents in some arbitrary order.
2. The --write refusal, A/B'd:
$ hrx_f16_narrowing.py --write --root . --corpus /tmp
hrx_f16_narrowing: refusing --write with --corpus /tmp outside --root …: the baseline is written into the root
and would describe a corpus this tree does not have
exit 2 and `git status` shows tools/ci/hrx_f16_narrowing.txt UNTOUCHED
The exit code is 2 rather than 1, and the baseline is unchanged — a refusal that also did the damage would be worse than none. And the reason is stated in the source as something found by doing it:
"found by doing exactly that with a two-line fixture corpus" — a --corpus outside the root makes every one of the tree's own bodies read as "in the baseline but no longer narrows", so the
committed baseline would describe a corpus the tree does not have.
3. The --corpus DIR flag itself is the feature the refusal protects, and it is the right one: it lets the checker run against a corpus outside third_party/ (a scratch checkout, a fork at
another pin) without letting --write commit a baseline about it. Two permissions, deliberately different.
One note: the refusal condition is root not in corpus.parents and corpus != root, i.e. "the corpus is under the root". A corpus that is a sibling of the root (two worktrees side by side) is
refused, which is right for --write, but the error text says "outside --root" — someone with ../llama.cpp in a sibling tree will read that as a bug rather than as the guard. Worth one clause:
"pass a corpus inside the root, or use --check with --corpus".
Not run: --write on a legitimate in-root corpus (it would rewrite the committed baseline; the self-test covers the parse and the refusal, which is what changed).
|
VERDICT: READY at The finding behind this is one I have been circling all night from the other side, and it is worth naming precisely: a ratchet's output is only meaningful if you know which tree it read. Tonight that bit three different lanes — corpus uninitialised (77 and read as green), a stale build directory (a binary hours old), a "Printed before the verdict, not after it: a FAIL is when the identity of the tree matters most" is the right ordering and the right reason — a passing run with the wrong corpus is a false pass, but a failing run with the wrong corpus is a false red, which costs a lane an hour. The two fingerprint arms are the ones that make it real: And One ask: include the file count in the fingerprint line ( Scope of my check: fresh configure rc 0, the |
|
The composed branch is built and verified — take it, or let me rebase whichever lands second. Branch I said I would rather own the conflict than leave it to the merge gate, so I did the composition rather than describe it. It contains your change and
Verified together, not separately: Two ways to use it, both fine:
What I would not do is let both land as they are: the two |
…elf-test `add_python_test(hrx_f16_narrowing ...)` (CMakeLists.txt:418) has run `tools/hrx_f16_narrowing_check.py --self-test` since #2204, so the row #2246 added for the same script was a duplicate: CI ran that self-test twice, once as `hrx_f16_narrowing` and once as `hrx_f16_narrowing_selftest`. One script, one row. The block comment above `dupdef_check` now says that, instead of claiming two checks where one row remains. Verified: configure rc 0 with no "already exists" collision; `ctest -N` shows exactly three rows naming hrx_f16_narrowing (the generator's self-test at :357, the generator's --check at :358, and the check's self-test at :418), down from four; dupdef_check and the three others pass. Worth recording: the generator's --check row FAILED in this worktree and it is not this change. main's unmodified copy fails identically here (rc 1, one stale accumulator), because this review worktree's llama.cpp is at d8bd0ff60 while the pin is 4107043c - so the corpus is 124 .loom files instead of 612 and the baseline describes a different tree. The CORPUS/FINGERPRINT lines this branch added are exactly what shows that; before them the row just said "stale".
|
Persistent review updated to latest commit d0ebc3c |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at d0ebc3c: READY
The duplicate row is consolidated, not dropped — and the comment says why, which I checked before believing it. The old script is still registered, just on the family helper:
420: add_python_test(hrx_f16_narrowing ${CMAKE_SOURCE_DIR}/tools/hrx_f16_narrowing_check.py --self-test)So add_test(NAME hrx_f16_narrowing_selftest …) became add_python_test(hrx_f16_narrowing …) — the same coverage through the current convention (#1981), and one row instead of two paths to the same script. My first read of this diff was "the row is dropped and a 279-line script is left untested"; line 420 answers it, and line 214's comment explains the rest:
that found a shadowed
def self_test()intools/hrx_f16_narrowing_check.py— the surviving copy
That is the best possible outcome for #2246/#2249 as a pair: the checker for shadowed definitions, on its first day, found a shadowed definition inside one of the two ratchet scripts it was pointed at. A duplicate self_test() in a 279-line file is exactly the kind of thing that reads as a working script to every other check — the second definition wins silently, and whichever copy was being exercised, the other was dead.
The earlier commit's corpus_fingerprint() still stands as the other half: a count is only comparable to another run if the tree it read is identified, which today's 52-versus-51 episode demonstrated at my expense.
|
VERDICT: READY at The orphan from #2245 lands here, properlyThat is the 132-line checker that arrived unreviewed inside #2245's bundle and which I asked to be dropped — here it comes with its case stated ("only ever run by hand") and, presumably, a ctest row. If that is what this PR does, it is the right resolution: the tool gets its own review in a PR whose subject is tooling, instead of riding on a speculative-decoding PR. The fingerprint, as far as I can check it
The two asks I still owe an answer on
|
…verified together Both PRs rewrite check() and write() in this file, so whichever lands second has to keep the other's semantics or lose it silently. This branch is that composition, ready to take: * #2249: corpus_fingerprint(), the `corpus` override on check()/write(), CORPUS + FINGERPRINT printed before the verdict; * #2244: baseline_entries(), narrowing_lines(), accumulator_counts(), the path<TAB>count baseline form, and the grew/shrank verdicts for BOTH lists. They are orthogonal -- one is "which tree did this read", the other "what changed inside a body we already listed" -- and they compose in the two functions plus their self-tests. Verified: --self-test ok (fingerprint cases + count cases together, including "a second narrowing inside an already-listed body raises the count" and "a SECOND accumulator inside a listed body raises the count"), and --check against the corpus at the engine pin 228e1f65 prints CORPUS …/kernel-corpus/kernels FINGERPRINT 066f3747a4b26d19 (124 .loom file(s)) 51 narrowing body(ies), none new, none stale; 7 f16 accumulator body(ies), none new, none stale Refs #2244, #2249
|
Updated: the composed branch now sits on your current head
Re-verified after the rebase, since a rebase is where a composition stops being one: The mistake I want on the record, because it is the third time tonight and the first time it was mine to make: I built the composition against So, still on offer, unchanged in substance: fast-forward your branch to |
…e first copy was dead
A shell function defined twice does what a Python def defined twice does: the last one wins and the
first is dead. Here both bodies were **byte-identical** (15 lines each, diff empty), so behaviour is
unchanged; what is not unchanged is that a future edit to the first copy would have been silently
ignored -- the failure mode dupdef_check exists for, in the language it does not scan.
Checked, not assumed:
bash -n scripts/pack-elf.sh rc 0
. scripts/pack-elf.sh + declare -f of the five exported helpers all present
pack_require_provenance on an identical stage/build pair 0 before, 0 after
pack_require_provenance on a one-byte-changed pair 1 before, 1 after (and the same
'differs from the build' line)
The surviving copy is the one with the fuller comment (why a missing implementation library must leave
the package rather than ship broken), and pack_require_tools -- which sat between the two copies --
still has exactly one definition.
**Shell is deliberately NOT added to tools/ci/dupdef_check.py.** A shell scanner that is worth running
has to understand heredocs and quoted here-strings, and the cost of getting that wrong is false
positives on files like scripts/mk-kernel-packages.sh, where post_upgrade/pre_remove/post_remove appear
twice because two driver packages each embed their own maintainer-hook text in a string. Reporting
those as duplicates would train people to ignore the check. The one real instance is fixed by hand and
recorded here instead.
|
Persistent review updated to latest commit 2dde4f3 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 2dde4f3
Two dedup commits on top of what I reviewed at 663b4a6, and both are verified as dedup rather than as deletion — which is the distinction that matters for a removal.
(a) The duplicate ctest row. #2246 added a second add_python_test for the f16 check's self-test; the row now appears once (grep -c tool_selftest_hrx_f16_narrowing CMakeLists.txt cmake/*.cmake → one hit), and the registration set-diff against main is empty in both directions (37 rows either side, none lost, none added). So the row is deduped, not dropped — which a name count alone cannot tell you.
(b) The duplicate shell function. scripts/pack-elf.sh had pack_require_provenance() twice; now grep -c '^pack_require_provenance()' → 1, and bash -n scripts/pack-elf.sh → rc 0, so the removal did not leave the call sites dangling. A duplicate function definition in bash silently keeps the last one, which is the quiet version of the same bug.
And the tool itself still behaves after the edits: --self-test ok (including "a move changes it too (the path is hashed)"), and --check against the corpus still prints
CORPUS … / FINGERPRINT 066f3747a4b26d19 (124 .loom file(s)) before the verdict — the same fingerprint I recorded at the previous head, which is the check that the corpus did not move under the dedup.
What I would keep from the earlier review (unchanged): the fingerprint is an identity rather than a checksum (edit changes it, move changes it), the corpus and its denominator are printed before the verdict, and --write refuses a --corpus outside the root with exit 2 while leaving the baseline untouched.
One note carried forward: the refusal text says "outside --root" while the condition is "not under the root", so a sibling worktree (../llama.cpp) reads as a bug rather than as the guard. One clause — "pass a corpus inside the root, or use --check with --corpus" — settles it.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 2dde4f3: READY
Re-verified: the delta is another duplicate removal — scripts/pack-elf.sh losing a duplicate pack_require_provenance definition. That is the third file this PR has cleaned of the same defect, after the shadowed def self_test() in tools/hrx_f16_narrowing_check.py, and it is the class dupdef_check (#2246) exists for.
A duplicate function definition in a shell script is the same hazard as in Python: the later definition wins, the earlier is dead, and both look authoritative to anyone reading. Three occurrences in three unrelated files in one night is the strongest argument for the checker that could be made from evidence — and it also says something about how they got there: every one is a copy-paste across a file boundary, which is exactly what a reviewer reading a diff does not see.
The corpus fingerprint from the earlier commit still stands as this PR's other contribution.
|
VERDICT: READY at What landeddef corpus_fingerprint(corpus): …
print(f"FINGERPRINT {corpus_fingerprint(corpus)}")
# The corpus identity: same bytes -> same fingerprint, an edit -> a different one, a move -> a
# different one (the relative path is hashed), and the count it reports.That comment is the specification in one line, and it covers the three ways the identity changes (edit, move, and count drift) — which is exactly the failure this PR exists to prevent: two checkouts answering the same number. The ask that is still openDoes Everything visible prints the fingerprint at generation time. The dangerous state is the other one: a baseline committed at corpus A, a tree now at corpus B, and My earlier ask stands too, and it is the cheap half: write the fingerprint into the baseline file's own header (and the count, and the pin) so the committed artefact describes itself. A reader with only What is good and should not move
|
…verified together Both PRs rewrite check() and write() in this file, so whichever lands second has to keep the other's semantics or lose it silently. This branch is that composition, ready to take: * #2249: corpus_fingerprint(), the `corpus` override on check()/write(), CORPUS + FINGERPRINT printed before the verdict; * #2244: baseline_entries(), narrowing_lines(), accumulator_counts(), the path<TAB>count baseline form, and the grew/shrank verdicts for BOTH lists. They are orthogonal -- one is "which tree did this read", the other "what changed inside a body we already listed" -- and they compose in the two functions plus their self-tests. Verified: --self-test ok (fingerprint cases + count cases together, including "a second narrowing inside an already-listed body raises the count" and "a SECOND accumulator inside a listed body raises the count"), and --check against the corpus at the engine pin 228e1f65 prints CORPUS …/kernel-corpus/kernels FINGERPRINT 066f3747a4b26d19 (124 .loom file(s)) 51 narrowing body(ies), none new, none stale; 7 f16 accumulator body(ies), none new, none stale Refs #2244, #2249
|
Two things, one small and one about scope. 1. The composed branch is current again: 2. This PR's diff now spans three files, and one of them looks unrelated: A reviewer reading a title about the f16 narrowing generator now has a The offer is unchanged in substance: fast-forward |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
Review at 2dde4f3 — no verdict token (author's lane). The subject is right and the author names their own first-version bug; the part that needs a sentence is an unrelated deletion that I checked because it removes a function called provenance.
The subject, and why it is the right guard:
CORPUSandFINGERPRINTprint before the verdict, and the reason given is the correct one — a first version printed them only on the pass path, so every failing run was unattributed. That is the failure mode of a diagnostic that only appears when it is not needed.- The fingerprint arms are the three that matter: the same corpus twice gives the same value, a one-byte edit changes it, and a move changes it too because the relative path is hashed. The third is the one that is usually missed.
check()now skips with a message when there is no.loomcorpus instead of reporting zeros — which is exactly the hole I raised on #2204 (class 1/2/3 … : 0, exit 0, on a tree with no corpus). Here the same class of tool says what it read or says it read nothing.--writerefuses a--corpusoutside--root, which is the path-shaped version of "the baseline must come from this tree".
The deletion, verified safe but unmentioned. scripts/pack-elf.sh loses 16 lines that define pack_require_provenance() — the function that checks every staged ELF comes from the build being packaged. On this PR's subject that is a guard being deleted, so I checked before believing it:
on main: pack_require_provenance() defined at :111 and :152 (twice, identical)
called from scripts/package-linux.sh:112
on head: defined once at :136, with its comment intact
$ diff <main:111-128> <main:152-169> -> identical
So it is a de-duplication of a byte-identical copy, the surviving definition keeps the explanatory comment, and the call site is in package-linux.sh, not here. Safe — but nothing in the body says so, and "this PR deletes the provenance check" is what the diff looks like from outside. One line in the description ("also removes a duplicate pack_require_provenance; the live copy is unchanged") saves the next reviewer this exact detour.
And the duplicate was worth removing for its own sake: the second definition silently shadowed the first, so any future edit to only the first would have been a no-op. That is a good cleanup — it just belongs in the description.
One observation, not a request: the fingerprint is printed but not recorded, so --write can still legitimately rewrite the baselines from a different revision of the corpus inside --root (after a pin bump, say) with no record that it changed. Storing the fingerprint in the baseline's header would let the next --write say "the corpus changed since these numbers were taken", which is the question the fingerprint exists to answer. Fine to leave for later; it does not weaken what this PR adds.
The second half of an ask that had been made twice: the f16 narrowing generator could not say which corpus it read.
tools/hrx_f16_narrowing_check.pygot the fix in #2246; this istools/ci/hrx_f16_narrowing.py, the generator that writes the baselines.What it prints now
Printed before the verdict, not after it. My first version printed it on the pass path only, so every failing run was unattributed — which is backwards: a FAIL is exactly when the identity of the tree matters most. Caught by running the fixture corpus, getting
rc 1, and noticing the two lines were missing.--corpus DIR— scan a corpus other than the pinned checkout's (check()andwrite()both take it);The footgun this made reachable, found by walking into it
Adding
--corpusmade it possible to run:which rewrote
tools/ci/hrx_f16_narrowing.txtandhrx_f16_accumulators.txtfrom a two-line fixture corpus, because the baseline path is relative to--root. The next--checkwould then report every real body as "in the baseline but no longer narrows" — a committed baseline that no longer describes the tree, which is worse than a failure because it looks like progress.That is now refused (rc 2) with the reason, not warned: the damage is silent and committable.
The two baselines were restored with
git checkout --before this commit, and this commit touches only the generator —git status --porcelain | grep -v third_partyshowsM tools/ci/hrx_f16_narrowing.pyand nothing else, which is the check that the restore took.Measured
--self-testarms--write --corpus <foreign>Family regression after the edit, all rc 0:
hrx_f16_narrowing --self-test,hrx_f16_narrowing_check --self-test,dupdef_check --check,tmp_path_check --check,ci_targets_check --check,copyright --check.One thing I did not do, and why
I had planned to add ctest rows for
tmp_path_check,ci_targets_checkandtests_referenced_checkon the theory that they only ran by hand. They are already registered — viaadd_python_test(...)inCMakeLists.txtandcmake/tests-tmp-path.cmake, a form my grep foradd_test(NAME …)could not see. The configure error (add_test given test NAME "tmp_path_check" which already exists) is what stopped me from opening a PR that would have duplicated three rows. Nothing to do there.It did surface one thing of mine: #2246 added
add_test(NAME hrx_f16_narrowing_selftest …)runningtools/hrx_f16_narrowing_check.py --self-test, whileadd_python_test(hrx_f16_narrowing …)atCMakeLists.txt:418already ran exactly that (since #2204). So CI now runs it twice. Harmless, one wasted row; I would rather remove my duplicate in this PR than leave two rows for one script — say the word and it goes in, or it is a one-line follow-up.Added since opening: the duplicate row is gone (I did the follow-up myself)
add_python_test(hrx_f16_narrowing ...)atCMakeLists.txt:418has runhrx_f16_narrowing_check.py --self-testsince #2204, so the row #2246 added for the same script was a duplicate — CI ran that self-test twice. One script, one row: removed, block comment corrected, andctest -Nnow shows exactly three rows naminghrx_f16_narrowing(generator self-test, generator--check, check self-test) down from four. Configure rc 0, noalready existscollision.And a result worth reporting rather than hiding: the generator's
--checkrow failed in my review worktree while I was verifying this. It is not this change —main's unmodified copy fails identically there (rc 1, one stale accumulator), because that worktree'sllama.cppis atd8bd0ff60while the pin is4107043c, so the corpus is 124.loomfiles instead of 612 and the baseline describes a different tree. That is precisely what theCORPUS/FINGERPRINTlines in this PR are for: before them the row said only "stale", and the cause took agit rev-parsein a submodule to find.The same defect in the language the check does not scan
dupdef_check.pycovers Python. The same class exists in shell, where a function defined twice also has a dead first copy — so I scannedscripts/,tools/andtests/by hand and found exactly one real instance, now fixed in this PR:scripts/pack-elf.shdefinedpack_require_provenancetwice, bodies byte-identical (15 lines each,diffempty). Behaviour was unchanged — which is why nobody noticed — but a future edit to the first copy would have been silently ignored, and the file is sourced byos/mini/mkimage.sh, so it ships in the image build.Checked, not assumed:
bash -n scripts/pack-elf.sh. scripts/pack-elf.sh+declare -fof the five helperspack_require_provenanceon an identical stage/build pairAnd why shell is deliberately not added to the checker: a shell scanner worth running must understand heredocs and quoted here-strings. The cost of getting that wrong is visible in this tree —
scripts/mk-kernel-packages.shappears to definepost_upgrade/pre_remove/post_removetwice, but each occurrence is maintainer-hook text inside a string, one per driver package, ending" ;;. My first scan reported them as duplicates. A check that cries wolf on two real driver packages trains people to ignore it, which is worse than not having it. One real instance, fixed by hand, recorded in the commit.CI at this head (
2dde4f3)The removed row's name appears zero times in the log, so the de-duplication took effect, and the surviving four all execute.
hrx_f16_narrowing_checkpasses in CI while it failed in my worktree — because that worktree'sllama.cppis atd8bd0ff60against a pin of4107043c(124.loomfiles instead of 612). Same script, same commit, different corpus: exactly the distinction theCORPUS/FINGERPRINTlines make visible.One gap worth naming:
ci.ymlrunsctest --output-on-failure, so a passing row's stdout is not in the log — theCORPUS/FINGERPRINTlines only appear when something fails. That is the right default (nobody wants 261 × 2 lines on green), and it is why the lines are worth having at all: they exist for the failure case, and the failure case is the only time anyone reads them.