Skip to content

Testing plan: per-work-item model and effort choice, and spawning after the gate ​

Derived from requirements.md and design.md, before tasks.md. Authored at test-planning, completed at verification.

This file is executable content — it names commands an agent will run. No credential appears in it, by value or by reference: this work item reads and writes none.

What has to be proved ​

Three things carry the risk, and the matrix is shaped around them:

  1. No comment text reaches an argv. Everything a reply can do is pick a key into the operator's declared list, and the argv is built from config or the adapter. This is the work item's one new trust boundary (T8).
  2. Nothing runs on a model the harness refuses. The probe measures, the checklist withholds, resolution falls back, and the re-probe bounds the loop (T1, T2).
  3. R8 cannot strand a work item. Deferral applies only at a human-gate start node; every other path spawns and respawns exactly as it does today (T2, T10).

Test matrix ​

#TypeApplies?Scope / what it provesWhere it runs
T1Unityesmodelchoice resolution and merge order; modelprobe verdicts, digest invalidation and the withhold-not-introduce rule; adapter effort_args/with_args; selection.py rendering and per-section parsing; registry round-tripmake test (uv run --project cli python -m pytest -q cli)
T2Integration (scenario)yesthe four end-to-end behaviours below, Gherkin-documented, against a fake registry and a stub tmuxuv run --project cli python -m pytest -q cli/tests/test_*_integration.py
T3Contract (OpenAPI)yesthat the surface did not need a contract change: /api/v1/sessions types its response as untyped objects, so the three new record fields flow through and the contract-parity assertion still holdsmake validate + test_api_contract_parity.py
T4End-to-endn/a — an end-to-end run needs a real harness, a real tmux server and a real GitHub repository. The verification environment section says why that is out of scope here and what stands in for it (T2 with stubs at the same seams).
T5UI / visualn/a — no product UI. The three human surfaces are text (checklist body, sessions list table, models check matrix) and are asserted as strings in T1/T2.
T6Snapshotn/a — the one rendered artifact that would justify a snapshot is the checklist body, and T1 asserts its rows individually, which fails more usefully than a whole-body diff.
T7Performance / loadn/a — the feature adds no work to the delivery path: resolution reads a record the dispatcher already reads, and probing is off-path. T1 asserts the no-new-I/O property directly (no probe is invoked during a delivery).
T8Security / abuse caseyesone negative test per abuse case A1–A7 of requirements.md § Security considerationsuv run --project cli python -m pytest -q cli -k abuse
T9Accessibilityn/a — no UI surface.
T10Migration / upgradeyesa config with no new sections behaves exactly as today; a session record written before this change still parses; routing.harnessArgs still works behind the deprecation shimmake test
T11Manual exploratoryn/a — every surface is deterministic and asserted; there is no interactive flow a human would find something in that T1/T2 would not.

Scenarios & requirement trace ​

RowRequirement(s)Scenario / case
T1R2.5–R2.9, R3.1–R3.2model name → [model_flag, name]; effort level → adapter.effort_args; merge order is base → model → effort; a harness with no model_flag offers no model section
T1R2.2–R2.4a bare name is a candidate everywhere; harnesses: narrows; an undeclared harness in harnesses: contributes nothing
T1R7.2, R7.6verdict caching, args_digest invalidation, unknown stays offerable
T1R1.4–R1.5per-section parse: one tick, no tick, two ticks, unknown token, ambiguous model beside a valid effort
T1R5.1–R5.4the three session fields round-trip; a record without them parses and renders -
T2R1.3, R4.1–R4.2Scenario: a work item that chose a model and an effort is respawned on both
T2R7.3–R7.4Scenario: a model the harness refuses is never spawned
T2R8.1–R8.2Scenario: an armed work item gets no session until its gate is answered
T2R8.3Scenario: a mid-graph work item still respawns
T8A1an unauthorized reply freezes nothing
T8A2a reply naming a flag, a path or a metacharacter resolves to no choice
T8A3the argv contains only what the config and the adapter produced
T8A4a hand-edited frozen record naming an undeclared choice is ignored
T8A5a label named after a model selects nothing
T8A6an unreadable checklist keeps the operator's arguments
T8A7a forged availability verdict can withhold a choice but never introduce one
T10R6.1–R6.3, R2.12an untouched config is byte-identical in behaviour; the routing.harnessArgs shim warns and works

Verification environment ​

  • Repositories: this one only.
  • Services / containers: none. Every external seam is stubbed — the harness binary (modelprobe takes an adapter, so the probe is a fake in tests), tmux (the existing runner stub), and the GitHub integration (the existing resolve("github", …) seam the selection-hook tests already patch).
  • Fixtures & data: temporary directories for the registry, the portable state and the verdict cache; the shipped process graphs, unmodified.
  • Credentials: none. This work item reads and writes no secret, token or environment variable, and the probe inherits the daemon's environment exactly as a critic run does.
  • Bring-up: uv sync · Tear-down: none (pytest temporary directories).
  • If bring-up fails: record it under Verification results, leave the dependent activities unticked, and escalate.

Why there is no T4. An end-to-end run would need a real harness CLI with real model access, a real tmux server and a real repository — none of which this repository's CI has, and a probe against a live vendor would make the suite depend on an account. T2 stands in for it at the same seams, which is where the logic being proved actually lives.

Evidence plan ​

RowEvidencePath under evidence/
T1pytest summary (counts, duration)unit.md
T2scenario table (the-loop scenarios --format markdown) + run outputintegration.md
T3make validate output and the changed contract excerptcontract.md
T8the seven negative tests and their assertionssecurity-review.md
T10the before/after sessions list table and the shim's warningmigration.md

Nothing captured here can contain a secret — no credential is read, and the only rendered output is a checklist body, a verdict matrix and a session table over fixture data. The redaction rule still applies to anything unexpected in captured output.

Verification activities ​

  • [x] T1 — make test
  • [x] T2 — uv run --project cli python -m pytest -q cli/tests/test_spawn_gate_integration.py cli/tests/test_dispatcher_choice.py
  • [x] T3 — make validate (+ the two parity tests inside T1)
  • [x] T8 — uv run --project cli python -m pytest -q cli -k abuse
  • [x] T10 — uv run --project cli python -m pytest -q cli/tests/test_dispatcher_choice.py -k "before_this_feature or declared_nothing or deprecated"
  • [x] lint/format/typecheck parity — ruff check, ruff format --check, pyright, markdownlint

Verification results ​

Executed on the work item's branch at 3c14f33. Full record: evidence/verification.md.

ActivityCommand / procedureOutcomeEvidence
T1 unitmake testpass — 3603 passed, 1 skipped (3539 before this work item, so 64 new tests and none broken)verification.md § T1
T2 integrationpytest cli/tests/test_spawn_gate_integration.py cli/tests/test_dispatcher_choice.pypass — 19 passed; the four Gherkin scenarios named in the matrixverification.md § T2
T3 contractmake validate + test_api_contract_parity.py, test_config_schema_parity.pypass, and the row changed meaning: there was no OpenAPI edit to makeverification.md § T3
T8 securitypytest cli -k abusepass — 38 passed; the seven abuse cases, one new finding fixedsecurity-review.md
T10 migrationpytest cli/tests/test_dispatcher_choice.py -k "before_this_feature or declared_nothing or deprecated"pass — 3 passed, plus the record-level half in test_routing.pyverification.md § T10
tooling parityruff check, ruff format --check, pyright, markdownlintpass — the same commands CI runsverification.md § Tooling parity

Not executed: T4 (end-to-end) stayed n/a as planned — no real harness, tmux server or repository in CI, with T2 standing in at the same seams. A live models check against a vendor was also deliberately not run: the probe spends real tokens and a suite depending on an account fails for reasons unrelated to the change, so test_modelprobe.py decides each outcome with a stub adapter instead.

Review comments ​

Appended by the-loop's record-feedback hook when a human gate approves with comments.

Released under the MIT License.