Skip to main content

Lifecycles

A lifecycle is a value that moves through states over time. It can be a status enum, a set of nullable timestamps, or booleans that together mean a state. These rules apply to the server. ADR 0078 records why we chose them. Some code that is older than ADR 0078 does not obey all the rules yet. Do not copy it.

Add a lifecycle​

  1. Use one enum column for the state. Do not hide a state in a JSON key or in a combination of booleans.
  2. Add a CHECK for the closed set of values, in the same migration as the column.
  3. Add a CHECK for each column that a state implies, such as a reason or a completion time.
  4. Put the legal transitions in one table beside the enum. IntegrationState shows the form.
  5. Write each transition as a compare-and-set on the expected state, as FeedbackSendRepository.claim does.
  6. Refuse an illegal transition, and record the reason.
  7. Decide what a crash, a replay and a retry do to each state. State the answer in a test.

Use a plain enum. Do not add a state machine framework.

Choose the rigor​

Rank a lifecycle by the damage of a wrong transition: trust, tenancy, data loss, then stuck work. The levels add up. Levels 1 to 3 follow the rank. Levels 4 and 5 apply only where their condition holds.

LevelAddWhen
1CHECK constraintsEach stored lifecycle
2A transition table and table-driven testsMore than one writer
3Seeded random walks over eventsA wrong transition costs trust, crosses a tenant, or loses data
4Optional: a small TLA+ model, outside CIA concurrent protocol that is hard to reason about: a lease, a permit or a fence
5An invariant query with a metric and an alertA rule across tables, which a CHECK cannot express

Close what you own​

Most defects sit where one lifecycle hands over to another, not inside one. When a parent ends, end its children with the parent's reason. Do this in the same transaction inside one module, and through a durable event across modules (Transactions and side effects). Do not leave a child in a state that only an active parent can move on. Do not report a child as finished when nothing measured it.

Repair old rows first​

A CHECK validates each existing row. Rows from before the 1.0 cutover can break a rule that today's code keeps. Count them on staging and in production first. Then repair them in the same migration.