fix(okf): the index entry point never requires 'type', frontmatter or not

navigate_bundle died on an index.md that carried a frontmatter block without
a 'type' field: _parse_index_entry keyed its tolerance on the ABSENCE of the
block, so a frontmatter-ful index fell through to parse_concept_file and
raised. Not merely the index read failed — the whole navigation did.

OKF v0.2 (A-E6) triggers this: it stamps okf_version into index.md. Our own
assumption ("a generated index has no frontmatter") is frozen in the golden,
so nothing caught it. The defect is ours, not theirs.

method-spec §3 Step 1 renders the index as "the index body (the summary)"
and every OTHER file as a "non-index concept file" — the index is not a
concept file, so the 'type' requirement never reached it in the first place.
The tolerance is now keyed on being the index, which is what it always meant.

Load-bearing, both directions detach-proved:
- test_index_with_frontmatter_lacking_type_navigates goes RED when the
  tolerance is re-keyed on absence-of-frontmatter.
- test_missing_type_is_an_error goes RED when the index default leaks onto
  concept files. Its vehicle moved from index.md to a non-index file: it
  proves the concept-file rule, and an index.md vehicle now proves the
  opposite of what the test is named for. This also closes a real gap —
  nothing tested a non-index file WITH frontmatter but WITHOUT 'type'.

Goldens untouched (shared bundle has type: index; ingest golden index has no
frontmatter). Suite 627 -> 628 passed.
This commit is contained in:
Kjell Tore Guttormsen 2026-07-26 14:48:37 +02:00
commit f41264fbd3
2 changed files with 58 additions and 19 deletions

View file

@ -70,6 +70,24 @@ class TestNavigation:
# The summary flows into the read-context as the leading section.
assert bundle_context(tmp_path).startswith("Summary line.")
def test_index_with_frontmatter_lacking_type_navigates(self, tmp_path: Path) -> None:
# The index is the bundle ENTRY POINT, not a "non-index concept file" — so
# `type` is not required OF IT, whether or not it carries a frontmatter
# block. Keying the tolerance on the ABSENCE of frontmatter was the defect:
# an index that carries frontmatter for some OTHER reason (an OKF v0.2
# generated index stamps `okf_version` there) reached `parse_concept_file`
# and killed the whole navigation — not just the index read. RED if the
# tolerance is keyed on absence-of-frontmatter again.
_write(tmp_path / "index.md", "---\nokf_version: 0.2\n---\nSummary line.\n- [A](a.md)\n")
_write(tmp_path / "a.md", "---\ntype: project\ntitle: A\n---\nBody A.")
concepts = navigate_bundle(tmp_path)
assert [c.path.name for c in concepts] == ["index.md", "a.md"]
index = concepts[0]
assert index.type == "index" # defaulted, not read from the file
assert index.frontmatter["okf_version"] == "0.2" # unknown fields preserved
assert index.body == "Summary line.\n- [A](a.md)" # frontmatter stripped from body
assert bundle_context(tmp_path).startswith("Summary line.")
def test_frontmatterless_tolerance_is_scoped_to_the_index(self, tmp_path: Path) -> None:
# LOAD-BEARING (honesty): the relaxation is for the index ENTRY POINT only.
# A NON-index concept file without frontmatter is still malformed and MUST
@ -142,7 +160,13 @@ class TestFrontmatter:
"""§3 Step 1: leading ``---`` block, line-oriented ``key: value``; ``type`` required."""
def test_missing_type_is_an_error(self, tmp_path: Path) -> None:
_write(tmp_path / "index.md", "---\ntitle: No type\n---\nBody.")
# LOAD-BEARING (honesty), and the scope guard for the index tolerance: the
# vehicle MUST be a NON-index concept file. `type` is required of concept
# files, never of the entry point — an index.md vehicle would only re-assert
# the entry-point behaviour, which is now (correctly) the opposite. RED if
# the index's `type` default ever leaks onto concept files.
_write(tmp_path / "index.md", "Summary. See [a](a.md).")
_write(tmp_path / "a.md", "---\ntitle: No type\n---\nBody.")
with pytest.raises(ValueError, match="type"):
navigate_bundle(tmp_path)