"""Step-7 load-bearing seam (målbilde §3 / §5 row 7 / §7): the LONG/async feedback loop — a verdict file that lands in a folder AFTER one run MUST be consumed by a separate, later run. The gap (fot-i-bakken, verified in code): the short loop captured the expert verdict inline (``verdict_input``) into an IN-MEMORY ``VerdictStore`` — so a verdict that arrives days/weeks later, in a separate run, could not influence any future hypothesis. Fase 5 adds a file inbox: ``run_project(verdict_dir=...)`` ingests expert/persona-authored verdict files (plain JSON, R2 raw layer) and MERGES them into the store BEFORE the Step-1 ExpeL fold, so the dropped verdict reaches the next run's hypothesis prompt through the existing fold. These two tests are a load-bearing PAIR (per the Fase-2 green-but-dead trap): - the positive test keys on a realization marker that occurs NOWHERE in the bundle (0.57, not the bundle seed's 0.82), carried in the dropped verdict's rationale — proving the learning PAYLOAD round-tripped end to end (write -> file -> ingest -> retrieve -> fold -> prompt), not merely that an object was retrieved. It goes RED the moment the ingest is detached. - the control proves causality: the SAME run with an EMPTY inbox carries no marker — so the signal is caused by the file loop, not incidental to the bundle. ``bundle_context`` excludes ``type: verdict`` files (okf.py), so the marker can ONLY arrive via ingest -> store -> fold. Critical invariant: run B uses a FRESH store (never run A's object) — otherwise the transfer would be in-memory carryover, not the file loop. """ 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 ( Verdict, VerdictStore, bundle_candidate_features, write_verdict, ) BUNDLE_DIR = Path(__file__).resolve().parents[1] / "shared" / "examples" / "bygg-energi-mikro" # A VALID BYGG-KONTOR-NORD proposal (same as the Step-1 test): 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 carried by the DROPPED verdict, by design: # - a realization value absent from EVERY bundle file (the bundle seed is 0.82). It can reach a # prompt only via the ingest->fold path -> isolates the file loop AND would catch a regression # where bundle navigation starts leaking verdict files (a 0.82 marker would pass for the wrong # reason and mask that). # - a sentinel verdict id (cannot appear in the bundle's raw text) for object identity. _MARKER = "realiseringsgrad=0.57" _DROP_ID = "STEG7-DROP" 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] def _drop_expert_verdict(inbox: Path) -> None: """Simulate the long loop: an expert/persona drops a verdict file into the inbox AFTER a run. Keyed on the bundle candidate's features (the same key the Step-1 fold queries on), carrying the realization marker in its rationale. ``write_verdict`` is the public authoring primitive a persona (sim) or human (prod) uses against the SAME folder interface.""" verdict = Verdict( id=_DROP_ID, proposal_features=bundle_candidate_features(str(BUNDLE_DIR)), decision="approved", rationale=f"LED-retrofit godkjent med realiseringskorreksjon ({_MARKER})", ) write_verdict(str(inbox), verdict) async def test_dropped_verdict_reaches_later_run(make_recording_client_factory, tmp_path) -> None: """LOAD-BEARING: a verdict dropped into the inbox AFTER run A reaches run B's hypothesis prompt. Goes RED if ``run_project`` stops ingesting ``verdict_dir`` before the Step-1 fold (the marker never arrives -> no fold -> the assertion fails).""" inbox = tmp_path / "verdict-inbox" inbox.mkdir() # Run A: an earlier run with an EMPTY store and an EMPTY inbox -> nothing folds in, and (no # auto-persist) run A does NOT write its own captured verdict back to the inbox. factory_a, recorded_a = 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=[]), verdict_dir=str(inbox), client_factory=factory_a, ) assert all(_MARKER not in p for p in recorded_a), "precondition: run A carries no marker" # Days/weeks later, out of band: the expert drops a verdict into the inbox folder. _drop_expert_verdict(inbox) # Run B: a SEPARATE, later run with a FRESH store (never run A's object) -> the dropped verdict # must reach the hypothesis prompt purely via the file loop. factory_b, recorded_b = 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=VerdictStore(verdicts=[]), verdict_dir=str(inbox), client_factory=factory_b, ) assert isinstance(result.outcome, ValidatedProposal) gen_prompts = _generation_prompts(recorded_b) assert gen_prompts, "the generation call must have happened" assert any(_MARKER in p for p in gen_prompts), ( "the dropped verdict's realization signal did not reach run B's hypothesis prompt — the " "async file inbox is not ingested before the Step-1 fold" ) assert any(_DROP_ID in p for p in gen_prompts), ( "the dropped verdict's id did not reach the prompt" ) async def test_empty_inbox_leaves_no_signal(make_recording_client_factory, tmp_path) -> None: """CAUSALITY CONTROL: the same run with an EMPTY inbox carries no marker into any prompt — proving the positive test's signal is caused by the file loop, not incidental to the bundle (``bundle_context`` excludes ``type: verdict`` files, so the marker has no other path).""" inbox = tmp_path / "verdict-inbox" inbox.mkdir() 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=[]), verdict_dir=str(inbox), client_factory=factory, ) assert all(_MARKER not in p for p in recorded), ( "the realization marker reached a prompt with an EMPTY inbox — the positive test is not " "load-bearing (the signal is incidental, not caused by the ingest seam)" ) assert all(_DROP_ID not in p for p in recorded), "the sentinel id leaked without a dropped file"