"""Door B writes the images beside the text, and says how many (0.10.0). The gate (`test_asset_gate.py`) asks whether the bytes arrive. This file pins the four properties that decide whether their arrival is trustworthy. - **A concept says how many images it carries.** `images: N`, conditional, so a document without figures pays nothing and every bundle built before this existed is byte-identical. Without the count a consumer cannot tell "this document had no figures" from "this build dropped them", which is the distinction the whole capability exists to restore. - **The key belongs to the profiles this repository owns.** `DEFAULT` states commons' ingest-spec SS 5 layer and `STRICT_V1` the wiki's ratified contract; naming a key in either from here is this repository editing someone else's contract (O2), the same reason `sources` sits on the segmented v0.2 profile alone. Under those profiles the images are still CARRIED and still POINTED at -- only the count is absent. - **`--no-assets` reproduces the pre-move bytes.** Every default this package has ever moved carries an opt-out that does, and the claim is measured on a whole bundle rather than asserted. - **The run log states the denominator.** SS 9's `log.md` already carries the gate's name and the file counts because they are the facts about a run that the bundle cannot otherwise recover. "51 images carried of 53 found" is the same class of fact, and without it a bundle whose figures were all refused looks exactly like a bundle of documents that had none. """ from __future__ import annotations import filecmp import warnings from pathlib import Path import pytest from llm_ingestion_okf import cli, corpus from llm_ingestion_okf.assets import ASSETS_DIR from llm_ingestion_okf.profiles import DEFAULT, SEGMENTED_OKF_V0_2, STRICT_V1, STRUCTURED_V1 FIXTURES = Path(__file__).parent / "fixtures" / "image-inbox" BUNDLE_ID = "asset-bundle-fixture" OKF_VERSION = "0.2" def _inbox(root: Path) -> Path: inbox = root / "inbox" (inbox / "graphics").mkdir(parents=True) for source in sorted(FIXTURES.rglob("*")): if source.is_file(): (inbox / source.relative_to(FIXTURES)).write_bytes(source.read_bytes()) return inbox def _build(inbox: Path, bundle: Path, *extra: str) -> int: with warnings.catch_warnings(): warnings.simplefilter("ignore") return cli.main( [ "build", str(inbox), "--bundle", str(bundle), "--bundle-id", BUNDLE_ID, "--okf-version", OKF_VERSION, *extra, ] ) def _concepts(bundle: Path) -> list[Path]: return [ path for path in sorted(bundle.rglob("*.md")) if path.name not in {"index.md", corpus.LOG_NAME} ] # --- the count ------------------------------------------------------------- def test_a_concept_that_carries_images_counts_them(tmp_path: Path) -> None: pytest.importorskip("pdfplumber") pytest.importorskip("llm_ingestion_guard") bundle = tmp_path / "bundle" assert _build(_inbox(tmp_path), bundle) == 0 counted = [path for path in _concepts(bundle) if "\nimages: " in path.read_text("utf-8")] assert counted, "no concept declared an image count" for path in counted: text = path.read_text("utf-8") line = next(row for row in text.splitlines() if row.startswith("images: ")) assert int(line.split(":", 1)[1]) == text.count("](/assets/") def test_a_concept_without_images_declares_no_count(tmp_path: Path) -> None: """Conditional, like `req_number`: absent is the document saying nothing.""" pytest.importorskip("llm_ingestion_guard") inbox = tmp_path / "inbox" inbox.mkdir() (inbox / "ren.md").write_text("# Kostnader\n\nIngen figurer her.\n", encoding="utf-8") bundle = tmp_path / "bundle" assert _build(inbox, bundle) == 0 for path in _concepts(bundle): assert "images:" not in path.read_text("utf-8") def test_the_key_is_named_by_the_profiles_this_repository_owns() -> None: assert "images" in STRUCTURED_V1.frontmatter.order assert "images" in SEGMENTED_OKF_V0_2.frontmatter.order # O2: naming a key in either of these is editing a contract owned elsewhere. assert "images" not in DEFAULT.frontmatter.order assert "images" not in STRICT_V1.frontmatter.order # --- the opt-out reproduces the pre-move bytes ----------------------------- def test_no_assets_writes_no_assets_directory(tmp_path: Path) -> None: pytest.importorskip("pdfplumber") pytest.importorskip("llm_ingestion_guard") bundle = tmp_path / "bundle" assert _build(_inbox(tmp_path), bundle, "--no-assets") == 0 assert not (bundle / ASSETS_DIR).exists() for path in _concepts(bundle): assert "](/assets/" not in path.read_text("utf-8") def test_the_two_runs_differ_in_the_asset_layer_and_nowhere_else(tmp_path: Path) -> None: """A document with NO images is byte-identical under both settings. The measurement that matters for every consumer who has already built a bundle: turning the capability on must move nothing in a corpus that has no pictures in it. Asserted on files rather than on a count, because a count is insensitive to exactly the change this would hide. """ pytest.importorskip("llm_ingestion_guard") inbox = tmp_path / "inbox" inbox.mkdir() (inbox / "ren.md").write_text( "# Kostnader\n\nIngen figurer.\n\n## Pris\n\nEn tabell uten bilde.\n", encoding="utf-8" ) on, off = tmp_path / "on", tmp_path / "off" assert _build(inbox, on) == 0 assert _build(inbox, off, "--no-assets") == 0 names = sorted(path.relative_to(on) for path in on.rglob("*") if path.is_file()) assert names == sorted(path.relative_to(off) for path in off.rglob("*") if path.is_file()) for name in names: if name.name == corpus.LOG_NAME: continue # the log names the setting on purpose assert filecmp.cmp(on / name, off / name, shallow=False), name # --- the log states the denominator ---------------------------------------- def test_the_log_carries_images_found_and_carried(tmp_path: Path) -> None: pytest.importorskip("pdfplumber") pytest.importorskip("llm_ingestion_guard") bundle = tmp_path / "bundle" assert _build(_inbox(tmp_path), bundle) == 0 log = (bundle / corpus.LOG_NAME).read_text("utf-8") assert "**Images**:" in log assert " of " in log.split("**Images**:", 1)[1].splitlines()[0] def test_the_log_names_the_capability_as_off_when_it_is(tmp_path: Path) -> None: """`--no-assets` is stated, never implied by a missing line. The same rule `--gate none` follows: a bundle built without screening says `NOTHING WAS SCREENED` rather than leaving a reader to infer it from an absent bullet. A bundle whose figures were never looked for must not read as a bundle of documents that had none. """ pytest.importorskip("llm_ingestion_guard") inbox = tmp_path / "inbox" inbox.mkdir() (inbox / "ren.md").write_text("# Kostnader\n\nIngen figurer.\n", encoding="utf-8") bundle = tmp_path / "bundle" assert _build(inbox, bundle, "--no-assets") == 0 log = (bundle / corpus.LOG_NAME).read_text("utf-8") assert "NOT CARRIED" in log