portfolio-optimiser-claude/tests/test_run_entrance_loadbearing.py
Kjell Tore Guttormsen 30ba68a703 test(loadbearing): close the vacuous-negative class across the whole suite
Oekt 17 found the class on four named files. This sweep ENUMERATES it: 42 negative
substring assertions across 21 test files (STATE's "~34 across 23" was a premise --
measured, it is 42/21). Sixteen of them measured an absence without ever having
shown presence; all sixteen now carry a positive control asserting the searched-for
string PRESENT in the source artifact, in EXACTLY the form the negative looks for.

Files touched: test_costsim, test_loop, test_okf (3 sites), test_preflight,
test_run_entrance, test_s10_run_layer, test_sdk_version_guard, test_simulation
(2 sites), test_step1_expel, test_step5_refine, test_step7_async_loop,
test_step8_promotion, test_valuereport.

VALUE-PROOF (green-without / red-with, per the oekt-17 rule that a detach proof is
not a value proof). Seven source/fixture mutations, each making the negative vacuous:

  M1 verdict fixture loses the realization signal        VALUE-PROVEN
  M2 decoy fixture loses its text                        VALUE-PROVEN
  M3 renderer stops emitting typed section headings      VALUE-PROVEN
  M4 promotion stops writing the marker                  VALUE-PROVEN (pass 2)
  M5 fold stops rendering the realization surface        VALUE-PROVEN
  M6 report stops labelling the cost section             VALUE-PROVEN
  M7 preflight stops importing the SDK                   VALUE-PROVEN

M4 needed pass 2: a PRECEDING assertion caught the same mutation, hiding the new
control behind it -- the oekt-17 lesson reproduced. The remaining nine controls are
vacuity guards (non-emptiness / form-presence) whose mutation would have to break
the source artificially; they are stated as guards, not claimed as value-proven.

MEASURED FINDING (test_loop): the FIRST-RUN-MARKER negative cannot be given a
positive control at all. Within a run only the CHECKER's critique is fed back --
the proposer's own prior reasoning crosses no prompt boundary, not even within a
run. So that negative holds trivially. Left in place with the limitation stated in
the test rather than dressed up as a controlled seam; the CRITIQUE negative beside
it IS controlled and is the real seam.

Mutations were in-place on src/ and shared/ with original bytes restored and
sha-verified; git status clean before and after. Suite 688 -> 688 (assertions added
inside existing tests, no new test cases). ruff + mypy --strict green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Vc5PmZGjwuJypdhzKnJa5
2026-07-31 21:39:28 +02:00

164 lines
7.1 KiB
Python

"""Run-entrance seams — LOAD-BEARING (C2.0; method-spec §3 Step 7, §5, §8, §11).
The seam this file keeps alive: the SHIPPABLE entrance (``run.py``) composes
the §5 sequence (merge inbox → seed → fold) and drives the same orchestration
the fasit run used — so the README's inbox claim is true of a deliverable
path. ``run_s10.py`` stays byte-frozen and is still never imported here.
Detach proofs: drop the merge call from the composition → the inbox verdict
never reaches the composed context → red. Drop the budget-stop persistence
branch from the entrance → the structured stop leaves no artifacts → red
(R-10: the orchestration branch the fasit suite could never bind, bound here
on the new path with a scripted client and a low cap).
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Callable
from _scripted import ScriptedClient, reply
from portfolio_optimiser_claude.contracts import Contracts, load_contracts
from portfolio_optimiser_claude.experience import CandidateFeatures
from portfolio_optimiser_claude.inbox import VerdictDocument, write_verdict
from portfolio_optimiser_claude.ir import load_validator_input
from portfolio_optimiser_claude.loop import ModelClient
from portfolio_optimiser_claude.run import compose_run_context, default_client_factory, main
BUNDLE = Path(__file__).resolve().parents[1] / "shared" / "examples" / "bygg-energi-mikro"
# Distinct from the bundle seed's own learning marker (realiseringsgrad=0.82),
# so its presence proves the INBOX path, not the seeding path.
MARKER = "INBOX-MARKER-79"
RATIONALE = f"Justert: i drift realiseres ~79% ({MARKER})."
ClientFactory = Callable[[Contracts, float], ModelClient]
def _inbox_document() -> VerdictDocument:
# The expert judges the bundle's own candidate, so retrieval ranks the
# verdict at similarity 1.0 and the fold must carry it.
features = CandidateFeatures.from_proposal(load_validator_input(BUNDLE))
return VerdictDocument.from_candidate(
features,
decision="approved_with_adjustment",
rationale=RATIONALE,
description="expert judgement of the bundle candidate",
)
def _scripted_factory(replies: list[object]) -> tuple[ClientFactory, list[ScriptedClient]]:
created: list[ScriptedClient] = []
def factory(contracts: Contracts, max_budget_usd_per_call: float) -> ModelClient:
client = ScriptedClient(replies=list(replies)) # type: ignore[arg-type]
created.append(client)
return client
return factory, created
class TestComposeRunContext:
"""§5 sequence, composed: merge inbox → seed → fold — read-only on the inbox."""
def test_a_dropped_verdict_reaches_the_composed_context(self, tmp_path: Path) -> None:
inbox = tmp_path / "inbox"
write_verdict(inbox, _inbox_document())
composed = compose_run_context(BUNDLE, inbox, k=3)
assert MARKER in composed.context
assert composed.inbox_merged == 1
assert composed.seeded == 1
def test_empty_inbox_control_keeps_the_base_composition(self, tmp_path: Path) -> None:
inbox = tmp_path / "inbox"
inbox.mkdir()
with_empty = compose_run_context(BUNDLE, inbox, k=3)
without = compose_run_context(BUNDLE, None, k=3)
assert with_empty.context == without.context
# Positive control: the base composition is non-empty — an empty context would
# satisfy the marker negative vacuously. That the marker CAN reach a context is
# pinned by the seeded-inbox test above; here it must not.
assert without.context
assert MARKER not in without.context
assert with_empty.inbox_merged == 0
def test_missing_inbox_folder_behaves_like_empty(self, tmp_path: Path) -> None:
composed = compose_run_context(BUNDLE, tmp_path / "never-created", k=3)
assert composed.context == compose_run_context(BUNDLE, None, k=3).context
def test_composition_never_writes_to_the_inbox(self, tmp_path: Path) -> None:
# Role split (§3 Step 7, unwaivable): the entrance READS the inbox.
inbox = tmp_path / "inbox"
write_verdict(inbox, _inbox_document())
before = {p.name: p.read_bytes() for p in inbox.iterdir()}
compose_run_context(BUNDLE, inbox, k=3)
after = {p.name: p.read_bytes() for p in inbox.iterdir()}
assert after == before
class TestEntranceHappyPath:
"""The thin CLI wires compose → run → persist; the inbox signal survives the chain."""
def test_exit_zero_persists_run_artifacts_and_threads_the_inbox(self, tmp_path: Path) -> None:
inbox = tmp_path / "inbox"
write_verdict(inbox, _inbox_document())
out = tmp_path / "out"
factory, created = _scripted_factory(
[
reply("debate reasoning"),
reply("VERDICT: APPROVE"),
reply(json.dumps(load_validator_input(BUNDLE).model_dump())),
]
)
code = main(
["--bundle", str(BUNDLE), "--inbox", str(inbox), "--out", str(out)],
client_factory=factory,
)
assert code == 0
(client,) = created
assert any(MARKER in prompt for prompt in client.prompts("proposer"))
run_result = json.loads((out / "run_result.json").read_text("utf-8"))
assert run_result["validator_decision"] == "validated"
for artifact in ("proposal.json", "provenance.json", "usage.json"):
assert (out / artifact).is_file()
class TestBudgetStopBinding:
"""R-10: the budget-stop branch — structured exit + persisted stop artifacts."""
def test_budget_stop_persists_stop_artifacts_and_exits_structurally(
self, tmp_path: Path
) -> None:
out = tmp_path / "out"
factory, _ = _scripted_factory([reply("debate reasoning", usage_tokens=10)])
code = main(
["--bundle", str(BUNDLE), "--out", str(out), "--max-tokens", "5"],
client_factory=factory,
)
assert code == 3
stop = json.loads((out / "stop.json").read_text("utf-8"))
assert stop == {"kind": "tokens", "limit": 5, "observed": 10}
usage = json.loads((out / "usage.json").read_text("utf-8"))
assert usage["tokens_used"] == 10
assert usage["max_tokens"] == 5
# A client without cost accounting persists an honest null, never a 0.0.
assert usage["cost_usd"] is None
assert not (out / "proposal.json").exists()
assert not (out / "run_result.json").exists()
class TestDefaultClientFactory:
"""The CLI's default factory constructs the REAL SDK client — wired, not executed."""
def test_default_factory_returns_the_sdk_client(self) -> None:
# Construction needs no API key (verified premise); no query() is made.
from portfolio_optimiser_claude.sdk_client import SdkModelClient
contracts = load_contracts(
data_source={"docs_dir": str(BUNDLE), "top_k": 3},
termination={"max_rounds": 1, "max_tokens": 1},
feedback={"decision": "approved", "rationale": "startup shape check (§10)"},
)
client = default_client_factory(contracts, 0.25)
assert isinstance(client, SdkModelClient)