v0.93.0 — Actor model and the users table
Shipped 2026-09-19 — merged to main as 210b9c27, tagged v0.93.0, pushed. Built on version/0.93.0, eight commits, one per task. The fourth of the
fifteen releases ROADMAP.md sequences from the August
2026 heuristic evaluation; the roadmap page is
user-management-actor-model —
phase A of the User Management Overhaul,
adopted by the permissions overhaul
as the part it inherits.
The platform had no user model of its own: Clerk's id string sat raw in
thirty-odd columns, and the closest thing to a user record was a settings
bag with is_admin bolted on. Now there is a users table, one server-side
resolveActor and one client-side usePlatformAuth that every consumer goes
through instead of the provider, and a username — the only identifier
the UI shows for another person.
The table
users(U.1): id, kind, the provider's subject and name (the swap seam), username, email, phone,is_admin, timestamps. Both dialects, migration 0030.- The backfill (U.2): one row per identity the database already knew,
id = <clerk id>(decision R1.5), so it is a pure insert with no UPDATE against any existing column. The inventory of user-bearing columns is read off the schema object; a newcomer gets their row on first sight. user_settingsfolded in (U.3): keyed intousersby a foreign key whose migration inserts the rows it needs first;is_adminlives onusers, and every reader goes through one funnel.
The seam
resolveActor(U.4):req.actor = { userId, kind, capabilities }from the provider's JWT, behaviour-identical to today; trial and invited actors are later phases' additions in this one place. Impersonation rewrites the actor as it rewritesreq.auth.- Routes consume it (U.5): 158 reads across 29 files;
req.authis read only by the seam, and a guard holds that. usePlatformAuth(U.6): the client's one seam, wrapping the provider's hooks one-to-one for 34 files;usercarries only what any surface may show of anyone; the viewer's own profile isuseOwnProfile; the provider's account widget isAccountButton; App.tsx names no provider.- The guard (U.7):
@clerk/*only undersrc/auth/on the client, and a ratcheting allow-list of four files on the server.
The username
users.username(U.8): seeded from the provider's username or a derived handle, editable in Settings with the same chips a taken slug gets, and read by the display-name resolver fromusersrather than the provider. The client seam reports the platform's username, so every surface that broadcasts a name broadcasts the right one without changing.