test(fixtures): replace sector-specific example material with generic, fictitious examples — green

Every fixture, test document, tool example and document now uses an invented
kitchen-and-baking handbook series, written in this repository. The package's
behaviour is unchanged; src/ changes are comments and help text only.

- Generated fixtures are regenerated from their generators. Their structural
  counts are identical before and after: elements, images, rows, cells,
  headings, bookmarks and the witness inventory's per-document totals. The
  image-inbox and accounting documents are renamed kapittel-84-*.
- tools/okf_accounting_gate.py: the two options that named one real corpus
  each are replaced by a generic, repeatable --corpus PATH with no default.
  Row 5 compares the PDF pair alone. Gate verdict unchanged: RED rows 2, 3, 6.
- tools/okf_witness.py: the STS JSON reader for one publisher's delivery is
  removed, along with its three twins and five tests. The mutation harness
  loses W09.
- docs/: 13 dated reports that documented runs on a retired reference corpus
  are removed, and 40 are neutralized. Dead links are removed, and no new
  dangling path is introduced.
- The synthetic MCP-gate corpus and the residual probe words are neutral.

Valgt: keep the `okf quality --fasit` bar value (the measured fraction, one corpus) and
rewrite only its provenance, because the verdict stays unchanged and the
number names nothing.

Term check with the local list: 0 of 411 tracked files, 0 file names, 0 of
27 binary fixtures. Suite after git add: 2457 passed, 1 skipped. The base
tree had 2460 passed and 2 skipped; five tests went with the JSON reader and
four were added by the term check. ruff, ruff format and mypy --strict src/
are clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-23 13:54:57 +02:00
commit 9d1f4b14ed
174 changed files with 1889 additions and 6512 deletions

138
CLAUDE.md
View file

@ -51,8 +51,8 @@ one boundary rule:
ceiling is structural rather than recovered. A NISO-STS zip from a
publisher's own viewer was **110 of 110 unreadable, 0 plans, exit 2** --
`.xml` was in neither registry -- and the one xml file in it IS the whole
product: R761 Prosesskoden:2025, the document round 12 met as a 701-page
PDF, carrying 7 715 `<sec>`, **2 761 with a `<title>`**, 4 954 with a
product: a large regulatory reference document, the one round 12 met as a
701-page PDF, carrying 7 715 `<sec>`, **2 761 with a `<title>`**, 4 954 with a
`<label>` and no title, 10 `<table-wrap>`, root `<standard>`, 0 `<!DOCTYPE`.
Its `<sec>`-nesting depths over the titled sections are
28/118/500/1141/868/97/9, row for row the fasit's own. The reader is stdlib
@ -121,8 +121,7 @@ one boundary rule:
publisher reuses a section number, the same 2 the PDF arm has, and 0 is not
reachable without inventing an id; round 13's 14 such directories were false
positives of the TEXT route reading the document's own contents listing and
are gone. Report:
`docs/2026-09-10-k3-runde14-deklarert-struktur-tar-ruten.md`.
are gone.
**Since K3-19 an STS document's own identity names its directory**
(`extract.declared_identity`, read by `cli._document_prefixes` and the door):
the directory was the delivery file's stem, a UUID occurring **0 times** in
@ -131,12 +130,12 @@ one boundary rule:
used by NEITHER -- the `slug_owners` gate would refuse both with "rename
one", which a name read from inside a document cannot obey. The `sources`
title is `<doc-number>` + `<year>`, then `<title-wrap>`, then the file name:
R761's `<full>` carries a COMMA, a flow terminator, so it is never written
the reference standard's `<full>` carries a COMMA, a flow terminator, so it is never written
and never cleaned up. A titled section's `description` is its own FIRST
spec point (first `<p>` of the first DIRECT-child `sec-type="spec"`, whole),
carried by the plan entry, screened by the gate, and written only where a
YAML reader reads it verbatim (`inbox._yaml_plain`): 2 026 of 2 761 titled
sections on R761 carry a point, **1 807** are written (2 have no `<p>`, 217
sections on the reference standard carry a point, **1 807** are written (2 have no `<p>`, 217
carry `: ` and PyYAML refused exactly those frontmatters), none invented.
SS 4.1 sets no length, so the one-paragraph limit is ours. **The directory
name reached the RANKING, and K3-20 closed it in `consume`:** signal 1 read
@ -145,13 +144,11 @@ one boundary rule:
-- measured, the known-positive went rank 1 -> not delivered at the default
`k` (13 at k=50) with S1-S6 unmoved. `consume.shared_id_prefix` now keeps
the leading directories EVERY id shares out of that signal: KP rank 1 at
both `k`, S1-S6 6/6, and K2 (12 payloads), N100/N200/N500 (15) and the
both `k`, S1-S6 6/6, and K2 (12 payloads), three downstream bundles (15) and the
five-document folder (5) byte-identical, because ids that share no prefix
read exactly as before. Dropping each concept's OWN document directory
instead was measured and felled -- a K2 hit@8 row went rank 5 -> not
delivered. Reports:
`docs/2026-09-11-k3-runde19-dokumentidentitet-og-frontmatter.md` and
`docs/2026-09-11-k3-runde20-delt-katalog-og-arvet-kontekst.md`. The
delivered. The
registries are COUPLED: a row in
`_CORE_EXTRACTORS` and not in `segmentation._STDLIB_EXTRACTOR_IDS` refuses
every proposal for the type, two layers away from the extractor.
@ -200,10 +197,10 @@ one boundary rule:
read, an STS `<graphic>` was walked past, a PDF was opened for text alone,
the converter's markdown writer dropped every picture, and the only writer
into a bundle was `materialize.write_bytes(..., content: str)`. Measured on
R761 Prosesskoden:2025: the process text is carried in full while **12
a 701-page reference standard: the section text is carried in full while **12
`Tabell N-N` and 9 `Figur N-N`** captions stand over nothing, because that
publisher ships those tables as raster pictures in BOTH the PDF and the
NISO-STS delivery -- process 84's "toleranseklasse ... er gitt i tabell 84-2"
NISO-STS delivery -- a section's "is given in table 84-2"
points at empty space. **Five readers PLACE and one module DECIDES:**
`assets.py` owns what an image is (sniffed from the bytes, never from the
claimed extension), what it is called (`<sha256[:12]>-<the source's own
@ -217,7 +214,7 @@ one boundary rule:
the `source_rows` locator read back out of it; 0 of 4 K2 workbooks hold any
media, so it is a stated limit and not a loss taken. **A PDF stream that is
already a file is carried VERBATIM** (`DCTDecode`, `JPXDecode` -- 29 of
R761's 50 objects), and raw samples are encoded to PNG with stdlib `zlib`.
the reference standard's 50 objects), and raw samples are encoded to PNG with stdlib `zlib`.
Rendering the page region was the alternative and was FELLED on
determinism: a rasterised crop's bytes, and therefore the asset's
content-addressed name and the bundle's digest, would depend on the installed
@ -491,7 +488,7 @@ structured reader (D1b); until then the constraint binds what we write.
**Every value is written so a YAML reader reads it back the same (K3-22).**
SPEC § 11 point 1 requires "a parseable YAML frontmatter block", and before
K3-22 the pinned K2 default bundle failed PyYAML on 41 of 455 blocks and each
R761 build on 1 -- block scalars written verbatim. A block scalar that is not
reference-standard build on 1 -- block scalars written verbatim. A block scalar that is not
plain-safe (`profiles.yaml_block_plain`, K3-19's rule) is now written
double-quoted with `\` and `"` escaped; every other value keeps its bytes. A
FLOW leaf has no quoted form -- the pinned guard refuses any quote inside a
@ -500,8 +497,7 @@ a quote, a leading indicator) is refused by `profiles.yaml_flow_plain` with the
door's existing code, never written. Readers unquote a `"`-wrapped value only:
0 such values existed in any measured bundle, while 11 193 `'`-wrapped ones do
and stay untouched. PyYAML is a dev dependency that validates the rules in
`tests/test_yaml_frontmatter.py`; `src/` imports no yaml. Report:
`docs/2026-09-11-k3-runde22-yaml-lesbar-frontmatter.md`.
`tests/test_yaml_frontmatter.py`; `src/` imports no yaml.
**Every upstream release runs `docs/upstream-okf-upgrade-runbook.md`.** Pin the
commit, enumerate the whole `okf/` tree, **read the shipped example bundles and not
@ -819,7 +815,7 @@ and fixtures, never code.
(`propose._link_shells`), passing over an empty ancestor; the door writes
the existing `parent:` key. Nothing is copied -- a consumer's own build of
the same standard copied the inherited text in and took hit@1 6/6 -> 2/6.
The route reads the PLAN, never the row: on R761 it names the ancestor the
The route reads the PLAN, never the row: on the reference standard it names the ancestor the
`<sec>` nesting names on **710 of 710** shells since K3-21 D (708 before: the
2 misses sit at depth 7, and the reader clipped the outline mark to 6 along
with the heading), where reading section numbers gets 686 (`12` begins with
@ -859,14 +855,13 @@ and fixtures, never code.
keys `(source_file, segment_id)` off the concept's own frontmatter and is
asked first, inside the pointing concept's document; a value no segment
answers to is a number, looked up as before, and a pointer naming nothing
keeps `?`. The one key keeps its two meanings (`inbox.py`). Report:
`docs/2026-09-11-k3-runde20-delt-katalog-og-arvet-kontekst.md`.
keeps `?`. The one key keeps its two meanings (`inbox.py`).
- **A TWELFTH flag, `--pdf-outline`, is OFF** (round 12, 2026-09-10) and it is
the only one here that does not read the extracted text at all: it cuts a PDF
at the boundaries its own `/Outlines` bookmark tree declares. It is NOT Arm D
-- `--outline-run`/`--outline-gate` are a TEXT heuristic over numbered lines
in the extracted text, and this opens a structure index the file already
carries. Measured on ONE 701-page process code whose publisher also ships a
carries. Measured on ONE 701-page reference standard whose publisher also ships a
NISO-STS structure for it, so the fasit is the publisher's own 2 761 titled
sections: the shipped default finds **1 967 of 2 761**, **0 of its 28**
chapters, and **794 of 794** misses have their heading text PRESENT in the
@ -890,15 +885,14 @@ and fixtures, never code.
stands under a candidate's first line, the right question for a guess and the
wrong one for a publisher's declaration; **683 of 2 762** marks are container
sections and applying it scores 2 079. An unresolvable `/Dest` is dropped and
COUNTED (R761 has 0 of 2 763; one of the eight reference PDFs has 2 of 2). NO
COUNTED (the reference standard has 0 of 2 763; one of the eight reference PDFs has 2 of 2). NO
new dependency: `pdfminer.six` already ships under `pdfplumber` in
`[extract]`, so `uv.lock` is untouched and `pypdf` stays out. Cost 119.22 s ->
183.31 s wall, peak RSS 3 252 -> 3 251 MiB, pages parsed 1 -> 1. **The default
did not move**, and the reach is why: **1 of 8** reference PDFs carries a
usable tree, and a bookmark tree is the publisher's CLAIM about its own
structure. On the folder where no PDF has one, `diff -r` is empty against both
the arm off and the pre-change tree. Report:
`docs/2026-09-10-k3-runde12-pdf-outlines.md`.
the arm off and the pre-change tree.
- **Three PDF READER flags, all off, and they sit BEFORE every segmentation
flag** -- an arm changes how the proposer cuts a text, these change what the
text says. `--pdf-headings font` infers a heading from typography (dominant
@ -1045,7 +1039,7 @@ and fixtures, never code.
counts allocations and is not disturbed by load). Known-positive: **258 of
258** chains over the permitted filters still carry a small image.
**10 of 10 mutants killed**, control green. **NOT A ROW'S DIFFERENCE ON
REAL DOCUMENTS**: paired `_pdf_images` over every PDF on this machine from two PINNED trees: **9 306 -> 9 306** bårne over 77 filer plus **50 -> 50** on R761 (**9 356 -> 9 356** together), **0 of 78** files moving a count and **0** moving a code. R761 also settles the `[/Fl /A85]` question: **50 objects, 29 `[/DCTDecode]`, 21 `[/FlateDecode]`, 0 ASCII85 links** -- round 2's count of **580** is reproducible from nothing on this machine, which changes no decision but should not stay unqualified. **`resource.setrlimit(RLIMIT_AS)` was
REAL DOCUMENTS**: paired `_pdf_images` over every PDF on this machine from two PINNED trees: **9 306 -> 9 306** bårne over 77 filer plus **50 -> 50** on the reference standard (**9 356 -> 9 356** together), **0 of 78** files moving a count and **0** moving a code. The reference standard also settles the `[/Fl /A85]` question: **50 objects, 29 `[/DCTDecode]`, 21 `[/FlateDecode]`, 0 ASCII85 links** -- round 2's count of **580** is reproducible from nothing on this machine, which changes no decision but should not stay unqualified. **`resource.setrlimit(RLIMIT_AS)` was
MEASURED before anything was built on it and is NOT usable here** --
Darwin 26.6.2 raises `ValueError: current limit exceeds maximum limit` and
does not enforce it -- so no child-process cap exists and the per-link
@ -1072,7 +1066,7 @@ and fixtures, never code.
`docs/2026-09-18-bildestien-holder-0-10-1.md`.
- **EVERY CARRIED IMAGE IS ONE A MODEL CAN BE SHOWN (0.10.1, 2026-09-19).**
Until this round the path carried whatever a publisher shipped. Measured over
the frozen R761 delivery's own `assets/`, denominator 50: 29 JPEG, 2 PNG and
the frozen reference delivery's own `assets/`, denominator 50: 29 JPEG, 2 PNG and
**19 "PC bitmap, Windows 3.x, 8-bit, compression 1"** -- byte-correct RLE8
BMP files that nothing reads, so 19 figures were present and invisible at
once while `images: N` said they had arrived. `assets.VIEWABLE_MEDIA_TYPES`
@ -1108,9 +1102,9 @@ and fixtures, never code.
a consumer loses 16 pictures and gains 16 statements that a picture was
there. **The judge had to move with it and it caught itself**: the gate's
`asset_holds` proved a carry by hashing the SOURCE file, which a converted
image's bundle cannot satisfy, so R761 went 0 -> **19 claimed and not
image's bundle cannot satisfy, so the reference standard went 0 -> **19 claimed and not
found** (exactly its RLE8 count) before the judge learned to read the two
digests the bundle states and then hash the asset itself; after, R761 is
digests the bundle states and then hash the asset itself; after, the reference standard is
u = 0, d = 0, 0 claimed and not found, 50 asset files, 31 081 carried
elements, and the gate's verdict is the same `RED: rows 2, 3, 6` as before
the round. Report:
@ -1118,7 +1112,7 @@ and fixtures, never code.
**THAT ROUND SHIPPED TWO DEFECTS AND 0.10.1 CLOSES THEM.** A TRUNCATED RLE8
stream was carried as a partly blank PNG: the decoder painted what the
stream held and left the frame at index 0, which is what the format says
about a SKIPPED pixel, so no decoder disagreed -- measured on a real R761
about a SKIPPED pixel, so no decoder disagreed -- measured on a real reference-standard
asset of 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 and no row. The
decode may now end at an explicit **end-of-bitmap escape and nowhere else**
@ -1154,7 +1148,7 @@ and fixtures, never code.
outside the build. A build with no accounting door reports a converted image
claimed-and-not-found rather than believing it. Measured: 3 of 3 forged ->
refused with the known-positive True in all three, the text-level regression
guard 3 arms -> 13, R761 rebuilt `diff -r`-identical, 50 assets, **19 of 19**
guard 3 arms -> 13, the reference standard rebuilt `diff -r`-identical, 50 assets, **19 of 19**
conversions confirmed against 19 declared, SHY 71, u = 0, d = 0. And the
TERMINATOR asks only that a stream say it is finished, which it may do
anywhere: a stream whose FIRST two bytes are the EOB escape was carried with
@ -1190,7 +1184,7 @@ and fixtures, never code.
**11 441** files scanned across the four raw deliveries and the K2 reference
corpus, the only **25** BMPs on this machine use an end-of-line at column 0
in **0 of 25** and a delta in **0 of 25**, and 25 of 25 still decode to
Pillow's pixels exactly (**3 117 220** compared). R761 rebuilt from a pinned
Pillow's pixels exactly (**3 117 220** compared). The reference standard rebuilt from a pinned
pre-change tree and from this one is `diff -r`-IDENTICAL, 5 551 files, 50 of
50 viewable, 19 of 19 lossless. `CURSOR_CASES` goes 8 arms to 12 (one for
the cursor rule's ROW clause, PM's `P8`, which survived 51 tests) and the
@ -1211,7 +1205,7 @@ and fixtures, never code.
- **`carried` is checked against the written concept bodies**
(alphanumerics, casefolded), so a gate that drops a line is FOUND.
Measured limit: short elements (labels, one-word titles) are often
present elsewhere in the document. With R761 cut to half, 4 823
present elsewhere in the document. With the reference standard cut to half, 4 823
paragraphs were reported lost but only 3 titles.
- **Two changes hold without the flag.**
- A build that extracted documents and persisted none exits 1. Door B and
@ -1219,8 +1213,8 @@ and fixtures, never code.
- An image file carried through a persisted document is its own K1b
column, never also `extractor_unknown`. The files come from
`ExtractedDocument.files` (what the resolver actually resolved), never
from byte equality, which would credit R761's 7 unpointed duplicates.
- **R761 WAS refused whole by guard 1.4.0 because of its 71 U+00AD soft
from byte equality, which would credit the reference standard's 7 unpointed duplicates.
- **A 701-page reference standard WAS refused whole by guard 1.4.0 because of its 71 U+00AD soft
hyphens, and since 2026-09-19 okf removes them itself.** Operator decision
2026-09-18, the chosen alternative verbatim: **one documented
normalisation door BEFORE the guard**, `extract.normalise_extracted`,
@ -1231,10 +1225,10 @@ and fixtures, never code.
for every consumer and a fix for one document. `output:zero-width-present`
is an ANY-TIER carrier, so the verdict was formally right at every trust
level and materially a false positive: the 71 are Norwegian hyphenation
points inside words (`ar[SHY]beider`, `bitu[SHY]men`), and the four real
points inside words (`ar[SHY]beider`, `sur[SHY]deig`), and the four real
zero-width characters (U+200B, U+200C, U+200D, U+FEFF) plus U+2060 are
**0 of 0** in both deliveries. Those four are NOT removed and U+00A0 NBSP
(6 633 in R761) is not either -- it is not in the guard's set at all.
(6 633 in the reference standard) is not either -- it is not in the guard's set at all.
Applied ONCE, at the end of `extract_document`, so `propose` and Door B
cannot disagree about the text a plan's `text_sha256` indexes; `_pdf_units`
measures its page offsets through the same door, because that table is
@ -1263,19 +1257,12 @@ and fixtures, never code.
move**: it belongs to the whole run, and a corpus holding one unreadable
file among many is ordinary -- so the order's other half, state it in the
accounting, is the one taken.
- **THE JSON ROLE MAP READS THE PUBLISHER'S OWN TAGS (H3, 2026-09-19).**
`count_sts_json` compared the RAW tag string where `count_sts_xml` has
always used `_local`, so N200 Vegbygging:2024's **74 `mml:math`** (2 811
characters) counted as 0; and the publisher's JSON writes a figure's
caption as `figcaption` under the `graphic`, not as the `fig/caption`
NISO-STS writes, so **49 `figcaption`** counted as 0. The `figcaption`
clause is in the JSON map ALONE -- the two maps are written twice on
purpose so row 5 can see a hole in either. NOT ONE OTHER COUNT MOVES,
measured role by role over the eight JSON deliveries on this machine and
the three committed twins: `figure_caption` 0 -> 123/127/62/49/43/27 and
`math` 0 -> 74/3/88, R761 and N601 unchanged, the two existing twins
unchanged. The known-negative for stripping a prefix is N101's **568
`tbx:`** tags, not one of whose local names is a role.
- **THE WITNESS READS NO PUBLISHER JSON (2026-09-23).** Its STS JSON
reader (`count_sts_json` and its role map, H3) existed for one
publisher's delivery and is removed with its tests and twins; a `.json`
file is counted as generic JSON, the way the package reads it. The XML
role map is now written once, so `W03`/`W07` in the mutation harness
edit its only copy and `W09` is gone.
- **The mutation harness is a GATE since 2026-09-19 (H4)**: a surviving
mutant exits 1, where `2 if errors else 0` made `killed 0 of 1` an exit 0.
PM's **X2** -- "a report may declare a document rejected while the bundle
@ -1284,18 +1271,18 @@ and fixtures, never code.
measures the DEFAULT sources and not the arguments (H5: row 6 is SKIPPED
exactly when the arguments' corpora are absent, so the branch was
unreachable), and row 6 now SAYS when a corpus measures no element class
at all (H6: on N200 the build proposes 0 plans and exits 2 before the
accounting door, so 16 549 elements land with no declared fate -- a
finding about the run that read as a finding about the build; what it
would take is an STS reader for the JSON delivery, and `standardContent`
occurs 0 times in `src/`).
at all (H6: a build that proposes 0 plans exits 2 before the accounting
door, so its elements land with no declared fate -- a finding about the
run that read as a finding about the build). Row 6 reads the corpora
given by `--corpus PATH` (repeatable: a directory, a zip or one file; no
default) and row 5 compares the PDF pair alone.
- **THE PUBLISHED DOOR CONTRACT NAMES WHAT THE GATE READS (2026-09-19).**
The JSON sketch at the top of `okf_accounting_gate.py` is what a consumer
implements `--accounting` from, and it did not name `conversions` -- which
`asset_holds`' conversion route DEPENDS on -- nor `normalised_soft_hyphen`,
`unaccounted` or `double_booked`. A door built from it writes a ledger the
gate reads as "nothing was converted", and every converted image comes out
claimed-and-not-found: **19 of 50** on R761. Two tests hold the sketch
claimed-and-not-found: **19 of 50** on the reference standard. Two tests hold the sketch
against both sides -- what the gate LOOKS UP (measured with a ledger that
records its own lookups, never by grep) and what the door SERIALISES -- so
"publish everything" is not a fix either.
@ -1349,12 +1336,11 @@ and fixtures, never code.
new fixture that delivers a passage (`DELIVERY`, set `set-passage.json`),
because no synthetic concept was long enough to be cut. `src/` untouched;
`MUTANT_BAR` unchanged.
**THE K2 TEST TRACK IS RETIRED (operator 2026-09-21, v1.1 K)**: material
tied to the operator's employer is not re-measured and not frozen, and
public tests and gates run on invented material. Row 9 (K2) and the `r761`
and `vegnormal` adapters of row 8 are gone (row 8 requires `wiki-20` alone),
and so are the tests that read the K2 bundles, the road-standard builds or
the R761/N101/N200 deliveries; `okf_accounting_gate.py` has no default real
**THE K2 TEST TRACK IS RETIRED (operator 2026-09-21, v1.1 K)**: it is not
re-measured and not frozen, and public tests and gates run on invented
material. Row 9 (K2) and the two retired-corpus adapters of row 8 are gone
(row 8 requires `wiki-20` alone), and so are the tests that read the K2
bundles or the retired reference corpus's builds and deliveries; `okf_accounting_gate.py` has no default real
corpus. The synthetic corpus was re-measured for BM25 (`MISS`, `LOOKUP`,
`QUOTA`), so rows 2 and 3 are green through their own fixtures again, and
row 7's mutants patch `bm25` where the default runs there; the three that
@ -1428,20 +1414,20 @@ and fixtures, never code.
survivors (document prior dead, `RRF_K` flattened) are reported with what
they measured: **0 ranks and 0 deliveries moved**, and a mechanism for each.
Rows 8 and 9 are never green by leaving something out: the three real sets
are read from path + sha256 (`--real wiki|r761|vegnormal`, one Question per
standard where a question cites two), and K2 is **0 of 6 because the gold
are read from path + sha256 (`--real wiki` plus two retired-corpus sets, one
Question per standard where a question cites two), and K2 is **0 of 6 because the gold
set exists nowhere** while the bundles do. Granularity is stated and the two
forms are NEVER summed: a citation-level set and a concept-level set are two
numbers. Nevnerne re-measured against the sources 2026-09-19 and they hold:
R761 **8** (S1-S6 + KP + KN), vegnormal **32** questions / **43**
`must_cite` / 32 `must_contain`, K2 **6**. The fourth set's own figures are
the first retired set **8** (S1-S6 + KP + KN), the second **32** questions /
**43** `must_cite` / 32 `must_contain`, K2 **6**. The fourth set's own figures are
a consumer's and are not restated here; the gate reads it by path and
sha256 like the others. PM's mixed sum over two sets at two granularities
is carried labelled as recorded, never as the gate's own.
- **Judge a bundle: `okf quality <bundle>` (G37, 2026-09-12).** A per-file-type
verdict, with the denominator on every line, and it is a SEPARATE command
from `okf check` on purpose: `check` is the contract check, and a green one
is not a quality gate -- measured 10.09 by `vegnormal-okf`, three arms over
is not a quality gate -- measured 10.09 by a downstream consumer repository, three arms over
one corpus all gave 0 findings and exit 0 while hit@k ranged 6 of 6 to 0 of
6. Three verdicts and no fourth (`PASS` / `FAIL` / `UNMEASURED`), a type with
no measured threshold is **never** `PASS`, and exit **3** exists for
@ -1465,7 +1451,7 @@ R761 **8** (S1-S6 + KP + KN), vegnormal **32** questions / **43**
corpus's**: a rejected document leaves no row at all (the pinned corpus holds
33 PDFs, the bundle shows 32), so the run log is printed beside the counts
and a bundle without one says so. Three of the four evidence corpora
(`n100`/`n200`/`n500`) carry `source_file` on **0 of 446 / 0 of 1 133 / 0 of
(three downstream bundles) carry `source_file` on **0 of 446 / 0 of 1 133 / 0 of
270** concepts, so they name no file type and every row is `UNMEASURED` --
the order expected them to PASS. Thresholds, the nine bundles and the
premises re-measured: `docs/2026-09-12-g37-terskler.md`. README publishes the
@ -1479,16 +1465,16 @@ R761 **8** (S1-S6 + KP + KN), vegnormal **32** questions / **43**
**The normalisation was derived before the metric was built**, not guessed:
strip all whitespace, lowercase, reproduces the fasit's own `norm` from its
own `title` on **2 761 of 2 761** rows (alphanumerics-only scores 58 -- it
eats the `.` in `2.1Hovedprosesser`). **A boundary is recovered in EITHER of
eats the `.` in `2.1Hovedkapitler`). **A boundary is recovered in EITHER of
two forms and neither is a fallback**: the concept's normalised title equal to
`norm`, or the pair `(concept's own directory, residual title)` -- the literal
form wants the declared title WITH its numbering token, the pair form WITHOUT,
and no bundle can offer both, because okf's default route moves that token
into the concept id. Measured on the known-good arm: literal **22 of 2 761**,
paired **2 737**, either **2 759 (99.9 %)**; on `r761-2025-d1` the split is
paired **2 737**, either **2 759 (99.9 %)**; on an earlier build of the same document the split is
exactly inverted (2 727 literal, 0 paired), so a gate scoring one form alone
reports a 99.9 % arm as 0.8 % and calls it a segmentation defect. The two
forms are `vegnormal-okf`'s M8 correction, which they took verbatim from THIS
forms are a downstream consumer repository's M8 correction, which it took verbatim from THIS
repository's round-14 report -- the instrument reproduces both so the two
repos cannot silently measure different things. One bar, at the pinned
artifact's own value: **2 759/2 761 with `corpora = 1`**, and P2 is in the
@ -1496,12 +1482,12 @@ R761 **8** (S1-S6 + KP + KN), vegnormal **32** questions / **43**
The known-bad arm is **1 148 of 2 761 (41.6 %)**, now `FAIL` + exit 1 where
the bundle-only gate gave exit 3. **`--fasit` is an ASSERTION** (the posture
`okf consume --ref` has) that this bundle is a build of the document the fasit
describes: the K2 reference and `n100-2023` both score **0 of 2 761** and read
describes: the K2 reference and a downstream bundle of another document both score **0 of 2 761** and read
`FAIL`, which is the assertion being wrong and not the bundle -- a gate
telling those apart would need a bar read off the bundles it judges. The bar
is TIGHT and the cost is published: 2 of 4 R761 builds fall under it (2 752
is TIGHT and the cost is published: 2 of 4 builds of that document fall under it (2 752
and 2 727 of 2 761), while **any bar between 41.6 % and 98.8 % separates the
known-bad arm from every R761 build measured** -- the shipped one is the only
known-bad arm from every build of that document measured** -- the shipped one is the only
point in that interval read off a pinned artifact. An unreadable fasit exits
**2** with its reason, never a quiet `UNMEASURED`, and a fasit under five rows
is `UNMEASURED` (`MIN_DECLARED_FOR_A_THRESHOLD`, the document floor in the
@ -1785,7 +1771,7 @@ R761 **8** (S1-S6 + KP + KN), vegnormal **32** questions / **43**
threshold on length or coverage separates them. The fourth candidate does:
the shared prefix must be a WORD the bundle uses. `bilateral` 400 -> 0 and
512 -> 0, `standhaftig` 219 -> 56 and 235 -> 33, every hit@8 row keeping rank
1 on BOTH bundles. `undersjoisk` stops at **162** because `under` IS a word
1 on BOTH bundles. `under…` stops at **162** because `under` IS a word
here -- a genuine Norwegian morpheme, so that residual is a different answer,
never a ceiling. The vocabulary is the BUNDLE's own, so the rule makes a
payload corpus-dependent the way `rarity_weights` already is.
@ -1839,11 +1825,11 @@ R761 **8** (S1-S6 + KP + KN), vegnormal **32** questions / **43**
TIE-BREAK
rather than a weight: RRF ranks every concept in every signal, including a
signal that scored them all the same, and the declared `(-score, concept_id)`
tie-break then orders that group by id. Measured on N500, whose document
tie-break then orders that group by id. Measured on a downstream bundle, whose document
prior has **two** distinct values over 270 concepts, that signal contributed
alphabetical UUID order and put a concept answering 7 of 7 question tokens at
fused rank 14 — outside the cut — behind concepts sharing only `tunnel` and
`vann`. Under shared ranks it is rank 3 and 2 of the 16 covering concepts are
fused rank 14 — outside the cut — behind concepts sharing only two common
words. Under shared ranks it is rank 3 and 2 of the 16 covering concepts are
delivered. **It shipped OFF on a measurement that was CONDITIONAL and stopped
being true in a commit reported as changing nothing.** The published cost —
hit@8 falling 5 of 6 to 4 of 6 — is real only at `DOCUMENT_PRIOR_EXPONENT`