Isolation model
RuleFlow runs pooled multi-tenant by default: shared services and a
shared database, with every row/partition scoped by the caller’s verified
tenant claim. The model, in one sentence: every operation acts only as
the tenant in the verified token. Its strength depends on exactly two
things — the token cannot lie about the tenant, and no code path can forget
the filter — both pinned structurally, not by convention. A conceptual
introduction is in Tenancy & isolation; this page
goes one layer deeper, into where the pooled model’s shared blast radius is
mitigated and where it is still open.
Root of trust for the tenant claim
- Direct users —
tenantis a per-user attribute mapped into the access token by a default client scope present on every client (studio, control-plane, CLI). It is admin-provisioned: self-service registration is disabled, so no user can choose their own tenant. - Federated (SAML) users — the critical case. When a tenant’s SAML IdP
brokers in, the
tenantclaim is set by a mapper hardcoded at onboarding time to the tenant that IdP was onboarded for. The SAML assertion’s owntenantattribute is ignored — an IdP cannot assert a different tenant. Brokering additionally requires signed assertions (blocking XML signature wrapping) and disables email-based trust (blocking account takeover via a spoofed email claim). - Service accounts — the control plane’s own machine client has no user
and therefore no tenant attribute; its tokens carry no
tenantclaim at all. This is safe only because of reject-on-missing: a tenant-less token is403on every tenant-scoped route. The standing rule: nothing may ever treat a missing tenant as a wildcard.
Three enforcement layers
- Identity — the claim’s provenance above.
- Contract — every persistence method takes
tenantas a required parameter; there is no method that reads across tenants. This is the load-bearing layer: because tenancy is a parameter of the storage interface itself, a handler cannot even express a cross-tenant query — it’s a compile-time impossibility, not a code-review hope. - Infrastructure — tenant is the leading dimension of physical data layout: partition keys, index leading columns, and mandatory search filters all lead with tenant.
Cross-tenant access looks like “not found”
Fetching another tenant’s object by id returns the same typed not-found
error whether the id doesn’t exist or belongs to someone else — a 404,
never a 403 that would reveal the object exists. Listings for another
tenant are simply empty.
project is a label, not a boundary
project is an unvalidated grouping label within a tenant. There is no
isolation between two projects belonging to the same tenant — any member of
a tenant can read any of that tenant’s projects. If you need per-project
confinement (e.g. contractors restricted to one project), that is a new
control on top of this model, not something the platform enforces today.
The shared blast surface, and what mitigates it
Pooled tenancy means several resources are genuinely shared. Here is the honest inventory:
| Shared resource | Risk | Status |
|---|---|---|
| One API gateway | one tenant’s traffic degrades others | Per-tenant rate quota — live |
| One relational database + one NoSQL table | noisy queries, hot partitions | Row/partition-scoped by tenant; no per-tenant DB resource quota |
| One decision-execution compute pool | a high-fanout tenant can starve others’ decision calls | Unaddressed — no reserved/provisioned concurrency per tenant yet |
| One encryption key | shared request quota | Per-tenant context is bound into every envelope (see Encryption); key-level caching deferred |
| No resource quotas on stored executions/artifacts | unbounded growth | Artifact storage is quota-capped; execution/task retention is not yet bounded |
Isolation tiers
Pooled is the only tier that runs in production today. Bridge (per-tenant schema/database, shared services) and silo (a fully dedicated stack per tenant) exist as deployment tooling for a customer that contractually needs stronger separation, but there is no app-level routing that switches a live tenant between tiers yet — moving a tenant is a data-placement operation the platform doesn’t automate today, not a flag you flip. Because authorization is always “act as the token’s tenant” regardless of tier, the API surface and authored rules are identical across all three — the tier only changes where the bytes physically live.
What this model does not (yet) cover
- Per-tenant compute isolation (decision-execution concurrency) — open; a candidate mitigation is reserved/provisioned concurrency per environment, escalating to silo for a tenant that needs a dedicated compute envelope.
- Data residency / region pinning — open.
- Retention on execution/human-task data — no automatic expiry yet; accumulates until a retention policy is added.
These are documented gaps, not silent ones — see the platform’s design records for the sprint that will close each.