"""A knowledge base that was only HALF read SAYS so — a structured trace, and one line when it fired. ``okf._walk`` tolerates a cross-link it cannot follow, exactly as OKF SPEC §4 requires: it skips and never raises. **That tolerance is correct and is not what this file changes.** What it changes is that the skip left NO TRACE. ``navigate_bundle`` returned only the files it FOUND, so a base whose other half was never reached is indistinguishable from a base where those documents were never written — and ``--live-dry-run`` exited 0 in silence over both. Measured (session 51, 2026-08-21, against ``b639722``): ``okf.py:182`` was ``continue # broken link: tolerated, never raised (OKF §4)``, ``Bundle`` carried ``dir`` and ``files`` alone, and ``grep -rn "Bundle(" src tests`` returned exactly ONE construction site (``okf.py:203``, inside ``navigate_bundle``) — so the trace has a single producer and cannot be forged by a second constructor. Same defect class and same SHAPE as order ``20260821T092039Z`` (visible un-anchoring, delivered in ``156312c``): a tolerance that is right, plus an absence that is not. Two teeth: 1. ``okf.SkippedLink`` + ``Bundle.skipped`` — a STRUCTURED trace, never a string, because "which document is missing" and "why" are two different operative questions (kø-(y)). ``_walk`` has TWO distinct skip reasons and they mean different things: ``outside-bundle`` (the target resolves outside the bundle root — often a deliberate link to a neighbouring base) and ``missing`` (it resolves INSIDE and no readable file is there — almost always a typo in the link). The third branch, ``canonical in seen``, is DE-DUPLICATION: correct behaviour, also what terminates cycles, and never a skip — arm (d) exists to keep it out of the trace. 2. ``run.skipped_links_notice`` — ONE renderer, taking the already-resolved value, returning ``None`` when nothing was skipped (omission, never an empty row — ``mandate.announce``'s rule, reused by ``cost_baseline_notice``). **The default DIFFERS from the previous order's, and that difference is the insight.** ``ProvenanceStamp.cost_baseline_anchored`` is REQUIRED with no default because both defaults lie: ``True`` claims an anchoring that may never have happened, ``False`` under-claims a real one. Here the honest reading is the opposite: an EMPTY tuple is a positive statement — "every cross-link was followed" — in the same class as ``ProvenanceStamp.external_calls`` ("nothing outside this process was contacted"). A caller that constructs a ``Bundle`` without a trace is not withholding a fact; it is stating one. So ``skipped`` defaults to ``()``, and the road path (which navigates nothing) is honestly empty rather than dishonestly required to invent a value. Arms: (a) a MISSING target yields exactly one entry, carrying its own reason; (b) an ESCAPING target yields exactly one entry, carrying the OTHER reason — asserted on the structured ``reason`` field, never on shared prose (the 08-09 class), and the two reasons are asserted to DIFFER so a single collapsed reason cannot pass both; (c) an intact base yields an EMPTY tuple and NO line (the control — without it (a) passes on a constant), with the navigation proved to have happened first; (d) the dedup branch (a repeated link, and a cycle) yields NO entry at all; (e) both CLI surfaces carry it — ``--live-dry-run`` and the full run — plus the typed carriers. The commons-owned ``nav-golden-escape`` fasit is a free independent witness that the SEMANTICS did not move: every link but one escapes there, and it must still render byte-identically. """ from __future__ import annotations import json import shutil from pathlib import Path import pytest from conftest import SyntheticUsageChatClient from portfolio_optimiser import okf, run from portfolio_optimiser.okf import SkippedLink from portfolio_optimiser.run import DryRunReport, RunResult, run_project, skipped_links_notice _DATA = Path(__file__).resolve().parents[1] / "src" / "portfolio_optimiser" / "data" / "bundles" INTACT_BUNDLE = _DATA / "bygg-energi-mikro-a" _VERDICT_INPUT = {"decision": "approved", "rationale": "expert reviewed (sim)"} #: A reply the pre-amendment fixture's un-anchored gate accepts far enough to produce an outcome. _REPLY = json.dumps( { "measure": "LED-retrofit", "affected_items": [{"code": "ENERGI-TOTAL-EL", "quantity": 180000, "unit_cost": 1.0}], "claimed_saving_nok": 30000, } ) #: The phrase the notice carries and an intact run cannot: an intact run prints NO line at all. _SENTINEL = "NOT followed" #: The link an arm appends to a COPY of the fixture index — a name nothing in the bundle provides. _DANGLING = "fantes-aldri.md" def _factory(reply: str = _REPLY): def factory(role: str): return SyntheticUsageChatClient(default_reply=reply) return factory @pytest.fixture(autouse=True) def _isolate_model_env(monkeypatch: pytest.MonkeyPatch) -> None: """Hermetic env: the operator's Foundry overrides must not reach the CLI arms.""" monkeypatch.delenv("PORTFOLIO_MODEL_MAP", raising=False) monkeypatch.delenv("PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT", raising=False) def _write(path: Path, body: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(body, encoding="utf-8") def _bundle_copy(tmp_path: Path, *, dangling: bool) -> Path: """A throwaway copy of the repo-local fixture — the shipped bundle is never touched. With ``dangling`` the root index gains ONE link to a file that does not exist.""" dest = tmp_path / "bundle" shutil.copytree(INTACT_BUNDLE, dest) if dangling: index = dest / "index.md" index.write_text( index.read_text(encoding="utf-8") + f"\n- [Borte]({_DANGLING})\n", encoding="utf-8" ) return dest # --- Arm (a): a MISSING target is recorded, with its own reason ----------------------------------- def test_missing_target_is_recorded_as_one_entry(tmp_path) -> None: """RED: the base still navigates and still raises nothing (§4 tolerance UNTOUCHED) — but the link it could not follow is now on the bundle, naming the file it was written in, the link text VERBATIM, and why. Detach the recording and the walk goes silent again.""" _write(tmp_path / "index.md", "---\ntype: index\n---\n\n- [Borte](fantes-aldri.md)\n") bundle = okf.navigate_bundle(str(tmp_path)) assert [f.name for f in bundle.files] == ["index.md"] # tolerated, not raised assert bundle.skipped == ( SkippedLink(from_file="index.md", target="fantes-aldri.md", reason="missing"), ) # --- Arm (b): an ESCAPING target is recorded, with the OTHER reason ------------------------------- def test_escaping_target_is_recorded_with_a_different_reason(tmp_path) -> None: """The two skip branches mean different things and must not be collapsed: this target EXISTS, one level up, and is refused because it is outside the bundle root — not because it is absent. Asserted on the structured ``reason``, never on prose the two branches could share.""" _write(tmp_path / "outside.md", "---\ntype: reference\n---\n\nSHOULD NOT BE READ\n") root = tmp_path / "bundle" _write(root / "index.md", "---\ntype: index\n---\n\n- [Ute](../outside.md)\n") bundle = okf.navigate_bundle(str(root)) assert [f.name for f in bundle.files] == ["index.md"] assert bundle.skipped == ( SkippedLink(from_file="index.md", target="../outside.md", reason="outside-bundle"), ) def test_the_two_reasons_are_distinct_values(tmp_path) -> None: """A single collapsed reason would pass BOTH arms above if they were read in isolation. This pins the discrimination itself: same shape of bundle, two skips, two different values.""" _write(tmp_path / "outside.md", "---\ntype: reference\n---\n\nx\n") root = tmp_path / "bundle" _write( root / "index.md", "---\ntype: index\n---\n\n- [Borte](fantes-aldri.md)\n- [Ute](../outside.md)\n", ) reasons = [s.reason for s in okf.navigate_bundle(str(root)).skipped] assert len(reasons) == 2 assert reasons[0] != reasons[1] # --- Arm (c): the control — an intact base records nothing and prints nothing --------------------- def test_intact_bundle_records_nothing(tmp_path) -> None: """Causality control. Without it arm (a) would pass on an implementation that records a constant entry for every base. The navigation is proved to have HAPPENED first (two files reached), so the empty trace is a measured absence rather than a base that was never walked.""" _write(tmp_path / "index.md", "---\ntype: index\n---\n\n- [A](a.md)\n") _write(tmp_path / "a.md", "---\ntype: project\n---\n\nA body\n") bundle = okf.navigate_bundle(str(tmp_path)) assert [f.name for f in bundle.files] == ["index.md", "a.md"] # the walk really ran assert bundle.skipped == () assert skipped_links_notice(bundle.skipped) is None # --- Arm (d): de-duplication is NOT a skip -------------------------------------------------------- def test_dedup_and_cycles_produce_no_entry(tmp_path) -> None: """``canonical in seen`` is correct behaviour, not a failure: it is what makes a repeated link one entry and what terminates a cycle. An implementation that recorded every ``continue`` would report a healthy base as half-unread. Both forms are exercised: ``a.md`` is linked twice from the index (once as ``./a.md``, deduped on the RESOLVED path) and links back to the index.""" _write(tmp_path / "index.md", "---\ntype: index\n---\n\n- [A](a.md)\n- [A again](./a.md)\n") _write(tmp_path / "a.md", "---\ntype: project\n---\n\nA body\n\n- [Back](index.md)\n") bundle = okf.navigate_bundle(str(tmp_path)) assert [f.name for f in bundle.files] == ["index.md", "a.md"] # deduped + cycle terminated assert bundle.skipped == () # --- Arm (e): the renderer and both CLI surfaces -------------------------------------------------- def test_notice_is_rendered_only_when_something_was_skipped() -> None: """One renderer, two branches sharing NO wording: a non-empty trace returns a line carrying the sentinel AND the operative facts; an empty trace returns ``None`` (omitted, never an empty row). The reason token printed is the STRUCTURED value itself, so there is no second display vocabulary free to drift from the field (kø-(p)).""" rendered = skipped_links_notice( (SkippedLink(from_file="index.md", target="fantes-aldri.md", reason="missing"),) ) assert rendered is not None assert _SENTINEL in rendered assert "index.md" in rendered assert "fantes-aldri.md" in rendered assert "missing" in rendered assert skipped_links_notice(()) is None async def test_dry_run_report_carries_the_trace(tmp_path, fresh_store) -> None: """The dry-run type is the carrier for the surface the order measured: a run that stops before the first model call already knows what it could not read.""" assert "skipped_links" in DryRunReport.__dataclass_fields__ bundle = _bundle_copy(tmp_path, dangling=True) report = await run_project( "BYGG-ENERGI-MIKRO-A", "local", docs_dir=str(bundle), bundle_dir=str(bundle), verdict_input=_VERDICT_INPUT, client_factory=_factory(), store=fresh_store, live_dry_run=True, ) assert isinstance(report, DryRunReport) assert [s.target for s in report.skipped_links] == [_DANGLING] async def test_run_result_carries_the_trace(tmp_path, fresh_store) -> None: """The full run too: navigation happens ONCE per run, before any proposal exists, so the trace is a RUN-level fact carried on ``RunResult`` — not on the per-proposal ``ProvenanceStamp``, which describes the gate that judged one candidate.""" assert "skipped_links" in RunResult.__dataclass_fields__ bundle = _bundle_copy(tmp_path, dangling=True) result = await run_project( "BYGG-ENERGI-MIKRO-A", "local", docs_dir=str(bundle), bundle_dir=str(bundle), verdict_input=_VERDICT_INPUT, client_factory=_factory(), store=fresh_store, ) assert [s.target for s in result.skipped_links] == [_DANGLING] async def test_road_path_has_an_empty_trace(docs_dir, fresh_store) -> None: """The road path navigates no bundle, so "nothing was skipped" is literally true there — which is exactly why the empty tuple is an honest DEFAULT rather than a withheld fact.""" result = await run_project( "FV42-GSV-E1", "local", docs_dir=docs_dir, verdict_input=_VERDICT_INPUT, client_factory=_factory( json.dumps( { "measure": "Reduce scope", "affected_items": [{"code": "05.2", "quantity": 4300.0, "unit_cost": 215.0}], "claimed_saving_nok": 200000.0, } ) ), store=fresh_store, ) assert result.skipped_links == () def _dry_run_argv(bundle: Path) -> list[str]: return [ "BYGG-ENERGI-MIKRO-A", "--docs-dir", str(bundle), "--bundle-dir", str(bundle), "--live-dry-run", ] def test_cli_dry_run_announces_the_skipped_link(tmp_path, capsys) -> None: """RED (the measured defect, verbatim): a dry run over a base with an unfollowable cross-link exited 0 with nothing said. It now names the document it never reached.""" rc = run.main(_dry_run_argv(_bundle_copy(tmp_path, dangling=True))) assert rc == 0 out = capsys.readouterr().out assert _SENTINEL in out assert _DANGLING in out def test_cli_dry_run_says_nothing_when_every_link_was_followed(tmp_path, capsys) -> None: """Control: the same base with its links intact prints NO navigation line at all. A line for something the run does not have is omitted, never rendered blank.""" rc = run.main(_dry_run_argv(_bundle_copy(tmp_path, dangling=False))) assert rc == 0 out = capsys.readouterr().out assert _SENTINEL not in out assert "Knowledge base:" not in out def test_cli_full_run_announces_the_skipped_link(tmp_path, capsys) -> None: """The full-run surface too, through the offline scripted door — so the notice is a property of a RUN, not of the dry-run branch alone. A run that PRODUCED a proposal from a half-read base is the case where the silence cost the most.""" bundle = _bundle_copy(tmp_path, dangling=True) replies = tmp_path / "replies.json" replies.write_text( json.dumps({"proposer": _REPLY, "checker": "Holder. VERDICT: APPROVE"}), encoding="utf-8" ) rc = run.main( [ "BYGG-ENERGI-MIKRO-A", "--docs-dir", str(bundle), "--bundle-dir", str(bundle), "--scripted-replies", str(replies), ] ) assert rc == 0 out = capsys.readouterr().out assert _SENTINEL in out assert _DANGLING in out