Shared-tenancy composition
Shared tenancy is Cheetah's fail-closed composition for mutually untrusted tenants using one data plane. It establishes a canonical tenant-and-user principal at every ingress, audits the component family for compatible scoping behavior, and prevents application callers from escaping that scope through an arbitrary identity argument.
Use the dedicated constructor
create_shared_tenant_nodes() is the supported entry point. It constructs the WebSocket, REST, and application-facing roles together with tenant-required settings. Ordinary role factories default to dedicated-principal mode; assembling them independently and adding a tenant field later does not provide the same contract.
The constructor requires:
- an authentication provider that yields validated tenant and user leaves;
- one coherent component family whose shared-tenancy behavior is accepted by the audit;
- strict WebSocket transport configuration and independently protected HTTP return;
- a real tenant-safe administrative authentication provider;
- compatible deployment and node identities for any distributed components.
The WebSocket and HTTP hosts must resolve credentials through the same identity semantics. The effective principal is the exact canonical tenant@user value created by the trusted authentication path, and all roles use it consistently.
Component audit
The shared constructor accepts audited built-in implementations for authentication, registry, dispatch, correlation, history, response notification, deduplication, and optional context or window registries. A custom concrete implementation can participate only when it explicitly declares shared-tenancy support and satisfies the same behavioral contract.
The declaration is checked on the concrete class. A subclass does not silently inherit a parent's safety marker because an override can change key construction, enumeration, cleanup, or administrative behavior. Unknown implementations fail closed rather than being accepted because their method signatures look compatible.
For every data-plane component, tenant support means more than storing a tenant column. Lookup, write, enumeration, notification, deduplication, expiry, cleanup, and diagnostic projection must all remain inside the effective principal. Cross-process components must use the same canonical keying rules.
Restricted application surface
The constructor returns SharedTenantAppNode, not the unrestricted App role. Its public entry uses an AuthenticatedTenantPrincipal proof to produce principal-scoped command and history operations. Raw App methods that accept an identity string are not exposed through this shared surface.
This boundary is important even when application code is trusted. It keeps a mistaken route parameter, background job, or LLM-generated call from selecting another tenant merely by supplying text. Authentication resolves the product account once; the resulting proof carries the scope through application operations.
The same principle applies to HTTP return. Shared REST ingestion accepts the authenticated tenant proof, validates the body client and runtime within that principal, and prefixes stream suffixes server-side. It does not accept a combined principal copied from a request body.
Deliberate exclusions in the current baseline
The component audit rejects facilities whose present built-in implementations do not provide a verified tenant-scoped contract:
| Facility | Current shared-tenancy status | Reason |
|---|---|---|
| active or custom tracing | excluded; shared composition accepts NoOpTracer | trace export and query isolation are not certified |
| payload token signer and offload | excluded when a signer is present | object access and lifecycle are not certified as a shared tenant boundary |
| central policy source | excluded when configured | the supplied policy path is not certified for tenant-scoped shared use |
| built-in parser registry | excluded | parser identity and lookup are global rather than tenant-scoped |
| Derived Views | not certified | aggregation and administrative visibility are not part of the shared audit |
An explicitly audited tenant-aware custom parser registry could satisfy the component marker; the supplied built-in registry does not. The other exclusions should be treated as product boundaries, not bypassed by hiding a component behind a wrapper.
Optional relays, capture pipelines, dashboards, or product services need their own tenant review before they are placed inside the same trust claim. Being able to consume a Cheetah stream does not automatically make a service tenant-isolated.
Administrative boundary
Shared tenancy still has platform administration that can span tenants. The constructor therefore requires real administrative authentication and rejects the no-op provider. Protect diagnostics and control surfaces with network policy, operator authorization, and redaction appropriate to cross-tenant visibility.
An operator is not a tenant administrator by default. If the product needs tenant-specific support roles, delegated administration, or customer-visible audit, those concepts belong in the product control plane and need explicit scoping.
Distributed namespace
Redis-backed deployments use a stable deployment_id as an outer namespace shared by all participating processes. It separates deployments; it is not a tenant ID. Tenant and user scope remains inside that deployment namespace through the canonical effective principal.
Each process also needs a unique node ID so connection ownership and notifications identify the correct role instance. Reusing a tenant ID as a deployment or node ID collapses concepts with different lifetimes and operational purposes.