feat(toolbox): the first four doors out of the toolbox, without a chat client on the way

B-gate row 1's premise, made callable. Every path through the framework CLI constructs a chat
client, so an outside caller -- a human at a terminal, or an agent that is NOT po -- could not
reach a single run-path step without paying for a model. These four steps need no model at all.

One CLI, four subcommands, one core call each:

  navigate-bundle  --bundle-dir                         -> okf.navigate_bundle
  cost-baseline    --bundle-dir --project-id            -> okf.derive_cost_baseline
  retrieve-chunks  --query --docs-dir [--top-k]         -> datasource.retrieve_chunks
  prepass-admit    --payload --bundle-dir [--dimension] -> prepass.admit_payload

Each handler is a thin adapter: strings in, the SAME function the run path calls, JSON on stdout,
and an exit code that says what happened (0 ran, 2 malformed call, 3 the step refused, named).
A handler that computed anything of its own would be a second implementation of a run-path step,
and the outside caller would stop getting what the debate gets.

Dispatch is an explicit branch per command, not argparse's `set_defaults(handler=...)`: the table
hides the one thing a reader wants to see, and B-gate row 1 asks the same question of the source
(it walks the call graph from `main` down to the step's symbol), where a callable in a Namespace
is a hop neither can follow.

Probes (`tests/test_toolbox_doors.py`, 10 arms): each starts the door as a SUBPROCESS with the
subcommand in argv and asserts on what it wrote -- never by importing the core function, which is
the whole difference the gate exists to measure. The yardstick is outside the door in every arm:
the filesystem (navigate-bundle, including the one deliberate outside-bundle link), a table
transcribed from the priced fixture (cost-baseline), the in-process seam it must equal byte for
byte (retrieve-chunks), and the producer's own checked-in payload (prepass-admit). Every refusal
arm has an rc-0 control beside it.

`portfolio-optimiser-toolbox` is the THIRD console script, and the pin test now says why: it is
the door the other two cannot be used for. README and CLAUDE.md updated with the command and the
reason it exists; every documented invocation was run.

Row 1: 1 -> 5 of 17 (four subcommands + `gate`, which the class fix in e47be68 stopped rejecting
on a name technicality). No other row moved; exit 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-20 08:11:58 +02:00
commit 38df79126f
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
8 changed files with 532 additions and 37 deletions

View file

@ -27,11 +27,14 @@ Python ≥3.10. MAF (`agent-framework-core` 1.16.0, `-orchestrations` 1.1.1 —
- `shared/` er en **git subtree** av [`portfolio-optimiser-commons`](https://git.fromaitochitta.com/open/portfolio-optimiser-commons) (source of truth, R1 realisert 2026-07-03; publisert i `open/` 2026-08-04 — `commons`-remoten peker fortsatt på den private namespacen og virker uendret). Synk er **pull-only**: endringer committes i commons og hentes med `git subtree pull --prefix=shared commons main --squash`. ALDRI `git subtree push` fra konsument — re-split lekker hele konsument-historikken inn i commons (observert + opprydd 2026-07-03). Se `shared/README.md`. Wheelen bærer treet som pakkede data siden Fase 4a — se invarianten under.
## Kommandoer
- Sync: `uv sync` — installerer to konsoll-kommandoer: `portfolio-optimiser` (CLI, `run:main`) og
`portfolio-optimiser-demo` (offline-beviset, `simulation:main`). `python -m`-formene virker
uendret og er byte-identiske på stdout (målt). Bevisst KUN to av fem `main()`
- Sync: `uv sync` — installerer tre konsoll-kommandoer: `portfolio-optimiser` (CLI, `run:main`),
`portfolio-optimiser-demo` (offline-beviset, `simulation:main`) og `portfolio-optimiser-toolbox`
(kjørestiens steg som rene kommandoer, `toolbox:main` — 20.09.2026). `python -m`-formene virker
uendret og er byte-identiske på stdout (målt). Bevisst KUN tre av seks `main()`
`costsim`/`hitl`/`preflight` er operatørverktøy, ikke produktets inngang, og hvert navn her er et
navn frysen må bære. Pinnet av `tests/test_console_entry_points.py` mot den INSTALLERTE
navn frysen må bære. Verktøykassen er der fordi den er det ENE de to andre ikke kan brukes til:
hver vei gjennom `run` bygger en chatklient, og stegene den bygger på trenger ingen modell.
Pinnet av `tests/test_console_entry_points.py` mot den INSTALLERTE
distribusjonens metadata, ikke mot TOML-en: en `[project.scripts]`-linje som aldri er `uv sync`-et
er en påstand, ikke en kommando.
- Test: `uv run pytest`