A public repository should not carry the layout of the machine it was written
on, and it should not carry the identifiers of a queue nobody outside can see.
Six places in material this span added or changed did.
Neutral text, same facts: a private queue path in the EOL report, a private
note path in two docstrings and one report header, and four order identifiers
opening four reports. Where a sentence lost its source it says `internal
measurement note` -- the measurement is still named, the path is not.
One of them was output, not prose: row 9 of the retrieval gate printed a
`~/corpora/...` path to stdout on every run. It says the bundles exist on the
machine the row was written against; the row already takes `--k2 SET SHA
BUNDLE` from anyone who holds them.
Measured with the patterns as given, and both validated against a
known-positive on `df5a118` first -- an unvalidated pattern reporting zero is
not a measurement:
* `~/\.claude|/Users/|coord/.*/orders` over the 74 files the span touches:
**9 -> 6**. The six left are deliberate: two README lines telling a reader to
install a skill into their OWN `~/.claude/skills`, one prose line arguing
that `/Users/...` is not more portable than a relative path, and three lines
of `tests/test_retrieval_gate.py`'s own known-positive for this exact check,
which removing would disarm it.
* `[0-9]{8}T[0-9]{6}Z-[0-9]+` over the 38 files the span ADDED: **4 -> 0**.
A note on the method, because the first run of the first grep reported 0 and
was wrong: zsh does not word-split an unquoted parameter, so
`git grep -- $FILES` passed 74 names as one argument and matched no file at
all. Both numbers above come from `$(git diff --name-only ...)`, which zsh does
split, and the known-positive is what made the false zero visible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
11 KiB
The content-accounting gate, hardened: the judge opens the bundle itself
2026-09-18. Raised by an independent adversarial review of 0b00de4,
commissioned after the accounting was built. The review's
verdict was that the gate is "an honest RED today, but cannot be trusted as a
judge once it turns green": it was a calculator over a report the judged party
writes. This is what changed, measured.
Nothing in src/ moved. The instrument was hardened; the thing it judges was
not touched.
1. BLOCKER B-1 — the gate never opened a concept file
okf_accounting_gate.account() compared BOOKED NUMBERS with the witness's
counts. Reproduced here before any code moved, as two tests over the real
fixture bundle: a report that changes not one byte of the bundle and books
every element as carried gave GATE GREEN, exit 0 — and so did booking
every element as rejected.
The witness now gives every element the PIECES OF TEXT IT IS MADE OF, and
the gate looks for each of them in the concept bodies the run wrote. Pieces
rather than one joined string, and that distinction is load-bearing: a reader
writes a heading's ATX marker and a picture's pointer block between the parts
of a container, so an STS <sec> is never one contiguous run in the bundle
even when every word of it is there. Measured — with one joined string per
element, 2 of the 13 fixture documents reported false losses; with pieces, 0.
The normalisation is the one the build already uses and no looser (strip
non-alphanumerics, casefold, remove a converter attribute block such as
{.mark}), and it is implemented in the gate rather than imported from
accounting.py: a judge borrowing the judged party's own comparison agrees
with it by construction.
Five more refusals, each driven from both sides by a test:
- a negative booking (
carried: 25, rejected: {x: -15}of 10 read as clean); - a document declared
persistedthat no concept in the bundle names; - a document declared
rejectedthat the bundle holds a concept from; - a rejection code outside a closed list, at the document AND at the element;
- an
accounting_versionthe gate does not read.
And: a document the build PERSISTED whose report carries nothing from it is
never clean. "Everything rejected" satisfied u = 0, d = 0.
m-1: an asset proved a carry by NAME, so a zero-byte file called
<sha12>-x.png read as a carry. It is now checked on the BYTES and on the
content address. The first attempt re-implemented the build's full naming rule
and reported 50 of 50 carried R761 images as missing — the build
lowercases the basename, folds separator runs and takes the suffix from the
bytes. The readable tail is the build's cosmetic choice and is deliberately
not reproduced here.
2. MAJOR M-1 and M-2 — what the witnesses could not see
Rows 2 and 3 require the build's inventory to EQUAL the witness's, so what the witness does not count, nothing can lose visibly. Thirteen classes were added, each with a red test first:
| format | now counted |
|---|---|
.docx |
header/footer, comment, endnote, text box |
.pptx |
speaker note, hidden slide (show="0", no longer an ordinary slide) |
.xlsx |
formula, hidden sheet |
.odt |
header/footer (they live in styles.xml), annotation |
.xml |
mixed-citation, mml:math, fig, a figure's caption |
.pdf |
the page's own TEXT (it counted pages and picture placements only) |
A text box's paragraphs are the box's own, or the text is booked twice. A
hidden sheet's state lives in workbook.xml and is reached through the
relationship id, so the sheet part itself says nothing about it. An annotation
counted as prose makes the accounting demand that a reader carry a note the
author wrote to themselves.
M-2: the two STS witnesses shared ONE role map, so row 5 — "two witnesses
agree" — could not see a hole in it. They are written apart now. Measured on
R761 2026-09-18: the XML delivery places a section's label at sec/label
7 714 times and inside a title 0 times; the JSON delivery puts
2 760 of them inside the title. The XML map was narrowed to the XML fact,
and a JSON twin fixture carrying the nested placement holds the difference
live — without it, swapping the maps changed nothing measurable.
3. MAJOR M-3 — 20 of 63 element types had a count of zero
A witness cannot be caught being wrong about something it never sees; that is
why six of seven witness mutants survived the original suite. Seven hand-built
documents close it (tests/fixtures/accounting/make_accounting_fixtures.py),
every element type now occurs at least once, and all twenty documents carry
a hand count read off the fixture's own bytes — four did before. The
operator's own proposed .xlsx image exception could not be exercised at all
until now.
Building them found three defects in the fixtures themselves, each of which
would have reported a loss the build never had: a shared string table not
related to the workbook (every t="s" cell converts empty), a graphicData
with no uri (pandoc does not see a table), and an odt styles.xml without
<office:styles/> (pandoc refuses the document).
4. Row 6 and the verdict line
- R761 refused whole under the default gate is now RED. It was GREEN with
31 096 of 31 096 elements booked as coded rejections and
okf buildexiting 1 unseen: every element had a fate, sou = 0, d = 0. The row asks the question separately now. - A skipped row never leaves the verdict unqualified.
GATE GREENnow readsGATE GREEN (row 6 not run: <reason>), and the exit code is non-zero locally when a corpus source exists on the machine and the row did not run. - N200 Vegbygging:2024 is a second real corpus. R761 holds 0
fig, 0 formulas and 0 references, so the gate's only real corpus could not have found the hole in the role map. N200 carries 194 citations, 49 figures and 135 footnotes. A.jsonfile whose root carries an STS node tree is counted as STS rather than as keys and leaves — the container is not the content.
5. MAJOR M-4 — mutation
The review's 26 mutants, ported to the code as it stands, plus 8 written for
the new checks: 34 of 34 killed (21 of the review's 26 were killed before
the last seven tests were added; all 26 are killed now, against the order's bar
of 23). The harness is tools/okf_gate_mutants.py, it runs on a copy of the
tree, and a pattern that does not match is an ERROR and exit 2 — a mutant
that could not be applied was never measured, and counting it as killed is the
same mistake as reading an empty search as an absence. That fired once, on
M13, after a refactor moved the line it edits.
6. The gate is REDDER than before, and that is the movement
At the shipped defaults, python3 tools/okf_accounting_gate.py, 3 m 51 s:
1 file types with a fasit fixture | 13 of 13 | GREEN
2 source inventory before build | 8 of 13 | RED
3 accounting after build | 17 of 22 | RED
4 a rejected document is reported | 1 of 1 | GREEN
5 two witnesses agree | 4 of 4 | GREEN
6 real corpora | 110 of 111| RED
GATE RED: rows 2, 3, 6
Row 3: u = 19 unaccounted, d = 2 double-booked, 0 booked carried and not in the bundle, 0 declarations the gate refuses — and 314 carried elements FOUND in the bundle, 6 carrying no text the gate can check. The zero matters as much as the nineteen: nothing the build DOES book as carried failed the bundle check, so every red is the build's and none is the instrument's.
The new "not carried" classes, per file type, with counts on the fixtures — the raw material for the next capability order and for the operator's exception list:
| file type | not carried | on the fixtures |
|---|---|---|
.docx |
comment, endnote, footnote, header/footer, text box | 1, 1, 1, 2, 1 |
.odt |
annotation, header/footer, image | 1, 2, 1 |
.pptx |
hidden slide, speaker note | 1, 1 |
.xlsx |
formula, hidden sheet, image | 1, 1, 1 |
.xml (STS) |
citation, figure, figure caption, math | 1, 1, 1, 1 |
Two are DOUBLE-booked rather than lost: a hidden slide and a hidden sheet are carried into the bundle but booked as an ordinary slide and an ordinary sheet. A third is a genuine double count in this package: a text box's paragraph is counted twice, once inside the box and once in the paragraph carrying it.
On the real corpora: R761 under --gate none is 31 081 carried elements
found in the bundle, 0 claimed and not found, 65 the gate cannot check, over
a document holding 31 096. N200 is 16 549 elements with no fate at all,
because okf build exits 2 on it (see § 7).
okf build --accounting over the fixture corpus now exits 1 with 6
unaccounted in the build's OWN vocabulary — its first real finding, and it
only became reachable when the fixtures carried the constructs.
7. Two facts about okf build this run establishes
R761 is felled 100 % by ONE rule, and it is not tier-dependent. The
document is refused whole under the default guard-trusted-source with
fail_secure. Asked directly, guard 1.4.0 answers
any-tier: invisible carrier (output:zero-width-present), severity HIGH,
assessment SEVERE — and PRESET_USER_UPLOAD gives the same disposition, so no
tier this package offers persists it. The cause is measured rather than
inferred: the document carries 71 U+00AD soft hyphens, and removing them
takes the same text to WARN, which persists. Whether that is intended is
below.
N200 gets 0 plans and exit 2. The JSON delivery is fenced verbatim, so no
line-anchored boundary grammar fires — the same behaviour a structureless
.txt produces, documented in CLAUDE.md. It is not an accounting defect, and
the gate reports it as what it is: 16 549 elements with no fate.
8. What this gate still cannot check
Printed on every run, beside the verdict, because a gate that reports only the build's gaps invites a green row to be read as a guarantee:
- a short element often stands elsewhere in the same document, so finding it proves it is present and not that THIS one is;
- two pointed files with identical bytes are one content-addressed asset, so one of them losing its pointer is invisible here (m-5);
- an image embedded in a binary container has no source file to hash;
- absence is never verified: an element booked REJECTED is not looked for;
- the witness is a second implementation of the same definitions, so a definition that is wrong for a format is wrong on both sides at once.
Every witness also prints, per file type, the named list of what it still does
not count. APPROVED_EXCEPTIONS was read by no row at all (m-3); each pair is
now checked against the witness's vocabulary and the run says, per pair, why it
moves no denominator — and would say WARNING if one ever did.
9. Reproduction
uv run python tools/okf_accounting_gate.py # 3 m 51 s, exit 1
uv run python tools/okf_gate_mutants.py # 34 of 34 killed, exit 0
uv run pytest tests/test_accounting_gate.py tests/test_content_accounting.py -q
uv run python tools/okf_witness.py tests/fixtures/accounting/corpus
python3 tests/fixtures/accounting/make_accounting_fixtures.py