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:
parent
e47be68b57
commit
38df79126f
8 changed files with 532 additions and 37 deletions
|
|
@ -44,7 +44,7 @@ _NOW = datetime(2026, 9, 19, 12, 0, tzinfo=timezone.utc)
|
|||
#: Repoets egne tall ved skriving (19.09.2026), talt av armene under mot KILDEN. De står her for
|
||||
#: at en stille endring i kjørestien skal vise seg som en rød test og ikke som et nytt tall.
|
||||
_STEPS_TODAY = 17
|
||||
_EXTERNAL_TODAY = 1
|
||||
_EXTERNAL_TODAY = 5
|
||||
_ROLES_TODAY = ("proposer", "checker")
|
||||
_PATTERNS_TODAY = 15
|
||||
|
||||
|
|
@ -106,16 +106,25 @@ def test_the_step_denominator_equals_an_independent_count_of_the_source() -> Non
|
|||
assert row.n == _STEPS_TODAY
|
||||
|
||||
|
||||
def test_row1_today_is_red_with_the_steps_that_have_no_door_named() -> None:
|
||||
"""Var «3 av 17» til 19.09, så 0 av 17 da proben ble BUNDET til steget. Fra 20.09 teller
|
||||
``gate`` igjen: den proben kjører v1-gaten som subprosess og leser JSON-en ut av den, og
|
||||
steget er alene bak den døren — den ble avvist på en navneteknikalitet, ikke på atferd."""
|
||||
#: Stegene som HAR en dør ut 20.09.2026, skrevet ut. Fire av dem er verktøykassens under-
|
||||
#: kommandoer (steg 2 i byggerekkefølgen); ``gate`` er v1-gatens egen kommando, som alltid var
|
||||
#: en ekte dørprobe og ble avvist på en navneteknikalitet til klasse-rettingen samme dag.
|
||||
_WITH_A_DOOR_TODAY = ("gate", "kontekst", "kostnadsgrunnlag", "pakke", "prepass")
|
||||
|
||||
|
||||
def test_row1_today_is_red_and_names_exactly_the_steps_that_have_a_door() -> None:
|
||||
"""Var «3 av 17» til 19.09, så 0 av 17 da proben ble BUNDET til steget, og 5 av 17 fra 20.09.
|
||||
Armen teller ikke bare k — den navngir HVILKE, så en dør som flytter seg uten å bli bygd
|
||||
(eller en probe som slutter å drive sin dør) blir rød her og ikke bare et annet tall."""
|
||||
passing = {n: "passed" for step in _CONFIG["steps"] for n in step.get("probe", ())}
|
||||
row = gate.score_toolbox(_CONFIG["steps"], _SRC, _CONFIG["run_path"], passing, _REPO)
|
||||
assert (row.k, row.n, row.status) == (_EXTERNAL_TODAY, _STEPS_TODAY, gate.RED)
|
||||
named = {x.split(":")[0] for x in row.exceptions}
|
||||
assert "validering" in named and "utboks" in named
|
||||
assert "prepass-artefakt" in named and "parse-feil" in named
|
||||
assert len(_WITH_A_DOOR_TODAY) == _EXTERNAL_TODAY
|
||||
uten_doer = {x.split(":")[0] for x in row.exceptions}
|
||||
alle = {str(s["id"]) for s in _CONFIG["steps"]}
|
||||
assert sorted(alle - uten_doer) == sorted(_WITH_A_DOOR_TODAY), row.exceptions
|
||||
assert "validering" in uten_doer and "utboks" in uten_doer
|
||||
assert "prepass-artefakt" in uten_doer and "parse-feil" in uten_doer
|
||||
bound = {x.split(":")[0] for x in row.exceptions if "atferdsprobe" in x}
|
||||
assert bound == {"rundebinding", "rapport"}, row.exceptions
|
||||
|
||||
|
|
@ -1862,3 +1871,38 @@ def test_the_row_limit_is_a_class_description_and_no_longer_an_enumeration() ->
|
|||
assert "tekstmønstre" in limit or "tekstvakt" in limit
|
||||
assert "uttømmende" in limit
|
||||
assert "git-manifestet" in limit
|
||||
|
||||
|
||||
def test_a_subcommand_door_is_only_driven_when_the_command_stands_in_argv(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""For en underkommando ER kommandonavnet døren. En probe som starter CLI-en uten det har
|
||||
kjørt en annen dør — eller ingen. Uten denne armen ville kravet kunne fjernes uten at noe
|
||||
ble rødt: verktøykassens egne prober har alle navnet i argv."""
|
||||
src = _fake_src(tmp_path, chat_client=False)
|
||||
_write(
|
||||
src / "verktoey.py",
|
||||
"import argparse\n\nfrom fakesrc.steg import gjoer_noe\n\n\n"
|
||||
"def main(argv=None):\n"
|
||||
" parser = argparse.ArgumentParser()\n"
|
||||
' sub = parser.add_subparsers(dest="kommando", required=True)\n'
|
||||
' sub.add_parser("naviger-pakke")\n'
|
||||
" parser.parse_args(argv)\n"
|
||||
" return gjoer_noe()\n\n\n"
|
||||
'if __name__ == "__main__":\n raise SystemExit(main())\n',
|
||||
)
|
||||
steg = _one_step()
|
||||
steg[0]["entry"] = {
|
||||
"kind": "subcommand",
|
||||
"module": "verktoey.py",
|
||||
"scope": "main",
|
||||
"command": "naviger-pakke",
|
||||
}
|
||||
_write_probe(tmp_path, door="fakesrc.verktoey", needle="naviger-pakke")
|
||||
row = gate.score_toolbox(steg, src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||
assert (row.k, row.n) == (1, 1), row.exceptions
|
||||
|
||||
_write_probe(tmp_path, door="fakesrc.verktoey", needle="gjoer_noe")
|
||||
row = gate.score_toolbox(steg, src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||
assert (row.k, row.n) == (0, 1), row.exceptions
|
||||
assert any("underkommandoen" in x for x in row.exceptions), row.exceptions
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue