feat(run): C2.0 — shippable step-7 run entrance + K2.9 seam bindings (closes C-N2, R-10, K2.9)

- run.py: compose_run_context (§5: merge inbox -> seed -> fold, read-only on
  the inbox) + execute_run (§8 meter, artifacts persisted on BOTH outcomes,
  structured exit 3 on budget stop) + thin CLI (python -m ..run). The model
  client is injected; only default_client_factory constructs the SDK client
  (wired, never executed by the suite). The navigated docs dir comes from the
  validated startup contract (resolves review OBS-2 on the shippable path;
  run_s10.py stays byte-frozen fasit -> won't-fix there).
- test_run_entrance_loadbearing.py: inbox verdict reaches the composed
  context (detach-proven: merge dropped -> red), empty/missing-inbox
  controls, read-only inbox byte-proof, R-10 budget-stop binding via the NEW
  entrance (detach-proven: stop persistence dropped -> red), happy path
  through the CLI with the inbox signal surviving the chain, SDK-wiring test.
- test_ingest_adoption.py (K2.9): the two library guarantees the consumer
  relies on, bound through the seam — empty CSV -> typed SourceError with NO
  partial bundle on disk; non-SELECT SQL -> SourceError 'returned no columns'
  (behavior verified empirically against pin dae0bd1a before binding).
- README: inbox section now points at the shippable entrance; run.py added
  to the run layer; stale test count 265 -> 395.

386 -> 395 tests, full gate green (pytest, ruff check+format, mypy strict);
goldens unchanged; runs/s10 and run_s10.py untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-07-17 03:28:31 +02:00
commit 3587854074
4 changed files with 448 additions and 3 deletions

View file

@ -13,7 +13,7 @@ human-in-the-loop, and the system learns from the verdicts.
> **Status:** the D7 build (S5S10) is complete, and the deterministic **ingest layer**
> (CSV and SQL source types) has since been added in front of the loop. The deterministic
> backbone, the agentic loop, the learning loop, and the ingest connectors are wired seam by
> seam, each proven by load-bearing tests (265 tests, all running offline without an API
> seam, each proven by load-bearing tests (395 tests, all running offline without an API
> key). The programme's single budgeted **live model run has been executed and validated**
> its artifacts are committed under [`runs/s10/`](runs/s10/) (see below).
@ -65,7 +65,8 @@ offline. Module by module:
**Learning loop** (§3 steps 78, §4§6)
- `inbox.py` — the async verdict-file contract: an expert drops a plain-JSON verdict into
an inbox folder after a run; a later run ingests it tolerantly and merges it before the
fold.
fold. The shippable entrance for that later run is `run.py`:
`uv run python -m portfolio_optimiser_claude.run --bundle <dir> --inbox <dir>`.
- `promotion.py` — the promotion gate, **fail-closed**: only an approved verdict is
lifted into the OKF context layer; anything else raises and writes nothing.
- `persona.py` — the expert-reviewer persona sourced from the shared artifact in
@ -77,6 +78,11 @@ offline. Module by module:
(`setting_sources=[]`) so no user/project config can leak into a run.
- `artifacts.py` — §9 citations plus deterministic run-artifact persistence, including on
structured stops (a budget stop still leaves artifacts behind).
- `run.py` — the generic run entrance: composes merge-inbox → seed → fold (§5) and drives
the loop under the budget meter, persisting artifacts on both outcomes — a structured
budget stop included. The model client is injected, so the offline suite proves the
same orchestration with a scripted client; only the CLI's default constructs the SDK
client.
- `run_s10.py` — the programme's ONE live run (cost discipline D6); run-path only.
### Load-bearing tests (§11)
@ -150,7 +156,7 @@ Python ≥3.10 · [`claude-agent-sdk`](https://pypi.org/project/claude-agent-sdk
```bash
uv sync # install dependencies
uv run pytest # 265 tests — run without any API key and without network
uv run pytest # 395 tests — run without any API key and without network
uv run ruff check . && uv run ruff format --check .
uv run mypy src # strict
```