# @suaveplan/api-tenant-resolver-header — header-based tenant resolver for the Suaveplan API plugin system. Implements the TenantResolver contract from @suaveplan/api-types by reading the tenant identifier from a configurable HTTP header (default X-Tenant-Id), validating against a regex (default ^[a-z0-9-]{1,64}$) and an optional registry (a `lookup`-shaped TenantLookup compatible with @suaveplan/api-tenant-provider), and enforcing trusted-source metadata so end-user clients cannot spoof tenant headers. Strict mode raises namespaced TENANT_HEADER_INVALID / NOT_FOUND_IN_REGISTRY / UNTRUSTED_SOURCE errors; non-strict mode returns null so the resolver chain advances. Emits api.tenant.resolves_total{resolver="header"} counters via the OTel global meter. Stage-8 tenant resolver. Implements the `TenantResolver` contract from `@suaveplan/api-types/tenant-resolver` by reading the tenant identifier from a configurable HTTP header (default `X-Tenant-Id`), validating its shape against a regex (default `^[a-z0-9-]{1,64}$`), optionally probing a registry, and enforcing trusted-source metadata so end-user clients cannot spoof the tenant. ```ts import { createHeaderTenantResolver } from "@suaveplan/api-tenant-resolver-header"; const resolver = createHeaderTenantResolver(); // service-mesh middleware stamps request.metadata.source = "service-mesh" const ctx = await resolver.resolve(request); // → { id: "acme", slug: "acme" } | null ``` ## Documentation tree - [error-codes](./src/error-codes/error-codes.md) — Defines the stable error-code registry for `@suaveplan/api-tenant-resolver-header`. Every public failure path raises a… - [error-codes](./src/error-codes/error-codes.md) — Defines the stable error-code registry for `@suaveplan/api-tenant-resolver-header`. Every public failure path raises a… - [instruments](./src/instruments/instruments.md) — Builds the OTel instrument bundle the header tenant resolver emits on every `resolve()` invocation. Centralising instru… - [instruments](./src/instruments/instruments.md) — Builds the OTel instrument bundle the header tenant resolver emits on every `resolve()` invocation. Centralising instru… - [options](./src/options/options.md) — Defines the Zod-validated options schema accepted by `createHeaderTenantResolver`. Construction-time validation guarant… - [options](./src/options/options.md) — Defines the Zod-validated options schema accepted by `createHeaderTenantResolver`. Construction-time validation guarant… - [resolver](./src/resolver/resolver.md) — Implements the Stage-8 tenant resolver factory `createHeaderTenantResolver`. The resolver reads the tenant identifier f… - [resolver](./src/resolver/resolver.md) — Implements the Stage-8 tenant resolver factory `createHeaderTenantResolver`. The resolver reads the tenant identifier f… - [trust](./src/trust/trust.md) — Encapsulates the trusted-source enforcement consulted by the header tenant resolver before it acts on a header value. T… - [trust](./src/trust/trust.md) — Encapsulates the trusted-source enforcement consulted by the header tenant resolver before it acts on a header value. T… - [validate](./src/validate/validate.md) — Owns the default tenant-id validator regex and the validator / registry function signatures consumed by `createHeaderTe… - [validate](./src/validate/validate.md) — Owns the default tenant-id validator regex and the validator / registry function signatures consumed by `createHeaderTe… ## Why a dedicated package The tenant-resolver chain (design doc §6 step 4) runs after the session resolver. Service-to-service traffic frequently carries the tenant in an HTTP header (`X-Tenant-Id`, `X-Acme-Tenant`, …) because no end-user session exists to resolve it from. This adapter handles that case while preventing header spoofing from public traffic via the trusted-source gate. ## Chain interaction The host runs resolvers left-to-right; the first non-`null` result wins: ```text [ api-tenant-resolver-jwt, api-tenant-resolver-header, api-tenant-resolver-subdomain ] | | | reads principal reads X-Tenant-Id reads sub.domain (typical bearer) (admin / S2S calls) (public site routes) ``` `api-tenant-resolver-header` returns `null` when the header is absent, when the request source is untrusted (non-strict), when the value fails validation (non-strict), or when the value isn't in the registry (non-strict). In strict mode each of those paths raises the corresponding `TENANT_HEADER_*` code. ## Modules - `./resolver` — `createHeaderTenantResolver`, `CreateHeaderTenantResolverOptions`. - `./options` — `HeaderTenantResolverOptions`, `parseHeaderTenantResolverOptions`, `ResolvedHeaderTenantResolverOptions`. - `./validate` — `defaultTenantIdValidator`, `DEFAULT_TENANT_ID_PATTERN`, `TenantIdValidator`, `TenantRegistryProbe`. - `./trust` — `defaultMetadataExtractor`, `isTrustedSource`, `RequestMetadataExtractor`, `RequestTrustMetadata`. - `./instruments` — `createHeaderResolverInstruments`, `HeaderResolverInstruments`, `HeaderResolveOutcome`. - `./error-codes` — `HeaderTenantResolverError`, `HeaderTenantResolverErrorCode`, `TENANT_HEADER_CODES`. ## Options | Option | Default | Effect | | ---------------- | -------------------- | ---------------------------------------------------------------------------- | | `id` | `"header"` | Resolver id used in chain logs and metric tags. | | `headerName` | `"X-Tenant-Id"` | HTTP header consulted; case-insensitive. | | `strict` | `false` | When `true`, failures raise typed errors. When `false`, failures yield `null`. | | `trustedSources` | `["service-mesh"]` | Values for `request.metadata.source` that may set the tenant header. | | `validate` | `^[a-z0-9-]{1,64}$` | Validator applied to the header value. | | `registry` | `undefined` | Optional sync-or-async membership probe; runs only after validator passes. | | `getMetadata` | reads `req.metadata` | Override for the trust-metadata extractor. | | `instruments` | OTel global meter | Override for the instrument bundle. | ## Error codes - `TENANT_HEADER.INVALID` — header value failed validator (strict-only). - `TENANT_HEADER.NOT_FOUND_IN_REGISTRY` — value not in registry (strict-only). - `TENANT_HEADER.UNTRUSTED_SOURCE` — request source not in `trustedSources` (strict-only). - `TENANT_HEADER.INVALID_OPTIONS` — construction-time Zod failure. ## Observability - Counter `api.tenant.resolves_total{resolver="header", outcome}` — exactly one increment per `resolve()` call. `outcome` is one of `resolved`, `absent`, `invalid`, `not_found_in_registry`, `untrusted_source`.