Tenancy
Flightdeck is multi-tenant from the schema up. Two founding rules — fail-closed tenancy (tenant resolution never defaults and never trusts request parameters) and credential-carries-ownership (the credential, not the caller, names the tenant) — govern every tenant-owned table and every tenant-facing route.
Fail-closed tenancy
Every tenant-owned table carries tenant_ref TEXT NOT NULL — no default.
A request’s tenant is resolved from its credential or token claim, never
from a request parameter and never defaulted. There is no “default tenant”
anywhere in the system. Cross-tenant reads use an explicit, reviewed
allTenants marker and are expected to be rare.
The branded TenantRef type (from @dscodotco/contracts) is enforcement
support — a compile-time nudge — not the safety mechanism itself. The
safety is the NOT NULL, no-default column plus the credential resolution
path.
Credential-carries-ownership
Tenant-facing auth follows one pattern everywhere: API keys are resource-bound rows, hashed at rest, issued against a closed scope allowlist. A key’s scope says “some tenant can do X”; the key row says which tenant. Every tenant-facing route resolves ownership from the credential that made the call — never from a path or body parameter the caller supplied.
This is the same pattern the merchant API (fdk_-prefixed keys) and the
storefront’s self-serve /v1/my/* routes both use — see
Reference -> Tenant-facing API for the concrete routes.
The one written exception — Host-header resolution
The storefront fleet renderer — one deployment serving every hosted store —
must resolve a tenant from the Host header, which is attacker-influenced
request data — on its face a violation of fail-closed tenancy. This is the one written
exception, and it is fail-closed:
Host header -> verified domain-mapping row -> tenant_refAn unknown or unverified host is a hard 404 — no fallback store, no default
tenant. The mapping row is created only by the domain-verification flow
(POST /v1/my/domains, POST /v1/my/domains/:id/verify in the stores
module). A misconfigured DNS entry must never serve one store’s checkout
under another store’s domain.