llm-ingestion-okf/docs/plan/references-cue-rule.md
Kjell Tore Guttormsen 9d1f4b14ed 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>
2026-09-23 14:52:02 +02:00

5.7 KiB

What a derived references subject is

Record of the decision taken 2026-08-29 on order …507632913, and of the measurement it rests on. Supersedes the reference half of docs/plan/structure-derivation.md; nothing else in that document moves.

The defect

STRUCTURED_V1 derived references by taking every number-shaped token in a document body. A consumer measured it on their own corpus and reported 12 false references out of 12, ten of them version numbers carried in titles and read as document numbers.

The order was not to inherit that figure. It was measured again here, on two corpora this repository can reach, before anything was changed.

Measured before the change

Both figures come from derive_document_structure itself, run over every unique document (deduplicated by sha256) and counting the subjects it returns with references marked derived.

corpus documents documents with derived references subjects
A — a consumer's normative bundle set, six bundles, Norwegian 2 561 1 010 2 838
B — this repository's own docs/, English and Norwegian technical prose 28 27 559

What those subjects were, classified against the position the scanner actually lifted each one from:

  • Corpus A. 702 of 2 838 (24.7 %) were hexadecimal fragments of UUIDs read out of id="…" attribute values inside embedded HTML and MathML. The rest of the false population was unit symbols (kN/m2 reduced to M2), table row and class labels, and formula numbers.
  • Corpus B. Dominated by software versions (v0.3.0, 3.14.0, 0.11.10, 1.28.2), a licence identifier (AGPL-3.03.0), a container format version (%PDF-1.61.6), a hash algorithm name (SHA256), and tokens lifted out of two-character escape sequences inside quoted source (\n60N60).

The consumer's reported class reproduces. Two classes they did not report — identifier fragments inside markup, and escape-sequence artefacts — are larger.

The decision

A derived reference subject is one of exactly two things: a bundle-local markdown link target, or a number immediately preceded by a reference cue from a closed set. Everything else number-shaped in body prose is not a reference.

The rule is positive rather than a blacklist, and that is forced by the data, not chosen for taste: corpus A proves W221 and W240 are genuine document numbers, corpus B proves V0.3.0 is a software version, and the two are the same token shape. Nothing structural separates them. Only the words in front of them do, so only a positive cue can be defended.

The cue must match at a word boundary. Without that guard a Norwegian compound ending in -klasse satisfies the cue se, which admitted 86 class designations on corpus A. The window is normalised to NFC first, so a cue carrying a Norwegian letter matches whether the file arrived composed or decomposed.

Two link targets are also excluded, on the same ground rather than a new one: a fragment-only target (#anchor) points inside the document it appears in, and a target carrying a brace is a template placeholder from prose about links. Neither can name a concept — reduce_to_id_grammar emits lowercase alphanumerics and hyphens — so neither is a pointer awaiting resolution. It is a claim that cannot come true.

Measured after the change

Same script, same corpora, HEAD against the working tree.

corpus subjects before subjects after dropped
A 2 838 1 279 1 559 (54.9 %)
B 559 72 487 (87.1 %)

Precision was checked by hand against the occurrence that actually passed the gate, not the subject's first occurrence in the file — reading the first one mis-attributes context and was a real error in an earlier pass of this measurement.

  • Corpus A: 30 subjects drawn at random from 1 279. 30 of 30 genuine.
  • Corpus B: all 60 cue-gated subjects. 60 of 60 genuine.

Residual known false positives: 9 of corpus B's 72, all in the link channel, all illustrative targets in prose about link syntax (link, target, /sti, subdir/). They are indistinguishable from real relative paths without guessing at path semantics, so they stay. Corpus A's link channel is empty, so this residue is not visible there at all.

The cost, stated rather than hidden

A corpus whose cross-references are phrased outside this vocabulary — a third language, a house style — derives nothing, and must declare references itself. That is the intended failure: a missing reference is visible to the reader, a false one is not.

Two recall losses inside the corpora measured, both accepted: a reference whose cue is a preposition (i henhold til … where the noun is absent) and one written as a prefixed compound (Q-W240) are dropped.

What was deliberately not done

The prefix-resolution rule (Q130 in a body not finding a document numbered Q130.2) stays open. The order allowed it in the same pass only if the data bore it. It does not: of 2 589 documents across both corpora, 2 562 carry a number and 0 of those numbers are dotted, so a unique-prefix match has nothing here to be defended against. Closing two things badly is worse than closing one well.

Adjacent defect found, not fixed — needs its own order

The line-oriented frontmatter parser lets a nested key overwrite a top-level key of the same name. A sources: block whose entry carries its own title: replaces the document's title, and the number derived from that title follows it. This is the same family as the known block-list finding but strictly worse: that one drops a value silently, this one substitutes one. It changes title, number and parent, so it is outside this order's scope, and it is why corpus A's numbers above are what they are.