Concept 4
Obligations, effects and uncertainty
Three things most models leave implied: what is still owed, what leaves the system, and what genuinely is not known.
Obligations — what is still owed
A transition can succeed and still leave something outstanding. An entry is recorded but not yet attested. A correction is applied but not yet propagated. Conventionally this lives in a flag somebody has to remember to clear, or in a background job that infers it.
An obligation makes it part of the state's meaning. The question "what is still outstanding here?" has an answer in the model rather than in a query somebody writes later.
Effects — what leaves the system
An effect is an externally observable consequence, requested as data rather than performed in place. The transition function returns a description of what should happen; something at the edge performs it.
The practical consequence is measurable. In the build with 0Runtime dependencies, every domain function is pure and knows nothing about storage, which is why the whole rule set could be tested without mocking anything — and why the integration tests that do touch real disk are a genuinely different mechanism rather than the same one again with more setup.
Uncertainty — what is not known
A write is attempted. The connection drops. Did it land?
Conventionally this becomes an exception, or a null, or a retry that hopes for idempotence. Ordo makes it a state. Both independent builds of the time-entry system arrived at a four-outcome result at the storage boundary — success, failure, conflict, and unknown — and both arrived at a matching four-outcome reconciliation for resolving the unknown case afterwards. Neither copied the other; they had not seen each other's work.
And here is where it went wrong anyway. In one of those builds, the reconciliation's Conflict outcome was declared in the type and could never actually be constructed. Modelling uncertainty explicitly did not, by itself, make the model reachable. That took a fresh reviewer, and it is one of the five findings for which 0 of 5Independent-verification findings the builder's own checks had already caught were already covered by the build's own checks.
Being honest about fault injection
Both builds needed to test the unknown-outcome path, and neither could produce genuine ambiguity from synchronous local file I/O. Both did the same thing: they built a labelled, test-only seam and documented plainly that it is a seam, rather than dressing a simulation up as a real concurrency test.
Both also recorded what that leaves unverified — true concurrent multi-process access is not covered by either suite. That statement is on both experiment pages, in the limitations section, where a reader looking for reasons to doubt the result will find it.
What an agent should do differently
- When an operation can fail ambiguously, model the ambiguity. Do not pick success or failure and move on.
- When you declare an outcome in a result type, find the code path that constructs it. If there is none, you have declared a contract you do not implement.
- Keep effects as returned data for as long as possible. A domain function that performs I/O cannot be tested without the world.
- Label test seams as test seams. A simulated failure documented as simulated is evidence; the same seam undocumented is a false claim about coverage.
How this is most often got wrong
Unknown collapsed into failure. The most expensive version of this: a write that actually succeeded is retried because the caller could not tell, and now there are two.
A type that describes more than the code does. Declaring four outcomes and producing three is worse than declaring three, because every reader downstream now handles a case that will never arrive and believes they are covered.