"""A faceted index: the entry carries the metadata its document carries. The measured defect this closes, as data. ms-ai-architect ran a pre-registered bake-off on 2026-08-26 over 55 documents and 40 gold questions; every arm hit 40/40, so retrieval quality could not separate them. The only axis that did separate them was trap exposure — 18/20 for the OKF-index arm against 8/20 for a frontmatter head-scan — and the reason was measured on both sides independently: the flat DEFAULT index carries title/date/status/supersedes 0 times while the documents in the same bundle carry them 55/55/55/5. The metadata IS in the bundle. The index throws it away. A facet policy is what lets an index keep it, at a cost the index arm can still afford. DEFAULT is untouched, and that is not caution: DEFAULT states commons' ingest-spec §6 layer, so changing its bytes from here would be this repo editing another repo's contract. Facets arrive as a new profile, additively. """ from __future__ import annotations import re import pytest from llm_ingestion_okf.profiles import DEFAULT, STRICT_V1, STRUCTURED_V1, FacetPolicy, IndexPolicy def test_default_carries_no_facets_and_renders_exactly_what_it_did() -> None: # The byte-level proof that this feature is additive. assert DEFAULT.index.facets is None assert DEFAULT.index.render_link("A Title", "inbox-a.md") == "- [A Title](inbox-a.md)" assert DEFAULT.index.entry_pattern is DEFAULT.index.link_pattern assert STRICT_V1.index.facets is None def test_a_faceted_policy_renders_present_facets_in_policy_order() -> None: policy = STRUCTURED_V1.index line = policy.render_link( "N500 Vegbygging", "inbox-n500-vegbygging.md", facets={"status": "gjeldende", "number": "N500"}, ) assert line == ( "- [N500 Vegbygging](inbox-n500-vegbygging.md) — number: N500; status: gjeldende" ) def test_facet_order_follows_the_policy_not_the_mapping() -> None: # Two callers passing the same facts must emit the same bytes; a dict # preserves insertion order, so ordering by the mapping would make a golden # depend on how a caller happened to build its argument. policy = STRUCTURED_V1.index forwards = policy.render_link("T", "a.md", facets={"number": "N1", "status": "x"}) backwards = policy.render_link("T", "a.md", facets={"status": "x", "number": "N1"}) assert forwards == backwards def test_an_entry_with_no_facet_values_renders_as_the_bare_link() -> None: # A separator with nothing after it is a half-written entry, and it would # cost every entry in a bundle that declares nothing. assert STRUCTURED_V1.index.render_link("T", "a.md", facets={}) == "- [T](a.md)" assert STRUCTURED_V1.index.render_link("T", "a.md") == "- [T](a.md)" def test_a_facet_the_policy_does_not_name_is_refused() -> None: # Silently dropping it would put a value in a file no reader of this # contract looks at — the same posture the root frontmatter takes. with pytest.raises(ValueError, match="not named"): STRUCTURED_V1.index.render_link("T", "a.md", facets={"invented": "x"}) def test_offering_facets_to_a_policy_that_has_none_is_refused() -> None: with pytest.raises(ValueError, match="carries no facets"): DEFAULT.index.render_link("T", "a.md", facets={"number": "N1"}) @pytest.mark.parametrize("bad", ["a; b", "a — b", "a\nb"]) def test_a_facet_value_that_would_break_the_grammar_is_refused_never_repaired(bad: str) -> None: # Validation, not repair — the standing posture everywhere in this library. with pytest.raises(ValueError, match="separator|joiner|single-line"): STRUCTURED_V1.index.render_link("T", "a.md", facets={"status": bad}) # --- round trip ----------------------------------------------------------- def test_render_and_parse_round_trip() -> None: policy = STRUCTURED_V1.index facets = {"number": "N500", "status": "gjeldende", "references": "[N200, N300?]"} line = policy.render_link("N500 Vegbygging", "inbox-n500.md", facets=facets) entry = policy.parse_entry(line) assert entry is not None assert entry.label == "N500 Vegbygging" assert entry.target == "inbox-n500.md" assert entry.facets == facets def test_a_bare_link_still_parses_under_a_faceted_policy() -> None: # An index built before facets existed must keep working: its lines are # managed lines with no facets, not unmanaged prose. entry = STRUCTURED_V1.index.parse_entry("- [A](inbox-a.md)") assert entry is not None assert entry.facets == {} def test_prose_is_not_an_entry() -> None: assert STRUCTURED_V1.index.parse_entry("Some curated prose about inbox-a.md.") is None assert DEFAULT.index.parse_entry("# Heading") is None def test_entry_pattern_is_anchored_to_the_whole_line() -> None: # Index maintenance keys on this pattern to decide what it may rewrite; # a substring match would let it edit curated prose. assert STRUCTURED_V1.index.entry_pattern.match("prefix - [A](a.md)") is None # --- construction guards -------------------------------------------------- def test_a_facet_policy_needs_at_least_one_key() -> None: with pytest.raises(ValueError, match="at least one"): FacetPolicy(keys=()) def test_a_link_pattern_that_is_not_line_anchored_cannot_carry_facets() -> None: # The faceted pattern is built from this one, so an unanchored base would # silently produce an unanchored faceted pattern. with pytest.raises(ValueError, match="anchored"): IndexPolicy( name="index.md", link_template="- [{label}]({target})", link_pattern=re.compile(r"^- \[(?P