The release boundary for this project is ten declared steps. Two of them are next to each other and are separate on purpose:
step 8 accept_read compute the acceptance verdict for every unit and print it
step 9 record write that verdict into the permanent record
Step 8 looks. Step 9 commits what was seen. The gap between them exists so a person can read the verdict before it becomes history.
I ran them as one command chain. Both steps are always needed and the second always follows the first, so joining them felt like removing a redundant keystroke.
What the read actually returned
Every one of the fifteen units came back red. Not one of them for a reason that had anything to do with the units:
FAIL_SEMANTIC reason=op_unknown op_unknown=51
A generator had started emitting a new form of annotation that morning. The checker that validates those annotations works from a closed vocabulary. That is the right design, and the 51 new names were simply not in it yet. The checker refused, correctly, the way a closed vocabulary is supposed to refuse.
Nothing was broken. One list needed 51 entries added to it.
The record step did not care
It had no reason to. Its input was a verdict and the verdict said red, so it wrote the truth it was handed:
ACCEPT_RECORDED rows=15 accepted=0/15 commit=f6d74b1
That file is append-only. Records are never edited and never removed, which is a property I chose and still want, because a record you can quietly fix is not a record. The 0/15 is permanent. It sits in the history with a later block superseding it, and anyone reading that history sees a day when everything failed followed by a correction.
Twenty minutes later, after the vocabulary was declared and the map regenerated, the read was run again and this time read first:
ACCEPT_RECORDED rows=15 accepted=9/15 commit=2536c12
Nine. The false number cost more to carry than the missing 51 entries cost to add.
&& protects the thing you were not worried about
The chain even looks like a safety property. "Only record if the read succeeds" sounds like exactly the guard you would want.
The read did succeed. It ran to completion, computed a verdict for all fifteen units and printed its reason, then exited 0. Exit 0 was correct. A measuring device that reports a red measurement has done its job perfectly.
| what the chain checks | what it does not check |
|---|---|
| did the read process run | is the verdict worth committing |
| did it exit non-zero | did a human look at the reason |
| did it crash | is the red about the subject or about the instrument |
That last row is the one that bit. op_unknown=51 is a statement about the checker's own configuration, not about the fifteen units it was pointed at. No exit code can carry that distinction, because both cases are a successful run that produced a red.
The rule I now hold
A step that observes and a step that commits the observation are never one invocation.
Not joined by &&. Not joined by ;. Not wrapped in a convenience script. Two commands, and in practice two turns, so that something slow enough to read sits between the measurement and the permanent consequence.
$ boundary --step=accept_read # look, and stop
$ boundary --step=record # commit, as a separate decision
This is the same shape as plan and apply, or a dry run against a real one. It is routinely collapsed for the same reason I collapsed it: the two commands always run together in the happy path, and the happy path is where the habit forms.
Two things I keep
Exit 0 means the measurement ran. It never means the measurement was good. A device that says "red" and exits 0 is a healthy device, so any automation keyed on the exit code is reading the wrong field for this question.
The cost of collapsing "look" into "act" is paid in permanent records, not in retries. If the consequence were a retry I would never have noticed, and I would have kept the shortcut. The reason this one taught me anything is that the file it wrote to cannot be edited.
Top comments (0)