# Capability Spec Delta: api-tenant-provider-memory (api-tenant-provider-memory) **Package**: `@suaveplan/api-tenant-provider-memory` **Status**: PROPOSED ## ADDED Requirements ### Requirement: Enumerate tenants from in-memory state The package SHALL implement `TenantProvider` with `list()` (sync snapshot), `listAsync()` (async iterable), and `get(tenantId)`. Initial state comes from `options.tenants`; runtime mutations are gated by `options.mutable`. #### Scenario: list returns the initial set - **WHEN** the provider is constructed with `tenants: [A, B, C]` - **THEN** `list()` returns an array of length 3 containing A, B, C in insertion order #### Scenario: async iterator yields every tenant exactly once - **WHEN** `for await (const t of provider.listAsync())` runs against an initial set of 3 tenants - **THEN** exactly 3 tenants are yielded AND each ID appears exactly once #### Scenario: get returns undefined for unknown tenant - **WHEN** `get("unknown")` is called - **THEN** the result is `undefined` (no throw) **Verified by:** `src/factory/create-memory-tenant-provider.test.ts::"list returns initial set in insertion order"` AND `src/factory/create-memory-tenant-provider.test.ts::"listAsync yields every tenant exactly once"` AND `src/factory/create-memory-tenant-provider.test.ts::"get returns undefined for unknown tenant id"` --- ### Requirement: Mutation API gated by mutable flag When `options.mutable: true`, the provider SHALL expose `add(tenant)`, `remove(tenantId)`, `update(tenantId, patch)`. When `mutable: false`, those methods MUST throw `TENANT_PROVIDER_MEMORY_IMMUTABLE`. #### Scenario: add rejects duplicate id - **WHEN** `add({ id: "acme", ... })` is called twice with the same id - **THEN** the second call throws `TENANT_PROVIDER_MEMORY_DUPLICATE_ID` #### Scenario: remove of unknown tenant throws - **WHEN** `remove("not-there")` is called - **THEN** the call throws `TENANT_PROVIDER_MEMORY_NOT_FOUND` #### Scenario: mutation blocked when immutable - **WHEN** the provider is constructed with `mutable: false` AND `add(...)` is called - **THEN** the call throws `TENANT_PROVIDER_MEMORY_IMMUTABLE` AND state remains unchanged **Verified by:** `src/factory/create-memory-tenant-provider.test.ts::"add rejects duplicate id"` AND `src/factory/create-memory-tenant-provider.test.ts::"remove of unknown tenant throws"` AND `src/factory/create-memory-tenant-provider.test.ts::"mutation blocked when immutable"` --- ### Requirement: Direct and worker-proxy surfaces both pass conformance The proxy surface SHALL support `listAsync()` across the `MessagePort` (chunked yield). #### Scenario: direct conforms - **WHEN** `runTenantProviderConformance("memory-direct", () => createMemoryTenantProvider(...))` runs - **THEN** every invariant passes #### Scenario: worker-proxy conforms with async iteration - **WHEN** `runTenantProviderConformance("memory-worker-proxy", ...)` runs over a real `MessageChannel` AND `listAsync()` is iterated to completion against a 1_000-tenant fixture - **THEN** every invariant passes AND every tenant id is yielded exactly once on the proxy side **Verified by:** `src/factory/create-memory-tenant-provider.spec-conformance.test.ts::"memory-direct conforms to TenantProvider contract"` AND `src/proxy/create-memory-tenant-provider-worker-proxy.spec-conformance.test.ts::"memory-worker-proxy conforms with async iteration across MessagePort"`