computed state

applies the generalized state machine contract to persistence placement. the root standard owns state semantics, fact precedence, terminal classification, guarded commits, replay; this page owns which durable forms may store facts or disposable projections.

why

a stored state that can disagree with its own facts eventually does: the producer that flips a flag can die before flipping it, and nothing reconciles the copy because the fold that would refute it was never declared. deriving state from facts is self-repairing — the fold recomputes from whatever committed, so a killed worker or raced writer cannot leave a permanent lie. one authority: the declared machine, written once, not three producers each re-encoding its edges.

rule

if a state can be computed, and needs to be computed — answers "what state is this entity in right now" — it is not allowed in the database. status columns, boolean progress flags, stored lifecycle fields are caches of a fold over facts; once written they can drift, and nothing reconciles them.

store the fact. derive the state. declare the state machine as the fold.

mandatory: no column that can go stale (operator directive, 2026-09-29)

"the displayStatus is a plague, it can be stale, we need to remove all column in database that can be stale and do computed state instead."

evidence (2026-09-29): youtube.channel.display_status read unknown for a 6.5M-subscriber channel the catalog served, and the website branched on it — rendered a "discover" card on top of the full discovered page. is_resetting / active_reset_id duplicate the active row in reset.session. every such column needs a producer to remember to flip it, and one day one does not.

the law covers every column whose value is a function of other rows we own, not only status enums:

kindexampleverdict
lifecycle / display statusdisplay_status, processing_stateforbidden — fold the run/step/item facts.
boolean progress flagis_resetting, is_processedforbidden — exists(active session) is the answer.
pointer to "the current" rowactive_reset_id, latest_run_idforbidden — select the current row by its own facts.
count of our own rowscandidate_count, skill_countforbidden unless it is an immutable receipt (see below).
copy of another row's fielda title copied onto a child rowforbidden — join to the owner.
external observationYouTube subscriber_countallowed — a recorded fact; carry when it was observed.
immutable receiptrow_count written once when a snapshot sealedallowed — a historical fact about that moment, never updated.
authored decisioninference_provider, learning_deliverablesallowed — the user or operator set it; nothing computes it.
a run's own statusworkflow.run.statusallowed only when it is the recorded outcome of that run (a fact), never a fold of its children.

a concurrency fence (reset_generation, version counters) is allowed only when no stored fact can serve as the fence; prefer a fact the guard already reads (the active session row, a unique constraint).

auditing a schema for stale columns

  1. list candidates: information_schema.columns matching status, state, is_*, has_*, *_count, active_*, latest_*, current_*, plus every column written by more than one code path.
  2. per candidate: what fact(s) does it summarize? who writes it (path:line)? who reads it? can it be recomputed from rows we own?
  3. prove staleness live: one sql diff of the column against its fold; every mismatching row is a lie already served.
  4. classify with the table above; for each forbidden column name the replacement placement (generated column, view, backend fold, recompute cache) and the readers that move to it.
  5. retire forward-only: readers move to the fold first, writers deleted, then the column dropped in its own migration.

fact versus computed state

questionanswer
did something happen or get recorded?fact. store it: settlement, transcript stored, admission granted, provider instance observed errored.
what state is the entity in, given everything?computed state. a pure function of facts. never a column.
is a mirrored external observation a fact?yes. an observed provider status is a recorded observation; copying it is storing evidence, not deriving.
is a parent state (topic over videos) a fact?no. parent states fold child states: topic = fold(videos), channel = fold(topics, pledge facts).

the dividing line is recomputation. a fact cannot be recomputed, only recorded. a state can always be recomputed — that is why storing it creates a second, lying copy of the truth.

persistence applies the fold

the state machine declares the closed states and the one exhaustive fold. persistence supplies canonical facts and the atomic fact-commit boundary. transition writers, status repair jobs, producer-owned progress flags remain forbidden — each creates another state authority beside the fold.

where the fold lives

four placements, ordered by increasing distance from the facts:

placementwhat it isfreshnessfits when
generated column (same row)GENERATED ALWAYS AS over columns of one rowcomputed at write, always truethe derivation only needs fields of its own row (video processed).
sql view (fold in the definition)CREATE VIEW folding fact tables with joins/aggregatescomputed at every readone sql statement can express the fold; readers want a named table.
backend fold (fold in app logic)typed pure function over fact reads, declared once, sharedcomputed at every readthe default. typechecked, exhaustive, testable, portable.
recompute cache (materialized)table rebuilt whole by re-running the folddeclared freshness windowfleet/list reads that must select by state (work queues, catalogs).

decision rules:

  • a derived value that only reads its own row is a generated column, not app code — the database maintains it and it cannot drift.
  • the backend fold is the default owner of entity state: one authored, typechecked function per entity is the declared state machine. push the fold down into sql (one composed statement or a view) only when reading every fact row into memory is measurably too costly for the read path.
  • a view or sql push-down is packaging, not authority: the same fold now lives in sql. keep exactly one expression of it; parse its output through the same typed contract the backend fold uses.
  • the recompute cache is the only stored form, and it stores the fold's output, rebuilt whole (next section). an incrementally maintained status column is not any of these placements — it is the forbidden cache.

when a fold is too expensive to run per read

a work queue or ranked list that must select by state without folding every row is the one sanctioned exception — a recompute cache, not a status column:

propertyrequired answer
ownerthe fold itself is the owner; the cache stores its output.
writerebuilt by re-running the fold, whole. never incrementally mutated by producers.
repairdrop and rebuild. there is no hand-repair, no flag flip.
honestya typed failure or a declared freshness window, never a silent stand-in for the fold.

an incrementally-updated status column with a reconciler is not this pattern; it is an undeclared cache pretending to be authority, and it is forbidden.

verification

  1. during migration, run the fold over live facts and diff against the legacy column; every disagreement is a real lie the column told.
  2. prove parent folds agree with child folds (channel versus its topics) on real data.
  3. kill a producer mid-flight: no state goes permanently wrong — the fold still answers from committed facts.
  4. prove the guard batch: committing a fact under a false precondition fails atomically.

good and bad

bad

// three producers hand-write the same machine:
await transitionLifecycle({ from: ["learning"], to: "learned" });
await topicsTable.update({ processed: true }); // a workflow was supposed to flip this
// a reconciler re-encodes the edges a third time to fix drift.

a stored state that can disagree with its own facts, does. the producer can die before flipping the flag, and the flag then lies forever — no reconciler refutes it because the fold that would was never declared. three producers encode the same machine three ways, so answers diverge under concurrency and retries; every divergence becomes a manual data-repair incident. the interface eventually renders a state the facts cannot support, and nobody can say which copy is authoritative.

good

const state = deriveChannelState(await readChannelFacts(channelId));
await commitFact({ kind: "run_completed", channelId, runId }, { guard: state === "releasing" });
// every reader derives; every guard folds; nothing to drift.

state is a function, not a row. the machine is declared once as that function, so every reader and guard derives the same answer from whatever facts actually committed; a killed worker or raced writer cannot leave a permanent lie. adding a state means editing one exhaustive, typechecked fold rather than hunting every producer that hand-wrote an edge — drift becomes structurally impossible instead of periodically repaired.

search pages

go to any page