Two planes, real Durable Objects. One Directory DO is the control plane (directory + sessions + RBAC + the gate); each workspace is its own Workspace DO — DB-per-tenant by construction, so no query can reach another tenant's rows.
A role expands to (action, resource) grants where resource = the workspace key, so a grant only applies in its tenant. The gate's can(account, action, workspace) matches the viewer's grants — wildcards too.
| owner | * (all actions) |
| admin | todo:createtodo:toggletodo:deletemember:manage |
| member | todo:createtodo:toggle |
GET /console → Worker → Directory DO /_console (control plane: directory + your roles) 46ms → fan-out to 3 Workspace DOs /_count (cross-tenant = N round-trips) 2466ms served from colo CMH # a DATA request, e.g. GET /w/acme/todos: → Worker → Directory DO /_gate/acme → verdict{member, role} → Workspace DO non-member → 403 at the gate, before any Workspace DO is contacted
example-tenant's createTenancy(store) slices, unchanged —
the apex home, the cookie session, the transition verbs, tenant-scoped RBAC, and the
/_gate the data plane is checked against.acting as anonymous — signed out. Probing any workspace is refused at the gate. Pick an identity to sign in.
other tenants — probe one to watch the gate refuse you
Recorded per DO via instrumentStore,
newest first, refreshing every 2s — the console's own polling reads are excluded. Open a workspace or
add/check a todo and watch the queries land (the gate's membership lookup, the todos CRUD).
select id, name
from workspaces
where id = ?