Smeldr's orchestration layer has five typed content modules: Signal, Task, Decision, Amendment, Goal, and every one of them moves through a registered StateFlow. It's the natural pattern: define the states, define the transitions, let validateTransition gate the moves.
Run, the sixth type, deliberately breaks that pattern. Here's why.
What a Run is
A Run is one mechanical episode of headless automated work, from the moment a listener claims a task to the moment it merges or gets abandoned. It's the coordination record for M3, Smeldr's headless-automation milestone: a webhook-fed process spawning claude -p unattended, with no human in the loop to notice a collision.
The trap
The obvious design is "a Run's lifecycle is just another state flow": claimed → working → merged, same shape as everything else. It's wrong, and the reason is structural, not stylistic.
validateTransition and the state-flow machinery gate a *status field*. They check whether a transition is *allowed*, then write. That's two separate steps, a read-time check and then a write, and nothing atomic connects them. Two concurrent transitions from the same state can both pass validation and both apply.
For most content that's fine: a human is either directly involved or reviewing shortly after. For a Run, it's the whole problem. Two listeners racing to claim the same row is exactly the failure this type exists to prevent.
What actually has atomicity
SQLRepo.Save does: a real INSERT ... ON CONFLICT ... WHERE rev = $N compare-and-swap, the only one in the framework. It's how the CAS-backed five orchestration types already avoid write races on ordinary field updates. It just isn't wired to Status.
So Run's design inverts the usual approach: claim, renew, and reassign are all Save calls guarded by that CAS, writing directly to two new fields, LeaseHolder and Outcome, never Status. Node.Status is still there (every content type carries it), but it's inert. No flow is registered for Run, so nothing gates it, and no Run row is ever published; it just sits at Draft for its entire life. Reads aren't affected: list_run/get_run don't filter by draft-visibility the way some other surfaces do.
The trap within the trap
The nearest real precedent, smeldr/agent's AgentJob, registers a state flow and routes its lifecycle through Status. It's a reasonable thing to copy and the wrong one for this case: AgentJob doesn't have Run's concurrent-claim problem. Precedent that looks adjacent isn't always precedent that applies.
The part we can't test
Every lease-touching write has to echo the rev value it last read. Skip that, and the framework's own update path silently seeds the current row's rev into the write, satisfies the compare-and-swap by construction, and the whole design degrades to last-write-wins, with no observable difference under a single-threaded test.
That's not a gap we're leaving quietly. It's stated directly in Run's own doc comment, because the code that actually performs claims and renewals, the M3 listener, doesn't exist yet. This task built the type, the storage, and the registration. The discipline that keeps it correct is a contract for whoever builds the listener next, not something this layer can enforce or verify on its own.