okf._walk toleret en ulesbar/utenfor-basen lenke uten aa etterlate spor (okf.py:182
"continue # broken link"), og navigate_bundle returnerte kun filene den FANT. En base
der halve innholdet aldri ble lest var derfor umulig aa skille fra en base der de
dokumentene aldri ble skrevet - og toerrkjoeringen sa ingenting.
Toleransen er UROERT: OKF SPEC §4 krever at navigasjonen ikke kaster, og den kaster
fortsatt ikke. Dette er synlighet, ikke en ny nekt.
To tenner (samme form som ordre 20260821T092039Z, synlig uforankring):
1. okf.SkippedLink + Bundle.skipped - strukturert spor, aldri en streng: hvilken fil
lenken sto i, lenketeksten ORDRETT (operatoeren redigerer den teksten, ikke den
resolverte stien), og hvilken av de TO grunnene som gjaldt - outside-bundle (escape,
ofte bevisst) eller missing (inne i basen, ingen lesbar fil, nesten alltid en
skrivefeil). Dedup-grenen (canonical in seen) registreres ALDRI: den er korrekt
navigasjon og det som terminerer sykler.
2. run.skipped_links_notice - EN renderer, tar den alt opploeste tuppelen, returnerer
None naar ingenting ble hoppet over. Printes paa BEGGE flater: --live-dry-run og
den fulle enkeltkjoeringen (en kjoering som PRODUSERTE et forslag fra en halvlest
base er der tausheten kostet mest).
Defaulten er MOTSATT forrige ordres, og forskjellen er innsikten: cost_baseline_anchored
er paakrevd fordi begge defaults lyver, mens en TOM tuppel her er et aerlig positivt
utsagn ("hver lenke ble fulgt") - external_calls-presedensen. Vei-stien navigerer ingen
base, saa tom er bokstavelig sant der ogsaa.
Sporet bor paa RunResult.skipped_links (RUN-nivaa: navigasjonen skjer EN gang per
kjoering, foer noe forslag finnes), aldri paa ProvenanceStamp, som beskriver gaten som
doemte EN kandidat. Ingenting av dette naar bundle_context - derfor er de commons-eide
nav-goldenene byte-uendret, og Bundle( har fortsatt EN konstruksjons-sted (maalt).
Load-bearing MAALT (tests/test_navigation_visibility_loadbearing.py), aatte mutasjoner
alle roede mot HELE suiten + groenn kontroll 897 passed / 5 skipped:
detach missing-registreringen (6 roede) · detach outside-bundle (2) · kollaps de to
grunnene til en (2) · registrer dedup-grenen (1) · renderer returnerer alltid linja
(3, inkl. kontrollene - omisjonen er selv gatet) · detach dry-run-printen (1) ·
detach full-run-printen (1) · konstant tom trace ut av run_project (4).
Docs rettet der de paasto det motsatte: kunnskapsbase-for-en-kjoring.md §5.7 + §6,
presentasjon-bygge-kunnskapsbase.html (steg 8, steg 9, fallgruve 3, avslutningen),
README-ens navigasjonsavsnitt, og CLAUDE.md-ens navigasjons-kontrakt-invariant.
328 lines
15 KiB
Python
328 lines
15 KiB
Python
"""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
|