Build step three is the driver contract: a shared conformance harness, one push module and one poll module, and the host process that supervises them. It was audited a fortnight ago against the documents that own it, and the first of those, The Driver Contract, went out last Wednesday.
Entry two was about silence. A specification that described things carefully, at length, and then never said whether they were required.
This one is the opposite failure, and I did not see it coming. The document is not silent. It says the thing. It says the thing twice, in two places, and the two do not agree.
Everything below is that same shape. Not a false statement about the system; a true statement in the wrong place, or two true statements that cannot both be the contract.
The contract said both, and the code believed the one with a code block
From Push and poll are the same thing:
The driver module implements one method: emit observations. Whether that is an MQTT callback or a poll loop is internal.
From Conformance, eighty lines further down, where the interface is printed:
read(points) → observations
Those are two different contracts. The first says a module emits, on its own initiative, whenever it has something. The second says a host asks and a module answers.
The driver host built the second. I would build the second again from the same document, and the reason is not subtle: it is the one with a code block beside it. A signature beats a sentence, every time, and it beats it silently.
So a push module latches the latest frame per point, and the host samples that latch on the assignment's interval; thirty seconds where a point declares nothing.
For a level reading that is fine. A temperature that arrives at an awkward moment gets read at the next tick, and it is still the temperature.
For a transient it is not fine at all. A button press. An alarm that raises and clears itself. A notification with no status counterpart. Those complete inside one interval and are never published. Not delayed; never published.
And the document has a sentence about exactly that, which it can no longer support. Buffering says of the bounded disk queue that
this is the single place in the whole path where data is dropped by design.
The latch is a second place. It has no drop count, and it has no self point. So the problem is not that a sentence went out of date; it is that the guarantee the sentence makes, that data is never lost quietly, is defeated by the other reading of the same document.
The audit did not mention it
This is the part that stings.
build-step-3.md is the audit of the step that built that interface. It runs to twenty thousand characters, it is unsparing elsewhere, and it does not mention the choice at all.
Not because anything was hidden. Because an audit asks whether the exit criteria pass, and "which of the document's two readings did you implement" was never an exit criterion. There was nothing there to fail.
Which means the pull shape has been load-bearing since step three with nothing behind it. No decision row. No open item. No line anywhere saying a fork had been reached and a side taken.
It is three rows now, because it is three different kinds of statement: the gap in the document, the reading the code took while it stood, and the build that closes it. Splitting them is not bureaucracy. The gap is discharged by the document saying which reading is the contract and by nothing else; the reading is history the moment the build lands; the build is work somebody has to do. One row would have to be wrong about two of those.
The step went amber for a day
The amendment landed over the weekend, and it brought four new exit criteria with it. None of them passed.
So step three, complete since a fortnight ago, went back to amber.
Nothing regressed. No code changed. Every test that passed before the amendment passed after it. The document simply started asking for more than had been built, and a step is finished when its owning document's exit criteria pass.
It was green again a day later, with the delivery path built and both hosts in the field running it.
I want to be precise about why that yo-yo is the machine working rather than the machine embarrassing itself, because a status marker that moves backwards looks like a mistake and this one is the opposite.
The marker follows the definition. The alternative is a marker that follows the work, and that quietly changes what the marker means: it stops recording whether the thing the document asks for exists, and starts recording how much effort has been spent. Those come apart precisely when a document is amended, which is the moment you most need the marker to be honest.
A step going amber because its document grew is not a regression. It is the only truthful thing the marker can do.
Three checks, and one place the third can go
Same shape, smaller blast radius, and it is sitting in the post that went out last Wednesday where anyone can check it. Conformance names
Three additions to the harness that come from later documents and belong in the same suite
and lists epoch rejection, publish scope, buffer bounds.
Only publish scope is in the harness. The other two are the host's, and the suite says so as a matter of design:
The suite is deliberately silent on anything the host owns. Backoff, buffering, epoch fencing, self-points, sequence numbers and subjects never reach a module, so a module cannot get them wrong and the harness does not ask.
That is the right call, and the argument for it comes from the document itself, in the section immediately before. What drivers must never do forbids a module to hold verification state or timers. A module that tracked the highest epoch would be holding a fencing token; a module that owned the durable queue would be holding buffering state. The queue is one per host, behind every instance, so there is not even a coherent thing a single module could be asked to demonstrate.
A check cannot be placed in a suite that runs against an interface which never sees the thing being checked. The code is right. The document put three checks in a place that can hold one of them.
And that is a gap in the document rather than in the code, which decides what happens next: shipping the code that routed around it does not close it. So it was filed against The Driver Contract, with the reading recorded separately so nobody would have to re-derive it meanwhile, and the document was amended a week later to say whose each check is. One of the three is the harness's; two are the host's.
The order that repair happened in is the part worth keeping. The requirements register carried a paraphrase of the original sentence, and the paraphrase was wrong in an extra way of its own: it said the check happens in the harness "as well as core-side", and buffer bounds has no core half at all. It was still left alone while the finding was open, and moved in the same commit as the document rather than ahead of it. Correcting a paraphrase before the thing it paraphrases does not fix anything; it just produces a second answer, and now you have two.
The register said otherwise, for three pull requests
This is the one I keep coming back to.
ROADMAP.md carried a sentence claiming that publish scope and buffer bounds "were module checks in the harness from the start". A docstring in the driver host repository said the same two live under tests/conformance/.
Buffer bounds never did. Not moved, not relocated: never there.
Epoch rejection's move out of the harness was noticed and recorded when it happened. Buffer bounds' move was not, and so a sentence asserting the opposite survived three pull requests, in two repositories, saying something false about the codebase it shipped with.
Entry one ended on the registers being under test. test_register_integrity.py asserts that ids are contiguous and unduplicated, that a requirement is never both a bare row and a set of sub-ids. Fifty-seven of those tests at the time of this audit. I wrote then that pointing the specification's own convention back at the process documents felt faintly ridiculous, and that it had caught things.
It had, and it does. It could not catch this, and the reason is worth saying plainly:
Not one of the fifty-seven can check a claim about where a test lives.
They check the shape of the registers, which is the thing a register can be wrong about on its own. A sentence asserting that a particular test sits in a particular file is a claim about the world outside the register, and the register has no way to reach out and look.
The same audit turned up two stale cross-reference anchors the same way: by a person reading, while all fifty-seven passed.
And those did have a fix available, which is what makes the boundary visible rather than merely assertable. An anchor points at a section inside the register, so a test can follow it and see whether the row is there. One was written, straight after the audit, and it found four more the moment it ran.
A sentence about which file a test lives in points outward, at a repository the register cannot see. There is nothing there for a test to follow.
So the idea holds, and its edge is now drawn, which is more useful than the idea was on its own. A register can be machine-checked for everything that is a statement about itself. Everything it says about the world outside is still something somebody has to go and look at, and the cadence at which that happens is the audit, not the test suite.
Two smaller ones, and they rhyme
The command dedup window is a fixed five minutes, deliberately not derived from the command's own deadline. Sizing it from the deadline is the obvious move and it is actively harmful: it puts the core's clock inside a driver-side decision, so a host running fast forgets a command id early and pulses the relay on exactly the redelivery the window exists to suppress. Wrong in the direction that costs memory, rather than wrong in the direction that moves a physical thing twice. The published copy still leaves the figure open as "a short window"; it is a number now, and fixed on purpose.
Two subjects were built and never called. Availability and quarantine each had a subject builder, an envelope type, and a publish path asserted against a real broker, and nothing in the driver host ever constructed a message for either. A test that proves the machinery works is not a test that proves anything runs it.
Availability has both ends now. The core subscribes at startup and narrows each envelope to what the declaring instance actually owns, and a driver declares on exactly two edges: losing the transport while it is serving, and stopping deliberately. Quarantine was lifted out of that row rather than carried along inside it, because its producer end turns out not to be buildable yet at all, and a resolved row still quietly carrying half its subject is a row nobody ever comes back for. That is entry two's distinction between deferred and forgotten, met again from the other direction: the danger this time was not an absence with no row, but a row marked done with an absence still in it.
What the shape is
Entry two found one root cause behind three symptoms. This step has a different unity, and it took me a while to see it.
Every finding is about placement. Which of two sentences is the contract. Which side of a process boundary a check belongs on. Which file a test is in, as asserted by a document that cannot see the file. Whether a built path has anything calling it.
None of that is the kind of error a test catches, because a test runs the code, and these are all statements about where things are.
And the drift check, which reads the code against the document and is the thing I would have expected to catch all of it, only works from one side. It finds where the code departs from the prose. When the prose departs from itself, both halves are documented, both are defensible, and the code picks one without recording that it picked.
The honest summary of the step: the specification was wrong in three places, none of them about the system, all of them about itself.
This Wednesday - Ingest Path & Log Schema. The specification's fourth part: what an observation does between arriving on the bus and being answerable, and the log that the current state is only ever a projection of.
Next Monday - the hole entry one ended on has been filled. Nothing reads the documents against each other was an open question a fortnight ago. It is a procedure now, and the first run of it broke its own rule on purpose. That entry waits for the documents it found things in to be published.
Start of the diary: Four Registers and a Drift Check. Why the registers exist at all, and the one rule about where a sentence is allowed to live.
Top comments (0)