Concept 1

Explicit domain state

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:

Time entry lifecycle: three states and four legal transitions A time entry is created into the Recorded state. Recorded can move to Voided, and Voided back to Recorded. Recorded can move to Superseded when the entry is split or merged. Superseded is terminal: no transition leaves it. create void restore split or merge Recorded Voided Superseded terminal
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.
Sourcekemiller2002/SDE-Engineering-Trial-1-Greenfield-Construction / metrics/raw/state-system/2026-09-03T00-59-15-753Z_fd2a013cb32a.json at claude/dev-metrics-instrumentation-dfnzhqprivate repository

24

Distinguishable rejection states

Derived

Measures
Named diagnostic variants: distinct reasons the system can refuse an action, each one a value rather than a thrown exception or a null.
Collected by
Counted by the same structural snapshot script, over the diagnostic union's variants.
Does not show
Counts how many refusals are distinguishable, not whether each one is correct or whether the set is complete.
Sourcekemiller2002/SDE-Engineering-Trial-1-Greenfield-Construction / metrics/raw/state-system/2026-09-03T00-59-15-753Z_fd2a013cb32a.json at claude/dev-metrics-instrumentation-dfnzhqprivate repository

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

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.