Tenants
A tenant is the top-level isolation boundary — above "application," above "user." Every event, policy, app credential, and admin action belongs to exactly one tenant, and every store query, realtime channel, and policy decision is scoped to it.
Tenant isolation is an access rule, not a convention
tenant_id is always derived from the authenticated principal, server-side, at
request time — never accepted from a request body, path, or query parameter (the one
exception is the tenant-registry resource itself; see below). Concretely:
core/tenant.Resolver.Resolve takes a minimal Principal interface
(TenantHint() string) and returns a Tenant{ID}, which every handler reads from
request context (tenant.FromContext) instead of trusting client input. A buggy
filter cannot leak across tenants, because there is no code path where a tenant id
originates from the client at all.
Phase 1: one profile, one tenant per process
Today, one running cns-server process serves one tenant, configured via
CNS_DEFAULT_TENANT_ID (default katyayani in the shipped default — set your own).
tenant.Resolver rejects any token asserting a different tenant. Going multi-tenant
later means keying the same Profile struct per
tenant_id instead of running one profile globally — not a rewrite. See
Advanced & Roadmap.
The tenant registry
Tenants themselves are managed through the Admin API, and are the one admin
resource not scoped to the caller's own tenant (the resource is a tenant record) —
restricted to the platform-operator role:
| Method | Path | Notes |
|---|---|---|
GET | /admin/v1/tenants | List all tenants. |
POST | /admin/v1/tenants | {id, name} → 201. |
GET | /admin/v1/tenants/{id} | → 200 / 404. |
PATCH | /admin/v1/tenants/{id} | {name?, status?}. |
DELETE | /admin/v1/tenants/{id} | → 204. Does not cascade-delete the tenant's apps/policies/catalog/RBAC grants (Phase-1 gap — no transactional store yet). |
Full shapes: API Reference → Admin API.
The four principal types
Every request authenticates as exactly one of these (spec's roles/access model):
| Principal | Identity | Scope |
|---|---|---|
| Platform operator | Amphoze operator credential | Cross-tenant: onboarding, adapter profiles, platform ops — never tenant employee PII |
| Tenant admin | Tenant-scoped admin credential | Own tenant only: apps, policies, analytics |
| App / publisher | Service credential (S2S) | emit for its own {tenant, app} |
| Employee / subscriber | Tenant IdP JWT → subject_id | subscribe/read own subject + authorized groups only |
See Authz planes for how these map to the publish, subscribe, and admin authorization decisions, and Apps & credentials / Authentication for how each principal proves who it is.