From 66065053b154585c076e6755fe947435f48e0c50 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Fri, 26 Jun 2026 12:07:28 +0200 Subject: [PATCH] test(fase3): SC3 load-bearing meter-isolation detach guard --- tests/test_portfolio.py | 47 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/test_portfolio.py b/tests/test_portfolio.py index 1281410..8517325 100644 --- a/tests/test_portfolio.py +++ b/tests/test_portfolio.py @@ -12,7 +12,7 @@ scanning the prompt for its id, so one production-shaped factory serves the whol import pytest -from portfolio_optimiser.run import PortfolioResult, RunResult, run_portfolio +from portfolio_optimiser.run import PortfolioResult, RunResult, run_portfolio, run_project # The synthetic reply IS the proposal: generate._parse_ir builds affected_items (each with its # own quantity/unit_cost) straight from this JSON, and the validator's P90 = 0.30 x Σ(qty·unit_cost) @@ -105,3 +105,48 @@ async def test_b_shared_store_accumulates_and_surfaces_prior_verdict( assert len(set(ids)) == 3 # pairwise distinct minted ids (no collision) # BRU surfaces FV42 (the structural match), ranked above the decoy. assert result.runs[2].retrieved[0].id == result.runs[0].verdict.id + + +async def test_c_execution_state_isolation_is_load_bearing( + make_client_factory, fresh_store +) -> None: + """SC3 (cap-independent automatic detach guard): each project's execution state (the budget + meter) is built FRESH per run, so per-project token_usage is its OWN only. Built on the + Step-1 ``meter=`` seam + Step-3 ``meter_factory``. The factory emits a UNIFORM but VALID + proposal (REPLIES["FV42-GSV-E1"] for every call) so both projects complete — a bare + unparseable default would loop the generate fetch to BudgetExceeded instead. Both arms run + every CI, so the detach is encoded automatically (no manual reviewer-revert).""" + from portfolio_optimiser.budget import Budget, TokenMeter + from portfolio_optimiser.reference_domain import load_reference_projects + + f = make_client_factory(REPLIES["FV42-GSV-E1"]) + rv13 = {p.id: p for p in load_reference_projects()}["RV13-RAS-TP"] + + # 1. Baseline: RV13 run standalone (its own fresh meter). + standalone = await run_project( + "RV13-RAS-TP", + "local", + docs_dir=rv13.docs_dir, + verdict_input=rv13.verdict_input, + client_factory=f, + ) + # 2. Isolated (default, no meter_factory): RV13 as project 1 in the portfolio. Its usage + # equals the standalone baseline — independent of project 0. THE load-bearing assertion: + # if run_portfolio shared a meter by default, runs[1] would be cumulative and this breaks. + iso = await run_portfolio( + ["FV42-GSV-E1", "RV13-RAS-TP"], + "local", + store=fresh_store, + client_factory=f, + ) + assert iso.runs[1].provenance.token_usage == standalone.provenance.token_usage + # 3. Shared (injected one meter across both): usage accumulates, proving the detach is real + # (and that the == arm above would redden under sharing). + shared = TokenMeter(Budget(max_tokens=1_000_000, max_rounds=1000)) + sh = await run_portfolio( + ["FV42-GSV-E1", "RV13-RAS-TP"], + "local", + client_factory=f, + meter_factory=lambda: shared, + ) + assert sh.runs[1].provenance.token_usage > sh.runs[0].provenance.token_usage