`IndexPolicy.root_frontmatter` carried two claims at once: which keys the root index may carry and in what order, and which it must carry. The judge read it as the second, so `OKF_V0_2` — which names `okf_version` only to fix its position, upstream granting it as a MAY (§8:509-510, §12:773-775) — reported every bundle exercising that MAY as violating. Measured in P1 over 17 bundles: 14 failed with exactly this one violation, upstream's own four reference bundles among them, while D5's emitter treated the same key as optional. We emitted a MAY correctly and graded it a MUST. `root_frontmatter` now permits and orders; `root_frontmatter_required` demands. A required key outside the ordered set fails at construction, the same contradiction `FrontmatterSchema` already refuses when `required` strays outside `allowed`. - `OKF_V0_2` requires none — upstream's MAY, stated on the judge side too. - `STRICT_V1` requires all three: the proving consumer's root index carries exactly those keys in that order on every bundle measured (`c5141f8`), so separating the meanings costs them nothing. - `DEFAULT` names no root keys and is untouched. Re-measured over the eight bundle-root indexes reachable locally (our four goldens, upstream's four): 7 of 8 failing under the old semantics, 0 of 8 after. Emit path byte-identical — the golden suite fails otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqCmfJ2ukpFXeFjfab8wvy
333 lines
15 KiB
Python
333 lines
15 KiB
Python
"""The index policy as configuration, and the reader that judges it (Phase 3).
|
|
|
|
Three index shapes have to be expressible without either becoming the default:
|
|
|
|
- **DEFAULT** — the ingest-spec shape. One index at the bundle root, entries
|
|
`- [label](target)`, curated prose alongside them, and no relationship
|
|
between the index and the directory it sits in.
|
|
- **STRICT_V1** — `claude-code-llm-wiki`, read out of their code rather than
|
|
recalled: an index at every level (`bundle.py:527-567`), exactly one `# `
|
|
heading, entries `* [Title](link) - description` and nothing else, the root
|
|
index carrying `okf_version`/`bundle_profile`/`okf_spec_commit` while nested
|
|
indexes carry no frontmatter at all, and gate `BUNDLE_INDEX_COMPLETE`
|
|
(`validate.py:1081-1120`, severity ERROR) demanding the index and the
|
|
directory match exactly in both directions.
|
|
- **The second-brain shape** — not built here, but it must not be excluded.
|
|
The catalog reported on 2026-07-25 that their index is an *authored* count
|
|
of a directory's children and never a filesystem lookup, that a reader or
|
|
validator enumerating the directory has implemented the wrong contract, and
|
|
that they require prose in the index but no per-entry description.
|
|
|
|
The two consumers are directly opposed on exactly one point: the wiki mandates
|
|
the directory-derived match the catalog calls the wrong contract. That is what
|
|
settles the design. Every one of these differences is a policy field, none is
|
|
a library invariant, and `violations` never touches the filesystem — a caller
|
|
supplies the listing, and a profile whose index is authored refuses to be
|
|
handed one at all.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
import pytest
|
|
|
|
from llm_ingestion_okf.profiles import DEFAULT, OKF_V0_2, STRICT_V1, IndexPolicy
|
|
|
|
# --- DEFAULT keeps the Phase 1/2 shape (assumption C1) --------------------
|
|
|
|
|
|
def test_default_index_policy_judges_nothing() -> None:
|
|
"""Every new field is off under DEFAULT, so the reader has nothing to say.
|
|
|
|
Door A emits an index with a curated summary above managed links, Door B
|
|
and Door C append to one that may be empty. A judging default would
|
|
declare invalid the bundles this library itself produces — the same
|
|
reasoning that kept a required key set off DEFAULT's frontmatter schema.
|
|
"""
|
|
text = "# Sales bundle\n\nCurated prose the operator wrote.\n\n- [Sales](ingest-sales.md)\n"
|
|
assert DEFAULT.index.violations(text) == ()
|
|
assert DEFAULT.index.violations(text, is_root=True) == ()
|
|
|
|
|
|
def test_default_index_is_root_only_because_upstream_binds_it_there() -> None:
|
|
"""Upstream OKF binds `index.md` to the bundle root alone.
|
|
|
|
A nested directory without its own index is not an error there, which is
|
|
why per-directory indexing is policy rather than an OKF rule: a DEFAULT
|
|
demanding one per level would declare upstream-conforming bundles invalid.
|
|
"""
|
|
assert DEFAULT.index.per_directory is False
|
|
assert DEFAULT.index.required_indexes(["", "concepts", "concepts/hooks"]) == ("index.md",)
|
|
|
|
|
|
def test_default_index_link_is_unchanged_and_needs_no_description() -> None:
|
|
assert DEFAULT.index.requires_description is False
|
|
assert DEFAULT.index.render_link("Sales", "ingest-sales.md") == "- [Sales](ingest-sales.md)"
|
|
|
|
|
|
# --- the description on an entry ------------------------------------------
|
|
|
|
|
|
def test_render_link_carries_a_description_when_the_template_names_one() -> None:
|
|
assert STRICT_V1.index.requires_description is True
|
|
line = STRICT_V1.index.render_link("Hooks", "hooks.md", "How hooks fire.")
|
|
assert line == "* [Hooks](hooks.md) - How hooks fire."
|
|
|
|
|
|
def test_render_link_refuses_to_emit_a_half_written_entry() -> None:
|
|
"""A template naming `{description}` and a caller omitting it must not
|
|
silently render `* [T](t) - `, which parses as nothing and reads as an
|
|
entry."""
|
|
with pytest.raises(ValueError, match="description"):
|
|
STRICT_V1.index.render_link("Hooks", "hooks.md")
|
|
|
|
|
|
def test_a_description_offered_to_a_template_without_one_is_refused() -> None:
|
|
"""The mirror case: silently dropping it would lose operator bytes."""
|
|
with pytest.raises(ValueError, match="description"):
|
|
DEFAULT.index.render_link("Sales", "ingest-sales.md", "dropped on the floor")
|
|
|
|
|
|
def test_strict_v1_link_template_and_pattern_describe_the_same_shape() -> None:
|
|
"""Rendering and recognising are separate operations; this is the pair's
|
|
round-trip, the same test DEFAULT's shape already carries."""
|
|
for label, target, description in (
|
|
("Hooks", "hooks.md", "How hooks fire."),
|
|
("Concepts", "concepts/index.md", "Machine-generated index of concepts."),
|
|
("", "a.md", "-"),
|
|
):
|
|
line = STRICT_V1.index.render_link(label, target, description)
|
|
match = STRICT_V1.index.link_pattern.match(line)
|
|
assert match is not None
|
|
assert match.group("label") == label
|
|
assert match.group("target") == target
|
|
assert match.group("description") == description
|
|
|
|
|
|
def test_strict_v1_link_pattern_is_anchored_to_the_whole_line() -> None:
|
|
assert STRICT_V1.index.link_pattern.match("see * [Hooks](hooks.md) - x below") is None
|
|
|
|
|
|
# --- per-directory indexes ------------------------------------------------
|
|
|
|
|
|
def test_per_directory_policy_names_one_index_per_level() -> None:
|
|
"""The caller supplies the directories; the policy never enumerates them.
|
|
|
|
This is the catalog's construction rule honoured at the type level: the
|
|
library cannot list a directory because it is never given one to list.
|
|
"""
|
|
assert STRICT_V1.index.per_directory is True
|
|
assert STRICT_V1.index.required_indexes(["", "concepts", "concepts/hooks"]) == (
|
|
"concepts/hooks/index.md",
|
|
"concepts/index.md",
|
|
"index.md",
|
|
)
|
|
|
|
|
|
# --- the reader: headings -------------------------------------------------
|
|
|
|
|
|
ROOT_HEAD = "---\nokf_version: 0.1\nbundle_profile: strict-v1\nokf_spec_commit: d44368c\n---\n"
|
|
|
|
|
|
def test_strict_index_wants_exactly_one_h1() -> None:
|
|
body = "\n* [Hooks](hooks.md) - How hooks fire.\n"
|
|
missing = STRICT_V1.index.violations(
|
|
ROOT_HEAD + body, is_root=True, expected_targets={"hooks.md"}
|
|
)
|
|
assert [violation.code for violation in missing] == ["index_heading_missing"]
|
|
|
|
two = ROOT_HEAD + "\n# Wiki\n\n# Again\n" + body
|
|
extra = STRICT_V1.index.violations(two, is_root=True, expected_targets={"hooks.md"})
|
|
assert [violation.code for violation in extra] == ["index_heading_extra"]
|
|
assert extra[0].subject == "# Again"
|
|
|
|
|
|
def test_a_second_level_heading_is_prose_not_a_heading() -> None:
|
|
"""Only `# ` is the heading; `## ` is body text the strict shape forbids."""
|
|
text = ROOT_HEAD + "\n# Wiki\n\n## Section\n"
|
|
codes = [v.code for v in STRICT_V1.index.violations(text, is_root=True, expected_targets=set())]
|
|
assert codes == ["index_prose_not_allowed"]
|
|
|
|
|
|
# --- the reader: prose ----------------------------------------------------
|
|
|
|
|
|
def test_strict_index_admits_nothing_but_the_heading_and_the_entries() -> None:
|
|
text = ROOT_HEAD + "\n# Wiki\n\nA sentence the generator would never write.\n"
|
|
found = STRICT_V1.index.violations(text, is_root=True, expected_targets=set())
|
|
assert [v.code for v in found] == ["index_prose_not_allowed"]
|
|
assert found[0].subject == "A sentence the generator would never write."
|
|
|
|
|
|
def test_prose_is_the_catalog_shape_and_stays_expressible() -> None:
|
|
"""Their index requires progressive-disclosure prose and no per-entry
|
|
description — the opposite of the wiki on both counts, and both have to be
|
|
constructible."""
|
|
second_brain = IndexPolicy(
|
|
name="index.md",
|
|
link_template="- [{label}]({target})",
|
|
link_pattern=DEFAULT.index.link_pattern,
|
|
per_directory=True,
|
|
heading_required=True,
|
|
allows_prose=True,
|
|
root_frontmatter=("okf_version", "okf_layout"),
|
|
)
|
|
text = "# Notes\n\nWhat lives here and why.\n\n- [A note](a.md)\n"
|
|
assert second_brain.violations(text) == ()
|
|
assert second_brain.entries_match_directory is False
|
|
|
|
|
|
# --- the reader: directory match, both directions -------------------------
|
|
|
|
|
|
def test_the_match_is_bidirectional_and_names_each_side() -> None:
|
|
text = ROOT_HEAD + "\n# Wiki\n\n* [Hooks](hooks.md) - How hooks fire.\n"
|
|
found = STRICT_V1.index.violations(
|
|
text, is_root=True, expected_targets={"skills.md", "concepts/index.md"}
|
|
)
|
|
assert [(v.subject, v.code) for v in found] == [
|
|
("concepts/index.md", "index_entry_missing"),
|
|
("hooks.md", "index_entry_unexpected"),
|
|
("skills.md", "index_entry_missing"),
|
|
]
|
|
|
|
|
|
def test_a_derived_index_cannot_be_judged_without_the_listing() -> None:
|
|
"""Skipping the check silently is the failure mode that matters: a gate
|
|
that quietly passes when its input is absent is worse than one that
|
|
raises."""
|
|
text = ROOT_HEAD + "\n# Wiki\n"
|
|
with pytest.raises(ValueError, match="listing"):
|
|
STRICT_V1.index.violations(text, is_root=True)
|
|
|
|
|
|
def test_an_authored_index_refuses_a_listing() -> None:
|
|
"""The catalog's rule, enforced rather than documented: a profile whose
|
|
index is authored cannot be asked to check it against the directory, so
|
|
code written to the wrong contract fails at the call instead of passing
|
|
every test anyone would think to write."""
|
|
with pytest.raises(ValueError, match="authored"):
|
|
DEFAULT.index.violations("- [Sales](ingest-sales.md)\n", expected_targets={"a.md"})
|
|
|
|
|
|
# --- the reader: root vs nested frontmatter -------------------------------
|
|
|
|
|
|
def test_the_root_index_pins_its_keys_in_order() -> None:
|
|
text = ROOT_HEAD + "\n# Wiki\n"
|
|
assert STRICT_V1.index.violations(text, is_root=True, expected_targets=set()) == ()
|
|
|
|
|
|
def test_a_root_index_missing_a_pinned_key_is_reported_per_key() -> None:
|
|
text = "---\nokf_version: 0.1\n---\n\n# Wiki\n"
|
|
found = STRICT_V1.index.violations(text, is_root=True, expected_targets=set())
|
|
assert [(v.subject, v.code) for v in found] == [
|
|
("bundle_profile", "index_root_key_missing"),
|
|
("okf_spec_commit", "index_root_key_missing"),
|
|
]
|
|
|
|
|
|
def test_the_pinned_keys_are_ordered_not_merely_present() -> None:
|
|
"""Their `ROOT_INDEX_KEY_ORDER` is what makes a regeneration byte-identical,
|
|
so an out-of-order root index is a finding even with every key present."""
|
|
text = "---\nbundle_profile: strict-v1\nokf_version: 0.1\nokf_spec_commit: d44368c\n---\n\n# Wiki\n"
|
|
found = STRICT_V1.index.violations(text, is_root=True, expected_targets=set())
|
|
assert [v.code for v in found] == ["index_root_key_order"]
|
|
|
|
|
|
# --- P1-F1: permitted-and-ordered is not the same claim as required -------
|
|
|
|
|
|
def test_a_permitted_root_key_may_be_omitted_without_a_finding() -> None:
|
|
"""P1-F1, measured 2026-07-31: `OKF_V0_2` names `okf_version` because
|
|
upstream §8/§12 say a bundle-root index MAY carry it — a permission, not a
|
|
demand. Judging the key as required made 14 of the 17 bundles swept fail
|
|
with exactly this one violation, upstream's own four reference bundles
|
|
among them, while D5's emitter treats the same key as optional (omitting
|
|
`root_frontmatter_values` writes no block). We emitted a MAY correctly and
|
|
graded it a MUST."""
|
|
text = "# Bundle\n\n- [Sales](ingest-sales.md)\n"
|
|
assert OKF_V0_2.index.violations(text, is_root=True) == ()
|
|
|
|
|
|
def test_the_permitted_key_is_still_ordered_and_still_root_only() -> None:
|
|
"""Separating the two meanings must not cost the checks that were right:
|
|
a nested index carrying the key is still a finding, and a root index that
|
|
carries it is still judged for order."""
|
|
nested = OKF_V0_2.index.violations("---\nokf_version: 0.2\n---\n\n# Concepts\n")
|
|
assert [(v.subject, v.code) for v in nested] == [
|
|
("okf_version", "index_root_frontmatter_unexpected")
|
|
]
|
|
root = OKF_V0_2.index.violations("---\nokf_version: 0.2\n---\n\n# Bundle\n", is_root=True)
|
|
assert root == ()
|
|
|
|
|
|
def test_a_required_root_key_is_still_reported_when_absent() -> None:
|
|
"""`STRICT_V1` mirrors the proving consumer's ratified contract, which
|
|
demands all three (they emit all three — measured across their bundle at
|
|
`c5141f8`). The fix separates the meanings; it does not relax them."""
|
|
found = STRICT_V1.index.violations(
|
|
"---\nokf_version: 0.1\n---\n\n# Wiki\n", is_root=True, expected_targets=set()
|
|
)
|
|
assert [(v.subject, v.code) for v in found] == [
|
|
("bundle_profile", "index_root_key_missing"),
|
|
("okf_spec_commit", "index_root_key_missing"),
|
|
]
|
|
|
|
|
|
def test_a_required_key_outside_the_ordered_set_fails_at_construction() -> None:
|
|
"""A key demanded but never named in the ordered tuple could never be
|
|
judged for position — the same contradiction `FrontmatterSchema` refuses
|
|
when `required` strays outside `allowed`."""
|
|
with pytest.raises(ValueError, match="ordered"):
|
|
IndexPolicy(
|
|
name="index.md",
|
|
link_template="- [{label}]({target})",
|
|
link_pattern=DEFAULT.index.link_pattern,
|
|
root_frontmatter=("okf_version",),
|
|
root_frontmatter_required=frozenset({"bundle_profile"}),
|
|
)
|
|
|
|
|
|
def test_a_nested_index_carries_no_frontmatter_at_all() -> None:
|
|
"""Confirmed independently in two consumers: the wiki writes frontmatter
|
|
only at the bundle root (`bundle.py:544-557`), and the catalog reports
|
|
their `okf_version`/`okf_layout` markers as root-exclusive. The asymmetry
|
|
is shape, not one repo's preference."""
|
|
found = STRICT_V1.index.violations(ROOT_HEAD + "\n# Concepts\n", expected_targets=set())
|
|
assert [(v.subject, v.code) for v in found] == [
|
|
("okf_version", "index_root_frontmatter_unexpected")
|
|
]
|
|
|
|
|
|
def test_a_nested_index_without_frontmatter_is_clean() -> None:
|
|
assert STRICT_V1.index.violations("# Concepts\n", expected_targets=set()) == ()
|
|
|
|
|
|
# --- determinism ----------------------------------------------------------
|
|
|
|
|
|
def test_violations_are_sorted_so_two_runs_agree() -> None:
|
|
text = "---\nokf_spec_commit: d\nokf_version: 0.1\n---\n\nz prose\na prose\n"
|
|
found = STRICT_V1.index.violations(text, is_root=True, expected_targets={"m.md"})
|
|
assert list(found) == sorted(found, key=lambda v: (v.subject, v.code))
|
|
assert found == STRICT_V1.index.violations(text, is_root=True, expected_targets={"m.md"})
|
|
|
|
|
|
def test_index_policy_stays_frozen() -> None:
|
|
with pytest.raises(Exception):
|
|
STRICT_V1.index.per_directory = False # type: ignore[misc]
|
|
|
|
|
|
def test_the_pattern_group_names_the_template_needs_are_present() -> None:
|
|
"""A template naming `{description}` whose pattern cannot recover one is a
|
|
pair that round-trips in only one direction."""
|
|
with pytest.raises(ValueError, match="description"):
|
|
IndexPolicy(
|
|
name="index.md",
|
|
link_template="* [{label}]({target}) - {description}",
|
|
link_pattern=re.compile(r"^\* \[(?P<label>[^\]]*)\]\((?P<target>[^)]+)\)$"),
|
|
)
|