# foundation-fix-gap-2-tenant-chain - **Status**: PROPOSED - **Spec deltas**: - ./specs/api-tenant-provider/spec.md - ./specs/api-tenant-provider-memory/spec.md ## What & why The post-Stage-4 foundation-coherence audit (Gap #2) flagged a shape divergence between `TenantRecord` (the inventory row exposed by `@suaveplan/api-tenant-provider`) and `TenantContext` (the per-request shape exposed by `@suaveplan/api-types/tenant`): `TenantContext` requires `slug`, but `TenantRecord` did not carry one. Every `TenantProvider`-backed resolver was forced to synthesise `slug = String(record.id)` at the boundary — the e2e foundation server did exactly that, and the `api-tenant-resolver-jwt` resolver hardcodes `slug = tenantId` for the same reason. A second half of the gap: hosts that already have a `TenantProvider` have to hand-roll the `header -> provider.lookup -> TenantContext` chain at the start of every fixture or server. No reusable factory existed. This change closes both halves: 1. **`TenantRecord` gains required `slug: string` + optional `metadata`.** Both are mirrored from `TenantContext`. `tenantRecordSchema` is updated; every in-tree fixture passes the slug explicitly so the `slug = id` synthesis bug cannot resurface. 2. **`@suaveplan/api-tenant-provider/chain` ships `tenantResolverChainFromProvider(provider, opts?)` + `recordToContext(record)`.** Three built-in strategies in fixed order (header / JWT-claim / subdomain), optional LRU memoisation, first-match-wins semantics. Returns a `TenantResolverChain` conforming to `@suaveplan/api-types/tenant-resolver`. 3. **e2e/foundation-stack/src/server.ts** drops the hand-rolled chain in favour of the factory, and the in-line fixtures gain explicit slugs. ## Impact ### Affected dependents - `@suaveplan/api-tenant-provider` — `TenantRecord` gains required `slug` and optional `metadata`. Source-incompatible: existing literal-constructed records without slug fail typecheck and schema parse. In-tree consumers are updated as part of this change (memory adapter, e2e fixture, all tests). Adds `./chain` subpath export with `tenantResolverChainFromProvider` + `recordToContext`. - `@suaveplan/api-tenant-provider-memory` — passes the `slug` through unchanged; all internal tests updated. - `@suaveplan/e2e-foundation-stack` — `buildTenantChain()` now wraps the provider via the new factory; in-line fixtures gain `slug: "tenant-1" | "tenant-2"`. - `@suaveplan/api-tenant-resolver-jwt` — unchanged; still synthesises `slug = tenantId` because the JWT carries only the id, not a slug. Documenting this as deliberate (the JWT resolver has no inventory lookup, so it cannot read slug from a record). ### Backward compatibility `TenantRecord.slug` is REQUIRED — no v0.1.0 compatibility shim. Genesis is bottom-up rebuild; no shipped consumer constructs `TenantRecord` outside the packages updated in this change. `tenantResolverChainFromProvider` is purely additive: a new subpath export. No existing import path moves. ## Parity Checklist This is a genesis-original fix; no upstream parity table required. ## Verification `bunx turbo lint typecheck test build --filter=@suaveplan/api-tenant-provider --filter=@suaveplan/api-tenant-provider-memory --filter=@suaveplan/api-tenant-resolver-jwt` → green. `bunx turbo test:coverage --filter=@suaveplan/api-tenant-provider --filter=@suaveplan/api-tenant-provider-memory` → 100/100/100/100 statements/branches/functions/lines. E2E smoke against the new chain: ```bash bun run src/server.ts & curl -sH 'x-tenant-id: t1' http://127.0.0.1:3500/api/echo/tenanted # => {"msg":"tenanted","tenantId":"t1","principal":null} ```