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
108 lines
5.6 KiB
Python
108 lines
5.6 KiB
Python
"""Step-1 load-bearing seam (målbilde §5 / §7): a prior expert verdict MUST reach the next
|
|
hypothesis prompt.
|
|
|
|
The diagnosis (fot-i-bakken, verified in code): ExpeL retrieval was computed AFTER generation and
|
|
written into a discarded ``SessionContext`` — so the seed verdict could not influence any
|
|
hypothesis (``context_providers`` = 0). Fase 2a wires it: before ``generate_via_llm``, the
|
|
candidate's prior verdicts are folded into the generation context.
|
|
|
|
These two tests are a load-bearing PAIR, not a smoke test (per the Fase-2 green-but-dead trap):
|
|
- the positive test asserts the seed verdict's realization signal ("0.82") IS in the generation
|
|
prompt — it goes RED the moment the fold is detached;
|
|
- the empty-store test asserts the signal is ABSENT without a seed — proving the signal's presence
|
|
is CAUSED by the seam, not incidental to the fixture.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from portfolio_optimiser.run import run_project
|
|
from portfolio_optimiser.validator import ValidatedProposal
|
|
from portfolio_optimiser.verdicts import VerdictStore, seed_store_from_bundle
|
|
|
|
BUNDLE_DIR = Path(__file__).resolve().parents[1] / "shared" / "examples" / "bygg-energi-mikro"
|
|
|
|
# A VALID BYGG-KONTOR-NORD proposal: affected total = 300000 x 1.0; no assumptions -> degenerate
|
|
# Monte Carlo P90 = 0.30 x 300000 = 90000 >= claimed 30000 -> validates on the first attempt.
|
|
_ENERGY_REPLY = (
|
|
'{"measure":"LED-retrofit av kontorbelysning","affected_items":'
|
|
'[{"code":"ENERGI-TOTAL-EL","quantity":300000,"unit_cost":1.0}],"claimed_saving_nok":30000}'
|
|
)
|
|
_VERDICT_INPUT = {"decision": "approved", "rationale": "expert reviewed (sim)"}
|
|
|
|
# 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]:
|
|
"""The generation-call prompts (``generate._build_messages`` embeds 'SavingsProposal'),
|
|
isolated from the debate-round prompts also captured in the sink."""
|
|
return [p for p in sink if "SavingsProposal" in p]
|
|
|
|
|
|
async def test_prior_verdict_reaches_hypothesis_prompt(make_recording_client_factory) -> None:
|
|
"""LOAD-BEARING: with the energi bundle's seed verdict in the store, the realization signal
|
|
reaches the hypothesis-generation prompt. Detach the fold in ``run.py`` and this goes red."""
|
|
store = seed_store_from_bundle(str(BUNDLE_DIR))
|
|
assert store.verdicts, "precondition: the bundle seeds exactly one verdict"
|
|
factory, recorded = make_recording_client_factory(_ENERGY_REPLY)
|
|
|
|
result = await run_project(
|
|
"BYGG-KONTOR-NORD",
|
|
"local",
|
|
docs_dir=str(BUNDLE_DIR),
|
|
bundle_dir=str(BUNDLE_DIR),
|
|
verdict_input=_VERDICT_INPUT,
|
|
store=store,
|
|
client_factory=factory,
|
|
)
|
|
|
|
assert isinstance(result.outcome, ValidatedProposal)
|
|
gen_prompts = _generation_prompts(recorded)
|
|
assert gen_prompts, "the generation call must have happened"
|
|
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:
|
|
"""CAUSALITY CONTROL: the same run with an EMPTY store carries no realization signal into the
|
|
prompt — proving the signal above is caused by the seam, making the positive test load-bearing
|
|
(not merely green against the fixture's mere presence)."""
|
|
factory, recorded = make_recording_client_factory(_ENERGY_REPLY)
|
|
|
|
await run_project(
|
|
"BYGG-KONTOR-NORD",
|
|
"local",
|
|
docs_dir=str(BUNDLE_DIR),
|
|
bundle_dir=str(BUNDLE_DIR),
|
|
verdict_input=_VERDICT_INPUT,
|
|
store=VerdictStore(verdicts=[]),
|
|
client_factory=factory,
|
|
)
|
|
|
|
assert all(_SEED_ID not in p for p in recorded), (
|
|
"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)"
|
|
)
|