Availability is a query
A row is held only while its claim window is live. Availability is computed at read time, so no job has to flip a flag back.
The situation
A dispatcher claims machines for thirty minutes. If it crashes, those machines must be free again when the window ends, with nothing running to free them.
The recipe
const free = await lt.escalations.searchByFacets({ role: 'cells', status: 'pending', available: true, facets: { state: 'ready' },});
const held = await lt.escalations.searchByFacets({ role: 'cells', status: 'pending', available: false,});available = assigned_to IS NULL OR assigned_until IS NULL OR assigned_until <= NOW()available: true keeps unclaimed and lapsed rows. available: false keeps rows held right now. Claims set assigned_to and assigned_until; default 30 minutes.
The guarantee
Held means assigned_to is set and assigned_until is in the future, evaluated in the query itself. A crashed holder’s rows return to the pool when their windows pass, with status still pending. Claims take rows with FOR UPDATE SKIP LOCKED, so many consumers can pull from one pool without contention. See Faceted routing.
Related patterns
Resolving from anywhere
A claim is a work lock
A claim names a holder and a window. While the window is live nobody else can resolve the row, and when it lapses the row is back in the pool.
Resolving from anywhere
An expired claim is recovery
When a holder walks away, the window lapses and the row is claimable again with no job, no status change and no lost input.
The queue as a market
Role is the wall, facets are capability
The role decides who can ever see a row. Metadata facets decide which consumer inside that role can take it.
The queue as a market
All-or-none claim
Claim exactly N rows or none. A dispatcher that needs four free stations either locks four or locks nothing and tries again.
