Status Axes
What status a folio actually carries — one stored axis, one derived, and two that were retired
Status is the thing a schema most often gets wrong by conflating, so each axis below is named separately and says whether it is stored, derived, or retired. The values themselves are declared in code and that declaration governs; this page says what they mean and why the split is drawn where it is.
| Axis | Field | Where it lives |
|---|---|---|
| Version commit state | versionStatus | Stored — a column on folioVersions |
| Collection progress | interviewStatus | Derived — computed from the answers, never stored |
| Reuse posture | reuseType | Retired — library standing derives from libraryPins |
| Render mode | generationMode | Retired with the payment and generation cluster |
Stored — versionStatus
Is this row a frozen save point, or the live editing head?
live is the mutable head: every edit, every answer, every computed-field
update lands there, and there is exactly one per folio at a time. versioned is
a sealed save point — named, immutable, never edited again.
convex/schema.ts declares the union; folioVersions carries it.
live → versioned is one-way, and minting is a user act. Sealing the draft
creates the next edition and opens a fresh draft for the round of edits after
it. Nothing about typing mints a version.
In plain terms: live is the working copy on the desk; versioned is the
engrossed copy in the file. A lawyer decides when one becomes the other, the
way a developer decides when to commit — not on every keystroke.
Why the axis exists at all. Without it, either every keystroke creates a committed snapshot (storage explodes) or there is no way to name a save point, and the lawyer cannot point at "the version we sent on Tuesday". One axis, two values, one transition. The immutability rule is Sealed Versions Are Immutable; the save lifecycle is in Folios Schema.
Derived — collection progress
How far along is this data collection?
Nothing stores this, and that is the point. The retired three-tier model
kept a moduleVersions.interviewStatus column that every write path had to
remember to recompute, so it went stale the moment a field was answered by a
path that forgot. The rule is now a projection —
packages/domain/src/interview/progress.ts, which holds the whole of it at all
three heights and governs. A derived status cannot go stale, because there is no
second copy.
| Value | Meaning |
|---|---|
not_started | Nothing a person could answer has been answered. |
in_progress | Some are done, not all. |
complete | Everything a person could answer is done. |
skipped | Deliberately passed over. Settable only where the field is not required — the field contract refuses the other case, so a required field is done only by its answer. |
A field is done when it holds a value or is skipped, and "complete" means every field a person could answer — not merely every required one. An optional question left open keeps the block in progress. That is the owner's ruling in Interview Completion Is Answered or Skipped, and it is the opposite of the "all required fields" test an earlier revision of this page stated.
Two consequences worth stating, because they are easy to get wrong:
- Empty is absence, not an answer. An empty string and an empty list leave
the collection exactly where an unasked question does. Treating
""as collected would report a document complete because someone tabbed through it. - Computed fields count for nothing either way. They are derived, so they can neither be answered nor left unanswered.
A document is never skipped. Skipping is something done to a part, and a
document made entirely of skipped parts has nothing left to ask — which is
complete. So a document's progress is the narrower three values.
Status reason. A status_reason may explain a non-obvious skip. It is a
statement of current state, not a transcript, and must never carry hidden
reasoning.
The local skill shares the vocabulary, mirroring per-module status as
status in the one-file corpus layout with the same optional status_reason —
see Local Skills → File roles.
Retired — reuseType
A folio no longer declares whether it is working or library content. Library
standing is a fact about a shelf, not about the card: a libraryPins row
names the exact sealed edition that sits on a library, and the folio itself
never moves or copies to get there
(A Library Is a Shelf, Not a Folio).
The rule the old axis carried survives in a better place: promotion is copy-write. Insertion from a library materialises an independent copy with its own lineage pointer; the source is untouched. What went is the column, not the discipline.
Retired — generationMode
preview / final was a render-time input declared through a document's
output.generation keys. That whole payment and generation cluster is retired
(see what the Folio schema does not carry),
and documentGenerations is dormant with no writer.
The product idea is alive and the mechanism is open: a free Draft render and a paid Final render are recorded and deferred. Whoever builds them decides how a render is marked. It will not be a status column on the folio, because a single folio is validly rendered both ways.
Why not one documentStatus
Because the facts move independently, and an overloaded enum forces every transition to invent a new value. A folio can be on its live head and have every question answered — the lawyer simply has not named a save point yet. It can equally be sealed mid-collection, as a checkpoint. Neither is a contradiction, and no single enum can express both without a combinatorial vocabulary nobody can read.
The same reasoning retired the older workflowStatus and publishStatus:
workflowStatus split into the version state and the derived progress above,
and publishStatus went because library standing is a pin, not a flag.
Libraries
The one consolidated statement of the Library — what a Library is, the tables behind it, how saving, searching, inserting and access work, the menu, and what is still open. The dated decision records are its history.
Flow Schema
Authored flow contract — condition and adapt — shared by modules and blocks, with the retired after anchor