The command protocol, and output that survives the run

Deliverable one of the hardware-probe workstream, plus the first item of its order.

The specification came first (D102). docs/PROTOCOL.md defines the grammar, the record shapes, capability negotiation, and the three ways a command can fail to answer. Ten captured exchanges in docs/examples/protocol/ are part of the contract rather than illustrations of it - they are what lets a consumer be built and tested with no hardware attached.

scripts/protocol.py parses every transcript against the grammar, and scripts/protocol-selftest.py breaks them twelve ways and requires the checker to catch each one. Both run in verify.sh, self-test first, because a gate has to be shown to say no before it is trusted to say yes (D125).

The file sink (D104). The report now reaches disk as well as a terminal, written a record at a time, never buffered - a buffered file loses exactly the records a crash makes valuable. The path is discovered from four candidates and the winning one is reported, so a run whose sink failed is distinguishable from a build that never had one.

The socket (D126). The protocol is served and driven end to end on the host. The console backend refuses deliberately: those signatures are not confirmed and D008 forbids guessing at them.

Surprises

A test that could not fail, testing a gate for whether it could fail. The first mutation test pointed the checker at a broken copy through an environment variable the checker ignored, so all eight runs re-checked the originals and reported every mutation caught. Fixing it immediately found a real hole: the checker accepted a truncated transcript whenever the file mentioned died anywhere, and one file mentions 03-died.txt in a comment.

The wire format was wrong from its first byte and no amount of reading found it. The record prefix went through the sanitiser that replaces separators inside fields, so every reply read OBS ack|1|hello. One real session over a real socket made it obvious.

The implementation was right and the specification was wrong, once, about acknowledging a repeated sequence number. Resolved in the document, which is the point of having one.

Three escaping mistakes in one session, all the same: building C or Makefile text inside a Python heredoc, where \n becomes a newline the target language did not want. It produced a Makefile continuation containing a literal n, and a fprintf broken across two lines. Structured text gets the editor, not string replacement - a lesson this session had to learn three times to keep.