"""Door C carries the sender's OWN metadata into the index — and nothing else. The measured defect this closes, as data. vegnormal-okf ran a pre-registered reasoning bake-off on 2026-08-27 over the same bundle through two index shapes: the arm reading DEFAULT's `index.md` scored 0 hits of 8, the arm reading a faceted index of the same frontmatter scored 25 of 29. The DEFAULT arm did not answer wrongly — it abstained, because the index it was given is a link list and the metadata never reaches it. Measured on their `index.md`: 30 974 characters over 269 requirements, 0 occurrences of any of the eight facts their concepts carry. The mechanism already existed (`FacetPolicy`, `STRUCTURED_V1`); it simply did not reach Door C's import path. WHY THIS DOOR NEEDS ITS OWN ANSWER, and not Door B's. Door B DERIVES structure from documents this library's own operator dropped. Door C merges documents a THIRD PARTY wrote, verbatim, and the whole door is built on refusing to put anything of ours inside their bytes. Deriving structure for a sender and publishing it in our index would break that guarantee in the one place nobody would look for it: not in the concept file, which stays verbatim, but in the index entry ABOUT the concept, where our inference would read as their claim. So Door C PROJECTS and never DERIVES. Every facet in a Door C index entry is a value the sender declared in their own frontmatter, copied. The projection is key-agnostic by construction — it asks the policy which keys to carry and never what a key means — which is also why it does not care whether a document is a numbered norm or a meeting note. """ from __future__ import annotations from dataclasses import replace from pathlib import Path from test_import_flow import EXTERNAL, INGESTED_AT, AUTOMATIC, StubImportGate, place from llm_ingestion_okf.importer import ImportResult, import_bundle from llm_ingestion_okf.profiles import DEFAULT, STRUCTURED_V1, BundleProfile, FacetPolicy # A profile whose facet policy also names `title`. Door C's index LABEL is the # concept path, which for a sender naming files by UUID carries no title at all # — so the title has to travel as a facet or not at all. Built here rather than # added to `STRUCTURED_V1` because that profile is Door B's shipped contract and # widening its facet set would move Door B's index bytes. TITLED = replace( STRUCTURED_V1, index=replace( STRUCTURED_V1.index, facets=FacetPolicy(keys=("title", *STRUCTURED_V1.index.facets.keys)), ), ) def run_with( tmp_path: Path, gate: StubImportGate, *, profile: BundleProfile, ) -> tuple[ImportResult, Path]: bundle = tmp_path / "bundle" result = import_bundle( tmp_path / "source", bundle, INGESTED_AT, origin=EXTERNAL, channel=AUTOMATIC, gate=gate, profile=profile, ) return result, bundle def index_of(bundle: Path, profile: BundleProfile) -> str: return (bundle / profile.index.name).read_text(encoding="utf-8") # --- the projection ------------------------------------------------------- def test_door_c_carries_the_senders_declared_facets_into_the_index(tmp_path: Path) -> None: # The 0/8 case, in one document: the sender declares the facts, and the # index the reasoning arm is handed now states them. place( tmp_path / "source", "krav/n500-3-1.md", "---\ntype: dataset\nnumber: N500\nstatus: gjeldende\ndate: 2024-06-01\n---\n\nBody.\n", ) _, bundle = run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) assert index_of(bundle, STRUCTURED_V1) == ( "- [krav/n500-3-1](import-krav-n500-3-1.md)" " — number: N500; status: gjeldende; date: 2024-06-01\n" ) def test_a_facet_key_the_policy_names_is_projected_whatever_it_means(tmp_path: Path) -> None: # The projection asks the POLICY which keys to carry. It has no opinion # about any particular key, which is what lets a consumer whose concepts are # named by UUID get the title into the index without a change here. place( tmp_path / "source", "0f9a.md", "---\ntype: dataset\ntitle: Vegtunneler\nnumber: N500\n---\n\nBody.\n", ) _, bundle = run_with(tmp_path, StubImportGate(), profile=TITLED) assert index_of(bundle, TITLED) == ( "- [0f9a](import-0f9a.md) — title: Vegtunneler; number: N500\n" ) # --- what the door refuses to claim --------------------------------------- def test_door_c_derives_nothing_the_sender_did_not_declare(tmp_path: Path) -> None: # This body is exactly what Door B's deriver reads a number and a title off. # Door C must read neither: an inference of ours, printed in an index entry # about someone else's document, reads as their claim. The concept file is # verbatim either way — the attribution is what would have been forged. place( tmp_path / "source", "b.md", "---\ntype: dataset\nstatus: gjeldende\n---\n\n# N500 Vegtunneler\n\nSe N100.\n", ) _, bundle = run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) index = index_of(bundle, STRUCTURED_V1) assert index == "- [b](import-b.md) — status: gjeldende\n" assert "N500" not in index assert "references" not in index def test_the_senders_own_derived_list_travels_verbatim(tmp_path: Path) -> None: # `derived` is the key that says which of the facts before it were INFERRED # rather than read. When the sender carries one, the index must carry theirs # unchanged: that is the whole ownership stamp at this door — the reader can # see which claims are the sender's own inference, and none are ours. place( tmp_path / "source", "c.md", "---\ntype: dataset\nnumber: N130\nstatus: gjeldende\n" "derived: [number, status]\n---\n\nBody.\n", ) _, bundle = run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) assert index_of(bundle, STRUCTURED_V1) == ( "- [c](import-c.md) — number: N130; status: gjeldende; derived: [number, status]\n" ) # --- content that is not a numbered norm ---------------------------------- def test_an_unnumbered_document_still_gets_the_facets_it_has(tmp_path: Path) -> None: # Operator directive 2026-08-27: everything built around OKF must work for # ALL content, so a design that only works for numbered norms is wrong even # when it scores well on N100/N200/N500. A meeting note has no number, no # parent and no version — and must still reach the index carrying what it # does have, with no half-written separator for what it does not. place( tmp_path / "source", "referat-styringsgruppe.md", "---\ntype: dataset\nstatus: utkast\ndate: 2026-08-27\n---\n\nProse, ingen nummerering.\n", ) _, bundle = run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) assert index_of(bundle, STRUCTURED_V1) == ( "- [referat-styringsgruppe](import-referat-styringsgruppe.md)" " — status: utkast; date: 2026-08-27\n" ) def test_a_document_declaring_none_of_the_facets_renders_the_bare_link(tmp_path: Path) -> None: # The floor of the same rule: a sender who declares nothing the policy names # gets the line they would have got without facets at all — never a # separator with nothing after it. place(tmp_path / "source", "d.md", "---\ntype: dataset\n---\n\nBody.\n") _, bundle = run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) assert index_of(bundle, STRUCTURED_V1) == "- [d](import-d.md)\n" # --- what must not move --------------------------------------------------- def test_the_default_profile_leaves_door_c_byte_identical(tmp_path: Path) -> None: # 171 branch bases were built through this door. `profile` defaults to # DEFAULT, and DEFAULT names no facets, so an existing call site emits the # bytes it always did — proven against the same frontmatter that WOULD # produce a facet tail under a faceted profile. place( tmp_path / "source", "e.md", "---\ntype: dataset\nnumber: N500\nstatus: gjeldende\n---\n\nBody.\n", ) bundle = tmp_path / "bundle" import_bundle( tmp_path / "source", bundle, INGESTED_AT, origin=EXTERNAL, channel=AUTOMATIC, gate=StubImportGate(), ) assert index_of(bundle, DEFAULT) == "- [e](import-e.md)\n" def test_the_concept_file_is_still_written_verbatim_under_a_faceted_profile( tmp_path: Path, ) -> None: # The verbatim guarantee is what makes projection safe, so it is pinned on # the faceted path too: it is `index.md` that gains bytes, never the file # the sender wrote. text = "---\ntype: dataset\nnumber: N500\n---\n\nBody.\n" place(tmp_path / "source", "f.md", text) _, bundle = run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) assert (bundle / "import-f.md").read_text(encoding="utf-8") == text def test_an_unrenderable_facet_value_drops_the_facet_not_the_concept(tmp_path: Path) -> None: # A value carrying the policy's own joiner cannot be rendered. Door C's # tolerance is structural — it judges no shape and refuses no sender on form # — so the concept still merges verbatim. But a claim the sender made that # our index cannot show is exactly the thing that must not vanish quietly, # so it is reported per concept and per key, like an unverified pointer. place( tmp_path / "source", "g.md", "---\ntype: dataset\nnumber: N500\nstatus: utkast; til horing\n---\n\nBody.\n", ) result, bundle = run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) assert [entry.concept_path for entry in result.merged] == ["g.md"] assert (bundle / "import-g.md").is_file() assert index_of(bundle, STRUCTURED_V1) == "- [g](import-g.md) — number: N500\n" assert [(entry.concept_path, entry.key) for entry in result.unrendered_facets] == [ ("g.md", "status") ] # --- the entry must not outlive the facts it states ------------------------ def test_a_reimported_concept_updates_its_index_entry_rather_than_going_stale( tmp_path: Path, ) -> None: # Measured, not assumed. A flat entry carries only a label and a target, # both stable, so it could never disagree with the file it points at. An # entry that carries the concept's FACTS can, and this is the path that # gets there: the collision gate refuses an updated concept outright, so # the operator's only way to accept an update is to remove the merged file # (the code's own refusal message says so) and import again. The concept # then becomes `gjeldende` on disk while the index still says `utkast`. # # An index that contradicts the bundle it indexes is worse than one that # says nothing, because a reasoning arm reads the index and stops. source = tmp_path / "source" place(source, "a.md", "---\ntype: dataset\nnumber: N500\nstatus: utkast\n---\n\nBody.\n") _, bundle = run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) assert index_of(bundle, STRUCTURED_V1) == ( "- [a](import-a.md) — number: N500; status: utkast\n" ) (bundle / "import-a.md").unlink() place(source, "a.md", "---\ntype: dataset\nnumber: N500\nstatus: gjeldende\n---\n\nBody.\n") run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) assert "gjeldende" in (bundle / "import-a.md").read_text(encoding="utf-8") assert index_of(bundle, STRUCTURED_V1) == ( "- [a](import-a.md) — number: N500; status: gjeldende\n" ) def test_refreshing_an_entry_leaves_curated_prose_around_it_untouched(tmp_path: Path) -> None: # The index is the one file this library writes beside somebody else's # prose. A refresh keyed on the managed pattern must rewrite the one line it # owns and nothing else — including a line that merely MENTIONS the target. source = tmp_path / "source" place(source, "a.md", "---\ntype: dataset\nstatus: utkast\n---\n\nBody.\n") _, bundle = run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) index_path = bundle / STRUCTURED_V1.index.name index_path.write_text( "# Katalog\n\nSe ogsaa import-a.md i teksten.\n\n" + index_path.read_text(encoding="utf-8"), encoding="utf-8", ) (bundle / "import-a.md").unlink() place(source, "a.md", "---\ntype: dataset\nstatus: gjeldende\n---\n\nBody.\n") run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) assert index_of(bundle, STRUCTURED_V1) == ( "# Katalog\n\nSe ogsaa import-a.md i teksten.\n\n- [a](import-a.md) — status: gjeldende\n" ) def test_an_unfaceted_profile_keeps_the_early_return_it_always_had(tmp_path: Path) -> None: # The refresh is scoped to the facets feature by construction: a policy # naming no facets has nothing that can go stale, so its idempotent-by- # target behaviour must be byte-identical to what it always was — including # leaving a hand-edited label alone. from llm_ingestion_okf.materialize import link_in_index bundle = tmp_path / "bundle" bundle.mkdir() (bundle / "index.md").write_text("- [Hand Edited](import-a.md)\n", encoding="utf-8") link_in_index(bundle, "import-a.md", "a", profile=DEFAULT) assert (bundle / "index.md").read_text(encoding="utf-8") == "- [Hand Edited](import-a.md)\n" # --- properties that were pinned only as a side effect --------------------- def test_the_door_writes_into_the_profiles_namespace_not_defaults(tmp_path: Path) -> None: # Every profile shipped today reuses `DEFAULT.paths`, so no existing test # could tell a threaded profile from a hard-coded DEFAULT here — the # mutation that reverts the threading survived the whole suite. That makes # this a property held by coincidence rather than by test, which is the one # kind of green that means nothing. A profile owning its own namespace says # it directly. profile = replace( STRUCTURED_V1, paths=replace(STRUCTURED_V1.paths, import_prefix="ekstern-"), ) place(tmp_path / "source", "a.md", "---\ntype: dataset\nnumber: N500\n---\n\nBody.\n") _, bundle = run_with(tmp_path, StubImportGate(), profile=profile) assert (bundle / "ekstern-a.md").is_file() assert not (bundle / "import-a.md").exists() assert index_of(bundle, profile) == "- [a](ekstern-a.md) — number: N500\n" def test_a_key_the_sender_declared_empty_produces_no_facet(tmp_path: Path) -> None: # Senders leave keys in with nothing after the colon. An empty value must # not become `status: ` in the index — a facet stating nothing is worse than # an absent one, because it reads as a value the sender chose. place( tmp_path / "source", "a.md", "---\ntype: dataset\nnumber: N500\nstatus:\ndate:\n---\n\nBody.\n", ) _, bundle = run_with(tmp_path, StubImportGate(), profile=STRUCTURED_V1) assert index_of(bundle, STRUCTURED_V1) == "- [a](import-a.md) — number: N500\n"