feat(p17b): ONE commission, SEVERAL bases -- reachable from the command line

``run_mandate_across_bundles`` has existed since session 58, reachable from FIVE
test files and from NO command line (measured: ``grep -n across-bundle run.py``
= 0 hits). ``--across-bundle <dir>``, repeated once per base, is that door.

The engine takes a CALLBACK rather than an outbox directory. Its own docstring
has always said N runs need N ``run_id``s and that minting them there would
default a key this repo requires a caller to supply -- so ``outbox_for`` is that
contract KEPT, not relaxed, and the operator-chosen ``<run-id>-<bundle_id>``
rule lives in ``main()`` where the decision was made. The order's alternative (a
caller running ``run_project`` itself over ``route_by_bundle``'s sub-mandates)
would be a second copy of the loop's id reconciliation, shared store, per-base
project resolution, collision accounting and both budget teeth.

``resolve_bundle_routing`` is ONE resolution shared by the engine and the
dry-run arm: a free trip answering with a different project id, or tolerating a
duplicate id the paid dispatch refuses, would rehearse a different run.

``{run-id}-multibase.json`` is written from a ``finally`` and every row is built
from the resolution plus disk, so the pass a cap cut short still leaves the
record. ``completed`` is a required field for ``ExplorationTrace.completed``'s
reason. ``stop_reason`` is read BACK from each base's own coverage artefact.

Load-bearing MEASURED (17 arms), four mutations all red against the WHOLE suite,
green control 1761/5 (from 1744/5, superset, 0 removed), golden byte-unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-15 04:24:48 +02:00
commit 5e4c497a84
5 changed files with 994 additions and 37 deletions

View file

@ -412,7 +412,7 @@ when the seam is detached, so the loop cannot silently degrade into theater.
are still made and discarded, and nothing can leave the process. The OTLP exporter packages are
not declared dependencies (they are egress, and heavy in a published wheel); install one yourself
if you use that mode.
- **Run:** the `run.py` CLI has **three modes** — a documented partition, since one invocation
- **Run:** the `run.py` CLI has **four modes** — a documented partition, since one invocation
cannot exercise every flag:
- **Single-project**`PROJECT_ID --docs-dir <dir>`, plus optional `--bundle-dir`,
`--verdict-dir`, `--outbox-dir` (which requires `--run-id`), `--dimension-config`,
@ -425,6 +425,19 @@ when the seam is detached, so the loop cannot silently degrade into theater.
shape the mandate this run evaluates — see below), and `--derive-cost-baseline` (opt-in:
anchor the validator on a priced schedule already inside `--bundle-dir` instead of a
hand-written `cost-baseline.json` — see below).
- **Multi-base (P17b)**`--across-bundle <dir>` repeated once per knowledge base, plus
`--mandate <file>`, `--run-id <id>` and `--outbox-dir <dir>` (all three required). One
commission, several bases: the mandate is partitioned by each approach's `bundle_id` and the
existing pipeline runs once per base, sequentially, threading ONE verdict store so a verdict
minted against base *k* reaches base *k+1*'s hypothesis. Each base writes its own full
artefact set under `<run-id>-<bundle_id>`, and one `<run-id>-multibase.json` beside them
records the spend order, the per-base `run_id`, `unreached`, `collisions`, `budget_stop` and
each base's own `stop_reason` — written even when a base is cut short, with a `completed`
field so "nothing was left unreached" cannot be read as "we never found out". `--live-dry-run`
drills every configured base and stops before the first model call. `--bundle-dir` stays ONE
directory and is refused here, as are `--portfolio`, `--explore`, `--prepass-payload` and
`--proposals-from-mandate` — each of those resolves one base, and picking which of N was meant
is not this layer's to decide.
- **Portfolio**`--portfolio`, plus optional `--goals`, `--ledger`, `--dimension-config`,
`--semantic-retrieval`; it stops early and prints a `goal reached: …` line when the
accumulated ledger meets a goal.