llm-ingestion-okf/docs/2026-09-11-k3-runde21-parent-til-leseren.md
Kjell Tore Guttormsen e98ca6d25f docs(k3): round 21, parent all the way to the reader
The record for K3-21 A-D: what an excerpt, a heading-only body, the checker
and the index now carry, and every gate with its denominator -- the pinned K2
bundle (7 passed in each export), 32 regression payloads, R761 with and
without `--shell-parent` (hit@1/8/50 6/6 at both k, the known-positive at
rank 1, 675 of 710 shells with exactly one link and 0 broken, 710 of 710
pointing at the source's own ancestor after D), consumption 6 of 6 in both
forms, the byte cost against the consumer's 51.0 %, the ranking cost the
link's path carries, and the 35 shells with no ancestor holding text, listed.
Deviations from the order stated first; honesty limits last.

CHANGELOG under Unreleased; README and CLAUDE.md say 710 of 710, 17 rules,
and that the outline mark keeps its depth. The README's project measurement
was re-run (26 concepts, 0 checkout paths, `okf check` 17 rules, 0 findings).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 14:28:42 +02:00

20 KiB

K3 round 21: parent all the way to the reader

Date: 2026-09-11 · Base: e717b1c · Commits: a5cd7c5 (red), 4f7bd61 (A), ca83dad (red), 839bd61 (B), 46e555d (B, defaults), c188a0c (red), 5970369 (C), 9822e55 (red), bd13c5f (D) · Measured from: frozen git archive exports of the base and of 4f7bd61, 839bd61, 5970369 and bd13c5f (Python 3.11.14, each __file__ under a scratch directory), never the working tree and never the okf on PATH.

Round 20 gave a section whose body is its heading alone a parent: key naming the nearest ancestor that holds text (okf build --shell-parent) -- 675 of 710 such sections in one 2 761-concept standard -- and no reader could see it: okf consume did not read the key, the payload did not carry it, the body held no link, and the index rendered it unresolved. Four deliveries, in order, each with its own red test and its own commit: A the pointer reaches the excerpt and the body; B the ancestor's text reaches the reader, in two forms, and both defaults are chosen on numbers; C the index resolves the pointer; D the depth-7 trap.

0. Where the measurement differs from the order, first

  • HEAD was e717b1c, not 4595dde, and the suite baseline 1 753 passed, 1 skipped, rc 0, not 1 667 / 1: K3-22 landed after dispatch. The shell rule (--shell-parent, propose._link_shells) is in the tree.
  • The order's R761 build command exits 2 on e717b1c. K3-22 refuses a --frontmatter sources= flow value whose URL carries ?. Every R761 bundle here is built with the same command minus that flag (utgave kept), so sources falls back to the document's own layer. The base row still reproduces round 20's numbers exactly: hit@1/8/50 6/6 at both k, the known-positive (KP) at rank 1 at both, delivered per question 7 8 7 8 8 8 7 7 at the default k.
  • The known-positive moved in every payload. SS 7.4's known-positive IS docs/consumption-contract.md, and A and B edit its SS 8, so budget.known_positive moves in every payload with no bundle changing (13 238 / 12 893 / 345 -> 14 455 / 14 083 / 372 -> 14 721 / 14 346 / 375). Every "byte-identical" row below after A and after B is measured with that block removed, and the whole-payload count is given beside it.
  • The hit@k instrument moved. The consumer repository stands on f783e52, not 4f70e9d; experiments/hitk_sk2.py was last changed in 6fad6a2, as stated. The scratch copy differs from it in two lines (the payload path, because a concurrent session writes the hard-coded one), and the form (b) copy in one more (--follow-parent on the okf consume call). The consumer repository's git status --porcelain is empty before and after.
  • The generated skill tells a reader to write /tmp/payload.json, a path a concurrent session shares. Each consumption run used a scratch copy of the skill whose only change is that path (and, for form (b), the flag line).
  • The link's first design failed on the fixture before it shipped. Read as body text, Enclosing section: [...](/...) is a bundle-local link target, and structure derivation turned it into a SECOND relation -- references: [/...md?] with derived: [references], rendered unresolved because nothing resolves the absolute form. The link is therefore appended after derivation and screened on its own, the rule description already follows.
  • One red test met a mechanism instead of the rule. B's "no room" case asked through build_payload at limit == spent, where the knapsack's 500-byte buckets admit nothing (budget_admits_nothing). The green commit holds the rule at attach_parent_text and says so.

1. The spec, verbatim, and which paragraph each half leans on

Canonical SPEC.md at ad30107:

  • SS 4.1, Extensions: "Producers MAY include any additional keys. Consumers SHOULD preserve unknown keys when round-tripping and MUST NOT reject documents with unrecognized fields." -- the ground for the parent: KEY and for the payload member.
  • SS 5.1: "Lineage is expressed through links, not a dedicated field." -- the ground for the LINK: the key alone is not the spec's form of lineage.
  • SS 6.1: "Absolute (bundle-relative): begins with /, interpreted relative to the bundle root. This is the recommended form because it is stable when documents are moved within their subdirectory." And: "The specific kind (parent/child, references, joins-with, depends-on) is conveyed by the surrounding prose, not by the link itself." And: "Consumers MUST tolerate broken links: a link whose target does not exist in the bundle is not malformed; it may simply represent not-yet-written knowledge." -- the link's form, its two words of prose, and why an unresolved pointer is named in the payload rather than refused.

Contract SS 8, first paragraph, changed: "Additional members are permitted and are not read by the checker." now reads "Additional members are permitted; the checker reads only the members this section names." -- because A's rule reads parent, which point 6 now names.

2. A: the pointer reaches the excerpt and the body

The payload. consume.link_parents resolves parent among the concepts sharing the pointing concept's source_file: a segment id is unique only inside one document's plan, so p1 exists in every document of a multi-document bundle. One pass over concepts already read, no file opened again. An excerpt carries parent: { concept_id, title } -- never the raw segment_id, which a reader can open nothing with -- and parent_unresolved: true where the pointer lands nowhere. The member is conditional, like req_number.

What the excerpt carries was chosen on this: the raw id costs least and can be followed only by enumerating the bundle, which SS 9.2 forbids; the concept id is one Read; the title is what a citation is made of. Measured cost of the chosen member: 5 members, about 528 B, over the 60 excerpts of the eight default-k payloads; 34 members, about 3 927 B, over 353 at k 50.

The body. A heading-only body whose plan entry has a parent gains ONE line: Enclosing section: [<title>](/<bundle-relative path>). Only such a body -- the segmented goldens' declared parents are bodies holding text, and stay byte-identical. The two words are English and generic, because the code cannot know the source's language; the relation they name is true of an adjudicator's parent and of a shell's nearest ancestor holding text alike.

The checker. Seventeenth rule, parent_unfollowable: a parent that is not a concept_id and a title, names its own excerpt, or names a concept in neither excerpts nor withheld. Those two lists together name every concept the pre-pass considered (SS 5.2), so the rule sees a dangling pointer without opening the bundle. parent_unresolved is not a finding (SS 6.1).

row result denominator
K2 pin in the export, gold set copied in 7 passed 7
K2 default + K2 Arm B + N100/N200/N500 + five-document bundle 32 of 32 identical without known_positive; 0 of 32 whole 12 + 15 + 5
K2 ranks, both bundles (1,1,1,1,1,5) unchanged; parent members 0 6 · 6
five-document bundle, known-negative row rank 1 unchanged ([1,1,1,1,2]) 5
R761 without the flag 16 of 16 payloads identical without known_positive; bundle diff -r only log.md (the converter's path) 16 · 2 761
R761 with the flag, hit@1 · 8 · 50 6/6 · 6/6 · 6/6 at both k, KP rank 1 / 1 6
shells carrying exactly one link 675 of 710, 0 with more, 0 broken, 675 of 675 targets = the parent 710
files differing from the flagged base bundle 676 = 675 concepts + log.md; 0 index files 5 518
okf check on the flagged payloads 16 of 16 rc 0, 17 rules, 0 findings 16
known-negatives a dangling concept_id and the raw "p1977": 1 finding each, rc 1 2
link bytes 72 265 B = 4.49 % of all body bytes (D2's inheritance section: 51.0 %); 69.2 % of the 675 linked bodies; median line 103 B, max 247 B 1 610 590 B
shells, PM script / concepts with no source text of their own 35 / 710 2 761

The honest shell count is 710. The PM script counts non-empty lines not starting with #, and a link line is one; 675 of the 710 now carry a line and still no text of their own.

What the link costs in ranking, measured. hit@k does not move. The delivered SET does: at the default k S2-S6 and KP are identical, while S1 exchanges 3 of 7 places and KN 2 of 7, every newcomer a linked shell; at k 50 three of eight questions move (KP 23 in / 19 out). Of the newly delivered shells, those matching the question ONLY through the link line are 1 of 3 (S1), 4 of 7 (S1 at k 50), 6 of 23 (KP at k 50) and 0 of 2 / 0 of 8 (KN), and in every such case through the PATH, never the title: the bundle-absolute form repeats the document's directory in 675 bodies -- the saturation round 20 took out of the id signal, back in through the body. The rest of the movement is not decomposed per signal.

3. B: the ancestor's text reaches the reader

Form (a), the pointer, is A. Form (b), okf consume --follow-parent, puts the enclosing concept's text inside parent, with that concept's own sha256, AFTER the cut and only from the room the cut left, in rank order. A parent the payload already holds, or one a higher-ranked excerpt carried, travels once; a text that does not fit is cut to the longest prefix that does and marked truncated. Inherited text therefore cannot displace an excerpt -- the mechanism a consumer measured when copied-in ancestor text pushed the right section to withheld place 504 and 1 069.

row (a) pointer (b) --follow-parent denominator
hit@1 · 8 · 50, default k 6/6 · 6/6 · 6/6 6/6 · 6/6 · 6/6 6
hit@1 · 8 · 50, k 50 6/6 · 6/6 · 6/6 6/6 · 6/6 · 6/6 6
KP rank, both k 1 / 1 1 / 1 1
delivered list and withheld list equal to (a) -- 16 of 16 16
consumption correct 6 of 6 6 of 6 6
the fasit section cited first 4 of 6 5 of 6 6
spent, default k, where it differs S1 28 020, KN 10 514 S1 30 508, KN 13 260 (the other six equal) 8
spent, k 50, largest difference KP 96 965 KP 117 782 (12 texts, 0 truncated) 8
okf check 16 of 16 rc 0 16 of 16 rc 0, 17 rules 16

The row that is the point: one heading-only section as a reader gets it. Question: a shell's own title (33.212). Top-1 is its sibling 33.211, whose title differs only in a token too short to score; both are shells under 33.2 "Sikringsbolter". Eight excerpts delivered in each form.

before A (a) (b)
the excerpt's text its heading line its heading + Enclosing section: [Sikringsbolter](/.../33-2/sikringsbolter.md) same
parent absent concept_id, title + sha256, text (3 847 characters, not truncated)
excerpt bytes 858 1 034 5 067
payload spent 6 864 8 272 12 305
what the reader must do to learn what the section inherits nothing it can do: no pointer one Read of the named concept (SS 2.2: the payload names it reachable) nothing: it is in the payload

The consumption difference is not the form. S1-S6's answers are never heading-only sections. The first-cited difference (S4) sits on a payload that carries no parent at all and is identical in both forms apart from the known-positive block, so it is the draw, not the form. S1's form (a) answer used the pointer on its own: it named a delivered shell's enclosing section by its parent.

Defaults, one sentence each:

  • --follow-parent: off. It delivers the same set as the pointer by construction and measured, and on six questions whose answers are never shells it bought nothing a reader scored, at +2 488 and +2 746 B on the 2 of 8 default-k payloads holding a linked shell and up to +20 817 B at k 50.
  • --shell-parent: off, with the reason rewritten. "okf consume reads no parent key" is false after A; the measured reason is the path saturation above -- hit@k intact, the delivered set moved on 2 of 8 questions at the default k and 3 of 8 at k 50. It moves the bytes of a bundle holding a heading-only section and nothing else: 0 of 453 K2 bodies are heading-only and the five-document folder builds byte-identical with it.

4. C: the index resolves the pointer

structure._segment_lookup keys (source_file, segment_id) off each concept's own frontmatter (DocumentStructure.declared, no file read again) and resolve_structure asks it first for a parent edge, inside the pointing concept's document. A value no segment answers to is a document number and is looked up as before; a pointer naming nothing keeps ?. The rendering rule is untouched -- a resolved relation renders as its subject -- so parent: p1977? becomes parent: p1977. consume's document prior reads only an index entry's label, never its facets, so C moves no rank.

row before after denominator
parent facets resolved, R761 with the flag 0 675 675
parent facets with ? 675 0 (? anywhere: 792 -> 117, the unflagged bundle's own count) 675
concepts whose derived: names parent 0 0 (the regex finds a fabricated positive) 2 761
files differing from A's flagged build -- 676: index files and log.md, 0 concepts 5 518
R761 without the flag, five-document folder -- only log.md differs 2
segmented goldens parent: s0? parent: s0 -- 4 lines; the four goldens test_the_four_existing_goldens_are_untouched guards are not among them 4
hit@k, both k; KP 6/6; 1 6/6; 1, delivered and withheld lists identical to A's, 16 of 16 16
regression payloads -- 32 of 32 wholly identical to B's 32
okf check -- 16 of 16 rc 0, 17 rules 16

5. D: the depth-7 trap

_StsReader._walk wrote one clipped level into the ATX heading and the OutlineMark alike. The heading keeps the clip; the mark carries the declared depth. The extracted text does not move one character.

The gate, every row measured:

row result denominator
mark levels 1-7 28 / 118 / 500 / 1 141 / 868 / 97 / 9 (before: 106 at 6, 0 at 7) 2 761
extracted text identical (text_sha256 equal) 1
plan without the flag 0 entries changed 2 761
plan with the flag 2 entries changed: p646 and p647 (36.73211, 36.73212), parent_id p644 (36.732) -> p645 (36.7321) 2 761
shells pointing at the ancestor the <sec> nesting names 710 of 710 (708 before); direct parent 533 (531) 710
concept count 2 761 --
bundle with the flag vs C the 2 concepts, their 2 index files, log.md 5 518
bundle without the flag, five-document folder only log.md 2
the four goldens untouched (the suite's own test, repository root) 4
regression payloads 32 of 32 wholly identical to C's 32
hit@k, both k; KP 6/6; 1, lists identical to C's, 16 of 16 16

_unit_fold's unit level, the risk the order named, is not reached: the declared route closes its candidates with unit_fold=False, and the plan confirms it with 0 changes outside the two entries.

6. The 35 shells with no ancestor holding text

No pointer was invented for them. 13 sit at depth 1 with no ancestor at all; 22 have ancestors, every one heading-only too. 28 are containers with titled descendants; 7 are leaves. None has a direct lettered point.

segment label depth titled descendants
p3 2 1 4
p9 4 1 8
p11 4.2 2 3
p21 8 1 7
p28 8.7 2 0
p32 12 1 0
p33 Hovedprosess 1 1 97
p34 11 2 11
p42 11.5 3 3
p46 12 2 28
p129 17 2 0
p130 18 2 0
p131 Hovedprosess 2 1 191
p132 21 2 25
p170 23 2 46
p217 24 2 56
p225 24.4 3 6
p228 24.43 4 3
p249 24.6 3 9
p306 27 2 16
p323 Hovedprosess 3 1 331
p653 Prosess 37 2 0
p654 Prosess 38 2 0
p655 Hovedprosess 4 1 218
p874 Hovedprosess 5 1 97
p972 Hovedprosess 6 1 94
p975 62 2 0
p1033 66 2 14
p1067 Hovedprosess 7 1 264
p1108 72 2 19
p1186 75 2 60
p1238 75.6 3 7
p1275 77 2 56
p2108 Hovedprosess 86 1 44
p2153 Hovedprosess 87 1 229

What they are in the source: the front-matter chapters 2, 4, 8 and 12 and the main processes 1-7, 86 and 87 are containers whose text lives below them, not above; 17, 18, 37, 38 and 62 are placeholder processes reserved for operation and maintenance; 8.7 is a worked-example section; the first 12 is the chapter that introduces the processes. A rule pointing at an ancestor WITHOUT text would reach at most 22 of the 35, and only with a pointer to another heading. It is not built.

7. Suites and gates

when working tree frozen export
base 1 753 passed, 1 skipped --
A red, after add and after commit 11 failed (all new), 1 754 passed, 1 skipped --
A 1 765 passed, 1 skipped 1 764 passed, 1 skipped, 1 failed
B red 7 failed (all new), 1 766 passed --
B / defaults 1 773 / 1 774 passed, 1 skipped 1 772 passed, 1 failed
C red / C 3 failed (new), 1 776 / 1 779 passed 1 778 passed, 1 failed
D red / D 2 failed (new), 1 780 / 1 782 passed 1 781 passed, 1 failed

The one export failure is the known artefact test_segmented_golden.py::test_the_four_existing_goldens_are_untouched (an export is no git repository). ruff check src tests tools, ruff format --check . and mypy --strict src were clean at every green commit, rc captured directly. The first red commit (a5cd7c5) carried a test file ruff format rewrote in the next one; no other file moved that way. uv.lock was never staged.

Honesty limits

  • Everything about shells is measured on ONE document (N = 1). "Every outline document" is a claim about the code, which reads the plan's level and order; the fixtures and the five-document folder (0 shells of 26) are the only other rows.
  • Consumption is one non-deterministic draw per question, twelve runs on a subscription (ANTHROPIC_API_KEY unset), judged by reading the answer against the fasit section. Answer texts are not reproduced. The six questions never have a shell as their answer, so form (b)'s value shows only in the probe row, and that probe is one question chosen here, not a fasit.
  • "Byte-identical" after A and B excludes budget.known_positive. The contract document is the known-positive, so whole-payload identity is 0 of 32 across A and B and 32 of 32 across B, C and D.
  • The K2 statement for --shell-parent is read off the pinned bundle (0 of 453 bodies heading-only), not off a rebuild with the flag.
  • The link's ranking cost is attributed only where a newcomer matches through the link alone; the rest of the delivered-set movement, and any effect of the heavier excerpts on the knapsack, is not decomposed.
  • The instrument is someone else's, with two lines changed (three for form (b)), and it scores a title or a section-number pair, not an answer.
  • C's "resolved" says a concept of the same document answers the id, not that it is the right ancestor; D's 710 of 710 is what checks that, against the source's own nesting.
  • Conformance is the floor and never the proof. okf check is green on all 48 flagged payloads here, including the known-negative question's, which answers nothing.