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

@ -315,6 +315,38 @@ def test_parse_frontmatter_reads_scalar_fields() -> None:
assert fm["decision"] == "approved_with_adjustment"
def test_parse_frontmatter_top_level_title_survives_nested_sources_title(tmp_path) -> None:
"""A concept's OWN ``title`` sits at top level; ``sources:`` is a block sequence whose nested
``title:`` names the SOURCE document, not the concept (measured on a real vegnormal-okf
concept, ``krav/N500/id-bfb0edb4-``). Top-level keys carry no indentation and must win
a nested line must never overwrite a top-level key of the same name, however late it appears
in the scan. Without this, ``directory_listing`` on ``krav/N500`` returns 269 documents that
all share the one nested title, ``N500:2024`` rung 2/3 of the navigation ladder collapse to
an opaque UUID filename and a character count (P14 finding,
``docs/2026-09-12-p14-kontekstsett.md`` § 5).
Known-negatives that must stay green: ``type`` and ``req_number`` are untouched top-level
scalars either way, and the ``verified:`` block-form decoder (SPEC §5.2) reads
``_split_frontmatter``'s lines directly and is unaffected by this fix."""
text = (
"---\n"
"type: Krav\n"
"title: Krav 10.4.3—1 Mekanisk ventilasjon (impulsventilator)\n"
"req_number: Krav 10.4.3—1\n"
"sources:\n"
" - resource: https://example.invalid/859990\n"
" title: N500:2024\n"
"---\n\n"
"## Krav\nbody\n"
)
path = tmp_path / "concept.md"
path.write_text(text, encoding="utf-8")
fm = okf.parse_frontmatter(path)
assert fm["title"] == "Krav 10.4.3—1 Mekanisk ventilasjon (impulsventilator)"
assert fm["type"] == "Krav"
assert fm["req_number"] == "Krav 10.4.3—1"
def test_render_frontmatter_roundtrips_consumed_fields(tmp_path) -> None:
"""Step-8 writer: ``render_frontmatter`` + ``write_concept_file`` emit a block that
``parse_frontmatter`` re-reads with the fields ``seed_store_from_bundle`` consumes