"""OKF bundle navigation (okf.py) — the framework-neutral, D7-portable context seam. These tests pin the minimal navigation the Step-1 ExpeL wiring depends on: read ``index.md``, follow intra-bundle cross-links, parse each file's frontmatter, classify by ``type``, and locate the candidate IR projection. Robustness (OKF SPEC §4): broken links are tolerated, never raised. No ``agent_framework``/``mcp`` import is allowed in ``okf`` — guarded by ``test_okf_is_maf_free``. """ from __future__ import annotations from pathlib import Path from portfolio_optimiser import okf BUNDLE_DIR = Path(__file__).resolve().parents[1] / "shared" / "examples" / "bygg-energi-mikro" def test_navigate_classifies_every_typed_file() -> None: """The energi bundle resolves to its six typed OKF files (index + project + hypothesis + methodology + reference + verdict), each carrying its declared ``type``.""" bundle = okf.navigate_bundle(str(BUNDLE_DIR)) types = {f.name: f.type for f in bundle.files} assert types == { "index.md": "index", "bygg-kontor-nord.md": "project", "tiltak-led-retrofit.md": "hypothesis", "metode-ipmvp-a.md": "methodology", "kilder-realiseringsgap.md": "reference", "verdict-led-fro.md": "verdict", } def test_navigate_exposes_verdicts_and_hypothesis() -> None: """The navigation surfaces exactly one ``type: verdict`` file (the ExpeL seed) and the candidate hypothesis — the two the Step-1 wiring keys on.""" bundle = okf.navigate_bundle(str(BUNDLE_DIR)) assert [f.name for f in bundle.verdicts] == ["verdict-led-fro.md"] assert bundle.verdicts[0].frontmatter["realization_rate"] == "0.82" assert bundle.hypothesis is not None assert bundle.hypothesis.name == "tiltak-led-retrofit.md" def test_navigate_index_summary_is_progressive_disclosure() -> None: """``index_summary`` is the index body (the progressive-disclosure entry point), not stuffing the whole bundle.""" bundle = okf.navigate_bundle(str(BUNDLE_DIR)) assert "progressiv disclosure" in bundle.index_summary.lower() def test_bundle_context_excludes_verdict_layer() -> None: """Fase 2b LOAD-BEARING (okf level): ``bundle_context`` renders the concept files for the agent prompt but EXCLUDES the ``type: verdict`` file, so the realization signal reaches a prompt only via the gated ExpeL fold — never by stuffing it into the read-context (målbilde §2/§4).""" bundle = okf.navigate_bundle(str(BUNDLE_DIR)) assert {f.type for f in bundle.context_files} == { "project", "hypothesis", "methodology", "reference", } context = okf.bundle_context(bundle) assert "0.82" not in context # the verdict's realization signal is NOT stuffed in assert ( bundle.verdicts[0].frontmatter["realization_rate"] == "0.82" ) # though it IS in the bundle assert "## hypothesis:" in context # concept files ARE rendered (progressive disclosure) assert "progressiv disclosure" in context.lower() # the index summary is the entry point def test_navigate_tolerates_broken_links(tmp_path) -> None: """OKF SPEC §4: a consumer MUST tolerate broken links. An index linking a missing file navigates without raising, simply omitting the absent target.""" (tmp_path / "index.md").write_text( "---\ntype: index\n---\n\nSee [gone](missing.md) and [here](real.md).\n", encoding="utf-8", ) (tmp_path / "real.md").write_text("---\ntype: project\n---\n\nbody\n", encoding="utf-8") bundle = okf.navigate_bundle(str(tmp_path)) names = {f.name for f in bundle.files} assert names == {"index.md", "real.md"} # missing.md silently skipped, no raise def test_load_ir_projection_returns_candidate_ir() -> None: """The bundle's IR projection (``validator-input.json``) is the candidate measure's cost-IR — the pre-hypothesis ExpeL query key source.""" ir = okf.load_ir_projection(str(BUNDLE_DIR)) assert ir["project_id"] == "BYGG-KONTOR-NORD" assert [a["code"] for a in ir["affected_items"]] == ["ENERGI-TOTAL-EL"] assert ir["claimed_saving_nok"] == 30000 def test_parse_frontmatter_reads_scalar_fields() -> None: """The minimal frontmatter reader returns the leading ``---`` block as key:value strings.""" fm = okf.parse_frontmatter(BUNDLE_DIR / "verdict-led-fro.md") assert fm["type"] == "verdict" assert fm["decision"] == "approved_with_adjustment" def test_render_frontmatter_roundtrips_consumed_fields(tmp_path) -> None: """Step-8 writer: ``render_frontmatter`` + ``write_concept_file`` emit a block that ``parse_frontmatter`` re-reads with the fields ``seed_store_from_bundle`` consumes (``type``, ``decision``, ``description``) intact. NOT a bijection — only these scalar fields are guaranteed to survive write -> read.""" fm = { "type": "verdict", "decision": "approved", "description": "LED-retrofit godkjent (realiseringsgrad=0.57)", "verdict_id": "abc123", } okf.write_concept_file(str(tmp_path), "promoted-verdict-abc123.md", fm, "body prose\n") parsed = okf.parse_frontmatter(tmp_path / "promoted-verdict-abc123.md") assert parsed["type"] == "verdict" assert parsed["decision"] == "approved" assert parsed["description"] == "LED-retrofit godkjent (realiseringsgrad=0.57)" assert parsed["verdict_id"] == "abc123" def test_render_frontmatter_single_lines_scalars(tmp_path) -> None: """A multi-line rationale must NOT corrupt the line-oriented frontmatter block (parse stops at ``---``). Newlines in a scalar value are flattened to spaces, so every following key survives.""" fm = { "type": "verdict", "description": "line one\nline two\n---\nnot a delimiter", "decision": "approved", } okf.write_concept_file(str(tmp_path), "f.md", fm, "body\n") parsed = okf.parse_frontmatter(tmp_path / "f.md") assert "\n" not in parsed["description"] assert parsed["decision"] == "approved" # the trailing key was NOT lost to a spurious --- def _minimal_bundle(tmp_path) -> str: (tmp_path / "index.md").write_text( "---\ntype: index\n---\n\n# Bundle\n\n- [proj](bygg.md)\n", encoding="utf-8" ) (tmp_path / "bygg.md").write_text("---\ntype: project\n---\n\nbody\n", encoding="utf-8") return str(tmp_path) def test_link_in_index_makes_concept_file_navigable(tmp_path) -> None: """Step-8 writer: a file written into a bundle is reachable by ``navigate_bundle`` only after ``link_in_index`` adds an intra-bundle cross-link the navigator follows (``_LINK_RE``).""" bundle_dir = _minimal_bundle(tmp_path) fm = {"type": "verdict", "decision": "approved", "description": "d"} okf.write_concept_file(bundle_dir, "promoted-verdict-x.md", fm, "body\n") assert "promoted-verdict-x.md" not in {f.name for f in okf.navigate_bundle(bundle_dir).files} added = okf.link_in_index(bundle_dir, "promoted-verdict-x.md", "Promotert") assert added is True bundle = okf.navigate_bundle(bundle_dir) assert "promoted-verdict-x.md" in {f.name for f in bundle.files} assert [f.name for f in bundle.verdicts] == ["promoted-verdict-x.md"] def test_link_in_index_is_idempotent(tmp_path) -> None: """Linking the same target twice adds exactly one bullet (returns ``False`` the second time) — so re-promoting an existing verdict does not double-link the index.""" bundle_dir = _minimal_bundle(tmp_path) assert okf.link_in_index(bundle_dir, "promoted-verdict-x.md", "A") is True assert okf.link_in_index(bundle_dir, "promoted-verdict-x.md", "B") is False body = (tmp_path / "index.md").read_text(encoding="utf-8") assert body.count("(promoted-verdict-x.md)") == 1 def test_okf_is_maf_free() -> None: """D7 portability: ``okf.py`` IMPORTS no ``agent_framework`` / ``mcp`` (the docstring may name them to document the constraint, exactly as ``retrieval.py`` does) — checked via the AST, not a raw substring, so the prose claim doesn't trip the guard.""" import ast src = ( Path(__file__).resolve().parents[1] / "src" / "portfolio_optimiser" / "okf.py" ).read_text(encoding="utf-8") imported: list[str] = [] for node in ast.walk(ast.parse(src)): if isinstance(node, ast.Import): imported += [a.name for a in node.names] elif isinstance(node, ast.ImportFrom): imported.append(node.module or "") forbidden = [m for m in imported if m.split(".")[0] in {"agent_framework", "mcp"}] assert forbidden == [], f"okf.py must not import MAF/mcp, found: {forbidden}"