portfolio-optimiser/tests/test_okf.py
Kjell Tore Guttormsen 6b645ad32a feat(fase6): gate-promote approved verdicts back into the OKF wiki (Steg 8)
Close the last agentic-loop seam (målbilde §3/§6/§7/§11 step 6): an
APPROVED verdict is promoted from the raw output layer into the context
layer (the OKF bundle) as a navigable `type: verdict` concept file, so
human/persona-approved knowledge reaches the next run's hypothesis.

- okf.py (pure stdlib, MAF-free): render_frontmatter / write_concept_file
  / link_in_index — the D7-portable OKF write counterpart of navigate.
- verdicts.py: promote_verdict + PromotionRefused gate (fail-closed; only
  approved decisions enter the wiki, never raw agent output), provenance
  stamp (who/experiment/when; timestamp a required kwarg), neutral index
  label (signal reaches a prompt only via the gated ExpeL fold, never
  bundle_context), _safe_filename_token (id sanitised for path/link).
- R4 = optional+gated: a public opt-in primitive, NOT wired into
  run_project (mirrors write_verdict — the system reads, the gate promotes).
- Load-bearing trio (test_step8_promotion_loadbearing.py): gate refuses a
  non-approved verdict, approved verdict is navigable, promoted signal
  stays out of the read-context — all proven RED-on-detach. Suite 144->148.

Design hardened by an adversarial plan-critic (12 findings; the BLOCKER —
index-link leak into bundle_context via index_summary — closed by the
neutral label + a no-leak test). Honesty limits documented: promoted file
is minimal (signal as prose only), and the learning-key id means
same-candidate approvals share a filename (last-write-wins).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHR8iKxJRxDiDfNw8HZmWE
2026-06-30 11:06:28 +02:00

181 lines
8.5 KiB
Python

"""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}"