Files
boxmaker/docs/plans/M3a/checks-d.md
T
kyleandClaude Opus 5 e3f37da232 Hand over the M3a plan: 22 tasks, their files, and the check record
Task files, the files they copy in (byte-identical to the reference on
m3a-ref), each area's check record, and a README with the per-task
table of what each check exposed. The handoff note is done with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 23:45:43 -07:00

7.2 KiB

M3a checks, fork D: bxctl (tasks 18, 20) and the runbook gate script (task 21)

What each task's given tests were checked against before hand-over, and what doing so exposed. Branch m3a-ref-d, three commits: 292780f (the task 18 state), 5f27808 (the task 20 state, skeleton), and the one after it (reference bodies, written afterwards as a measurement).

What was checked how

Task Check Ran green Compiled only
21 check-runbook.sh Real script, run for real. Self-test passes; five mutations of the script (no -x, exit at the first missing anchor, no -prune, a narrower anchor pattern, no "no pointers" check) each make the self-test fail. all none
18 escape.rs Written for real: 12 lines of logic, cheaper than a stub. escape 8 of 8 none
18 cli.rs, main.rs Written for real. Not by choice: the 12 existing chat tests drive the binary, and the binary cannot parse its arguments through a todo!(). cli 11 of 12; chat 12 of 12 cli 1 (it needs admin)
18 admin.rs Skeleton (todo!() bodies; reason_name real, because the printer calls it). admin 1 of 21 admin 20
20 Printer, stream_turn, main.rs Written for real, for the same reason as cli.rs. chat_print 9 of 9; chat_approvals 3 of 20 none
20 handle_pending Skeleton. none chat_approvals 17

So "skeleton" came to two things: the bodies of admin.rs and one function in chat.rs. 38 of the 82 tests only compiled at hand-over quality. Everything was clippy-clean and formatted, and the task 18 state (new command dispatch, M2b chat code) was built and tested on its own commit, because main.rs is rewritten in both tasks.

Measurement: what a reference found that the skeleton had not

After the skeleton state was committed, the missing bodies were written (about 150 lines) and the 38 compile-only tests were run: all passed on the first run, then six more runs under CPU load with no failure. The reference exposed no defect that the skeleton and a desk-check had missed. For this kind of task (formatting and a request/answer client, exact strings) the skeleton was enough. The reference bodies are kept in the last commit because the straylight check needs a working bxctl approve; they are not handed to the implementer.

Defects the checks exposed

Task 21

  1. Spec 11 does not say what a pointer is when its anchor is not written out. A pointer built with format!("see docs/runbook.md#{anchor}"), or the placeholder docs/runbook.md#<anchor> in a comment, has no anchor the script can read, and a script that skipped these would let a computed pointer through unchecked. The script fails on them. This binds every crate: under crates/, in source and in tests, a pointer must be a literal, and no comment may hold docs/runbook.md# followed by a placeholder. Run sh scripts/check-runbook.sh on the merged reference tree before hand-over. Proposed wording for spec 11: "An anchor must be written out in the source; a pointer whose anchor the script cannot read is an error."
  2. Spec 11 says "in crates/" without saying which files. Chosen: *.rs, test files included, target/ excluded. grep on the binary frame fixture would otherwise need -a, which is not POSIX.
  3. No pointer at all is a failure (tip I2): otherwise a change to the message format would turn the check off silently. Not in the spec; proposed: "It fails if it finds no pointer."
  4. Not fixed, out of scope: scripts/check-lines.sh still exits at the first long file, inside a pipeline subshell (M1 finding 8).

Task 18

  1. Spec 9's example block contradicts spec 6. The example shows {"command": "rm …", "cwd": "…"} with spaces; section 6 says the arguments are serde_json's serialisation of the typed value, which has none. bxctl prints what brokerd sent, escaped, and the tests use the compact form. The example should be compact.
  2. Spec 9 leaves the block's details open; the tests now fix them: two spaces between parts; spans are whole seconds under a minute, whole minutes under an hour, else whole hours, rounded down; expired once now >= expires; a clock behind the broker's shows 0 s ago; a session id over 10 characters is its first 9 and ; an empty list prints no pending approvals; approve_result with outcome ask prints runs (the re-decision lets an ask run); any other error frame prints bxctl: <code>: <detail> on stderr and exits 1.
  3. GrantProblem.problem can be several lines (toml's errors are), which would break <file>:<line>: <problem>. bxctl puts file and problem through escape_json_text, so each problem is one line whatever arrives. Better fixed at the source too: task 06 should give one-line problems.
  4. The error type had to change while the test was being written (the T16 kind of defect): a message that names the socket needs AdminError::Connect(PathBuf, io::Error), not Connect(io::Error). Found by the skeleton.
  5. "+41".parse::<u64>() succeeds. An id is now "ASCII digits only, then parse".
  6. Task order. Task 18 creates main.rs's audit verify arm, but verify::run is task 19's. Task 18 therefore creates crates/bxctl/src/verify.rs with the final signature and a placeholder body, and adds all four pub mod lines. Task 19 must say "Modify verify.rs", not "Create", and must not touch lib.rs or main.rs.
  7. A contract file changed: crates/bxctl/src/lib.rs gained pub mod cli;, and chat::code_name became pub.

Task 20

  1. Spec 9, "--say and --json print the event only", is ambiguous: without --json there is no printed form of the event. Chosen, and tested: --json prints the event's JSON line and never contacts brokerd; --say shows brokerd's block and asks nothing, and the owner answers with bxctl approve from another terminal. Proposed wording: "--say shows the block and does not ask. --json prints the event as JSON and nothing else."
  2. Spec 9 escapes "reasoning and content" only. The model also chooses tool names, which were printed raw in three places, and the final answer on stdout was raw. All are escaped now. Proposed wording: "everything the model wrote: reasoning, content, tool names, and the answer on stdout."
  3. Exits the spec does not cover, now defined: the end of the input at the question refuses; brokerd unreachable, an approval that expired between the list and the answer, and any other failed answer are each reported on one line and the turn goes on; a failed write to the terminal is an error and nothing is answered.
  4. One reader for stdin. Designing the binary-level test showed that the approval's answer must come from the same BufReader as the chat lines: with a pipe, everything typed is in the first reader's buffer, and a second reader sees the end of the input. main.rs holds one reader for the whole run; interactive_mode_reads_the_answer_from_the_same_input_as_the_chat fails otherwise.
  5. tests/admin.rs is 497 lines and tests/chat_approvals.rs 487, against the limit of 500. A test added later to either needs a new file.