llm-ingestion-okf/docs/2026-09-19-pekerblokken-bindes-til-kjoringen.md
Kjell Tore Guttormsen 44ad845e29
test(mutants,assets): a mutant is judged by the suite that owns it, 45 of 45
The runner could only run one test file, which is why PM's three
survivors from `43331fc` could not be added: two are held by the gate's
row 3 and one by the soft-hyphen door's suite. A mutant now names its
suite; the catalogue goes 39 to 45.

X3 and X4 rewritten against the code as it now stands -- a mutant table
is a copy of the code it mutates, and this round moved the lines both of
them quoted. X6 is the defeated state exactly, X7 cuts the ledger off at
its source, X8 removes the cursor rule, P6/P11/P12 are PM's three.

Two survivors on the first run, both findings, both closed:
- X4 survived because every forgery arm now fails on the ledger check
  before the binding is reached. An arm was added where the run DID book
  the pair and the block stating it points at another picture.
- X5 survived the WHOLE suite -- 2134 passed with the disarming removed
  -- because a document-supplied field can no longer reach the gate. The
  property is about the BUNDLE and not about one judge, so it is kept and
  measured in `tests/test_assets.py`, with a known-positive counting the
  run's own two fields on the same expression.

killed 45 of 45, exit 0. Report, CHANGELOG and CLAUDE.md written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 19:33:23 +02:00

8.8 KiB

The pointer block, bound to the run

2026-09-19. PM's checkpoint on ae441ab judged the previous round PARTIAL with one load-bearing rest and two small ones. The rest is the same class the round before it: the judge's fasit came from the reader it judges. It had been narrowed, not closed. This closes it, closes the two small ones, and clears three further residuals PM had listed. No new format, no new dependency, no version bump and no tag.

1. A form is not a signature

tools/okf_accounting_gate.py read the conversion claim out of the bundle text. The round before bound it to a POINTER BLOCK, which is the two lines assets.render_block writes:

![<label>](/assets/<sha12>-<name>)
Image: <name> (<w>x<h> px) -- converted from <type> sha256:<A> to <type> sha256:<B>

That closed the two routes PM had measured (an alt attribute and plain body text) and left the class open, because a document can write the whole block. Reproduced here through the real okf build, three ways, each a whole build:

way before known-positive in the same build
two <p> elements in one HTML file forged held
one <p> with a <br> forged held
a markdown note beside the HTML carrier forged held

"Forged" means asset_holds returned True for a BMP declaring 50 000 x 50 000, refused asset_too_large, absent from assets/. The forger needs two digests: the one it wants vouched for, and one of a picture the bundle really carries — public in the bundle, and computable in advance for a PNG carried verbatim.

What was chosen, and why

Two fixes were on the table. The claim is now read from the RUN's own ledger:

  • assets.conversion(image) names the (source digest, asset digest) pair.
  • DocumentAssets.conversions carries it out of the run.
  • DocumentAccount.conversions books it, and okf build --accounting writes conversions: [{from, to}] per document.
  • _declared_conversions reads that file; _conversions believes a pair only when the ledger holds it AND a pointer block confirms it for the asset it points at.

The confirmation can be forged and the ledger cannot, which is why the ledger decides. The alternative — neutralising pointer-shaped text at extraction, the way the soft-hyphen door neutralises U+00AD — was felled: it changes what every document SAYS in order to defend a tool outside the build, a source quoting a bundle listing would come out altered, and the bytes of existing bundles would move. This reads a file the run already writes.

A build with no accounting door has no ledger. A converted image is then reported claimed-and-not-found rather than believed, which is the reading the gate had before the conversion route existed, and it is visible in the row.

After

3 of 3 forged → refused, with the known-positive True in all three. The text-level regression guard goes from 3 arms to 13: PM's eleven ways that the shape anchoring already refused, kept so a later narrowing cannot widen one of them, plus the two forms that defeated it — a perfectly written pointer block the run never booked.

R761 rebuilt from the frozen delivery: bundle diff -r-identical to the build before the change, 50 assets (29 JPEG + 21 PNG, 0 BMP), 19 of 19 conversions confirmed against 19 declared, soft hyphens 71 → 0, u = 0, d = 0, exit 0.

2. A terminator is not a coverage proof

The previous round's terminator rule asks only that a stream SAY it is finished, and a stream may say so anywhere. Measured: a stream whose FIRST two bytes are the end-of-bitmap escape was carried, with 32 of 32 pixels never decoded and all of them palette index 0. Pillow refuses the same file.

_bmp_rle8_rows now also requires the cursor to stand at or past the end of the last row, refusing with the same asset_samples_invalid otherwise.

The line is the cursor, not the pixels. A delta escape and an end-of-line escape STATE their skip, so the pixels they pass over keep index 0 and every decoder produces the same picture; a pixel-coverage count would refuse both constructions the format defines. The corpus cannot choose between the two rules — over the 25 RLE8 BMPs the R761 delivery ships (24 distinct; the bundle carries 19), 25 of 25 paint every pixel, 25 of 25 reach the end of the frame and 0 of 25 use a delta. An independent decoder can:

stream over one 8x4 frame cursor reaches the frame Pillow
end-of-bitmap before one pixel is decoded no refuses
one 4-pixel run, then end-of-bitmap no refuses
last row one pixel short, no end-of-line no refuses
every row painted and closed yes reads
last row one pixel short, then end-of-line yes reads
a delta skipping a whole row yes reads
last row painted to its end, no end-of-line yes reads
the shipped fixture, which uses a delta yes reads

Eight of eight agree with the new rule, and a second test holds Pillow to the table so it is not our own rule restated.

Both docstrings PM named are rewritten. The test no longer says "NO PIXEL MAY BE GUESSED: either every one of them is decoded from the stream, or the picture is refused" — that was false as measured. _bmp_rle8_rows no longer frames the delta argument as read off the corpus, which it never was: 0 of the 25 files use a delta, so the corpus had nothing to say about it.

3. One number, read from one place

assert sum(tbx.values()) == 568 sat behind a skipif on a delivery only this machine has, so on a fresh clone the sentence five files publish was unguarded again — the state in which 574 survived in four docstrings until PM counted it. N101_TBX_TAGS is now the one place the number lives, and a second test reads the published sentence out of CHANGELOG.md, CLAUDE.md, tools/okf_witness.py, tests/test_accounting_gate.py and this round's predecessor report, holding all five to it. It needs no corpus and no clock.

It was red at birth, and for a reason worth keeping: the scan read the test file's own known-positive string (574) as a sixth publisher. That string is now assembled from pieces, and the failure is the demonstration that the scan reads what it is pointed at. What the guard does NOT prove is stated in its docstring: five files agreeing is agreement, not a count. The measurement stays where it was.

4. A clean extract is not a checkout

test_the_four_existing_goldens_are_untouched ran git status with check=True in a git archive extract and raised CalledProcessError. It was the single failure of the whole suite run from a clean extract, twice reported as a round's one failure by a round that had not touched the file. Both arms measure the checkout with git, so an extract has nothing to ask: it now skips with the reason stated, and a checkout still has to answer.

5. The mutant runner

tools/okf_gate_mutants.py could only run one test file, which is why PM's three survivors from 43331fc could not be added: two are held by the gate's row 3 and one by the soft-hyphen door's own suite. A mutant now names the suite it is judged by, and the catalogue goes 39 to 45:

  • X3 and X4 were rewritten against the code as it now stands. A mutant table is a copy of the code it mutates, and this round moved the lines both of them quoted.
  • X6 is the defeated state exactly: a pointer block believed without the run having booked it.
  • X7 cuts the ledger off at its source.
  • X8 removes the cursor rule.
  • P6, P11, P12 are PM's three, now held by what runs AS the gate.

Two survivors appeared on the first run and both were findings.

X4 — "the claim need not be about the asset its block points at" — survived because every forgery arm now fails on the ledger check before the binding is reached. An arm was added where the run DID book the pair and the block stating it points at another picture; under the mutant that reads as a carry.

X5 — "a document-supplied label may emit a checksum field" — survived the WHOLE suite, measured: 2134 passed with the disarming removed. The gate no longer reads its claim out of the bundle, so a document-supplied field cannot reach it. The property did not stop mattering — the line stands in every concept body, and a bundle must not state a conversion in a sentence the run did not write — so it is measured where it lives, in tests/test_assets.py, and the mutant is judged there. Deleting an unfellable guard was the other option and was felled: this guard is about the bundle, not about one judge.

6. What this round does not do

  • It does not teach the gate FIDELITY. A converter writing a blank PNG is still accepted, for the reason asset_holds states: both routes ask whether a file holds the bytes the run names, and neither decodes a pixel.
  • It does not touch the guard, the goldens, the proposer, any profile, any version or any dependency.