Park every state
Each state an item passes through is one escalation row. The queue becomes a complete time series of the process with no extra instrumentation.
The situation
You want to know how machines spend their day: idle, running, waiting to be unloaded, in service. Adding timers and a metrics pipeline to every step is work that drifts from the process itself.
The recipe
const run = await conditional<RunResultV1>(`run-${serialNumber}-${cycle}`, { role: 'machine-fleet', type: 'fleet', subtype: 'running', metadata: { serialNumber, model: 'mx-200', coolant: false },});
const unloaded = await conditional<{ ok: boolean }>(`unload-${serialNumber}-${cycle}`, { role: 'unload', metadata: { serialNumber, model: 'mx-200' },});role is the queue that attends the state. subtype names the state within a role that holds several. metadata carries the entity key as a string plus any categorical facts worth slicing by.
The guarantee
Every row is one interval [created_at, ended_at). ended_at is resolved_at for resolved rows and the guarded transition time for cancelled or expired rows, and stays null while the row is live. The workflow already writes everything the analytics read; there is nothing else to emit. See Analytics.
Related patterns
Analytics
The entity is a shared facet
Roles that declare the same entity key form that entity's system. One dial per role, and every analytics query can scope to the whole journey.
Analytics
Dwell and membership
One call answers how long entities spent in each state over a window, or how many are in each state right now.
Resolving from anywhere
The row is the production log
A station resolves its in-flight row with the run's result, so the work queue is also the record of what was produced and how long it took.
Analytics
Gaps are a signal
One entity's timeline lists every interval across every queue and keeps the gaps between them, so hand-off delay is visible, not averaged away.
