`okf_fetch` resolved a concept through `connectors.safe_resolve` from the day
the server was written. The other three ways into the same bytes did not.
`okf consume` and `okf_ask` reach `consume.build_payload`, `okf_describe`
reaches `mcp_server.card`, and both built the concept path by joining the
index's own name onto the bundle root. `consume._join` refuses a `..` segment
and an absolute target, but it is a STRING rule over the index text, and a
symlink is a fact about the filesystem that reading that text cannot see: the
index could name `lekkasje.md`, that name could be a link to a file outside the
bundle, and the file came back in the answer.
Measured before the fix, on a bundle carrying one honest concept and one
escaping link: 8 of 11 new rows red, the 3 green ones being `okf_fetch` on the
same two links and the known-positive that the clean bundle still answers. So
the suite was not red for an unrelated reason, and the fix is not "refuse every
bundle holding a link".
One place, not three copies: `consume.resolve_in_bundle` makes the check and
`consume.read_path_in_bundle` adds the file's presence. Every reader here goes
through them -- the index walk, the ref, the document prior, the payload, the
card, `okf_fetch`, and the three outside `consume` (`skill`, `quality`,
`project`) that joined the same way.
Two more failure modes in the same check, because they are the same question:
* A NAMED PIPE is not a regular file. `read_text` on one blocks for as long as
nobody writes to it, which on a server is the whole process; the red row for
it ran 60 s to a subprocess deadline and now returns in under a second.
* A DEAD INDEX LINK raised `FileNotFoundError`, and the broad handler in
`handle` wrote `{error}` into the refusal -- the SERVER's absolute path,
handed to whoever asked, over one index entry naming a file nobody wrote.
It is `concept_unreadable` now, naming the concept and not the machine.
The returned path is the JOINED one, never the resolved one: `read_concept`
derives a concept id by taking the read path relative to the bundle root, and
once containment holds the two are the same bytes.
2334 passed, 2 skipped (was 2323 + 2). `mypy --strict src/` clean over 25 files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The eval was written RED at `5f1772e` with no server in the tree. This is the
capability it was written against.
`okf mcp --bundle <dir>` serves exactly one bundle, whose tools take no bundle
argument. `okf mcp --root <dir>` (repeatable) serves every bundle under the
roots and knows NONE of them by name. Four tools -- `okf_list`,
`okf_describe`, `okf_ask`, `okf_fetch` -- each carrying its reason in the
description a client actually reads.
Gate today: 1 (7/7) - 2 (83/181) - 3 (4/4) - 4 (9/9) - 5 (3/3) - 6 (6/6),
`GATE RED: rows 2`, exit 1.
THE PROTOCOL IS STDLIB, AND THAT IS THE PACKAGING INVARIANT KEPT RATHER THAN
A TASTE. An MCP SDK would be this package's second runtime dependency on the
DEFAULT install path, for four JSON-RPC methods and a newline framing, and
`test_the_only_runtime_dependency_is_the_security_boundary` pins that list
literally. Chosen hand-written because the surface needed is `initialize`,
`notifications/initialized`, `tools/list` and `tools/call`; `uv.lock` is
untouched.
NOTHING IS CACHED ACROSS CALLS, and row 3 is why. Every call re-walks the
roots and recomputes `bundle_ref`, so a bundle added, removed or rebuilt while
the process runs is seen by the next call with no restart, no configuration
edit and no code change -- 9 of 9 discovery checks over three bundles written
while the server was serving. The cost is paid per call and is published
rather than hidden: 0.75 s for the identity of a 2 756-concept bundle, 5.6 s
for one ask, 4 min 13 s for row 2's full run over four bundles.
CONTAINMENT IS TWO INDEPENDENT CHECKS: the bundle's own index must name the
concept, AND `connectors.safe_resolve` must place it inside the bundle. A
mutant removing either one alone still refuses -- with a DIFFERENT code, which
row 6 asserts by name -- and one removing both is killed. Row 6 declares a
code set per case because its first run had the 10 MB concept refused as
`concept_unknown`: the fixture had not named the file in the index, so the
size ceiling never ran and the row was green for a reason unrelated to the
attack.
`okf card <bundle>` and `okf skill --generic` are the one-to-many skill
candidate. The card is DERIVED on every run and never written into the bundle:
storing it would move the bytes of all six `examples/*/expected-bundle` trees
(23 files compared byte-for-byte) and of the pinned reference bundle, to keep
something recomputable in under a second, and a stored card is one more
artefact that can disagree with what is beside it. Measured here rather than
taken from the order: two per-bundle skills are identical on 280 of 312 and
310 lines; the 62 that differ are identity, concept count, the
conditional-field table, the whole-bundle cost and the breaking point. The
generic skill carries none of them, and `render_generic()` takes no argument,
so there is no bundle it could have read.
Row 2 decomposes into three numbers and the middle one is the finding: 99 of
181 (bundle, anchor) pairs are present in the bundles at all, 83 of those 99
were reached, and 0 of 83 were met by `okf_fetch` on the anchor as a concept
id. The set's anchors and this library's concept ids are different
vocabularies, so every pair met was met through the ranker -- 83 is a FLOOR on
the ceiling, never the ceiling.
13 mutants in a scratch copy, never in the working tree: 12 killed, 1 survived
with its mechanism printed, 0 errors, control green first. Suite 2323 passed,
2 skipped. The architecture choice between the two shapes is the OPERATOR's;
these rows are its input. Report: docs/2026-09-20-mcp-to-varianter.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No capability lands in this commit. `tools/okf_mcp_gate.py` measures six
rows against a server that does not exist, and the reason is the
interpreter's own words rather than this file's opinion: `python -c 'import
llm_ingestion_okf.mcp_server'` raises ModuleNotFoundError, printed in row 1.
Today: 1 (0/7) - 2 (0/0) - 3 (1/4) - 4 (0/9) - 5 (0/3) - 6 (0/6),
`GATE RED: rows 1, 2, 3, 4, 5, 6`, exit 1.
Row 3 is not 0, and that is a measurement rather than a slip. The order
predicted 0 of N on every row; the incumbent one-to-one skill already
refuses out loud when its bundle is rebuilt, because `okf check`'s
`bundle_mismatch` rule exists and compares the declared ref against the
payload's. One of the four artefact classes therefore passes the loudness
clause today -- at the cost the row also prints: one artefact to recreate
and one manual step per consuming project.
The server is a SUBPROCESS and never an import. Every row speaks
newline-delimited JSON-RPC over stdin/stdout starting at `initialize`, with
a client written narrowly here: a client built from the server's own framing
helpers would agree with the server by construction. Nothing in the gate
ranks, serves or writes a bundle.
Denominators are pinned in the gate and recounted a second time in the
tests. `REQUIRED_TOOLS` (7 across two variants), `DRILL_ARTEFACTS` (4),
`DISCOVERY_BUNDLES x DISCOVERY_CHECKS` (9), `CROSS_CHECKS` (3) and
`HOSTILE_CASES` (6) do not come from the run -- a row counting what the
server happened to offer goes green by offering less, which is the defect
rows 2 and 3 of the retrieval gate were caught on.
Row 2's denominator DOES come from the frozen graded question set, at run
time, and the set is never committed here: `--sett` plus `--frys`, with the
sha256 verified against the freeze file and a freeze version below the
ordered one refused as a usage error, not measured. Without them the row is
`0 of 0` with the reason stated. Its machinery carries a known-positive over
the synthetic corpus this file invents, and a green test proves those quotes
really are in the fixture on disk without asking any server -- so a 0 there
means the surface could not reach text that is demonstrably present.
Six tests are red, every one of them on an assert about behaviour: 0 of 7
tools answered, 1 of 4 artefacts loud, 0 of 9 discovery checks, 0 of 3
cross-bundle checks, 0 of 6 hostile cases, and `find_spec` returning None
rather than an ImportError. Eight instrument tests are green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three guards for the code the two step-0 commits added, each written
against a mutation that would otherwise survive: a wiki-shaped file
carrying the pinned 20 questions and 29 fasit entries but invented content
(so the counts alone are a one-line forgery), a threshold of 80 or 1.5
(a share or a typo, and guessing is not this row's job), and a hold-out set
of no questions -- the shape every row-5 test used until today, which would
have made the new comparison vacuous the moment it was added.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`coverage` carries three lists: the terms the pre-pass read the question as,
the terms no concept in the bundle answers, and the terms no delivered
excerpt answers. Without it a reader holding eight excerpts cannot tell a
bundle that ANSWERED its question from one that merely ranked something --
the two payloads have the same shape.
FACTS, AND NO VERDICT, which is a measurement and not caution. Two readings
were built and both falsified over 81 questions (16 synthetic, 65 across the
three real sets, 2026-09-20): the share of question terms a delivered
excerpt answers separates the synthetic controls at 0.33 against 0.50 and
REVERSES on real data (covered questions down to 0.27, one genuinely
uncovered question at 0.71); the share of a bundle tying the best lexical
match is ~0.00 for every real question either way. Question style dominates
the first, corpus size the second.
The one bar this repository declares is the gate's: `UNANSWERED_BAR = 2/3`
over `unanswered_in_bundle`, swept and collapsing at both ends -- at 0.50
eleven real covered questions are marked, at 0.70 the row falls to 5 of 6,
at 2/3 the row is 6 of 6 and 0 of 65 real questions are marked. The margin
is thin (0.6087 against 0.6667) and is published that way, together with
what it does not catch: r761-sk2's own known-negative sits at 0.2857.
Row 4: 3 of 6 RED -> 6 of 6 GREEN, with the 10 answered synthetic questions
held unmarked as the known-negative. The contract's SS 8 gains point 7, the
consumption skill is told to read the block, and the SS 7.4 known-positive
moves with the document (14 721/375 -> 16 389/417). Suite 2292 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Red first, on behaviour: three asserts about what the payload carries and
what row 4 scores, none on an import or an attribute.
Measured 2026-09-19: N3, N4 and N5 come back with 8, 8 and 1 excerpts and
nothing that says they are weak, so `marked = nothing delivered` reads
three uncovered questions as answered ones. The second gate test is the
known-negative that a marking firing on everything would fail: the 10
questions the three synthetic sets DO answer must stay unmarked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The quota filters the WHOLE candidate list, so every over-quota candidate
came back `source_quota_exceeded` -- including the ones the RANK had
already put outside k, which the quota only reached because it ran first.
`_fates_without_quota` asks the same cut what would have become of each
candidate with no quota, and the drop keeps THAT rule; only a candidate the
quota-off cut would have delivered is named as the quota's. The packer is
lifted into `_pack` and used by both, so the quota-off fate is decided by
the code the run itself uses and never by a second implementation.
The retrieval gate's row 3 goes 2 of 5 RED to 5 of 5 GREEN. Row 7 is
unchanged at 12 of 14; M01 and M02 lose their row-3 credit, which was the
lying label moving and not the ranking. Suite: 2288 passed, 0 failed; no
committed payload moves a byte.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Red first, on behaviour: the withheld set names c4 and c5 as
`source_quota_exceeded` where the same cut without the quota withholds them
`below_k`. The test measures the truth itself, off a second cut, and
carries a known-positive so the assertion is not vacuous.
Measured by PM on 25 real misses 2026-09-17: 13 were labelled by the quota
and decided by the rank.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two checks replace `bool(threshold)`: `_as_share` parses it as a share in
[0, 1] -- so `report-only; any number is acceptable for v1` is a NO, and so
is `80` -- and `_hold_out_verdict` RUNS the registered set against the
registered bundle and prints `answered of asked = share against threshold`.
A registration naming an absent set, an unreadable bundle or an empty set
is a NO with its reason, never an exception and never a silent pass. The
row is 11 checks; it stays RED today because no registration exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Red first, on behaviour: both assertions are about the checks the row
prints, neither is an import or an attribute error.
Measured by PM on 23588e5: `bool(threshold)` was the whole check, so the
threshold `report-only; any number is acceptable for v1` read as `a
threshold is written: yes`. A threshold that is never compared with a
number cannot fell anything.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Red first, on behaviour: the identity assertion fails on the rendered
details, and the two pin assertions fail with DID NOT RAISE. Neither dies
on an import or an attribute.
PM measured both on 23588e5: one pinned r761 set gives 4 of 7 on three
bundles and 0 of 7 on a fourth while the session recorded 7 of 7, from a
bundle the output never names; and three self-written one-question files
plus a self-written bundle read `3 of 3 GREEN`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
RED, 3 of 117 on assert about behaviour, 0 on import.
Two rests from PM's checkpoint on 44ad845, both about what a reader OUTSIDE
this repository is told.
The contract sketch in the gate's own module docstring is what a consumer
implements the `--accounting` door from. It does not name `conversions`,
which the gate now DEPENDS on, nor `normalised_soft_hyphen`, `unaccounted`
or `double_booked`, which the door has written for longer. A consumer
following it writes a ledger this gate reads as "nothing was converted", and
every converted image in their bundle is reported claimed-and-not-found.
The first test measures what the gate READS rather than what its source
mentions: the ledger is handed to `_declared_conversions` as a mapping that
records every lookup at any depth, so the assertion is about lookups and not
about grep. The second measures the other direction, from the DOOR's own
serialisation, so the fix cannot be "publish everything": contract and
output must be the same set.
The third is N5. Without the door there is no ledger, so a converted image
cannot be proved carried and is counted claimed-and-not-found -- the reading
the ledger round removed, back again for every reader who builds without the
flag, and stated nowhere. Asserted through `row3`, not through a signature
that does not exist yet, so the red is behaviour.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
RED, 3 of 23 on assert about behaviour, 0 on import.
PM's checkpoint on 44ad845 found that four end-of-line escapes and an
end-of-bitmap carry an 8x4 frame with 32 of 32 pixels never decoded, while
Pillow -- the independent decoder this file names -- refuses the same bytes.
The sentence the last round wrote into `assets.py`, that a delta and an
end-of-line both leave pixels every decoder agrees on, is false for the
end-of-line half.
Measured here before anything is fixed, and the class is wider than the one
construction: over every opcode sequence of length 1 to 4 on a 4x3 frame
(22 620 streams), 703 are carried by this package and refused by Pillow, and
1 492 more are carried by both and drawn DIFFERENTLY. PM's recommendation on
its own -- refuse a stream that painted nothing -- leaves 512 and 1 171 of
those, so it would narrow the class a third time rather than close it.
Four new arms in CURSOR_CASES, one per clause the table could not see:
the ROW clause (a stream stopping one row early with that row complete, which
is PM's P8 mutant `height - 1` -> `height - 2`, a mutant that survived 51
tests), and three end-of-line constructions that reach the end of the frame
without closing a row. The sweep is the guard the table cannot be: a
hand-picked table holds the shapes its author imagined, and this defect has
now been closed one shape at a time twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
X2 of this round's own mutation run SURVIVED: put the headline back to
`quoted + concept` over `quoted_units + concept_units` and all 64 tests stayed
green. The reason is the fixture, not the row -- three sets of one fasit entry
each make "questions answered" and "units hit" the same number, so the test
agreed with the mutant by construction. The same shape as round 158's X5, and
the same lesson as `konstruert-rad-kan-vaere-fasitradens-speil`.
One set now carries TWO fasit entries, which is the only shape where the two
readings differ: the headline must be 3 of 3 questions where the sum would be
4 of 4. `_hitting_set` takes `entries` and says why in its docstring.
64 passed. Mutation run in a scratch copy of HEAD, twelve mutants plus a
control that must survive: killed 11 of 12 before this commit, and X2 is the
twelfth.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PM ran eight cheating attacks and four were already refused. Three of those
four already had a test (a tampered sha is exit 2, a moved set is refused, a
tuned corpus now meets a pin as well as row 2's forced classes); the fourth --
a cut that delivers nothing, which came back as an error from `consume` rather
than as a quiet row -- did not. The table is 8 of 8 now, not 4 of 4.
64 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PM's checkpoint left seven small findings beside the four bearing ones. Six
are closed here (the seventh, running row 8 against the real sets, follows).
G9 -- THE CONFIRMATION TERM IS OBSERVED NOW. `hit = bool(hit_ids) and
bool(confirmed)` survived all 46 tests, because every mutation of the text
empties `hit_ids` one step earlier. The shape that reaches it is a delivery
that still CARRIES the citation and is no longer the concept file's bytes:
`M14` is that mutation and it is FELLED (row 7 goes 11 of 13 to 12 of 14, bar
12 of 13 to 13 of 14, still RED, the same two survivors), and a test drives it
with its known-positive in the same test. No production line changed: the term
was always observable, it was unobserved.
AND THAT MEASURES THE JUDGE'S INDEPENDENCE RATHER THAN ASSERTING IT. The judge
does read the bundle through `consume.read_concept` and `delivered_text` --
PM's finding -- but the index is warmed BEFORE the first mutation, so the two
sides do not move together. Measured both ways: index warmed first, every unit
is a miss with `confirmed False`; index built UNDER the same patch, every unit
is a hit. The gate never builds one under a mutation. Stated in `LIMITS` with
that measurement, rather than closed by re-implementing a normalisation rule
this repository already owns once.
SPECS -- the synthetic corpus is pinned like the sets (`SPECS_SHA256` over
`specs_digest`). PM's corpus tuning was caught by row 2's forced classes and
not by a pin, and a more careful tuning was left standing.
ROW 9 TAKES AN INPUT. `--k2 SET SHA256 BUNDLE` reads a gold set in this gate's
own set shape; `K2_QUESTIONS` stays the denominator whatever the file carries,
and a set of another size is refused (exit 2) as another set wearing this
one's name. Without a set the row stays RED and not NOT RUN -- ITS denominator
is known, six recorded questions, so the absence is measured; row 8's is not
known until the sets arrive. Both fail the gate identically. This is a
deliberate divergence from the order's parenthetical, stated here and in the
row.
MYPY. `mypy --strict` on this file goes 8 errors to 0, the four in
`read_real_set` among them (`questions = []` against a name inferred
`tuple[Question, ...]`) -- the adapters that meet the real sets.
63 passed. The verdict is unchanged: GATE RED: rows 3, 4, 5, 7, 8, 9.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PM's J3. `MUTANT_ROSTER` carries the thirteen labels and `MUTANT_COUNT` their
number, both written apart from `MUTANTS`, and `row_seven` refuses to run
unless the labels it was handed ARE that roster, in order, with no duplicate.
The bar is taken from the roster's length, not from `len(mutants)`.
Why a pin and not a share: the bar is a percentage, so a longer list is a
lower bar per survivor. Seven copies of `M03 k = 1` took the row to 18 of 20
GREEN with the same two survivors -- nothing new felled, the bar lowered.
Lengthening the list honestly now costs three edits in three places, each
readable as what it is; a duplicate label is refused outright, because two
copies of one mutation are one mutation whatever the roster says.
Row 7 is unchanged on the shipped list: 11 of 13, bar 12 of 13, RED, the same
two survivors with the same measured notes. 56 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PM's J3: seven duplicate `M03 k = 1` mutants appended to `MUTANTS` took row 7
from `11 of 13 RED` to `18 of 20 GREEN` with the SAME two survivors. The bar
is `MUTANT_BAR` of `len(mutants)`, and `MUTANTS` is a module constant in the
file a capability session edits -- a gate whose pass mark is a list the
candidate writes.
Reproduced here at 18 of 20 exactly. 1 red on an assertion about behaviour.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PM's J1. Three of row 5's ten checks now read the repository's history instead
of the registration's own fields:
- the registration is COMMITTED and unmodified against HEAD -- a file written
in a working tree is not a pre-registration;
- the commit that ADDED it is not itself a change to the ranking -- a
threshold and the change it is supposed to bind, in one commit, is neither;
- a change to the ranking landed AFTER it. This is the one that cannot be
self-attested: it is satisfied only in the order a pre-registration actually
happens, and it is red today because neither half has happened.
The three come from `git_provenance`, a `Provenance` the row receives the way
it already receives a path, so both directions are driven from the test and
the default is the real git. Outside a git tree, or with no git on PATH, the
fields come back in their REFUSING form with the reason printed -- unknown is
not unknown-and-therefore-fine.
WHAT GIT CANNOT PROVE IS STATED IN THE ROW, not implied: history shows ORDER,
never that nobody read the number before writing the threshold -- a number can
be read from an uncommitted tree. Order is what these three checks are.
Row 5 is unchanged where it matters today: `0 of 1 RED`, no registration. The
two existing direction tests move 7 -> 10 checks and a third arm is added that
drives each git check red on its own. 54 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PM's J1: two files written by the session under test came back `7 of 7 GREEN`.
Every one of row 5's checks reads a field the registration owns. "Written by
another session than the ranking change" is `bool()` of a string the file
sets; "no reading predates the threshold" reads a `readings` list from the
same file. The order's point -- report-only without a pre-written threshold is
not a protection -- is not met by a protection that is itself a claim.
1 red on an assertion about behaviour (GREEN for a registration this test
wrote seconds earlier), and it names what a session cannot write about
itself: that git already carried the file before the ranking moved.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PM's J10 and J8. Two mechanisms, one per attack, and neither is a pin a
capability session can edit in the same breath as the code.
J10 -- THE DENOMINATOR IS THE SET'S. `Unit` now carries the class its question
DECLARES. Row 2's denominator is the misses plus every forced fixture that
came back a hit, and row 3's is every unit whose set declares a withheld class
(b, c, e -- (a) is not in the bundle and (d) was delivered, so neither can
carry a printed reason) plus whatever the run withheld besides. A fixture that
stops producing its declared class is a BROKEN PREMISE, printed as one, and it
counts against its row: at `k = 32` row 2 stays RED with its denominator held
and row 3 keeps 5 units where it had shrunk to 2 and called that green.
J8 -- THE ROW CARRIES A KNOWN-POSITIVE. With `--source-quota` off, every
printed reason is true; that reading is not a lie, it is an empty measurement,
and row 3 must say so rather than print `6 of 6 GREEN` beside row 1 falling to
8 of 9. A set may now declare `source_quota_in_force`, and the row is NOT RUN
for such a set when the default cut and the quota-off cut deliver the same
concepts everywhere.
THE CONTROL'S OWN PREMISE WAS MEASURED FIRST, and it was false where it was
first put: over the five existing sets the two cuts deliver the SAME concepts
(the quota is topped back up), so 52 labels move `source_quota_exceeded` ->
`below_k` without one delivery changing. `set-quota.json` is the set where the
quota genuinely decides -- measured, `oversikt-08` is delivered without the
quota and withheld with it, and the fasit `svar/broennproeve` is delivered
only with it -- so the requirement is declared there and nowhere else. It
survives the honest fix, which changes labels and not the cut.
Rows 1 and 6 go 9 of 9 to 10 of 10: one added fixture, one added hit, both
green before and after. Rows 2 (7 of 7), 3 (2 of 5), 4, 5, 7, 8, 9 and the
verdict `GATE RED: rows 3, 4, 5, 7, 8, 9` are unchanged. 52 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PM's J10 and J8, the two attacks on the denominator, written as tests that
must refuse them.
J10 (`k = 32`): rows 1, 2, 3 and 6 all came back green at once and not one
label had become true. Rows 2 and 3 count against the misses and the withheld,
so a cut that delivers more broadly does not answer their question -- it
shrinks their denominator to the cases that were already honest. The three
fixtures that DECLARE class b are delivered under that cut, so their premise
is broken, and a broken premise is not an absence.
J8 (`--source-quota` off): every printed reason became true and row 3 read
`6 of 6 GREEN` while row 1 fell to 8 of 9. That reading is not a lie -- with
no quota there is no quota to name falsely -- but the row measured nothing,
because the label it judges was never printed.
3 red on an assertion about behaviour (GREEN where RED or NOT RUN is required,
and a broken premise counted as 0 of 0), 49 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two of PM's eight attacks, closed where they were measured.
J2 -- a set left out. `REQUIRED_REAL_SETS` names the three (`wiki-20`,
`r761-sk2`, `vegnormal-32`) and the row is NOT RUN until all three are given,
whatever the ones that ran scored. The numbers the run DID measure are still
printed: a missing set must not cost the reader the set that was measured, and
"not run" is the row's status, never a reason to withhold a figure.
J2b -- the headline was `quoted_hits + concept_hits` over
`quoted_units + concept_units`, written one line above the detail that says
the two are not summed. The three sets do not share a unit: `wiki-20` names a
citation, `r761-sk2` a section title, `vegnormal-32` a requirement number, and
a citation hit plus a concept hit is a number that is neither. A QUESTION is
the one unit all three have, so the headline is questions answered of
questions asked -- answered meaning at least one of the question's fasit
entries arrived, the reading row 1 already prints beside its own units -- and
the two unit totals keep their own denominators below it, unsummed.
49 passed (was 46 passed, 3 failed). The gate is unchanged where it did not
run: still exit 1, still `0 of 3 NOT RUN` with PM's recorded figures carried.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PM's checkpoint on 2c8296b ran eight cheating attacks at this gate and four
went through: a row came back GREEN without one label becoming true or one
concept ranking better. This is the first of them, written as a test that must
refuse it.
J2: `row_eight` iterates over whatever `--real` handed it and counts. One set
of three came back `6 of 6 GREEN`, and the realistic route is `--real r761`
alone -- the set PM's own registration puts at 7 of 7, with the two that miss
left out. The docstring already said "never green by leaving a set out"; the
code did not, and THIS REPOSITORY'S OWN TEST asserted the opposite at
`tests/test_retrieval_gate.py:486`, `(1, 1, GREEN)` for a single wiki set.
That assertion is corrected here rather than worked around: a test that pins
the defect is the defect.
J2b: the headline `k of N` is `quoted_hits + concept_hits` over
`quoted_units + concept_units`, written on the line directly above the detail
that says the two granularities are NOT summed into one number.
3 red on an assertion about behaviour (GREEN where NOT RUN is required, and a
headline that is a sum), 46 passed. The green direction -- all three named
sets present -- is in the same commit and passes already, so the fix cannot
close the rows by making the row unreachable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
`test_the_four_existing_goldens_are_untouched` ran `git status` with
`check=True` in a `git archive` extract and raised `CalledProcessError`.
Measured 2026-09-19 it was the single failure of the whole suite run from
a clean extract -- twice reported as a round's one failure, twice by a
round that had not touched the file.
Both arms measure the CHECKOUT with `git`, so an extract has nothing to
ask. It skips with the reason stated, never silently: a checkout still
has to answer. Reproduced before and after on a non-repository copy --
`CalledProcessError` -> `SKIPPED [1] ... not a git checkout`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`assert sum(tbx.values()) == 568` sat behind a `skipif` on a file 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, the delivery test
asserts against it, and a second test reads the published sentence out of
all five files and holds them to it. It needs no corpus and no clock:
editing CLAUDE.md to 600 is red on a fresh clone.
It was red at birth for a reason worth keeping: the scan read this test
file's own known-positive string (`574`) as a sixth publisher. The
known-positive is now assembled from pieces, and that failure is the
demonstration that the scan reads what it is pointed at.
What it does NOT prove is stated in the docstring: five files agreeing is
agreement, not a count. The measurement stays where it was.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Chose the CURSOR over a pixel-coverage count because the corpus cannot
choose -- 25 of 25 of the R761 delivery's RLE8 BMPs paint every pixel,
25 of 25 reach the end of the frame, 0 of 25 use a delta -- and an
independent decoder can: a delta and an end-of-line escape state their
skip, so every decoder agrees on the index-0 pixels they pass over, while
a pixel count would refuse both constructions the format defines.
`_bmp_rle8_rows` now refuses (`asset_samples_invalid`) when the terminator
arrives with the cursor short of the last row. Pillow reads 5 of the 8
streams in the table and refuses the same 3, one of them short by a
single pixel.
Both docstrings the round was sent to correct are rewritten: the test no
longer claims every pixel is decoded (it is not -- a stated skip keeps
index 0), and `_bmp_rle8_rows` no longer frames the delta argument as
read off the corpus, which it never was.
R761 rebuilt: bundle `diff -r`-identical to the build before this commit,
50 assets (29 JPEG + 21 PNG), 19 of 19 conversions, SHY 71, u = 0, d = 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PM measured it on `ae441ab`: a stream whose first two bytes are the
end-of-bitmap escape is carried with 32 of 32 pixels never decoded, while
Pillow refuses the same file. The round before closed a truncated stream
by requiring the terminator, and a stream can say it is finished anywhere.
Eight streams over one 8x4 frame, each labelled with whether the cursor
reaches the end of the frame at the terminator. Three do not and are
carried today. The table's values are the INDEPENDENT decoder's, measured
on those eight files, and a second test holds Pillow to them -- otherwise
the table is our own rule restated.
The line is the cursor and not the pixels, and the corpus cannot choose:
over the 25 real RLE8 sources of the R761 delivery, 25 of 25 paint every
pixel, 25 of 25 reach the end of the frame and 0 of 25 use a delta.
Red on behaviour: 1 of 2 (the decoder table already holds).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Chose the side channel over neutralising pointer-shaped document text,
because the second fix 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 existing bundles would move bytes. This reads a
file the run already writes.
`assets.conversion` names the pair, `DocumentAssets.conversions` carries
it out of the run, `DocumentAccount.conversions` books it, and the
accounting JSON states it per document. `_declared_conversions` reads it;
`_conversions` now believes a pair only when the RUN booked it AND a
pointer block confirms it for the asset it names. The confirmation can be
forged and the ledger cannot, which is why the ledger decides.
Measured through the real `okf build`: the three arms PM reproduced
(two `<p>`, one `<p>` with `<br>`, a markdown note beside the carrier)
go forged -> refused, 3 of 3, with the known-positive True in all three.
The text-level regression guard goes 3 arms to 13, the two new ones being
a perfectly written pointer block the run never booked.
R761, rebuilt: 25 BMP sources, 19 held, 19 of 19 conversions confirmed
against 19 declared, 50 assets (29 JPEG + 21 PNG, 0 BMP), SHY 71,
u = 0, d = 0, exit 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PM measured it on `ae441ab` and this reproduces it through the real `okf
build`: a BMP refused `asset_too_large`, absent from `assets/`, read as
CARRIED because the document wrote two paragraphs. `_POINTER` matches a
FORM, and a form is not a signature.
Three arms, each a whole build, each a way to put two markdown lines in a
bundle without the run writing them: two `<p>` elements in one HTML file,
one `<p>` with a `<br>`, and a markdown note beside the HTML file that
carries the real PNG. All three forge on this commit. The known-positive
is in the same builds -- a 2x2 BMP the run really does convert -- so an
arm cannot pass by the route going dark.
Red on an assertion about behaviour: 1 of 1 (the loop stops at the first
arm; the other two are reproduced in the same shape).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One command, one exit code, and no capability: nothing here changes the
ranking, the fusion, the tokenisation or the cut. For a frozen question set
against a bundle it asks how many of N measurement units the payload carries
the fasit for, gives every miss exactly ONE class, and asks the thing the
payload does not do at all -- say so when it does not know.
Today, on the synthetic corpus this repository generates and the five sets it
ships pinned by sha256: rows 1 (9 of 9), 2 (7 of 7) and 6 (9 of 9) green;
3 (2 of 5), 4 (3 of 6), 5 (0 of 1), 7 (11 of 13), 8 (not run) and 9 (0 of 6)
red. GATE RED, exit 1.
- Row 3 is the finding to read first. In a bundle built from ONE source
document, every concept past the first two carries that document's
`source_file`, so a concept the RANK had already lost is withheld as
`source_quota_exceeded`. The truth is decided by the SAME cut run without
the quota -- PM measured the same lie on 13 of 25 real misses 2026-09-17.
- Row 4 is the second. No payload key says "this bundle does not answer
that", so the only honest reading today is `delivered == 0`; three of six
controls come out right and the three that do not get eight excerpts and no
statement. A control the bundle DOES answer must come back unmarked, or the
marking says nothing, so both directions sit in the same row.
- A miss has exactly one class -- (a) not in the bundle, (b) below k, (c) cut
by quota or budget, (d) delivered without the citation, (e) other -- and
each is FORCED by its own fixture. An unplaced miss takes the row to 0 of N.
- The judge opens the bundle. Every concept is read off disk before a payload
is built, a delivered excerpt whose text is not the concept file's bytes is
not a hit, and the two title forms come from `quality` rather than being
written a second time.
- FELLED means a row got WORSE, never merely that something changed: removing
the quota makes row 3 green, and counting that as a kill would credit this
gate with a check it does not have. The two survivors are reported with what
they measured -- the document prior dead and RRF_K flattened each moved 0
ranks and 0 deliveries on these fixtures -- and with a mechanism for each.
- Rows 8 and 9 are never green by leaving something out. The three real sets
are read from path + sha256 and never committed: a gold set names a
consumer's documents and this repository is public. K2 is 0 of 6 because the
gold set exists nowhere while its bundles do.
Denominators re-measured against the sources 2026-09-19, and all four hold:
wiki 20 questions / 29 fasit entries (sha 972d0f57...), R761 8 (S1-S6 + KP +
KN), vegnormal 32 questions / 43 must_cite, K2 6. PM's 45 of 70 is itself a
mixed sum -- wiki at question level, vegnormal at citation level -- so this
gate carries it labelled as recorded and never sums the two granularities.
46 tests, each failing row driven both ways through inputs rather than by
editing the row. No network (the socket guard is fired before its silence
counts), no private corpus, no clock.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
killed 39 of 39, exit 0 (36 before this round). Three new mutants, one per
check that closes PM's measured forgery, each felled by its own arm:
- X3 restores the pre-round `_conversions` verbatim -- the clause read from
anywhere in the bundle text. Felled by the body-text and table-cell arms.
- X4 drops the tie between the clause and the asset its block points at.
Felled by the arm where the clause stands in another asset's block.
- X5 drops `_inline`'s checksum disarming. Felled by the end-to-end arm where
an `<img alt>` carries the clause.
X5 IS WHY THE HARNESS CHANGED, and it is a finding about the harness rather
than about the code: it SURVIVED 112 green tests on the first run, and it had
never been applied. The copy is run with the venv's interpreter, which carries
an editable install pointing at the working tree, so `import llm_ingestion_okf`
in the copy resolved to the original `src/`. The subprocess now gets the copy's
own `src/` on `PYTHONPATH`, which wins over the editable finder, and X5 dies on
the first run under it -- the survivor/killed pair is the measurement. The gate
and the witness were never affected; the suite reaches those through the copy's
own `tools/`.
The docstring's "the twenty-six mutants" now says "twenty-six of them", since
the list has been 39 for a while and the sentence is about their provenance.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
160 passed over the three affected files. Two guards this round introduced are
removed, each because it was measured and found unkillable, and one real cut is
added in their place.
The end-of-line anchor on the judge's clause expression: with the pointer-block
route and the asset-name tie already in place, the only case it covered is a
label forging a clause inside a block that also carries a real one -- which
`assets._inline` now makes unreachable from a document. Removed with `$` gone
the whole suite stays green, which is the measurement: a guard nothing can fell
is not a guard, and it would also have made the mutant below unfaithful.
The length check on a short absolute run in `_bmp_rle8_rows`: unobservable by
construction. A run shorter than its declared count means the stream ran out,
so the loop cannot reach an end-of-bitmap afterwards and the picture is refused
either way -- the partial paint is discarded with the frame.
A FIFTH CUT REPLACES THEM, and it makes an existing bound load-bearing: a
stream cut in the middle of a delta escape, 17 of 32 pixels wrong by this
file's own decoder. Without the two-byte bound in that branch the cut raises
IndexError instead of a coded refusal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
19 passed in this file, 0 skipped. Green on arrival and stated as such: no
production code changes here, so there is nothing for a red commit to precede
-- the defect is that the property was never MEASURED on a core install.
Measured by PM 2026-09-19 on one: 4 of the 13 guards in this file were
SKIPPED, the lossless one among them, because they decode through Pillow --
which this package neither uses nor ships and which arrives only as a
transitive dependency of `pdfplumber` under `[extract]`. A plain
`pip install llm-ingestion-okf` therefore never measured the property this
round is named after.
The new arm decodes the carried PNG with `zlib` and the five filters of PNG
SS 9.2, and compares against pixels written out in this file: `rle8_indices`
for the RLE8 source and `RGB_ROWS` for the 24-bit one. Still independent in
the way that matters -- the package compresses rows it filtered, the test
reverses filters it decompressed. Two arms: one over `read_image`, one over
what the BUILD wrote.
The environment is MADE rather than assumed: a `sys.meta_path` finder raising
for every `PIL` name, with a known-positive that it fires before the guards
re-run under it. Control, run once and not committed: a Pillow-dependent line
placed inside the blocked section turns the guard red.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 of 1 red, on an ASSERT about the published sentence. The behavioural half of
the same test is already green and that is the point: PM's M10 measured that a
mutated converter writing a BLANK PNG gives `asset_holds = True`, because the
bundle is internally consistent -- the digest it claims to have written really
is the asset's digest -- and the judge has no opinion about whether those bytes
hold the source's picture.
The limit is therefore asserted in both directions: it is REAL (the blank PNG
is accepted here) and it must be STATED (the docstring names what the route
cannot see). "A bundle claiming a conversion it did not perform still fails"
reads wider than the route reaches; the suite fells that mutant, this gate
cannot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 of 2 new guards RED, both on an ASSERT about behaviour (`assert True is
False`), and the second REPRODUCES PM's measured path end to end through the
real `okf build`: a BMP declaring 50 000 x 50 000, refused `asset_too_large`
and absent from `assets/`, gives `asset_holds = True` because an `<img alt>`
in the document states the conversion clause. The unit arms reproduce the
other two ways in -- ordinary body text and a table cell -- and a third the
report did not name: the clause inside a pointer block for a DIFFERENT asset.
The judge's first sentence is "THE FASIT NEVER COMES FROM THE READER IT
JUDGES". Before the conversion route landed, `asset_holds` hashed the source
file and looked in `assets/`, so no document could reach it; the route added
for converted images reads two digests out of the bundle text with a free
expression, and `claimed and not found` can now be silenced by a document
that asks for it.
Each arm carries a source that was never carried while the bundle holds one
unrelated REAL asset, so the digest the forgery names is genuinely in
`assets/` -- the property that made the measured forgeries work. The
known-positive on the same bytes keeps the arms from passing on a route that
has merely stopped working.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 of 3 new guards RED, on an ASSERT about behaviour: `DID NOT RAISE
ExtractionError` on all four cuts. The other two are the section's own
known-positives and pass by describing what is already true -- where each
cut lands in the fixture's opcode stream, and that the UNCOMPRESSED BMP path
already refuses the same shape with `asset_samples_invalid`.
Measured by PM 2026-09-19 on a real R761 asset (352x548 = 192 896 pixels): a
stream cut to 90 % was carried with 13 923 pixels wrong, to 50 % with 95 890,
to 10 % with 166 525 -- no code, no row, a partly blank PNG standing under a
content address that says it holds the source's pixels.
The test brings its OWN permissive RLE8 decoder, so the pixel cost of each cut
is a number this file computes (8, 11, 0 and 0 of 32) rather than the package
agreeing with itself; two of the four cuts lose no pixel at all, which is the
whole reason the rule has to be the terminator and not a pixel count.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`normalised_soft_hyphen` is read off the RUN (`result.normalised`) and never
recounted from the source. It is the one number the accounting publishes with
no independent denominator behind it, and nothing in the output said so.
RED on the claim:
assert "The count is the door's own, read off the run and not recounted" in log
AssertionError: ... '* **Normalisation**: 5 soft hyphen(s) (U+00AD) removed
from 1 of 1 document(s) before the persist gate. No other character is
touched.'
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
P11 and P12, PM 2026-09-19: the `refused={u.refused}` column on row 3's
detail line and the clause "N element(s) lost with R of D document(s) refused
whole" in its reason could each be deleted with this file green at 106
passed. The LOSS is held -- `Unit.refused` keeps the unit unclean and the
note names the source and its code -- but what the ROW says about it was
decoration nothing pinned, and the row is what a reader of the gate's output
sees first.
R, D and the element total are counted over the units the test builds, never
read back off the row. The known-negative is the same units with no refusal:
the sentence must move with them, or it is a constant that happens to read
true.
Red proven in a scratch copy of HEAD (`/tmp/shy-mut`), control green at 109
passed:
P11 detail line drops `refused={u.refused}` -> 1 failed (line 666)
P12 reason drops the whole "... refused whole" clause -> 1 failed (line 661)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`log.md` writes that sentence on every run and README and CLAUDE.md repeat
it. Until now nothing held it: PM's mutant P6 -- a door that ALSO removes
U+00A0 -- passed the entire suite green (2 171 passed, RC 0) while it would
have eaten all 6 633 NBSP in R761 with the log still claiming otherwise.
Ten characters, each a way for the door to reach past its own name: the four
real zero-width carriers, U+2060, U+00A0, the three hyphens a reader confuses
with a soft one (U+002D, U+2010, U+2011) and a combining accent (the door is
the one step that does NOT normalise to NFC). Each appears at least twice in
the fixture -- asserted, so the loop cannot pass over an empty set.
The invariant is the stronger of the two equivalent forms: rather than
reinserting the removed characters at their booked positions, the test builds
the expected string with its OWN filter over the source. That pins ORDER as
well as multiset, and needs no positions -- which the door does not return.
All three numbers (21 removed, the expected string, every surviving count)
come from the test, never from the door. The second test repeats it through
`extract_document`, the one place the door is applied, so a second remover
beside it is red too.
Red proven in a scratch copy of HEAD (`/tmp/shy-mut`, verified that the
scratch `src/` is the code that runs), control green at 7 passed:
P6 door also removes U+00A0 -> 2 failed
`assert text == expected` / `AssertionError: assert 'arbeider pa...'
== 'arbeider \xa...'`
P2010 door also removes U+2010 -> 2 failed
P6b door collapses U+00A0 to " " -> 2 failed
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Counted three ways over `N101-2025-860031.json`, none of them importing this
package: raw `tbx:` substring 568, regex over `"tag": "tbx:` 568, node
traversal 568 across 10 distinct tags (94 x 6 + 1 x 4), out of 7 374 tags in
the delivery. The number is corrected in all four places that published it --
CLAUDE.md, `tools/okf_witness.py`'s `_sts_role_json` docstring,
`tests/test_accounting_gate.py` and the round's report -- and the test from
the previous commit now counts it rather than repeating it.
The one remaining `574` in the tree is `docs/2026-09-11-k3-runde8...md:89`
("11 016 and 6 574"), a character count of two documents and a different
number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The known-negative for stripping a namespace prefix -- "N101 ships 574 `tbx:`
tags, and not one of their local names is a role" -- was published in four
docstrings (CLAUDE.md, tools/okf_witness.py, this file, the round's report)
and asserted nowhere. A measurement presented as a fact, with nothing that
could turn red when it stopped being true.
The walk is written HERE rather than taken from `okf_witness`: the witness's
own reader is what this known-negative judges, so counting through it would
make the two agree by construction.
RED on the claim, not on an import:
assert sum(tbx.values()) == 574
AssertionError: assert 568 == 574
where 568 = sum(dict_values([94, 94, 94, 94, 94, 94, 1, 1, 1, 1]))
Three independent counts of the delivery agree on 568 -- raw `tbx:` substring,
regex over `"tag": "tbx:`, and node traversal -- against 574 in the tree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Chosen: a stdlib BMP reader, because `read_image` is on the CORE path and an
asset's name is its content digest. Measured first, as the order requires:
Pillow 12.3.0 IS in this tree (transitively under `pdfplumber`) and it DOES
decode RLE8 correctly -- a hand-written stdlib decoder and Pillow agree on
19 of 19 of R761's real files, RGB per pixel. So the choice does not rest on
capability. It rests on two properties of this package: `.html` and `.xml`
carry images with no `[extract]` extra installed, so a Pillow converter
either makes a core path depend on an optional binary wheel or buys the
second runtime dependency; and encoding through an installed library would
make a bundle's identity move with that library's version, which is the
property 0.10.0 felled page rasterisation over and `encode_png`'s docstring
already defends. Pillow keeps the job it is good for: the INDEPENDENT decoder
in the tests, on neither side of the conversion.
The defect, measured over the frozen R761 delivery's `assets/`, denominator
50: 29 JPEG, 2 PNG and 19 RLE8 BMP. The 19 are byte-correct files nothing
reads, so 19 figures were present and invisible while `images: N` reported
that they had arrived.
- `VIEWABLE_MEDIA_TYPES` is tested against every asset's SNIFFED type, so it
is a property and not a list of formats we met. WebP is on it and `sniff`
does not recognise one; the limit is stated, not implied.
- `bmp_to_png`: 8-bit uncompressed, 8-bit RLE8, 24-bit uncompressed. All five
RLE8 opcodes. 19 of 19 real files convert with RGB identical to Pillow's
decoding of the source, 2 366 365 pixels compared.
- `asset_not_viewable` and `asset_bmp_unsupported`, both published, both
leaving the concept's "not carried" line.
- Traceability on the pointer's second line, where the rest of the asset
metadata already lives: original media type, original sha256 in full, new
sha256 in full. A converted asset is ONE asset.
- The ceiling is paid on the DECLARATION before a row is allocated, and an
RLE run is one clipped slice -- painting pixel by pixel leaves the memory
bounded and the CPU unbounded.
Two repairs the change forced, each measured rather than assumed:
- `tests/test_assets.py`'s "dimensions absent is absent" used a TIFF, which
is now refused before `read_image` returns. The property still has a
reachable case -- a JPEG whose frame header never arrives -- and uses it.
- `asset_holds` in the accounting gate proved a carry by hashing the SOURCE
file, which a converted image's bundle cannot satisfy. It now also reads
the two digests the bundle states and HASHES THE ASSET ITSELF, so a bundle
claiming a conversion it did not perform still fails.
`tools/okf_asset_census.py` is the committed instrument for the
known-positive: one row per image, from two pinned trees. It was caught by
the rule it serves -- its first version handed `_pdf_images` the wrong page
object and reported 0 images over 67 PDFs with exit 0. The attribute is
asserted now and a known-positive runs before the sweep.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by running the gate against the working tree: R761 goes from 0 to
**19 claimed and not found** the moment the conversion lands, and 19 is
exactly its RLE8 BMP count.
The cause is a rule that was right until this round. `asset_holds` proves a
carry by hashing the SOURCE file and looking for those bytes under their own
content address in `assets/`. A converted image's source bytes are not in the
bundle and never will be -- the run carried the picture under a new digest,
which the bundle states on the pointer line beside the old one.
Red on the claim: `asset_holds` returns False for a build whose bundle
declares the conversion and whose `assets/` holds the named file. Two
known-negatives are in the same test so the second route cannot become a way
of believing the report: a bundle that claims a conversion and ships no file,
and a bundle shipping a file under a digest it never tied to this source.
Both must stay False, because the judge has to hash the asset itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Written after walking the five mutants the order names against the eval as
committed. Two of them SURVIVED it, which makes them holes in the eval and
not in the code that does not exist yet.
1. "the format is read from the file extension instead of the bytes" survived,
because every image in the fixture is named after what it is: a `.bmp` maps
to image/bmp either way and a `.tiff` to image/tiff either way, so the
fixture could not tell a sniffed type from a claimed one. A BMP named
`graphics/figur.png` can. Red today: `image/bmp` != `image/png`.
2. "the ceiling is checked after decoding instead of before" survived because
the guard only asserted that the refusal happens, and `encode_png`'s own
`check_size` refuses too -- one frame later, after the memory is spent. The
guard already measured the peak; what it could not do was measure it
affordably, because a 50 000 x 50 000 frame is 2.5 GB. At 7 000 x 7 000 the
declaration is still over the 40 MP bound and the unbounded frame is 49 MB
-- measurable, and two orders of magnitude over the 4 MB the guard allows.
Nine of thirteen guards are now red on their claim; the four green ones state
properties that already hold.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The eval the round is judged on, written before the decoder exists. Eight of
twelve guards are RED on their claim, four are green because they state
properties that already hold.
Red, and each on an assertion rather than an import:
1. Visibility as a PROPERTY of every carried asset, read off the bytes: 3 of
3 carried assets in the fixture bundle are image/bmp or image/tiff, which
no model displays. The denominator is counted in the test from the inbox,
so a build that carried nothing could not pass over an empty set.
2. Lossless: 0 carried assets in the viewable set hold the source's pixels.
The holder must BE viewable -- a BMP carried verbatim trivially holds its
own pixels, so a guard without that clause would be green today.
3. Traceability: the bundle states neither source checksum and neither
original format.
4. One asset, not two: the assets directory holds 3 files for 2 convertible
images plus one that should be refused.
5. An RLE stream far longer than its declared 8x4 frame is carried, in a
format nothing reads.
6. A TIFF and a truncated BMP are both carried silently; neither raises.
7. The concept says "not carried" nowhere.
Green already: the fixture's own known-positive (Pillow decodes the
hand-built RLE8 opcode stream to the literal grid this file states), the two
byte-identity controls for PNG and JPEG, and the bound on a BMP declaring
50 000 x 50 000.
The fixture is built byte by byte in the test and exercises all five RLE8
opcodes -- encoded run, absolute run with its pad byte, delta, end-of-line,
end-of-bitmap. A fixture of runs alone would pass with a decoder that
implements none of the other four. The independent decoding is Pillow, which
this package neither uses nor ships, so "identical pixels" is not this
module agreeing with itself; the viewable set and the rejection codes are
written out in the test rather than imported, for the same reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator decision 2026-09-18, the alternative chosen verbatim: okf gets
ONE documented normalisation door before the guard, removing U+00AD and
BOOKING the count in the content accounting. The guard is not touched.
The defect it answers, PM's measurement: R761 Prosesskoden:2025 is
refused whole by guard 1.4.0 as `output:zero-width-present` -- an
any-tier carrier, `fail_secure` at every trust level -- over 71 U+00AD
and 0 real zero-width characters. The 71 are Norwegian hyphenation
points inside words. Reproduced here in the small on this tree before
any code moved: three soft hyphens in one markdown file give `0 of 1
extracted document(s) persisted; rejection codes: fail_secure 1` and
exit 1.
Five tests, each red on its claim:
- the source passes the DEFAULT gate, the accounting carries
`normalised_soft_hyphen: 5` at both levels, and `log.md` says it;
- text preservation as an EXACT invariant and never a share: the
extracted text of the hyphenated source EQUALS the extracted text of
the same source written without them;
- the known-negative, U+200B, still `fail_secure` and still in the
text -- removing a real carrier would be the guard's decision and
would take a screen away from every consumer;
- a PDF's `source_pages` table is CHARACTER offsets rebuilt from the
pages while the text comes back through the door, so both must be
measured over the same rendering or every locator drifts;
- R761's own 71, on the delivery the decision was taken for.
U+00A0 NBSP is untouched: `_ZERO_WIDTH_CPS` is exactly {200B, 200C,
200D, FEFF, 00AD} on guard 1.4.0, read off the installed source, so
the guard has no rule about NBSP and nothing here needs one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reproduced 2026-09-19: `okf build` on N200 proposes 0 plans, prints
`FAILED - no segmentation plans` and exits 2 BEFORE the accounting door
is reached -- no accounting file is written at all. Every one of the
16 549 elements then lands as `u` with `no declared fates`, which reads
like a finding about the build and is a finding about the run.
Two tests and a known-negative, so a rule that calls every corpus blank
fails too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The guard at the end of `main` asked whether the corpora named by the
ARGUMENTS are available. Row 6 is SKIPPED precisely when none of them
is, so the branch could never fire; PM read it as dead code and no test
covered it. The question it meant to ask is about the machine: a corpus
that is HERE and was pointed away from is a row that did not run, and
with `CI=1` the gate then printed a qualified GREEN and exited 0.
Two tests, so a guard that fires on everything fails too: with the
default source present and the arguments pointing elsewhere the run
exits 1 and names the row; with no corpus on the machine at all it
exits 0, because nothing to measure is not a row that did not run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>