Skip to content

Tenant isolation

Every piece of data in Neureus is scoped to a tenant. This is enforced at the database level, not the application level.

The rule

Every nr_* table includes a tenant_id column. Every query filters by it. No exceptions.

-- Every SELECT includes tenant_id
SELECT * FROM nr_agents WHERE tenant_id = ? AND id = ?
-- Every INSERT includes tenant_id
INSERT INTO nr_agents (tenant_id, name, ...) VALUES (?, ?, ...)

This is ADR-4 — a foundational architectural decision that cannot be waived.

Authentication boundary

tenantMiddleware verifies the Bearer token and sets tenantId on the Hono request context. Route handlers read tenantId only from the verified context — never from request headers or body.

Bearer token → SessionManager.verify() → tenantId on context
Route handler uses ctx.get('tenantId')

Storage isolation

StoreIsolation mechanism
D1tenant_id column on every table
R2Key prefix neureus/{tenantId}/ (enforced via r2Key() helper)
KVKeys namespaced with tenantId
VectorizeMetadata filter on tenantId

What’s not tenant-scoped

nr_models — the model catalog is platform-global. All tenants share the same model list.

Auth-boundary lookups (nr_api_keys by key_hash) are exempt and use tenant_id != '' as a sentinel to satisfy the constraint without filtering on a specific tenant.