feat(fase2b): OKF-navigated bundle context replaces chunk-stuffing

Closes the honest Fase 2a limitation: docs_dir==bundle_dir let keyword
chunk-stuffing leak the verdict's realization rate ("0.82") into the debate /
generation prompt regardless of the ExpeL fold (it surfaced from both
verdict-led-fro.md AND golden.json). The realization signal now reaches the
hypothesis prompt ONLY via the gated ExpeL fold.

- okf.py: bundle_context() + Bundle.context_files render the navigated bundle
  (index + frontmatter + cross-links) as the agent read-context, EXCLUDING
  type: verdict (maalbilde §2/§4). Pure stdlib, still MAF-free.
- datasource.py: bundle_citations() derives first-class citations from the
  navigated non-verdict files.
- run_project: on the bundle path context + citations + debate tools come from
  navigation (tools=[]; navigation replaces query-time RAG); the road path keeps
  chunk-stuffing unchanged.

Load-bearing (maalbilde §7): the marker is upgraded from the minted verdict id
to the realization signal itself. The empty-store control now asserts "0.82"
reaches NO prompt — RED against the pre-2b chunk-stuffing path, green after
navigation (TDD red->green). New okf-level test_bundle_context_excludes_verdict_layer
guards the seam directly.

Suite 133->134 passed, 4 skipped; mypy + ruff check clean. Reverted unrelated
ruff-format drift (backends/budget/verdicts/test_contracts) to keep the diff
surgical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHR8iKxJRxDiDfNw8HZmWE
This commit is contained in:
Kjell Tore Guttormsen 2026-06-30 06:42:19 +02:00
commit 8814a698c2
7 changed files with 117 additions and 29 deletions

View file

@ -48,6 +48,26 @@ def test_navigate_index_summary_is_progressive_disclosure() -> None:
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."""

View file

@ -31,13 +31,16 @@ _ENERGY_REPLY = (
)
_VERDICT_INPUT = {"decision": "approved", "rationale": "expert reviewed (sim)"}
# The load-bearing marker is the seed verdict's MINTED id (a 16-char content hash that
# ``ExpeLContextProvider.format_fewshot`` emits as ``- [<id>] ...``). It cannot appear in the
# bundle's raw text, so its presence in the prompt isolates the STRUCTURAL ExpeL path. (The
# realization rate "0.82" is NOT a usable marker here: ``docs_dir == bundle_dir``, so keyword
# chunk-stuffing reads the verdict file and would leak "0.82" regardless of the fold. Layer
# separation — verdicts out of the keyword docs folder — is Fase 2b.)
# Two load-bearing markers, by design:
# - the seed verdict's MINTED id (16-char content hash ``format_fewshot`` emits as ``- [<id>] ...``);
# it cannot appear in the bundle's raw text, so it isolates the STRUCTURAL ExpeL path.
# - the realization rate "0.82". Fase 2b replaced keyword chunk-stuffing with OKF-navigation that
# EXCLUDES ``type: verdict`` from the bundle context, so the signal now reaches a prompt ONLY via
# the gated ExpeL fold. Pre-2b (``docs_dir == bundle_dir``) chunk-stuffing leaked "0.82" from the
# verdict + golden files into the debate prompt regardless of the fold — which the empty-store
# control below asserts is no longer true (it goes RED against the pre-2b chunk-stuffing path).
_SEED_ID = seed_store_from_bundle(str(BUNDLE_DIR)).verdicts[0].id
_REALIZATION_SIGNAL = "realiseringsgrad=0.82" # the exact ``_verdict_rationale`` fewshot string
def _generation_prompts(sink: list[str]) -> list[str]:
@ -69,6 +72,11 @@ async def test_prior_verdict_reaches_hypothesis_prompt(make_recording_client_fac
assert any(_SEED_ID in p for p in gen_prompts), (
"the seed verdict did not reach the hypothesis prompt — the ExpeL->prompt fold is detached"
)
# Fase 2b: the realization SIGNAL itself (not just the id) is now a clean marker — it can only
# reach the prompt via the gated fold, since OKF-navigation excludes the verdict from context.
assert any(_REALIZATION_SIGNAL in p for p in gen_prompts), (
"the realization signal did not reach the hypothesis prompt via the ExpeL fold"
)
async def test_empty_store_leaves_no_signal_in_prompt(make_recording_client_factory) -> None:
@ -91,3 +99,10 @@ async def test_empty_store_leaves_no_signal_in_prompt(make_recording_client_fact
"no seed verdict in the store, yet its id appeared in a prompt — "
"the assertion is not actually load-bearing"
)
# Fase 2b LOAD-BEARING: with no fold, the realization rate must appear in NO prompt. This goes
# RED against the pre-2b chunk-stuffing path (which leaked "0.82" from the verdict + golden files
# into the debate prompt) and only passes once OKF-navigation excludes the verdict from context.
assert all("0.82" not in p for p in recorded), (
"the realization rate leaked into a prompt without a seed verdict — bundle context is still "
"chunk-stuffing the verdict/golden files (Fase 2b OKF-navigation not in effect)"
)