Six days after the required-operation rename shipped, every attempt to ratify or supersede a Decision on the live instance -- including from a real admin token -- came back forbidden. The rename was correct. The database just never heard about it.
The upsert that only inserts once
App.RegisterFlow is the one function that writes a state flow's rows -- flow, states, transitions -- and it's documented, and meant, to be safe to call on every boot: register the same flow again, and whatever changed about it takes effect. That's true for the flow row itself (renamed in place since T268) and for description. It was not true for a transition's own required_role column. That insert's own conflict clause was ON CONFLICT (...) DO NOTHING -- the first value written was the value that stayed, forever, no matter how many times the same flow was registered again with something different.
Nobody had reason to notice, because nobody had changed a transition's role requirement since the row was first created -- until the required- operation rename did exactly that, on rows that already existed on every running instance. The column kept the literal string "admin". The new authorization check no longer matched role names -- it matched operation words. "admin" isn't one of those. Every actor lost.
A limitation that was already written down
This wasn't a fresh discovery. A changelog entry from six weeks earlier -- the one that first added the Strict gate -- already named the exact mechanism: *"RegisterFlow's transition upsert is ON CONFLICT DO NOTHING, so re-registering an already-registered flow with a changed RequiredRole/RequiredReason/Strict silently does not update the stored row."* Flagged, filed, and left, because nothing had needed it fixed yet. The rename six weeks later is what turned a latent gap into a live outage.
Fixing it meant answering a question the bug report didn't assume
The obvious fix -- swap DO NOTHING for DO UPDATE, matching the flow row's own upsert three lines above -- raised a real question first: does blindly accepting the latest values on every call risk overwriting a customization someone made through a different path? The answer turned out to be structural, not a judgment call: there is no different path. Every write to a transition's role/reason/strict columns goes through this same function, and the tool that exposes it to a running instance (define_state_flow) already documents itself as "idempotent, safe to re-run on every restart." DO NOTHING was the thing breaking that promise, not protecting anything. The fix applies everywhere RegisterFlow is called -- five built-in flows and any self-hoster's own dynamic ones alike -- because scoping it narrower would have left the documented contract broken for exactly the callers it was written for.
The next normal redeploy heals every existing database on its own: the five orchestration flows are already re-registered on every boot, so fixing the upsert is the whole fix. No migration, no backfill, no manual step -- the boot sequence that was silently discarding the update starts applying it instead.