portfolio-optimiser/tests/test_okf.py
Kjell Tore Guttormsen 0d50ab89d3 feat(okf): conform the context seam to the pulled navigation + stamp-integrity contracts
The commons pull (7aa53fc -> a2b57d2) rewrote method-spec §3 Step 1 and added two §11
seams. Measuring okf.py against the new normative text found six contradictions; this
closes all six, gated by the commons-owned nav-goldens that came with the pull.

method-spec §3 Step 1 (navigate_bundle / bundle_context):
- follow cross-links RECURSIVELY, depth-first in first-seen order (there was no
  recursion at all — only the root index's links were read, so no hierarchy was
  navigable even with the other fixes in place);
- resolve a leading `/` against the BUNDLE ROOT, anything else against the LINKING
  file's directory, and drop the retired "a path separator means out-of-bundle"
  heuristic, which conflated depth with escape and forbade valid nesting;
- de-duplicate on the RESOLVED path (`./a.md` == `a.md` == `/a.md`), which is also
  what terminates cycles;
- exclude index files by BASENAME at every level, so a nested index is navigation and
  never renders as content (flat rendering regardless of depth);
- bind index_summary to the ROOT index alone.

safe_resolve stays the sole in-/out-of-bundle test, fail-closed: a target that fails to
resolve for ANY reason is skipped, never raised.

ingest-spec §3 (write_concept_file): it is the repo's one authoring primitive that
materialises a concept file from caller-supplied frontmatter, so it now refuses the
COMPLETE ownership stamp (`generated: true` + `ingest_manifest`) with IngestStampError,
while permitting either field alone. A validation, never a repair — nothing is written.

Gates (tests/test_okf.py, 529 -> 537):
- nav-golden-hierarchy and nav-golden-escape compared against the shipped
  expected-read-context.md fasit (trailing-whitespace normalisation only, which the
  fixture README explicitly permits; internal blank-line structure stays gated);
- traversal order pinned separately from the rendered output, so a right-looking render
  from a wrong walk still fails;
- unit seams for the recursion in isolation, resolved-path dedup, and the leading-`/`
  rule's breach case (a real out-of-bundle file addressed by its absolute path).

Load-bearing MEASURED, not asserted: seven mutations each go red — detach the recursion,
restore the separator prefilter, dedup on the raw target, read `/` as filesystem-absolute,
render nested index bodies as content, drop the stamp guard, and the fully naive navigator
with no boundary check (which is what makes the `/`-trap test bite). okf.py restored from a
checksum-verified copy after each.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WetWTHpdRbqinN5XHFTaTb
2026-07-31 21:07:29 +02:00

470 lines
24 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
import ast
import os
from pathlib import Path
import pytest
from portfolio_optimiser import okf
# Framework-neutral, D7-portable modules that must never import MAF/mcp (C2:
# the guard previously scanned only okf.py; dimension.py is now covered too).
_MAF_FREE_MODULES = [
"okf.py",
"dimension.py",
"outbox.py",
"costsim.py",
"hitl.py",
"notify.py",
"semretrieval.py",
]
_EXAMPLES_DIR = Path(__file__).resolve().parents[1] / "shared" / "examples"
BUNDLE_DIR = _EXAMPLES_DIR / "bygg-energi-mikro"
def _normalise_trailing_ws(text: str) -> str:
"""The ONLY normalisation the nav-golden gate applies, and the README explicitly permits it
("byte-exact or after trailing-whitespace normalization"): per-line trailing whitespace and the
file's terminating newline. Internal blank-line structure is NOT normalised — the serialisation
shape (``## {type}: {title}`` + blank line + body, sections separated by one blank line) stays
gated, so a renderer that drops or doubles a separator still goes RED."""
return "\n".join(line.rstrip() for line in text.rstrip("\n").split("\n"))
def _nav_golden(case: str) -> tuple[str, str]:
"""A commons-owned nav-golden case: ``(bundle_dir, expected_read_context)``. The fixture class
is bundle-in / read-context-out — the only shape that can express method-spec §3 Step 1's
cross-implementation property "two conformant implementations MUST produce an identical
read-context from the same bundle"."""
root = _EXAMPLES_DIR / case
return str(root / "bundle"), (root / "expected-read-context.md").read_text(encoding="utf-8")
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_nav_golden_hierarchy_read_context_matches_commons_fasit() -> None:
"""CONFORMANCE GATE (method-spec §3 Step 1, commons-owned nav-golden). The positive case: the
rendered read-context of a HIERARCHICAL bundle must equal the shipped ``expected-read-context.md``
— the cross-implementation fasit. One assertion covers the whole Q3 navigation contract at once:
recursive depth-first traversal, both link forms, resolved-path dedup, cycle termination, the
root-only binding of the missing-index rule, recursive verdict exclusion, and FLAT rendering
(nested index bodies are navigation, never content)."""
bundle_dir, expected = _nav_golden("nav-golden-hierarchy")
context = okf.bundle_context(okf.navigate_bundle(bundle_dir))
assert _normalise_trailing_ws(context) == _normalise_trailing_ws(expected)
def test_nav_golden_hierarchy_traversal_is_depth_first_first_seen() -> None:
"""The fasit above pins the RENDERED output; this pins the TRAVERSAL that produced it, so a
renderer that accidentally reproduced the right text from a wrong walk still goes RED. Order is
the commons README's trace verbatim: root index -> overview -> /a/index -> a/doc-a ->
a/verdict-nested (REACHED, then excluded by type) -> a/b/index -> a/b/doc-b, with ``/a/index.md``
and ``/overview.md`` deduped on their RESOLVED paths (cycle termination). ``c/orphan.md`` is
unreachable — a link-following navigator never sees it, a directory-walking one wrongly would,
and ``c/`` having no ``index.md`` is NOT an error (that rule binds the bundle root alone)."""
bundle_dir, _ = _nav_golden("nav-golden-hierarchy")
bundle = okf.navigate_bundle(bundle_dir)
assert [f.name for f in bundle.files] == [
"index.md",
"overview.md",
"a/index.md",
"a/doc-a.md",
"a/verdict-nested.md",
"a/b/index.md",
"a/b/doc-b.md",
]
# Verdict exclusion is a TYPE check on each reached file, applied recursively — never a property
# of the link graph: the nested verdict IS navigated, and IS kept out of the context.
assert [f.name for f in bundle.verdicts] == ["a/verdict-nested.md"]
assert [f.name for f in bundle.context_files] == ["overview.md", "a/doc-a.md", "a/b/doc-b.md"]
def test_nav_golden_escape_read_context_matches_commons_fasit() -> None:
"""CONFORMANCE GATE, negative case (a gate that can only pass proves nothing). Every link but
the first escapes the bundle; a conformant navigator skips them all, reads none of them, RAISES
NOTHING, and still returns the one valid sibling. The decoy really exists one level up."""
bundle_dir, expected = _nav_golden("nav-golden-escape")
bundle = okf.navigate_bundle(bundle_dir)
assert [f.name for f in bundle.files] == ["index.md", "valid.md"]
context = okf.bundle_context(bundle)
assert _normalise_trailing_ws(context) == _normalise_trailing_ws(expected)
# The decoy's CONTENT, not its filename: the name legitimately appears in the index body as the
# (skipped) link target, so asserting on the name would pass for a navigator that read the file.
decoy = (_EXAMPLES_DIR / "nav-golden-escape" / "SHOULD-NOT-BE-READ.md").read_text(
encoding="utf-8"
)
assert decoy.split("---")[-1].strip() not in context
def test_navigate_follows_links_recursively(tmp_path) -> None:
"""§11 "Navigation boundary", the RECURSION on its own: a pure same-directory chain
``index -> a.md -> b.md``. Deliberately flat, so it is isolated from link-form and boundary
concerns — a navigator that only reads the ROOT index's links returns ``[index, a]`` and goes
RED here even though every target is a legal same-dir sibling."""
(tmp_path / "index.md").write_text("---\ntype: index\n---\n\n- [A](a.md)\n", encoding="utf-8")
(tmp_path / "a.md").write_text(
"---\ntype: project\n---\n\nA body\n\n- [B](b.md)\n", encoding="utf-8"
)
(tmp_path / "b.md").write_text("---\ntype: reference\n---\n\nB body\n", encoding="utf-8")
bundle = okf.navigate_bundle(str(tmp_path))
assert [f.name for f in bundle.files] == ["index.md", "a.md", "b.md"]
def test_navigate_dedups_on_resolved_path(tmp_path) -> None:
"""Dedup is on the RESOLVED path, not the raw target string: ``./a.md``, ``a.md`` and ``/a.md``
are one entry. A navigator de-duplicating raw targets reads (and renders) the same file three
times and goes RED."""
(tmp_path / "index.md").write_text(
"---\ntype: index\n---\n\n- [1](a.md)\n- [2](./a.md)\n- [3](/a.md)\n", encoding="utf-8"
)
(tmp_path / "a.md").write_text("---\ntype: project\n---\n\nA body\n", encoding="utf-8")
bundle = okf.navigate_bundle(str(tmp_path))
assert [f.name for f in bundle.files] == ["index.md", "a.md"]
def test_navigate_root_relative_link_is_bundle_root_not_filesystem(tmp_path) -> None:
"""§11 "Navigation boundary", the ratified leading-``/`` rule and its breach. A leading ``/``
denotes the BUNDLE ROOT, never a filesystem-absolute path — so an index carrying the real
absolute path of a file OUTSIDE the bundle resolves to ``{bundle}/{that path}`` (missing ->
skipped), and the outside file is never opened. An implementation reading ``/`` as
filesystem-absolute exfiltrates it: that is the path-traversal breach the escape golden's
``/etc/passwd`` trap describes, made assertable here with a file this test owns."""
outside = tmp_path / "outside.md"
outside.write_text("---\ntype: secret\n---\n\nEXFIL-SENTINEL\n", encoding="utf-8")
bundle_dir = tmp_path / "bundle"
bundle_dir.mkdir()
(bundle_dir / "index.md").write_text(
f"---\ntype: index\n---\n\n- [trap]({outside.resolve().as_posix()})\n", encoding="utf-8"
)
bundle = okf.navigate_bundle(str(bundle_dir))
assert [f.name for f in bundle.files] == ["index.md"] # resolved under the bundle -> missing
assert all("EXFIL-SENTINEL" not in f.body for f in bundle.files)
assert "EXFIL-SENTINEL" not in okf.bundle_context(bundle)
def _dimension_bundle(tmp_path) -> str:
(tmp_path / "index.md").write_text(
"---\ntype: index\n---\n\n# Bundle\n\n"
"- [energi](energi-method.md)\n"
"- [asfalt](asfalt-method.md)\n"
"- [shared](shared-note.md)\n"
"- [verdict](verdict-x.md)\n",
encoding="utf-8",
)
(tmp_path / "energi-method.md").write_text(
"---\ntype: methodology\ndimension: energi\n---\n\nENERGI-SENTINEL body\n", encoding="utf-8"
)
(tmp_path / "asfalt-method.md").write_text(
"---\ntype: methodology\ndimension: asfalt\n---\n\nASFALT-SENTINEL body\n", encoding="utf-8"
)
(tmp_path / "shared-note.md").write_text(
"---\ntype: reference\n---\n\nSHARED-SENTINEL body\n", encoding="utf-8"
)
(tmp_path / "verdict-x.md").write_text(
"---\ntype: verdict\ndimension: energi\n---\n\nVERDICT-SENTINEL body\n", encoding="utf-8"
)
return str(tmp_path)
def test_bundle_context_dimension_filter(tmp_path) -> None:
"""SC7 forutsetning: with ``dimension="energi"`` only energi-marked + unmarked concept files
render; an asfalt-marked file is omitted. ``dimension=None`` is byte-identical to the no-arg
call (backward compat — protects the verdict-exclusion + step7/8 load-bearing tests).
``type: verdict`` stays excluded in every case."""
bundle = okf.navigate_bundle(_dimension_bundle(tmp_path))
scoped = okf.bundle_context(bundle, dimension="energi")
assert "ENERGI-SENTINEL" in scoped # energi-marked concept file rendered
assert "SHARED-SENTINEL" in scoped # unmarked knowledge is never dropped
assert "ASFALT-SENTINEL" not in scoped # other-dimension file filtered out
assert "VERDICT-SENTINEL" not in scoped # verdict layer still excluded
default = okf.bundle_context(bundle)
assert okf.bundle_context(bundle, dimension=None) == default # None == today, byte-identical
assert "ASFALT-SENTINEL" in default # no filter -> asfalt present
assert "VERDICT-SENTINEL" not in default # verdict still excluded
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_navigate_skips_null_byte_link(tmp_path) -> None:
"""OKF SPEC §4 (okf.py:8-9): a broken link is NEVER raised. A null byte is an INVALID PATH
COMPONENT, so path resolution raises ``ValueError`` — which MUST be absorbed fail-closed
(skipped), not propagated out of ``navigate_bundle``. This is the *malformed* sub-class of
method spec §3 Step 1's "a target that fails to resolve for ANY reason (missing file, invalid
path component, escape) is skipped, not raised" (§11 row "Navigation boundary").
The rationale rests on the RESOLVE guard alone, never on the retired "a path separator means
out-of-bundle" heuristic: this test stays green — and its reason stays true — once that
heuristic goes and nested targets become legal."""
(tmp_path / "index.md").write_text(
"---\ntype: index\n---\n\nSee [bad](a\x00b.md) and [ok](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)) # must not raise
names = {f.name for f in bundle.files}
assert names == {"index.md", "real.md"} # null-byte target silently skipped
def test_navigate_skips_bundle_escaping_symlink_link(tmp_path) -> None:
"""OKF SPEC §4 + fail-closed path-safety: a same-dir link whose target is a symlink escaping the
bundle is skipped (``safe_resolve`` raises ``PathSecurityError`` -> ``_load_file`` -> None),
never read. Closes the untested path-safety-at-link-resolution branch of ``navigate_bundle``."""
outside = tmp_path / "outside.md"
outside.write_text("---\ntype: secret\n---\n\nEXFIL\n", encoding="utf-8")
bundle_dir = tmp_path / "bundle"
bundle_dir.mkdir()
(bundle_dir / "index.md").write_text(
"---\ntype: index\n---\n\nSee [escape](evil.md).\n", encoding="utf-8"
)
os.symlink(str(outside), str(bundle_dir / "evil.md"))
bundle = okf.navigate_bundle(str(bundle_dir))
names = {f.name for f in bundle.files}
assert names == {"index.md"} # escaping symlink target refused, never navigated
assert all("EXFIL" not in f.body for f in bundle.files)
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 test_write_concept_file_refuses_complete_ingest_stamp(tmp_path) -> None:
"""§11 "Stamp integrity (curated writers)" (ingest-spec §3, third bullet). The ingest stamp is
the SOLE mark separating ingest-owned files from curated ones — and re-materialization deletes
what carries it. ``write_concept_file`` materialises a concept file from CALLER-SUPPLIED
frontmatter, so it is exactly the authoring primitive that could forge the stamp; it MUST refuse
the COMPLETE stamp and write NOTHING. A validation, never a repair: the file must not appear
stamp-stripped either."""
fm = {"type": "reference", "generated": "true", "ingest_manifest": "bygg@0123456789abcdef"}
with pytest.raises(okf.IngestStampError):
okf.write_concept_file(str(tmp_path), "forged.md", fm, "body\n")
assert not (tmp_path / "forged.md").exists() # refused, not silently repaired
def test_write_concept_file_permits_either_stamp_field_alone(tmp_path) -> None:
"""The check is on the COMPLETE stamp, never on the individual field names: curated content may
legitimately carry a single provenance field, and a genuine verbatim round-trip is preserved.
Both halves alone are written unchanged."""
okf.write_concept_file(str(tmp_path), "a.md", {"type": "reference", "generated": "true"}, "b\n")
okf.write_concept_file(
str(tmp_path),
"b.md",
{"type": "reference", "ingest_manifest": "bygg@0123456789abcdef"},
"b\n",
)
assert okf.parse_frontmatter(tmp_path / "a.md")["generated"] == "true"
assert okf.parse_frontmatter(tmp_path / "b.md")["ingest_manifest"] == "bygg@0123456789abcdef"
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_link_in_index_success_preserves_existing_bytes_and_order(tmp_path) -> None:
"""LOAD-BEARING (ingest-spec §6 byte-preservation, Step-8 promotion invariant): on the SUCCESS
path (a link IS added), ``link_in_index`` preserves the pre-existing index byte-for-byte AND in
order, appending ONLY the new bullet. Existing coverage asserts byte-equality solely on the
REFUSAL path (``test_step8_promotion_loadbearing.py`` Test A) and mere line MEMBERSHIP elsewhere,
so a writer that kept every link but reordered/rewrote the existing body would pass the whole
suite. The existing links are deliberately NON-sorted so a reordering mutation (e.g. ``sorted``)
flips this RED."""
original = (
"---\ntype: index\n---\n\n# Bundle\n\n"
"- [zeta](zeta.md)\n- [alpha](alpha.md)\n- [mid](mid.md)\n"
)
(tmp_path / "index.md").write_text(original, encoding="utf-8")
added = okf.link_in_index(str(tmp_path), "promoted-verdict-x.md", "Promotert")
assert added is True
result = (tmp_path / "index.md").read_text(encoding="utf-8")
assert result == original + "- [Promotert](promoted-verdict-x.md)\n"
def _dynamic_import_targets(node: ast.Call) -> list[str]:
"""Name any dynamic-import call: ``__import__(...)`` or ``<anything>.import_module(...)``.
Shared by the MAF-free guard here and the no-network guard in
``tests/test_semretrieval_loadbearing.py`` — both walk imports statically, and both are blind
to a dynamic import by construction."""
func = node.func
if isinstance(func, ast.Name) and func.id == "__import__":
return ["__import__"]
if isinstance(func, ast.Attribute) and func.attr == "import_module":
return ["import_module"]
return []
@pytest.mark.parametrize("module_name", _MAF_FREE_MODULES)
def test_okf_is_maf_free(module_name: str) -> None:
"""D7 portability: each framework-neutral module IMPORTS no ``agent_framework`` / ``mcp`` (a
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. Parametrized over
``_MAF_FREE_MODULES`` so ``dimension.py`` is guarded alongside ``okf.py`` (C2)."""
src = (
Path(__file__).resolve().parents[1] / "src" / "portfolio_optimiser" / module_name
).read_text(encoding="utf-8")
imported: list[str] = []
dynamic: 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 "")
elif isinstance(node, ast.Call):
dynamic += _dynamic_import_targets(node)
forbidden = [m for m in imported if m.split(".")[0] in {"agent_framework", "mcp"}]
assert forbidden == [], f"{module_name} must not import MAF/mcp, found: {forbidden}"
# A RATCHET, green today: none of these modules imports ``importlib`` or calls ``__import__``.
# The sweep above walks only ``ast.Import``/``ast.ImportFrom``, so a single
# ``importlib.import_module("portfolio_optimiser.verdicts")`` would sail straight past it and
# pull MAF into a module this guard certifies as MAF-free. Any dynamic import is refused
# outright rather than argument-inspected: a computed target cannot be judged statically.
assert dynamic == [], (
f"{module_name} performs dynamic import(s) {dynamic} — the MAF-free guard is a STATIC "
"check and cannot see through them; use a normal import so it stays enforceable"
)