Name what can be true. Do not leave it to be inferred from which fields happen to be set.
In plain language
Most systems answer "what is going on with this record?" by reading several fields and
combining them in your head. Is it cancelled? Well, cancelledAt is set, but
active is still true, and there is a replacedBy pointing at
something. Somebody knew what that meant once.
Explicit state replaces that inference with a name. The record is Recorded, or
Voided, or Superseded. There is no combination to decode, because the states are the thing
stored, not a consequence of other things stored.
Precisely
A semantic state is a named, explicit representation of what can be true, as opposed to an
inferred combination of flags and nulls. The set of states is closed: adding one is a change
to the model, which is the point, because it makes every place that must now handle it into
a place the compiler can find.
The strongest form goes further: data that belongs to one state is only constructible in
that state. A field meaningful only when superseded does not type-exist on a recorded entry.
In real code
A time-entry system built twice, independently, from the same frozen requirements. Both
builds arrived at the same three states and the same four transitions:
The lifecycle implemented independently by three separate builds of the same requirements. Nothing leaves Superseded: every guard refuses it, and two acceptance criteria assert the refusal rather than relying on its absence from the code.
The measured structure of one of those builds:
4
Distinct legal transitions
Derived
Measures
Distinct from-state to to-state pairs the entry lifecycle permits: create, void, restore, and supersede by split or merge.
Collected by
Counted by a structural snapshot script whose counting rule is stored in the snapshot record itself.
Does not show
A regex-derived count over one implementation, with the counting rule recorded because it is a judgement. No AST parser existed when the metric was defined.
Twenty-four distinguishable rejection states is the number worth pausing on. Each is a named
reason the system can refuse an action — a value, not a thrown exception and not a null.
"It didn't work" is not a state this system can be in.
The divergence, and why it is the interesting part
The two independent builds modelled illegal states differently, and both were right about
their own tradeoff.
One used a discriminated union keyed by status, so supersededBy only exists on
the superseded branch and the compiler refuses it everywhere else. It cost nothing extra: the
union was designed in from the start.
The other used one flat record with optional fields, and prevented the illegal combination
with construction discipline — only transition functions ever build an entry — plus a
load-time validator that rejects a tampered or corrupted file exhibiting the inconsistency.
Its own self-review names this, unprompted, as the most honest limitation it found in
itself, and its reasoning for not refactoring is sound: the retrofit cost was already high,
and the defect class was already covered.
Two defensible points in one design space. What separates them is not care; it is when the
decision was made.
What an agent should do differently
Before adding a boolean or a nullable field to a domain record, check whether the thing
being expressed is a state. If it is, add a state.
When asked "can this record be X?", answer from the state, not by reassembling a
condition from fields. If that is not possible, the state is not explicit yet.
When adding a case to a closed set, let the compiler enumerate the sites. Do not search for
them first — measured at 8 of 12 sites found mechanically on an unhardened architecture,
and at every targeted site after hardening.
How this is most often got wrong
The status field that is not the state. A status column is
added, and the old booleans stay, and now there are two answers to the same question with
nothing keeping them in agreement. This is uncoordinated duplication with a new name.
Assuming explicit state buys correctness. It does not. The same programme
that measured these state models also measured a semantic no-op surviving every mechanical
check on both a hardened and an unhardened architecture. Explicit state makes a class of
mistake unrepresentable. It has nothing to say about whether the code that runs does the
right thing.