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.
The situation
Machine SN-123 finished a run at 09:30, but the unload row did not open until 09:42. Twelve minutes of nobody owning the machine is exactly what a summary chart hides.
The recipe
const { data } = await lt.escalations.timelineByFacet({ facet: { key: 'serialNumber', value: 'SN-123' }, query: { entity: 'serialNumber' },});// intervals: [{ role, subtype, status, startedAt, endedAt, durationSeconds }, ...]ltc esc timeline serialNumber SN-123 --entity serialNumber --order desc --limit 100ltc esc timeline serialNumber SN-123 --entity serialNumber --order desc --limit 100 \ --before 2026-10-22T09:15:00ZIntervals come back in start order. An open interval reports endedAt: null. The time between one interval’s end and the next one’s start is untracked time, and it is left as a gap.
The guarantee
The facet match is GIN-served containment, so the stored value must be a JSON string. Gaps are preserved, never filled. Timelines always take the full read gate (read_all on every role in scope), since an entity’s history is item-level disclosure. See Analytics.
Related patterns
Analytics
Reconstruct any moment
Pass a past instant and membership rebuilds the live set as it stood then, from the same rows, with no snapshot table.
Analytics
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.
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.
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.
