Files
2026-05-27 23:29:53 +01:00

79 lines
3.3 KiB
Markdown

<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: api-tenant-resolver-header (api-tenant-resolver-header)
**Package**: `@suaveplan/api-tenant-resolver-header`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time.
## ADDED Requirements
### Requirement: Resolves tenant from configured header
The package SHALL expose `createHeaderTenantResolver({ headerName, validate, registry })` that reads `headerName` (default `X-Tenant-Id`) and returns `{ tenantId, source: "header" }` when the value validates AND (when `registry` is supplied) is a member of the tenant registry.
#### Scenario: well-formed header resolves
- WHEN a request carries `X-Tenant-Id: acme` AND `acme` is in the registry
- THEN the resolver returns `{ tenantId: "acme", source: "header" }` AND `api.tenant.resolves_total{resolver="header"}` increments
**Verified by:** `src/resolver/resolver.test.ts::"well-formed header resolves"`
---
### Requirement: Strict mode behaviour for invalid values
The package SHALL react to invalid header values per `strict` config: when `strict: true`, raise `TENANT_HEADER_INVALID` (HTTP 400). When `strict: false`, return `null` so a downstream resolver may match.
#### Scenario: strict raises 400
- WHEN the header value fails the validator AND `strict: true`
- THEN the resolver raises `TENANT_HEADER_INVALID`
#### Scenario: non-strict returns null
- WHEN the header value fails the validator AND `strict: false`
- THEN the resolver returns `null`
**Verified by:** `src/resolver/resolver.test.ts::"strict mode raises 400 on invalid"` AND `src/resolver/resolver.test.ts::"non-strict mode returns null on invalid"`
---
### Requirement: Trusted-source enforcement
The package SHALL ignore the header when `request.metadata.source` is not in `trustedSources` (default `["service-mesh"]`). When ignored AND `strict: true`, raise `TENANT_HEADER_UNTRUSTED_SOURCE`; when ignored AND `strict: false`, return `null`.
#### Scenario: untrusted source raises in strict
- WHEN the header is present AND `request.metadata.source = "edge"` AND `trustedSources = ["service-mesh"]` AND `strict: true`
- THEN the resolver raises `TENANT_HEADER_UNTRUSTED_SOURCE`
**Verified by:** `src/trust/trust.test.ts::"untrusted source raises in strict mode"`
---
### Requirement: Not-found-in-registry distinct from invalid
The package SHALL distinguish a tenant that fails validation (`TENANT_HEADER_INVALID`) from one that is well-formed but not in the registry (`TENANT_HEADER_NOT_FOUND_IN_REGISTRY`). Both surface in strict mode; both return `null` in non-strict mode.
#### Scenario: well-formed but unknown tenant
- WHEN the header is `X-Tenant-Id: unknown-tenant-1` AND the registry does not contain it AND `strict: true`
- THEN the resolver raises `TENANT_HEADER_NOT_FOUND_IN_REGISTRY`
**Verified by:** `src/resolver/resolver.test.ts::"well-formed but unknown raises NOT_FOUND_IN_REGISTRY in strict"`
---
### Requirement: Passes `runTenantResolverConformance`
The package SHALL pass the conformance harness exported by `@suaveplan/api-tenant-provider/testing`.
#### Scenario: conformance passes
- WHEN `runTenantResolverConformance("header", makeHeaderResolver)` runs
- THEN every harness `it(...)` passes
**Verified by:** `src/resolver/resolver.spec-conformance.test.ts::"header resolver passes runTenantResolverConformance"`