docs(release): R7, v0.8.5 tagged locally and measured from the tag

The release report in R6's form: premises re-measured, the one-line lockfile
diff, the README lines the tag would have made false, the gates, and the
acceptance gate run from a local `git archive v0.8.5` export.

Two bookkeeping figures in K3-25's report corrected after re-measuring: the
tests/ call denominator was 129 occurrences on 128 lines (not 112 across 13),
and the outside-tests figure was 19 on 17 lines in 5 files (not 129, which was
the tests/ number written into the wrong paragraph). The "exactly 1 changed
result" finding is unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-12 19:47:30 +02:00
commit 313a1497b7
2 changed files with 275 additions and 6 deletions

View file

@ -86,9 +86,12 @@ byte-identical under both readings, section 3's gate in miniature -- and the
characterisation of what the line does, which is an invariant about the line. characterisation of what the line does, which is an invariant about the line.
**One existing test changed with the code**, and it is the whole test-caller **One existing test changed with the code**, and it is the whole test-caller
denominator: `tests/` holds **112** call occurrences of the four functions denominator: `tests/` holds **129** call occurrences of the four functions
across **10** files, of which **13** pass the parameter explicitly. Exactly on **128** lines across **10** files, of which **20** lines pass the parameter
**1 of 112** changed result -- the characterisation that called explicitly (re-counted 2026-09-12 for the `v0.8.5` release; the figures first
published here, 112 and 13, were a line count read as an occurrence count and
a file count read as a call count). Exactly
**1 of 129** changed result -- the characterisation that called
`concept_scores` with no parameter because the reading it characterises used to `concept_scores` with no parameter because the reading it characterises used to
be the default. It now names that reading; same fixture, same number. The two be the default. It now names that reading; same fixture, same number. The two
other test files whose fixtures carry the door's form other test files whose fixtures carry the door's form
@ -129,9 +132,11 @@ exactly what the unflagged build delivers. It does, to the byte, on every row.
## 5. The consumers, measured ## 5. The consumers, measured
`grep` over `build_payload(`, `concept_scores(`, `searchable_text(` and `grep` over `build_payload(`, `concept_scores(`, `searchable_text(` and
`body_without_link_line(`, excluding `.git`, `tests/` and `.venv`: **129** hits `body_without_link_line(`, excluding `.git`, `tests/` and `.venv`: **19** hits
in total, of which **4** fall outside `consume.py` and one of those is prose in on **17** lines across **5** files (re-counted 2026-09-12 for the `v0.8.5`
`CLAUDE.md`. **3 call sites in 2 files**, plus the CLI's own `main` inside the release; the 129 first published here is the `tests/` figure above, written
into the wrong paragraph). **8 of the 19** fall outside `consume.py`: one is
prose in `CLAUDE.md` and four are this report counting itself. **3 call sites in 2 files**, plus the CLI's own `main` inside the
module: module:
| call site | what it is | passes the parameter | what the change means for it | | call site | what it is | passes the parameter | what the change means for it |

View file

@ -0,0 +1,264 @@
# R7: release v0.8.5
A patch release with no new functionality and one change that reaches outside
this repository: `okf.parse_frontmatter` is public API, and after K3-24 it
returns a flow STRING for a block `sources:` where it returned an EMPTY string.
The tag carries what rounds 23, 24 and 25 landed and no tag had -- K3-23's
`link_in_signal` instrument, K3-24's block-`sources` fix in all three flat
frontmatter readers, and K3-25 making the reading without the door's link line
`consume`'s default. No behaviour change in `src/` beyond the version string,
the guard pin untouched at `v1.4.0`, no push.
Tag: `v0.8.5`, local and annotated, on `64661c7` (`chore(release): 0.8.5`).
`v0.8.4` stays on `69dc51f`, `v0.8.3` on `0963dfa`, `v0.8.2` on `b6c54e9`,
`v0.8.1` on `3daf983` and `v0.8.0` on `4d1f9d3` -- verified with
`git tag --points-at`, all five still resolve there.
## 1 What this round did, and what it did not
Did: the version on the three lines it lives on (`pyproject.toml:7`,
`src/llm_ingestion_okf/__init__.py:79`, and the package's entry in
`uv.lock:550`), `CHANGELOG.md`'s `## [Unreleased]` to
`## [0.8.5] — 2026-09-12` with the em dash copied from the `[0.8.4]` head, the
five `@v0.8.4` install lines to `@v0.8.5`, the two pairing paragraphs, the tag
history list, README's test counts and its history parenthesis, and one local
annotated tag.
Did NOT: move the guard pin, which stays at `v1.4.0` on all six tracked places;
move the dependency floor `>=1.2,<2.0`, which R6 measured and deliberately left
standing as an operator decision; add a rule, an extractor, an arm or a flag;
move `--shell-parent`, `--pdf-outline` or `--bold-title`, all three still OFF
and all three operator questions with numbers already delivered; touch
`skills/`; rewrite `docs/`; or push anything.
`link_in_signal`'s default is K3-25's delivery, not this round's. This round
releases it.
## 2 Premises, re-measured
Every premise the order carried was reproduced before anything was edited.
| premise | measured here |
|---|---|
| inbox empty | `find ~/.claude/coord/llm-ingestion-okf/inbox/ -type f \| wc -l` = 0, rc 0 |
| order queue | 0 pending, 1 claimed (this order); K3-25's order already archived |
| `git status --short` | empty |
| HEAD | `06c057f`; `git log --oneline 7faa380..HEAD` = exactly K3-25's three commits (`3816ed5`, `38320cd`, `06c057f`), nothing else |
| five tags stand | `v0.8.0` `4d1f9d3`, `v0.8.1` `3daf983`, `v0.8.2` `b6c54e9`, `v0.8.3` `0963dfa`, `v0.8.4` `69dc51f`, all `git cat-file -t` = `tag` |
| remote | `refs/heads/main` = `150c726`; all five `v0.8*` tags present, `v0.8.4^{}` = `69dc51f`. R6's tag backlog is gone, so the push list is ONE line |
| `okf check` rules | `len(contract_check.RULES)` = **17** in the work tree and **17** from the tag. K3-25 added none |
| suite | **1827 collected, 1826 passed, 1 skipped**, rc 0 (`pytest -q` and `pytest --collect-only -q`, repo root, `[extract]` installed) |
| ruff | 0.16.6, the version R4, R5 and R6 measured |
The order's line numbers had moved, as it warned: `CHANGELOG.md`'s `[0.8.4]`
head is at **103**, not 82, and README's fourth install line at **589**, not
580. Every number used here was read off this base.
The CHANGELOG's `[Unreleased]` block held **5** posts under **4** headings when
this round started -- `### Added` (K3-23), `### Changed` (K3-25), `### Fixed`
(K3-24), `### Changed` (K3-24, two posts) -- counted with
`sed -n '9,102p' CHANGELOG.md | grep -c '^- '` and `grep -n '^### '`, not read.
**Gate 1 was already closed by K3-25.** The order expected K3-23's Added post
to still end in "in a later round", a sentence the tag would falsify. It does
not: the post now says the recommendation was "carried out in the same
unreleased block below". The query that could find it is the joined one --
`tr '\n' ' ' < CHANGELOG.md | grep -c 'in a later round'` = 0 -- run beside the
known-positive `tr '\n' ' ' < CHANGELOG.md | grep -c 'recommended as'` = 1, so
the zero is a measurement. Nothing was edited.
**Gate 2 was open and one post was written.** K3-24's `### Fixed` post says the
rendering "is a READING projection, not a claim that the value is writable",
but names no cost and no denominator. One post was added under `### Changed`
carrying the measurement in section 7 below. It is the only post in the
`[0.8.5]` block this round wrote; the other five moved byte-identically,
verified by diffing the old block's non-heading lines against the new block's
(the only addition is the 13 lines of the new post; the two K3-24 `### Changed`
posts appear on both sides unchanged).
**The two `### Changed` headings were merged into one**, in the order Added,
Changed, Fixed: K3-25's post, then K3-24's two, then the new one. Each post
body moved byte-identically and no other line changed. Leaving both would also
have been legal; one was chosen because a single version block with two
identically named sections is a reader's problem, not a record.
## 3 The lockfile
`pyproject.toml` and `__init__.py` were corrected first, then `uv lock` was run
and the diff measured. **Exactly one line**, the okf version at `uv.lock:550`:
`0.8.4` -> `0.8.5`. No other package moved. The guard lines `:546` and `:578`
are untouched and still carry `v1.4.0`. `tests/test_guard_adapter.py` passed
**21 of 21** afterwards -- the check that `uv lock` did nothing to the guard it
was not asked to do.
R6's diff was four lines, three of them the pin's. This round's is one, because
the pin does not move.
## 4 README lines the tag would otherwise make false
- The **five install lines** (`README.md:12`, `:62`, `:77`, `:589`,
`llms.txt:9`), measured on this base, not quoted from the order.
- The **two pairing paragraphs**. `README.md:66-67` and `:80-81` each had ONE
number to correct this round, not two: the okf tag. The guard tag `v1.4.0`
stays in both, and `README.md:76` -- the pip fallback that installs the guard
-- was not touched at all.
- The **tag history list**: a `v0.8.5` row as "the current tag", written from
this round's own CHANGELOG wording and carrying **seventeen** rules as
measured here, not copied from the row above; `v0.8.4` demoted from "the
current tag" to past by striking those three words and nothing else;
`v0.8.3`, `v0.8.2`, `v0.8.1`, `v0.8.0` and `v0.7.0` untouched. The row makes
no claim about the guard pin, so README's `v1.4.0` count stays at 4.
- The **test counts** (`:950-952`): 1783/1782/1 of 2026-09-11 to
**1827/1826/1** of 2026-09-12, both measured here.
- The sentence that follows them -- "the figure published before the `v0.8.2`
release was the PASSED count, and `pytest --collect-only -q` reported one
more" -- was **left standing**, because it is still true on this round's
numbers: 1827 is exactly one more than 1826, and the one is the skip.
- The **history parenthesis**: 1783 becomes a past leg ("through the `v0.8.4`
release, after K3-22 and K3-21") and the current figure becomes the `v0.8.5`
release's, after K3-23, K3-24 and K3-25. Changing only the date would have
made the previously published figure disappear rather than become history.
`tests/test_docs_promises.py` (**5 of 5**) and `tests/test_packaging.py`
(**6 of 6**) were run after the README and version edits, both green.
## 5 Two bookkeeping figures in K3-25's report
Both were re-measured rather than taken from the order, and both were wrong in
the published file.
- Paragraph 2 said `tests/` holds **112** call occurrences of the four
functions across 10 files, of which **13** pass the parameter. Measured:
**129** occurrences on **128** lines across **10** files, with **20** lines
passing `link_in_signal=`. The substantive finding -- exactly **1** changed
result -- stands, and `1 of 112` became `1 of 129`.
- Paragraph 5 said **129** hits outside `tests/`. Measured: **19** occurrences
on **17** lines across **5** files. The 129 is the `tests/` figure written
into the wrong paragraph. Of the 19, **8** fall outside `consume.py`: one is
prose in `CLAUDE.md` and four are that report counting itself.
Both corrections name what the wrong number actually was, so a reader meeting
the old figure elsewhere can place it.
## 6 The gates
Run after `git add` and after the commit, never before:
| gate | result |
|---|---|
| `ruff check src tests tools` | All checks passed (ruff **0.16.6**) |
| `ruff format --check .` | 132 files already formatted |
| `mypy src` | Success: no issues found in 21 source files |
| `pytest -q` | **1826 passed, 1 skipped**, rc 0 |
Grep criteria, each with a control:
| query | result |
|---|---|
| `grep -n 'okf\.git@v0\.8\.4' README.md llms.txt` | 0 hits, rc 1 |
| `grep -c 'okf\.git@v0\.8\.5' …` | README **4**, `llms.txt` **1** = 5 -- the known-positive that makes the zero above a measurement |
| `grep -n 'v0\.8\.4' README.md llms.txt` | **2**, both named: `README.md:102` the tag-history row, `README.md:959` the history parenthesis. Not 0, and not to be made 0 |
| `grep -n 'v1\.4\.0' pyproject.toml uv.lock README.md` | `pyproject` **1**, `uv.lock` **2**, README **4** -- unmoved, which is what says the pin was not touched |
| `grep -n 'v1\.3\.0' pyproject.toml README.md llms.txt` | 0 hits, rc 1 |
| `grep -rn 'v0\.7\.0\|0\.7\.0' README.md docs llms.txt CLAUDE.md \| wc -l` | **35** lines over **8** `docs/` files -- unchanged. README's one hit is the `v0.7.0` tag-history row and stays |
## 7 The acceptance gate: installation FROM THE TAG
Local export only. `git archive v0.8.5 | tar -x` into
`/private/tmp/okf-r7-scratch/r7-install/export`, then
`uv sync --frozen --extra extract` (plain `--frozen` drops `[extract]` and
`mypy src` falls on pdfplumber). No `uv tool install`, no `--force`, no
uninstall: the operator's own `okf` at `/Users/ktg/.local/bin/okf` was read and
left alone. Everything below ran from `<export>/.venv/bin/`.
| row | denominator | result |
|---|---|---|
| version | 1 | `importlib.metadata.version('llm-ingestion-okf')` = **0.8.5** |
| guard version | 1 | `llm-ingestion-guard` = **1.4.0**, unmoved |
| `uv lock --check` | 1 | rc **0**, captured directly |
| `contract_check.RULES` | 1 | **17**, equal to the work tree's |
| `okf check` on the shipped skill | 1 | rc 0, `conformant: 17 rules over 3 excerpts and 0 withheld entries, 0 findings` |
| `okf project` on the five-document folder | 5 documents | **26** concepts, **52** md files, **0 of 5** left out -- R6's numbers to the unit |
| HTML corpus | 828 documents | `proposed 828 plan(s); 0 document(s) with no boundary; 0 unreadable`, **6 015** md |
| R761 XML | 110 source files, 1 at top level | **2 761** concepts over 2 737 directories, of which `11` and `12` hold two each; document directory is `r761-prosesskoden/`, not the UUID |
| hit@1 / @8 / @50 on R761 | **N = 6** scored of 8 asked | **6/6 · 6/6 · 6/6** |
| known-positive rank | 1 | rank **1** at the default `k` and rank **1** at `k` 50 |
| known-negative (K3-17) | 1 question | rank 1 = the RITB section (**6 793 B**), rank 2 = «Prøvedrift» (**10 590 B**); both concepts present and delivered |
| block `sources:` read to 0 empty | 4 605 files | see below |
| the flow string through PyYAML | 4 605 files | see below |
**The hit@1 row matters more this round than last**, because K3-25 makes
`link_in_signal=False` the default and that is a ranking change on a published
payload form. K3-23's claim was that none of the five shipped bundles carries
the door's link line, so the row should not move. It did not: every S1-S6 row
is rank 1, the known-positive is rank 1 at both `k`, and S1's `spent` is
**31 031** -- the value STATE records for the default reading.
**The four bundles, read from the tag's bytes.** Source directory, named
because it is not named in K3-24's report:
`/Users/ktg/repos/vegnormal-okf/build/ferdig/`. Chosen after verifying its
denominators against K3-24's table first: files carrying a frontmatter block
are **2 757 / 447 / 1 134 / 271** for `r761-2025`, `n100-2023`, `n200-2024`,
`n500-2024`, exactly K3-24's. Of those, the ones carrying a BLOCK `sources:`
are **2 756 / 446 / 1 133 / 270** = **4 605**. All three flat readers
(`materialize.parse_frontmatter`, `structure._split_frontmatter`,
`profiles._split_frontmatter`) return a non-empty value on **0 empty of each**,
reading nothing but the bundles.
**The known-positive control for that zero.** The same read with `v0.8.4`'s
flat reader (`git archive v0.8.4 src/llm_ingestion_okf` into scratch,
`sys.path.insert`) returns EMPTY on **2 756 / 446 / 1 133 / 270** -- 4 605 of
4 605, K3-24's before-numbers to the file. The instrument can find, so the zero
is a measurement.
**The reservation, measured and not asserted.** The string
`parse_frontmatter` now returns for a block `sources:` was passed to
`yaml.safe_load` on each of the same 4 605 files. It is read back on **0 of
4 605**. The reason is visible in one value:
```
[{ resource: https://…/api/nisosts/859990?languageCode=nb, title: N500:2024 }]
```
The `?` opening the query string ends the flow scalar, and PyYAML raises
`ParserError while parsing a flow mapping`. So the fix is an improvement for a
consumer who read the empty value and concluded the address was absent, and a
REGRESSION for a consumer who passed the return value straight to a YAML
reader: they now get a parse error where they got something empty that parsed.
The emitter `materialize._render_sources` still writes flow, so no bundle bytes
move. PyYAML 6.0.3 is a dev dependency and the export carries it; this row was
measured with the export's interpreter, and reproduced identically with the
work tree's.
## 8 Honesty limits
- **One machine, one interpreter, a LOCAL export.** The installation is
measured on one Intel Mac from `git archive v0.8.5`, not from the Forgejo
tag, which does not exist until the operator pushes. It is not a portability
claim and not a claim that the channel works.
- **`okf project`'s 26 is ONE corpus of five documents** -- a regression
control, not a sample.
- **828 of 828 and 2 761 of 2 761 are ONE publisher's files from ONE product.**
- **hit@1 and the known-positive rank are 6 scored questions and ONE
known-positive on ONE bundle, against a fasit someone else set.**
- **The known-negative is ONE constructed question on ONE small bundle.**
- **The four bundle rows are ONE producer's four bundles from ONE generator.**
"0 empty" says nothing about a bundle someone else built, and the directory
they were read from is named above because K3-24's report does not name it:
the denominators match K3-24's table, which is the evidence it is the same
material, not a certificate that it is the same directory.
- **The grep criterion is deliberately NOT "0 hits on `v0.8.4`".** Two
occurrences remain and must. The zero on `okf.git@v0.8.4` ran beside a
known-positive that returned 5, so it is a measurement and not a query that
could never match.
- **`okf.parse_frontmatter` CHANGES PUBLIC READ BEHAVIOUR.** A consumer who
passed its return value for `sources` to a YAML reader and got something
empty that parsed now gets a parse error: PyYAML reads the returned string
back on **0 of 4 605** block files. That is a regression for them even though
it is a fix for everyone who read the empty value as an absent address. It is
stated here, in the CHANGELOG and in the tag-history row, because a release
that sells a read fix without saying who it costs lies by omission.
- **"The tag installs" is not "the tag is correct".** No functionality is
verified here beyond what K3-23, K3-24 and K3-25 already measured; the rows
above prove the exported bytes run and reproduce the counts the repository
already published.