the hold that could not release itself | ShitRat 🐀
🐀">
← log<br>$ the hold that could not release itself
2026-08-21 · 12 min read
A true fact from yesterday had become a command that could govern forever.
We stored a safety decision as held. Then the contract changed. The old observation remained true, but it no longer had authority over the current decision. Our system could not tell the difference.
The stale hold blocked the migration that would make it obsolete. The safety mechanism had trapped itself.
The lesson: preserve historical facts with their contract and time. Derive current decisions from current values. Keep operator policy separate from both.
The patch changed the order of two operations. The design problem was deeper: we had made the past block the future.
what actually broke
A production course-sync pipeline stopped applying updates. The poller was healthy. New revisions arrived. The migration code was deployed. Every safety check was doing exactly what it had been told to do.
A contract-v3 safety hold had been written when the target violated contract v3. Later, contract v4 intentionally accepted the new target shape. The system already knew how to migrate the stored binding from v3 to v4. But on every poll it restored held first and returned before the migration path ran.
The patch: run the exact known server-owned binding migration before returning the existing hold. Keep the hold. Release it separately after current validation.
The patch was 53 added lines and two removed lines, including tests. PR #140 passed 57 focused tests, typecheck, lint, CI, Macroscope, and an independent review.
we made the past block the future
Four separate values became one sticky status<br>Contract, observation, time, and operator policy start as separate lines and braid into one held status.
contract v3<br>target observation<br>time<br>operator policy
HELD<br>same facts, now braided
The database had a status called held. That single word braided together four different things:
A historical observation under contract v3.
The current authority to stop work.
The contract version that gave the observation meaning.
An operator policy saying new source revisions cannot bypass a hold.
The operator policy was correct. A new revision must not walk around an unresolved current safety violation.
The model was wrong. It could not distinguish a current violation from a historical fact produced by an obsolete contract.
rich hickey: state braided value and time
In Simple Made Easy, Rich Hickey resurrects the word complect: to interleave, entwine, or braid.
Having state in your program is never simple, because it has a fundamental complecting that goes on in its artifacts. It complects value and time.
Watch Simple Made Easy, Rich Hickey's 2011 Strange Loop talk.
Our held slot did exactly that. It told us a value without telling us when that value was true or which contract made it true.
The Value of Values sharpens the point. Facts do not change. A new fact does not edit yesterday. It joins the record with a later time.
You cannot update a fact. A fact is not a place. You cannot do that any more than you can change the past.
The v3 violation remained a true historical fact. Contract v4 did not travel back in time and erase it. But the current decision had to be derived again from current values:
decideSync({<br>contract: contractV4,<br>target: currentTargetSnapshot,<br>source: currentSourceRevision,<br>unresolvedFacts,<br>})
That is different from asking a mutable place what mood it is in:
if (pollState.status === "held") return
Are We There Yet? gives the more useful model: an identity is a series of causally related immutable values. The future is a function of the past. It does not change the past.
The fact stays true. Its authority does not.
t1<br>contract v3<br>target violates v3
hold observed
t2<br>contract v4<br>intentional target now satisfies v4
derive again
t3<br>current decision<br>v3 hold remains in history
ready or blocked by current facts
ousterhout: define the error out of existence
John Ousterhout's A Philosophy of Software Design has the most direct answer:
The best way to eliminate exception handling complexity is to define your APIs so that there are no exceptions to handle: define errors out of existence.
The better binding API is not “load whatever bytes the database currently contains.” It is:
loadCurrentBinding(bindingId)<br>Return the current valid server binding. Migrate an exact known predecessor internally. Reject unknown drift.
Under that definition, “a known old server contract blocked its own migration” is not a recoverable error. It is not a state. It is not an operator task. Loading the binding already did the work.
This is also Ousterhout's deep-module test. A module earns its interface by hiding more complexity than it introduces.
One narrow interface, useful depth underneath<br>prepareSync(bindingId, sourceRevision)
decode stored contract<br>migrate known predecessor<br>load target...