Multi-Tenancy
Every tenant-scoped row carries a tenantId. Ordinary queries are filtered by it. Isolation is enforced in the query, not in each controller.
A tenant is an organizational boundary: its own identities, users, projects, credentials, channels, and billing. Multiple humans and multiple AI identities can share a tenant. Nothing crosses the boundary without an explicit exception.
Canonical host: https://api.atamaia.ai.
The row
Tenant-scoped entities have id, guid, tenantId, audit timestamps, isActive, and isDeleted. tenantId is required. Deletes set isDeleted.
Not tenant-filtered, on purpose: system settings, interest and support registrations, and log/audit archive tables. Archive jobs have to see every tenant.
The filter
Ordinary reads match:
NOT is_deleted AND (system_scope OR tenant_id = @current_tenant_id)The tenant itself is soft-delete only. A tenant is not filtered by its own id.
System scope is the declared wide read, used by housekeeping such as consolidation and log archive. Writes in that scope must set tenantId explicitly. There is no ambient tenant to inherit.
The current tenant comes from the authenticated principal. It is not a caller-supplied header. Changing the provider after the database context is constructed does not change the tenant already captured for that context. (Operator elevation, #1333, is the one principal-derived extension: a minted acting_tenant_id claim — never a header — pins the ambient tenant for that request; see the bypass table below.)
An authenticated principal without a valid tenant_id claim is refused (#1405): a 401 at the door on every surface (the tenant-claim gate in token validation, plus a door middleware that covers the schemes token validation never sees), a hard throw from the provider as the backstop — never a silent resolution to tenant 1. Valid means parsable and non-zero: 0 is the unset write sentinel, not a tenant; negative ids stay valid (-1 is the platform tenant). Tenant 1 remains the answer only for genuinely anonymous requests: health checks, bootstrap, and signup before a token exists. Background work states its tenancy explicitly (system scope or AsTenant(id)).
On insert, a non-zero tenantId wins over the ambient tenant. That is how provisioning stamps child rows. After create, tenantId is immutable.
Provisioning
Provisioning does not depend on the caller's tenant.
- Create the tenant on the Free plan, with a unique slug.
- Set the tenant row's own
tenantIdto its id. - Stamp every child row with that id.
- Seed roles and give the owner every permission.
- Seed org unit types and a root org.
- Create the free subscription and mark the tenant provisioned.
Plans and policy
Plan values are Free, Starter, Pro, and Enterprise.
GET /api/tenant/policy
PUT /api/tenant/policy/force-readable-personal-memoriesforceReadablePersonalMemories overrides encrypted-by-default personal memories. It is an operator choice, not a default, and it does not widen through the org tree.
primaryUserId is the one account-holding human who may read session state besides the owner. Null means deny. It does not grant access to personal memory. See Memory surfaces.
What is shared
Global AI providers (isGlobal) are a shared catalogue. Tenants attach their own credentials. See AI routing.
Standing rules, personal and agent memories, sessions, the code graph, audit events, routes, agents, billing, and channels are tenant-scoped.
Named filter bypasses
A few call sites lift the filter. Each has a reason. A new bypass without one is a leak.
| Area | Why |
|---|---|
| AI catalogue and model sync | Global catalogue, or a sync key with no tenant principal |
| Auth and signup | Find a user or tenant before a principal exists |
| Stripe webhook | The event names a Stripe customer, not a JWT |
| Hydration system health | Platform rows live on the system tenant |
| Log and audit archive | Must see every tenant |
| Stale agent recovery | Recovery runs in system scope |
| Operator elevation (#1333) | The acting tenant arrives as a MINTED CLAIM on the principal — acting_tenant_id, issued only by POST /api/auth/operator-elevation after a live OperatorElevationGrant (or the operator-tenant PrimaryUser arm) was verified from DB truth at mint, with a mandatory reason. Never caller-supplied, never inferred before a principal exists (#1320). Under an elevated token the acting tenant is read-only if it is suspended — writes refuse and audit. See design/operator-elevation. |
Elsewhere
Work memories are tenant plus identity, and optionally project. Personal memories are owner-only unless the tenant policy flag is on. Task cycle detection only sees the current tenant. Agent runs inherit the creator's tenant. Consolidation is system-scoped. See Autonomic layer and Tasks.
Memory content and fact values are encrypted per tenant. Credentials are per tenant. Raw keys are never returned.
Last updated