fix(okf): top-level frontmatter title survives a nested sources: title

P15 (order 20260912T220951Z). okf._frontmatter_from_text was linewise
last-write-wins over EVERY line regardless of indentation, so a curated
concept's own top-level `title:` got silently overwritten by the nested
`sources:\n  - title: ...` block's title. Fix: a top-level (unindented)
key always wins over an indented one of the same name; a nested line
with no top-level counterpart is still preserved (SPEC §4).

Red-before/green-after: new test
test_parse_frontmatter_top_level_title_survives_nested_sources_title
(tests/test_okf.py) failed on 45edbf5 (fm["title"] == "N500:2024",
expected the concept's own), green after the fix.

Re-measured on all four vegnormal-okf bases (concept files / distinct
titles): n100-2023 446/446 (was 1) - n200-2024 1133/1133 (was 1) -
n500-2024 270/270 (was 1) - r761-2025 2756/2407 (genuine repeated
process names, not a collapse). directory_listing on krav/N500:
269 documents / 269 distinct titles (was 1).

tests/test_context_sets_loadbearing.py:
- The P14 tripwire test (asserting parse_frontmatter DID collapse
  titles) is INVERTED, not deleted, per the order: it now asserts the
  fix holds, as a live regression guard.
- own_frontmatter() stays (not replaced by parse_frontmatter): measured
  29,500 field reads (type/title/req_number/prosessnr, all four bases)
  agree exactly except for quote-stripping (2,728/29,500, zero value
  mismatches) - own_frontmatter unquotes for fasit comparison,
  parse_frontmatter deliberately doesn't (D1/(a)/(i): unquote_scalar is
  the ONE unquoting rule).

docs/2026-09-12-p14-kontekstsett.md Part B correction: the "22 of 22
cost words absent from n100/n200/n500" claim was false - n500-2024
carries `kroner` as a false positive (substring match inside
"borkroner", drill bits, not money). The original 22-word list was
never persisted, so only ~9 of the 22 survive named. Replaced with a
newly named, persisted 22-word list and the actual re-measured count:
n100 22/22 absent - n200 22/22 - n500 21/22 (kroner via borkroner) -
r761 18/22 (4 genuine cost words). No gate touched (no fasit anchor is
`kroner`).

Verification: full suite 1643 passed / 5 skipped (was 1642/5 on
45edbf5, +1 new test, 0 removed) - `uv run pytest -q`. ruff check +
ruff format --check clean on the three changed source/test files.
Golden transcripts byte-unchanged: shasum -a 1
tests/golden/demo-transcript.stdout = ea8c534773acdbe41ae68f2c55724d69aaf8be4f,
demo-transcript.stderr = ede3e2f685ce6a14ad9888e9de421d1a66f6c611.
No version bump, no push (both forbidden by the order).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-13 07:40:57 +02:00
commit f13dc64a0a
4 changed files with 127 additions and 38 deletions

View file

@ -57,22 +57,31 @@ _MIN_ANCHOR_CHARS = 4
def own_frontmatter(path: Path) -> dict[str, str]:
"""The concept's OWN frontmatter: top-level keys only, FIRST occurrence winning.
**``okf.parse_frontmatter`` cannot be used for this, and that is measured rather than assumed.**
It is linewise and last-write-wins by documented design, so a nested block overwrites a
top-level key of the same name. Every vegnormal concept ends its frontmatter with
**P15 (2026-09-13) fixed the finding this helper was written against.** Before P15,
``okf.parse_frontmatter`` was linewise and last-write-wins over EVERY line regardless of
indentation, so a nested block overwrote a top-level key of the same name. Every vegnormal
concept ends its frontmatter with
sources:
- resource: https://
title: N500:2024
and the indented ``title`` therefore replaces the concept's own. MEASURED on n500-2024:
``okf.navigate_bundle`` yields 270 concept files carrying **1 distinct title** (``N500:2024``,
270 times), and ``okf.directory_listing(bundle, path="krav/N500")`` returns 269 documents whose
``title`` is that same string so rung 2 and rung 3 of the navigation ladder tell a navigator
apart only by an opaque UUID filename and a character count. That is a finding about the
PRODUCT (recorded in ``docs/2026-09-12-p14-kontekstsett.md § 5``), not about this test; what it
means here is that a fasit assert against ``parse_frontmatter``'s ``title`` would compare every
concept to the same constant and be VACUOUS.
and the indented ``title`` used to replace the concept's own. MEASURED on n500-2024 before the
fix: ``okf.navigate_bundle`` yielded 270 concept files carrying **1 distinct title**
(``N500:2024``, 270 times). ``okf.parse_frontmatter`` now makes indentation load-bearing
a top-level (unindented) key always wins over a nested one of the same name and re-measured
AFTER the fix, the same base's 269 ``krav/N500`` documents carry **269 distinct titles**.
**This helper still isn't a plain call to ``okf.parse_frontmatter``, and that remains
measured rather than assumed:** ``own_frontmatter`` also strips one layer of enclosing
``'`` quotes (``.strip("'")``) so a value matches the fasit's stored plain-text title
verbatim, while ``okf.parse_frontmatter`` deliberately leaves scalars quoted unquoting is
``okf.unquote_scalar``'s ONE job (D1/(a)/(i)), and a second copy of that rule here would be
the drifting one. Re-measured across all four bases (29 500 field reads: ``type``, ``title``,
``req_number``, ``prosessnr`` on every concept file) the two now agree EXACTLY except for
quoted scalars (2 728 of 29 500 checks every one a quote-stripping difference, none a value
difference), so this helper stays for that one reason, not for the nested-override bug P15
closed.
Uses ``okf._split_frontmatter`` deliberately: it is the module's ONE place ``---`` is compared
(B4), and a second delimiter rule here would be the copy that drifts.
@ -376,14 +385,16 @@ def test_the_fasit_titles_are_distinct_not_the_collapsed_sources_title(set_dir:
"""The fasit's recorded titles must tell the cited concepts APART.
Paired with ``own_frontmatter``'s measurement, this is what keeps arm (b) from being vacuous: if
the recorded titles were ``parse_frontmatter``'s, every one of them would be the base's
``sources`` title and the assert would hold against any concept in the base.
the recorded titles were ``parse_frontmatter``'s PRE-P15 titles, every one of them would be the
base's ``sources`` title and the assert would hold against any concept in the base.
**TRIPWIRE, deliberately.** The second half asserts that ``okf.parse_frontmatter`` DOES still
collapse them. The day that stops being true an okf bump, or a decision to read block
mappings here this arm goes red, and whoever sees it should read this docstring, confirm the
listing now carries real titles, and DELETE this half rather than weaken it.
"""
**Former tripwire, INVERTED 2026-09-13 by P15 (deliberately, per that order not deleted).**
Until P15 the second half asserted that ``okf.parse_frontmatter`` DID still collapse the
titles, as a red flag that would fire the day the production bug was fixed here instead of at
the call site. P15 fixed it AT THE SOURCE (``okf._frontmatter_from_text``: a top-level key now
always wins over a nested one of the same name), so the second half now asserts the opposite
that ``parse_frontmatter`` agrees with the fasit's own distinct titles — as a live regression
guard against the collapse coming back."""
declared = read_bundle_txt(set_dir / "bundle.txt")
base = _require_base(declared)
fasit = json.loads((set_dir / "fasit.json").read_text(encoding="utf-8"))
@ -391,8 +402,8 @@ def test_the_fasit_titles_are_distinct_not_the_collapsed_sources_title(set_dir:
cited = [c for row in fasit["must_cite"] for c in row["concepts"]]
assert len({c["title"] for c in cited}) == len(cited), "recorded titles do not tell them apart"
collapsed = {okf.parse_frontmatter(base / c["path"]).get("title", "") for c in cited}
assert len(collapsed) == 1, (
"okf.parse_frontmatter no longer collapses these titles onto the sources block — read this "
"test's docstring: the finding it guards may be gone, and this half should be deleted"
titles = {okf.parse_frontmatter(base / c["path"]).get("title", "") for c in cited}
assert len(titles) == len(cited), (
"okf.parse_frontmatter collapsed these titles onto the sources block again — the P15 fix "
"in okf._frontmatter_from_text has regressed"
)