"""Requirement 6 — `profile` threaded through `materialize_bundle`'s disk phase. `OKF_V0_2` landed in D2, but nothing outside the library could ask for it: the content phase already accepted `profile`, while `materialize_bundle` neither took one nor passed one down. The parameter is keyword-only behind the `*` the signature already carried, so every three-positional call site stays source-compatible — which is what the consumer who asked for it needs. Two of these tests would be untestable by construction if written only against the shipped profiles: `OKF_V0_2.paths is DEFAULT.paths` and `.index is DEFAULT.index`, so no assertion can tell `profile.index.name` apart from `DEFAULT.index.name` when they are the same object. That identity is what makes the change byte-neutral, and it is also what would let six of the nine threading sites stay hard-coded with every shipped-profile test still green. `_SYNTHETIC` exists to close exactly that gap: it renames the index and the concept files, so a site still reading `DEFAULT` writes to the wrong path and fails. It is a test instrument, never a supported profile. What this does NOT claim: the materializer honours `profile.types` (validation still runs against `DEFAULT` in `manifest.py`; both policies compare equal today, so nothing is hidden — but the parameter does not reach it), nor `IndexPolicy`'s judging fields (`per_directory`, `entries_match_directory`, `root_frontmatter`). `STRICT_V1` sets all three, so passing it here produces a bundle that does not meet its own profile. Supported here: `DEFAULT` and `OKF_V0_2`. """ from __future__ import annotations import json from dataclasses import replace from pathlib import Path from typing import Any import pytest from llm_ingestion_okf.materialize import materialize_bundle from llm_ingestion_okf.profiles import DEFAULT, OKF_V0_2, BundleProfile INGESTED_AT = "2026-07-16T12:00:00Z" # Renames both halves the disk phase reads: a threading site left on `DEFAULT` # writes `ingest-orders.md` / `index.md` instead, and the assertions below say # so by name rather than by a byte diff that cannot point at the cause. _SYNTHETIC = replace( OKF_V0_2, paths=replace(DEFAULT.paths, ingest_prefix="x-", concept_suffix=".mdx"), index=replace(DEFAULT.index, name="contents.md"), ) def _manifest_data() -> dict[str, Any]: return { "manifest_version": 1, "source": {"type": "file", "id": "catalogue-1", "root": "data"}, "bundle_summary": "A test bundle.", "extractions": [ { "id": "orders", "title": "Orders", "query": "orders.csv", "okf_type": "dataset", "max_rows": 100, } ], } @pytest.fixture def file_setup(tmp_path: Path) -> tuple[Path, Path]: """A manifest + CSV catalogue in tmp/src, and an empty bundle target path.""" src = tmp_path / "src" src.mkdir(parents=True) manifest_path = src / "manifest.json" manifest_path.write_text(json.dumps(_manifest_data()), encoding="utf-8") (src / "data").mkdir() (src / "data" / "orders.csv").write_text("a,b\n1,x\n2,y\n", encoding="utf-8", newline="") return manifest_path, tmp_path / "bundle" # --- the profile reaches the content phase --- def test_v0_2_profile_reaches_the_emitted_frontmatter(file_setup: tuple[Path, Path]) -> None: """`_render_concept_file` took `profile` since D2; the call site did not pass one, so `OKF_V0_2` was unreachable through the only public door. """ manifest_path, bundle = file_setup materialize_bundle(manifest_path, bundle, INGESTED_AT, profile=OKF_V0_2) text = (bundle / "ingest-orders.md").read_text(encoding="utf-8") assert f"generated: {{ by: process:okf-ingest, at: {INGESTED_AT} }}" in text assert "sources: [{ id: catalogue-1, resource: data }]" in text assert "generated: true" not in text def test_default_run_is_unchanged_by_the_new_parameter(file_setup: tuple[Path, Path]) -> None: """Byte-neutrality at the signature: passing `DEFAULT` explicitly and passing nothing must produce the same bundle, or the default is not the default. """ manifest_path, bundle = file_setup materialize_bundle(manifest_path, bundle, INGESTED_AT) implicit = (bundle / "ingest-orders.md").read_bytes() index_implicit = (bundle / "index.md").read_bytes() explicit_dir = bundle.parent / "bundle-explicit" materialize_bundle(manifest_path, explicit_dir, INGESTED_AT, profile=DEFAULT) assert (explicit_dir / "ingest-orders.md").read_bytes() == implicit assert (explicit_dir / "index.md").read_bytes() == index_implicit assert b"generated: true" in implicit def test_profile_is_keyword_only(file_setup: tuple[Path, Path]) -> None: """The parameter sits behind the `*` the signature already had. A positional fourth argument must not be accepted, or a later reordering would silently rebind existing call sites. """ manifest_path, bundle = file_setup with pytest.raises(TypeError): materialize_bundle(manifest_path, bundle, INGESTED_AT, DEFAULT) # type: ignore[misc] # --- A-E5: two runs into the SAME directory --- def test_second_v0_2_run_into_the_same_directory_succeeds( file_setup: tuple[Path, Path], ) -> None: """A-E5, end to end. The emitter and the ownership predicate are coupled through the stamp value; `OKF_V0_2` changes that value. If the predicate is not given the same profile the emitter used, the library stops recognising its own output and the §3 collision gate fires `collision_unstamped` on the files the previous run wrote. One run compared byte for byte cannot see it. """ manifest_path, bundle = file_setup materialize_bundle(manifest_path, bundle, INGESTED_AT, profile=OKF_V0_2) first = (bundle / "ingest-orders.md").read_bytes() first_index = (bundle / "index.md").read_bytes() materialize_bundle(manifest_path, bundle, INGESTED_AT, profile=OKF_V0_2) assert (bundle / "ingest-orders.md").read_bytes() == first assert (bundle / "index.md").read_bytes() == first_index # --- the paths/index half of the profile reaches disk --- def test_profile_paths_and_index_name_reach_disk(file_setup: tuple[Path, Path]) -> None: """Six of the nine threading sites read `paths`/`index`, which are the same objects on every shipped profile. `_SYNTHETIC` renames both so the sites are provable rather than merely regression-covered. """ manifest_path, bundle = file_setup result = materialize_bundle(manifest_path, bundle, INGESTED_AT, profile=_SYNTHETIC) assert (bundle / "x-orders.mdx").is_file() assert not (bundle / "ingest-orders.md").exists() assert (bundle / "contents.md").is_file() assert not (bundle / "index.md").exists() assert [path.name for path in result.written] == ["x-orders.mdx"] assert "- [Orders](x-orders.mdx)" in (bundle / "contents.md").read_text(encoding="utf-8") def test_second_synthetic_run_replaces_rather_than_colliding( file_setup: tuple[Path, Path], ) -> None: """The §3 ownership scan globs `{ingest_prefix}*{concept_suffix}` and excludes `index.name`. Left on `DEFAULT`, the glob finds nothing under a renamed prefix and suffix, `owned` is empty, and the gate refuses to overwrite the file this same code just wrote. The index must also not gain a second link for the same target. """ manifest_path, bundle = file_setup materialize_bundle(manifest_path, bundle, INGESTED_AT, profile=_SYNTHETIC) first = (bundle / "x-orders.mdx").read_bytes() materialize_bundle(manifest_path, bundle, INGESTED_AT, profile=_SYNTHETIC) assert (bundle / "x-orders.mdx").read_bytes() == first index_text = (bundle / "contents.md").read_text(encoding="utf-8") assert index_text.count("- [Orders](x-orders.mdx)") == 1 def test_removed_extraction_is_unlinked_under_a_renamed_index( file_setup: tuple[Path, Path], tmp_path: Path ) -> None: """`_update_index_lines` runs only on an EXISTING index, so it is reached solely by a second run — and only its `link_pattern`/`render_link` are profile-owned. Dropping an extraction is what forces the removal branch. """ manifest_path, bundle = file_setup materialize_bundle(manifest_path, bundle, INGESTED_AT, profile=_SYNTHETIC) data = _manifest_data() data["extractions"][0]["id"] = "invoices" data["extractions"][0]["title"] = "Invoices" manifest_path.write_text(json.dumps(data), encoding="utf-8") (tmp_path / "src" / "data" / "invoices.csv").write_text( "a,b\n1,x\n", encoding="utf-8", newline="" ) materialize_bundle(manifest_path, bundle, INGESTED_AT, profile=_SYNTHETIC) index_text = (bundle / "contents.md").read_text(encoding="utf-8") assert "- [Invoices](x-invoices.mdx)" in index_text assert "x-orders.mdx" not in index_text assert not (bundle / "x-orders.mdx").exists() def test_synthetic_profile_is_not_a_shipped_profile() -> None: """Guards the instrument: if a future profile ever shares these overrides, the two tests above stop proving anything and would go quietly green. """ assert isinstance(_SYNTHETIC, BundleProfile) assert _SYNTHETIC.paths is not DEFAULT.paths assert _SYNTHETIC.index is not DEFAULT.index assert OKF_V0_2.paths is DEFAULT.paths # `OKF_V0_2.index` stopped being DEFAULT's object at D5, which named # `okf_version` in its `root_frontmatter`. That is NOT the distinctness this # guard is about: every field that carries a NAME — the index filename and # the concept filenames — is still shared, so a threading bug that passed # the wrong profile would still emit identical bytes under either. Object # identity was only ever a proxy for that; assert the thing itself, or this # guard would go green on a change that leaves the instrument blind. assert OKF_V0_2.index.name == DEFAULT.index.name assert OKF_V0_2.index.link_template == DEFAULT.index.link_template assert _SYNTHETIC.index.name != DEFAULT.index.name assert _SYNTHETIC.paths.concept_suffix != DEFAULT.paths.concept_suffix