docs(consume): the Claude Code recipe, measured end to end on two bundles

Four questions, two bundles, one run each, in a scratch project outside this
repository with a generated skill per bundle. All four passed, and zero numbers
or identifiers appeared in any answer that were not in the delivered set or in
the payload's own identities (62, 45 and 35 unique numeric tokens checked).

The skill triggered WITHOUT being named in the prompt and selected the right one
of two installed skills from the question alone, so no special invocation syntax
is needed: the generated `description`, which carries the bundle id, the concept
count and the ref, is enough to route on.

One defect the runs found, and it was in the prose rather than the payload. The
citation guidance listed the four locator keys this library writes, so on the
270-concept third-party bundle the model reported "no page locator, the address
is at document level" while the excerpt in front of it carried
`source_element_id` - that bundle's own locator, correctly delivered by the
prefix rule. The guidance now tells the reader to cite whichever `source_*` keys
are present. On the re-run the same question returned the element id. Two runs
of one question, the second measuring a changed artefact and not retrying the
first.

One finding that is not a defect in this chain: the first attempt at a
known-negative was not one. The bundle covers water and frost protection on 17
of its 270 concepts and the ranker put none of them in the cut. The consumer
behaved exactly as the contract asks - refused, named its denominator, reported
its own zero as unmeasured because `withheld` entries carry no titles, and did
not go around the cut. Recorded as a retrieval miss rather than replaced, and
it is the same shape as the open fusion finding.

A correction to this session's own measurement is in the record too: a first
sweep used `grep -rhoE "^source_[a-z_]+:"`, whose character class excludes
digits, and so missed `source_sha256` on 270 of 270 concepts. A pattern that
cannot match what it is looking for returns a zero that reads like a fact.

README gains "Consume in Claude Code": folder to answer in three commands, every
one of them run in this session. A test holds that the recipe invokes only
scripts this repository ships, at the paths it names.

Suite 1373 (1339 at the session baseline), ruff clean, mypy src clean. No
version bump, no tag, no push.

Co-Authored-By: Claude <claude-opus-5>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-08 15:32:32 +02:00
commit 171798ed32
5 changed files with 477 additions and 15 deletions

View file

@ -191,6 +191,67 @@ hit@8 was **5 of 6** questions at rank 1 against a chance baseline of **1.35 of
6** — with one control that failed, and both are in
`docs/2026-09-07-okf-konsumskill-maaling.md` with the honesty limits stated.
## Consume in Claude Code
A folder of documents to an answer a model can cite, in three commands. Every
command below was run end to end on 2026-09-08 against a nine-document folder
and a 270-concept third-party bundle; nothing here is untested.
```sh
SRC=/tmp/c1-fresh-src # the folder of documents
BUNDLE=/tmp/c1-fresh-bundle # where the OKF bundle goes
PROJECT=/tmp/c1-scratch # the project you will ask the question from
```
**1. Build the bundle.**
```sh
okf build "$SRC" --bundle "$BUNDLE" --bundle-id c1-fresh-20260908 --okf-version 0.2 --ingested-at 2026-09-08T00:00:00Z
```
**2. Generate a skill for that bundle**, straight into the project's skill
directory. The skill is instantiated for these bytes: its id, ref, concept
count, per-field denominators, whole-bundle cost and breaking point are all
measured from the bundle, and it ships a reference payload the checker accepts.
```sh
python3 tools/okf_skill.py "$BUNDLE" --out "$PROJECT/.claude/skills/c1-fresh-20260908-consume"
```
Repeat for every bundle you want reachable; each one gets its own skill named
after its `bundle_id`, which is what lets a model pick between them. A bundle
you only have read access to is fine — the generator only reads it.
**3. Ask.** From `$PROJECT`, in Claude Code:
```sh
claude -p "Hvordan skal prisene fylles ut?"
```
Measured with two bundles installed side by side: the model selected the right
skill from the question alone, ran the pre-pass and the contract check itself,
quoted the requirement verbatim, and named the document, the requirement number,
the source resource and the locator inside it. Across three questions, **0**
numbers or identifiers appeared in an answer that were not in the delivered set
or in the payload's own identities. On a question the bundle does not cover it
answered `[sourced-not-sufficient]` and reported the denominator rather than
inventing an answer.
The pre-pass and the checker are the same two commands the skill runs for you,
if you want to see the payload first:
```sh
python3 tools/okf_consume.py "$BUNDLE" --question "your question" --out /tmp/payload.json
python3 tools/okf_contract_check.py --skill "$PROJECT/.claude/skills/c1-fresh-20260908-consume/SKILL.md" --payload /tmp/payload.json
```
The honest limits: this was measured on **four questions** across two bundles,
which is a demonstration and not a hit rate. The ranking is lexical, and one of
the four found a topic the bundle **does** cover and did not rank it into the
cut — the skill then said so with its denominator instead of answering, which is
the behaviour the contract asks for, but a miss is still a miss.
`docs/2026-09-08-claude-code-skill-vilkaarlig-bundle.md` has the runs.
## Implemented scope (v1)
The library provides three entry points for getting content into an OKF

View file

@ -0,0 +1,372 @@
# Connecting an arbitrary OKF bundle to Claude Code
**Date:** 2026-09-08. **Order:** `20260908T124346Z-4668330384` (C1).
**Commits:** `17c49fc` (step 0), `c95d189` (the prefix rule and the generator),
and the commit this document lands in.
**Baseline:** `b6a8c8b`, suite 1339.
The question this answers is the operator's, in their own words: *"That Claude
Code should be able to work with an arbitrary OKF bundle that we give Claude
information about is extremely important."* Three things had to hold — build a
bundle from a folder, connect it to Claude Code, and ask it a question whose
answer is a document or part of one. The first existed. The second existed only
as one skill hand-instantiated for one corpus plus a template full of holes,
installed nowhere, never run inside Claude Code. The third was therefore not
testable at all.
---
## 0. What was measured and what was not
| | measured | not measured |
|---|---|---|
| the excerpt fields | yes, on three bundles and two fixtures | — |
| the `source_*` pass-through rule | yes, on the bundle that broke the allowlist | whether a fourth producer uses a key not starting with `source_` |
| the form choice (generator vs generic skill) | yes, including a control that came out against the obvious gate | operator preference; only the mechanics are here |
| the generator's refusals | yes, four of them, each with its code | — |
| end to end in Claude Code | **four questions, two bundles, one run each** | a hit rate. Four questions is a demonstration |
| the ranking | unchanged, byte-for-byte, on the K2 control | — |
| cost per answer in tokens | — | no tokenizer on this machine; costs are in `BUDGET_UNIT` bytes |
---
## 1. Reproduction, first
The known-positive, before anything was changed:
```
python3 tools/okf_contract_check.py --skill skills/okf-consume/SKILL.md \
--payload <a K2 payload>
# conformant: 15 rules over 8 excerpts and 621 withheld entries, 0 findings
# exit 0
```
Then a fresh bundle, built the way an operator would build one: nine documents
(two spreadsheets, three word-processor files, four PDFs) copied out of the
corpus tree into a scratch folder.
```
okf build /tmp/c1-fresh-src --bundle /tmp/c1-fresh-bundle \
--bundle-id c1-fresh-20260908 --okf-version 0.2 --ingested-at 2026-09-08T00:00:00Z
# merged + coded rejections = 9; N = 9 (substantive 9/9, rejected 0/9)
# 5.15 s, 0.572 s per file
```
54 concepts, ref `sha256-tree:7245851d…9685f71`. The index walk reaches 54; a
directory walk finds 55, and the one extra is `log.md`, which the walk excludes
by name. 54 = 54, controlled.
One question with a known gold concept:
```
python3 tools/okf_consume.py /tmp/c1-fresh-bundle \
--question "Hvordan skal prisene fylles ut?" --out /tmp/c1-fresh-payload.json
```
`considered 54 = withheld 46 + delivered 8`, `spent` 82 030 of 120 000, and the
gold concept — the priced spreadsheet — at **rank 1**. That is the position an
operator starts from.
---
## 2. Step 0: the excerpt carries the key the question is asked ON
Two independent measurements pointed at the same missing field.
`portfolio-optimiser` ran three paid arms: the gold concept was delivered at
**rank 1 of 8 on 3 of 3 bundles**, and the model answered correctly on **1 of
3**, because a delivered excerpt carried `concept_id`, body text and nothing the
document is known by. The previous session measured it from the other side: the
provenance it had just written into every concept did not reach the payload at
all — nine members, not one of them provenance.
`excerpt_for` now carries `title` unconditionally, and `req_number`, the SPEC
§ 5.1 address `sources`, and the locator keys when the producer wrote them. Four
design decisions here were made by measurement rather than by taste.
**(a) `sources` is read in both YAML forms.** The two real bundles disagree
completely:
| | flow `sources: [{...}]` | block `sources:` + indented | a locator key |
|---|---|---|---|
| the 629-concept procurement bundle | **629 of 629** | 0 | 618 `source_offset`, 586 `source_pages` |
| the 270-concept normative bundle | 0 | **270 of 270** | 0 of the five this library writes |
`parse_frontmatter` skips indented lines on purpose — a nested `title:` arriving
later would substitute for the document's — so the block form arrives as `''`,
present and unreadable. A flow-only reader delivers the second bundle with **no
address at all**. Reading the block form is not a licence to write it; the
emission rule is untouched.
**(b) An undecodable address is named, not dropped.** `sources_unreadable: true`
is a third state beside "an address" and "no address". Collapsing it into either
reports something nobody measured.
**(c) An absent key stays absent.** Writing `req_number: ""` would assert that
the producer wrote an empty identifier. Contract § 6.4 forbids exactly that
inference, from the other direction.
**(d) The pass-through is a PREFIX, and this one came from a peer's
measurement.** The first implementation used the five locator keys this library
writes. The PM measured it against the normative bundle and found the excerpt
still lost that bundle's locator: 269 of its 274 files carry
`source_element_id`, a key that repository chose under this chain's own rule
("the key says what it indexes") and that this library never writes. An
allowlist is a list of the producers its author thought of. The rule is now
every top-level key beginning with `source_`, and a prefix rather than a
substring — `resource_owner` contains the literal and is not a locator.
*A correction to my own measurement, stated because it is the reason the peer
had to send it twice:* my first sweep used `grep -rhoE "^source_[a-z_]+:"`,
whose character class excludes digits, so it reported `source_element_id` and
silently missed `source_sha256` on 270 of 270. The generator's own counting —
over parsed frontmatter, not a regex — found both. A pattern that cannot match
what it is looking for returns a zero that reads like a fact.
### The K2 control: the field moved, the ranking did not
Same bundle, same question, same `k`. The "before" run is a copy of the tool
taken from `git archive b6a8c8b`, run with its own `docs/` beside it, so the
editable install cannot leak the new code into the baseline.
| | before (`b6a8c8b`) | after |
|---|---|---|
| delivered concept ids, in order | 8 | **identical** |
| `text_sha256` per excerpt | — | **identical** |
| `withheld` list | 621 entries | **identical** |
| denominators | 629 = 621 + 8 | **identical** |
| payload bytes | 108 877 | 113 143 (+3.92 %) |
| `spent` (`BUDGET_UNIT`) | 18 606 | 22 210 (+450.5 B per excerpt) |
| excerpt members | 9 | **17** |
| changed lines in the whole payload | — | 99 |
| payload sha256 | `afb7e811…` | `c94919c4…` |
**It is the field, not the ranking.** Nothing about which concepts were chosen,
or in which order, moved by one byte.
The contract document's own bytes moved twice while § 8 was being written, so
the budget instrument's known-positive moved with them — by design, since a
stale known-positive is meant to be a loud failure: 10 349 → 12 049 → **12 563**
measured, 10 060 → 11 719 → **12 227** raw, delta 289 → 330 → **336**.
Contract § 8 gains `title` as a MUST with its own checker code
(`excerpt_unnamed`, so the checker now runs **15** rules, was 14), and
`req_number`, `sources` and the locators as SHOULD — they are conditional on the
producer, and a bundle whose concepts carry no identifier cannot deliver one.
---
## 3. The form: a generator per bundle, chosen against a control that failed
Two candidates. (a) A generator that instantiates the template for one bundle.
(b) One generic skill taking the bundle root as a runtime argument.
**The obvious gate does not work.** The contract checker was asked to tell them
apart, and cannot:
```
python3 tools/okf_contract_check.py --skill skills/okf-consume-template/SKILL.md \
--payload /tmp/c1-fresh-payload.json
# conformant: 15 rules over 8 excerpts and 46 withheld entries, 0 findings — exit 0
python3 tools/okf_contract_check.py --skill skills/okf-consume/SKILL.md \
--payload /tmp/c1-fresh-payload.json
# conformant: 15 rules over 8 excerpts and 46 withheld entries, 0 findings — exit 0
```
The **unfilled template** passes against a real payload, and a skill built for a
different corpus passes against this one's. So conformance could not decide the
form, and any claim that "(b) fails the checker" would have been false. The
checker reads section headings, the marking vocabulary and the payload; it does
not read whether a number in the prose is true of this bundle.
**What decides it is what the skill has to state.** § 5's denominators, § 6.4's
conditional-field list and § 7.6's breaking point are per-bundle numbers. A
generic skill can only leave them as holes — the template's own definition of
*unfinished* — or carry another corpus's numbers, which is worse than a gap,
because a stated cost that is false for this bundle is a measurement failure.
Instantiating is what makes them true.
Steps from "here is a bundle" to the first correct, marked answer:
| | (a) generator | (b) generic skill |
|---|---|---|
| one-time setup | 1 command per bundle | 1 copy, once |
| typed at question time | the question | the question **and the bundle path, every time** |
| with two bundles installed | the model selects on the skill name — **measured, it did** | nothing selects; the caller must say which |
| per-bundle denominators | measured into the file | absent or false |
(a), and the deciding column is the third: the operator's requirement is
"one *or more* bundles", and (b) has no mechanism for the "more".
`tools/okf_skill.py` therefore instantiates. It measures, per bundle: id, ref,
concept count, the conditional-field table with a denominator for every field
(the `source_*` rows **discovered** from the bundle rather than listed), the
whole-bundle cost by the gate's own instrument, the share one measured answer
spent, and the concept count at which the `withheld` bookkeeping alone reaches
the limit. It also runs the index-walk-against-directory control **once, at
generation time, never on the question path** — § 9.2 binds a consumer reaching
for context at query time, and a build-time control is what turns "the walk
loses nothing" into a number the skill can quote.
It lives in `tools/` for the reason `okf_consume.py` states for itself — outside
`src/`, so no consumer's install surface changes — and for one more: a
wheel-installed `okf skill` would emit a command pointing at
`tools/okf_consume.py`, which the wheel does not contain.
**Red tests first, and every gate the checker lacks is one of them**: no
placeholder survives (with the pattern shown able to find, against the template);
the skill names its own bundle's id and ref and **not** the other bundle's; its
commands are absolute and point at files that exist; it reports a denominator per
conditional field; it is byte-deterministic for a given bundle and destination.
Four refusals, each with its code: a directory with no index
(`bundle_unreadable`), an index with no `bundle_id` (`bundle_id_missing`), a
bundle with no concepts (`bundle_empty`), and an occupied target without
`--force` (`target_occupied`). No model call, no new dependency.
---
## 4. End to end in Claude Code
Two skills installed side by side in a scratch project outside this repository,
one per bundle. Each row is **one run**; none was repeated to get a better one.
```
python3 tools/okf_skill.py <bundle> --out /tmp/c1-scratch/.claude/skills/<id>-consume
cd /tmp/c1-scratch && claude -p "<the question>"
```
| # | bundle | question | skill named in the prompt? | result |
|---|---|---|---|---|
| i | fresh, 54 concepts | "Hvordan skal prisene fylles ut?" | yes | **pass** |
| ii | fresh, 54 concepts | a topic measured absent from the bundle | yes | **pass** — refused |
| iii | third-party, 270 concepts | "Hva krever Krav 10.2—2 i N500? Gjengi det sentrale vilkåret." | **no** | **pass** |
| iv | fresh, 54 concepts | "Hvordan skal prisene fylles ut?" | **no** | **pass** — and selected the right one of two |
**(i) The known-positive.** Rank 1 of 8 delivered. The answer opened by naming
the document and the sheet, and cited the locator the previous session put into
the concept: *"kilde `<the spreadsheet>`, ark `Prissammenstilling`, rader 1 til
100"*. It marked with all five required literals, reported
`considered 54 / withheld 46 / delivered 8`, named `below_k` as the rule that
dropped a concept it flagged as possibly relevant, reproduced the budget
known-positive (12 563 = 12 563), and ran the contract check itself: 15 rules,
0 findings.
**(ii) The known-negative.** The absence was measured **before** the question was
asked, with the query shown able to find first (`pris` matches 15 files;
`jernbane|signalanlegg` matches 0 of 54). The answer: *"dokumentene i bundelen
stiller ingen krav til jernbanesignalanlegg"*, marked
`[sourced-not-sufficient]`, with its own known-positive control for the regex it
used to check the delivered texts, and with the 46 withheld concepts named as
`[unread]` rather than as absent. It named external standards only under
`[unverifiable-from-bundle]`.
**(iii) The third-party bundle.** Measured on the tree on disk at the time:
`vegnormal-n500-2024`, 270 concepts, ref
`sha256-tree:673a0c2c…d6b5b3ad`**not** the V1 tree the order named, because
that repository landed a rebuild during the day. The gold concept came back at
rank 1, and the answer quoted the requirement verbatim against the concept body,
named the title and the `req_number`, the `sources` resource, the concept id and
the excerpt digest, and stated `adjudication: unknown` and
`trust_tier: unverified` as measured absences at the producer rather than as
judgements about the source.
**One defect this run found, and it was in the prose.** The first (iii) run said
*"the producer wrote no page locator (no `source_pages`), so the address is at
document level"* — while the excerpt in front of it carried `source_element_id`,
that bundle's own locator. The excerpt was right and the **skill's citation
guidance was wrong**: it listed the four keys this library writes instead of
telling the reader to cite whichever `source_*` keys are present. Fixed in the
template and in both instantiated copies; on the re-run the same question
returned *"element `id-4ca67e2f-…`"* in the source pointer. That is two runs of
one question, and the second is a measurement of a **changed artefact**, not a
retry of the first.
**(iv) The trigger control.** No skill named in the prompt, two skills installed.
It selected `c1-fresh-20260908-consume` on the question alone, ran the pre-pass
and the checker, and cited `source_sheet` and `source_rows` by name. So no
special invocation syntax is needed; the generated `description` — which names
the bundle id, the concept count and the ref — is enough to route on.
### Invented numbers: zero
Every numeric or identifier-shaped token in each answer was extracted and
checked against a denominator stated per run.
| run | unique numeric/id tokens | not found in the denominator |
|---|---|---|
| i | 62 | **0** |
| ii | 45 | **0** |
| iii | 35 | **0** |
For (iii) the denominator is the delivered payload itself, reproduced exactly —
the model's reported `spent` of 11 941 equals the payload rebuilt here. For (i)
and (ii) the model composed its own question string, so its payload is not
byte-reproducible; the denominator there is the whole bundle text **plus** the
payload's own identities (the bundle ref, every concept-file digest, every
delivered-text digest) **plus** the figures the skill itself supplies. A first
pass that omitted those identities reported eight and nine "missing" tokens,
every one of them a fragment of a correctly quoted digest — the wrong
denominator, corrected here rather than published.
---
## 5. A finding about the ranking, not about the skill
The first attempt at (ii) asked the normative bundle about water and frost
protection in a subsea tunnel. The model answered
`[sourced-not-sufficient]`, said the relevant requirements "if they exist, are
among these 262 withheld", and refused to go around the cut. Checked
afterwards, **the bundle does cover it**: 17 files match `frostsikring`, 10 match
"vann- og frostsikring", 13 match `undersjøisk`, and none of them was ranked
into the cut.
So it was never a known-negative — it was a **retrieval miss**, and it is
recorded as one rather than quietly replaced. Two things follow, and they point
in opposite directions. The consumer behaved exactly as the contract asks: it
did not fabricate, it named its denominator, and it reported its own zero as
unmeasured because the `withheld` entries carry only ids and rules, no titles.
The ranker did not: a compound Norwegian question ("vann- og frostsikring",
"undersjøisk") was out-ranked by concepts sharing only "tunnel" and "vann". That
belongs with the fusion work already open, and it is the same shape as the
`MIN_SHARED_PREFIX` finding.
A second, smaller observation: `withheld` entries carry `concept_id` and `rule`
and no title, so a reader who wants to know *what* was dropped cannot tell
without reading the bundle — which § 2.2 forbids. The consumer in run (ii)
identified this itself and reported it as unmeasured. Not fixed here; noted.
---
## 6. Suite
| | before (`b6a8c8b`) | after |
|---|---|---|
| `pytest -q` | 1339 | **1373** |
| `ruff check src tests tools` | clean | clean |
| `ruff format --check .` | clean | clean |
| `mypy src` | 17 files, clean | 17 files, clean |
No version bump, no tag, no push.
---
## 7. Honesty limits
- **Four questions across two bundles is a demonstration, not a hit rate.** One
run each; nothing was repeated to get a better answer. The one repeat is
named, and it measures a changed artefact.
- The end-to-end runs allowed the model a tool set (`Bash`, `Read`, `Skill`,
`Glob`, `Grep`). Without permission to run the pre-pass the skill cannot do
its first step, so this is not an optional flag; it is part of what was
measured.
- **No token figure anywhere.** There is no tokenizer on this machine, so every
cost is in `BUDGET_UNIT` bytes. Quoting an o200k number would be repeating a
published figure, not measuring one.
- The generated skill's prose is thinner than the hand-instantiated copy for the
corpus that was measured over days. It states every number it can measure and
claims nothing it cannot; a corpus that needs more — an extension marking, a
measured cost distribution over many questions — needs a hand-edited copy, and
the generator refuses to overwrite one without `--force` for that reason.
- A scratch project inherits the machine's global session-start hooks: run (i)
ended by reporting an unrelated 17-message coordination inbox. Harmless here,
worth knowing before anyone reads a generated answer as self-contained.

View file

@ -55,13 +55,18 @@ excerpt it rests on — `(bundle_id, concept_id)` and the excerpt's `sha256`.
**Name the document, do not merely point at it.** Each excerpt also carries
`title`, and — when the producer wrote them — `req_number`, the § 5.1 address
`sources`, and a locator into that address (`source_pages`, `source_sheet` with
`source_rows`, or `source_lines`). Quote those values as they stand; they are the
difference between "the bundle says X" and "X, from `<title>` `<req_number>`,
`<resource>` at `<locator>`". Absent keys are absent because the producer wrote
none — never because the source has none, and never something to fill in. An
excerpt carrying `sources_unreadable` has an address this reader could not
decode: say so rather than reporting no address.
`sources`, and **every key whose name begins with `source_`**. That last one is a
prefix and not a list: which locator a bundle uses is its producer's choice, so
one bundle locates by `source_pages`, another by `source_sheet` plus
`source_rows` or by `source_lines`, and another by a key this library never
writes, such as `source_element_id`. **Read the excerpt's own keys and cite
whichever ones are there** — do not look for a fixed set and report "no locator"
when the one present is simply named something else. Quote the values as they
stand; they are the difference between "the bundle says X" and "X, from
`<title>` `<req_number>`, `<resource>` at `<locator>`". Absent keys are absent
because the producer wrote none — never because the source has none, and never
something to fill in. An excerpt carrying `sources_unreadable` has an address
this reader could not decode: say so rather than reporting no address.
| Marking | Use when |
|---|---|

View file

@ -83,13 +83,18 @@ excerpt it rests on — `(bundle_id, concept_id)` and the excerpt's `sha256`.
**Name the document, do not merely point at it.** Each excerpt also carries
`title`, and — when the producer wrote them — `req_number`, the § 5.1 address
`sources`, and a locator into that address (`source_pages`, `source_sheet` with
`source_rows`, or `source_lines`). Quote those values as they stand; they are the
difference between "the bundle says X" and "X, from `<title>` `<req_number>`,
`<resource>` at `<locator>`". Absent keys are absent because the producer wrote
none — never because the source has none, and never something to fill in. An
excerpt carrying `sources_unreadable` has an address this reader could not
decode: say so rather than reporting no address.
`sources`, and **every key whose name begins with `source_`**. That last one is a
prefix and not a list: which locator a bundle uses is its producer's choice, so
one bundle locates by `source_pages`, another by `source_sheet` plus
`source_rows` or by `source_lines`, and another by a key this library never
writes, such as `source_element_id`. **Read the excerpt's own keys and cite
whichever ones are there** — do not look for a fixed set and report "no locator"
when the one present is simply named something else. Quote the values as they
stand; they are the difference between "the bundle says X" and "X, from
`<title>` `<req_number>`, `<resource>` at `<locator>`". Absent keys are absent
because the producer wrote none — never because the source has none, and never
something to fill in. An excerpt carrying `sources_unreadable` has an address
this reader could not decode: say so rather than reporting no address.
| Marking | Use when |
|---|---|

View file

@ -1118,6 +1118,7 @@ def test_no_corpus_document_name_reaches_any_file_this_work_tracks() -> None:
PROJECT_ROOT / "docs" / "2026-09-08-prisform-og-loggen-k2.md",
PROJECT_ROOT / "docs" / "2026-09-08-kravnummer-tokenisering.md",
PROJECT_ROOT / "docs" / "2026-09-08-sjeldenhetsvekt.md",
PROJECT_ROOT / "docs" / "2026-09-08-claude-code-skill-vilkaarlig-bundle.md",
PROJECT_ROOT / "README.md",
PROJECT_ROOT / "CLAUDE.md",
]
@ -1128,12 +1129,30 @@ def test_no_corpus_document_name_reaches_any_file_this_work_tracks() -> None:
def test_the_readme_consume_section_states_the_rule_count_the_code_emits() -> None:
# A published number must have a test that goes red when it goes false.
readme = (PROJECT_ROOT / "README.md").read_text(encoding="utf-8")
assert readme.count("## Consume") == 1
assert readme.count("## Consume\n") == 1
assert readme.count("## Consume in Claude Code\n") == 1
assert len(okf_consume.WITHHOLDING_RULES) == 6
assert "closed set of six" in readme
assert "tools/okf_consume.py" in readme
def test_the_readme_recipe_names_only_commands_this_repository_ships() -> None:
# Every command in the "Consume in Claude Code" section was run in the
# session that wrote it. This test cannot re-run them; what it can hold is
# that each script the recipe invokes still exists under the path it names.
readme = (PROJECT_ROOT / "README.md").read_text(encoding="utf-8")
recipe = readme.split("## Consume in Claude Code", 1)[1].split("\n## ", 1)[0]
scripts = set(re.findall(r"python3 (tools/\S+\.py)", recipe))
assert scripts == {
"tools/okf_skill.py",
"tools/okf_consume.py",
"tools/okf_contract_check.py",
}, scripts
for script in scripts:
assert (PROJECT_ROOT / script).is_file(), script
assert "okf build " in recipe
# --- Step 11: the measurement scorer -----------------------------------------