# Capability Spec Delta: api-tenant-resolver-subdomain (api-tenant-resolver-subdomain) **Package**: `@suaveplan/api-tenant-resolver-subdomain` **Status**: PROPOSED > Every requirement below carries a `**Verified by:**` line at authoring time. ## ADDED Requirements ### Requirement: Resolves tenant from request host subdomain The package SHALL expose `createSubdomainTenantResolver({ baseDomain, validate, registry })` that reads `request.host`, strips `baseDomain`, validates the remaining label, and returns `{ tenantId, source: "subdomain" }` when valid. #### Scenario: leftmost label resolves as tenant - WHEN `request.host = "acme.api.example.com"` AND `baseDomain = "api.example.com"` - THEN the resolver returns `{ tenantId: "acme", source: "subdomain" }` **Verified by:** `src/resolver/resolver.test.ts::"leftmost label resolves as tenant"` --- ### Requirement: Base-domain mismatch handled per strict mode The package SHALL raise `TENANT_SUBDOMAIN_BASE_DOMAIN_MISMATCH` in strict mode when the host does not end with `baseDomain`. In non-strict mode it returns `null`. #### Scenario: mismatched base domain raises in strict - WHEN `request.host = "acme.evil.example.com"` AND `baseDomain = "api.example.com"` AND `strict: true` - THEN the resolver raises `TENANT_SUBDOMAIN_BASE_DOMAIN_MISMATCH` **Verified by:** `src/resolver/resolver.test.ts::"mismatched base domain raises in strict"` --- ### Requirement: Multi-label deployments via `tenantLabelIndex` The package SHALL accept `tenantLabelIndex` (default 0) to support hosts like `..api.example.com`. #### Scenario: middle-label deployment - WHEN `request.host = "acme.eu.api.example.com"` AND `tenantLabelIndex = 0` AND `baseDomain = "api.example.com"` - THEN tenant is `acme` (label 0 of `acme.eu`) **Verified by:** `src/resolver/resolver.test.ts::"multi-label deployment resolves by index"` --- ### Requirement: `X-Forwarded-Host` trust requires CIDR allowlist The package SHALL only consult `X-Forwarded-Host` when `trustForwardedHost: true` AND the upstream IP matches `trustedProxyCidrs`. Untrusted upstream raises `TENANT_SUBDOMAIN_FORWARDED_HOST_UNTRUSTED` in strict mode. #### Scenario: untrusted proxy ignored - WHEN `X-Forwarded-Host` is set AND the upstream IP is not in `trustedProxyCidrs` AND `strict: true` - THEN the resolver raises `TENANT_SUBDOMAIN_FORWARDED_HOST_UNTRUSTED` #### Scenario: trusted proxy honoured - WHEN the upstream IP matches `trustedProxyCidrs` AND `X-Forwarded-Host` is well-formed - THEN the forwarded host is used to resolve the tenant **Verified by:** `src/forwarded/forwarded.test.ts::"untrusted proxy raises FORWARDED_HOST_UNTRUSTED"` AND `src/forwarded/forwarded.test.ts::"trusted proxy honours forwarded host"` --- ### Requirement: Passes `runTenantResolverConformance` The package SHALL pass `runTenantResolverConformance` from `@suaveplan/api-tenant-provider/testing`. #### Scenario: conformance passes - WHEN `runTenantResolverConformance("subdomain", makeSubdomainResolver)` runs - THEN every harness `it(...)` passes **Verified by:** `src/resolver/resolver.spec-conformance.test.ts::"subdomain resolver passes runTenantResolverConformance"`