79 lines
3.3 KiB
Markdown
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"`
|