Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
# `@suaveplan/api-tenant-provider` — abstract tenant enumeration contract
**Package**: `@suaveplan/api-tenant-provider`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 1 (Contracts) — see umbrella `openspec/changes/api-plugin-foundation/`
## Why
`perTenant: true` cron / queued jobs (design §5.4) need a way to list every tenant the host knows about so the scheduler can fan one job invocation out per tenant, each with a tenant-scoped context. The same enumeration is needed by background reconciliation, per-tenant cache warmers, and admin tooling. Different deployments source tenant lists from different places: an in-memory list (tests + simple setups), a database row, a directory service, a remote API. This package owns the abstract contract — concrete sources plug behind it (`api-tenant-provider-memory` in Stage 4; production adapters land alongside the host metadata DB or a plugin's own DB).
Per design §5.4: "`tenant.provider` ships in v1 (in-memory reference impl + adapter slot) so retrofitting tenant enumeration into a stable contract doesn't have to happen later."
`tenant.provider` is intentionally distinct from `tenant.resolver` (which extracts `tenantId` from a request — that contract lives in `api-types` / consumed by `api-tenant-resolver-*` adapters).
## What changes
Net-new package. Contract + pagination shape + conformance harness.
- ADDED: `./provider` — abstract `TenantProvider`: `list(opts?): AsyncIterable<TenantRecord>` (stream-friendly for large tenant counts); `count(): Promise<number>`; `lookup(tenantId): Promise<TenantRecord | null>`; `dispose(): Promise<void>`.
- ADDED: `./tenant-record` — `TenantRecord`: `{ id: TenantId, displayName?: string, status: "active" | "suspended" | "archived", createdAt: string }`. Zod schema. Only `active` tenants are enumerated by default; `list({ includeSuspended: true })` surfaces those too.
- ADDED: `./pagination` — `ListOpts`: `{ pageSize?: number, includeSuspended?: boolean, cursor?: string }`. The `AsyncIterable` MAY be page-driven internally; callers iterate without knowing.
- ADDED: `./capability-id` — adds `"tenant.provider"` to the `CapabilityId` union.
- ADDED: `./error-codes` — frozen `TENANT_PROVIDER_CODES`: `TENANT_NOT_FOUND`, `SOURCE_UNAVAILABLE`, `INVALID_CURSOR`.
- ADDED: `./testing` — `runTenantProviderConformance(factory)` reusable harness.
## CapabilityId additions
This package contributes `"tenant.provider"` to the `CapabilityId` union. Per-tenant jobs declared in manifest implicitly require this capability (the host resolves it automatically when `perTenant: true` is set — plugins don't need to list it manually, but it shows up in the registry).
## Out of scope
- Concrete sources — `api-tenant-provider-memory` (Stage 4); DB-backed sources are deployment-specific.
- Tenant CREATE / UPDATE / DELETE — that's an admin concern, not a foundation contract.
- Tenant resolution from a Request — that's `tenant.resolver`, contract in `@suaveplan/api-types`.
## Source mapping
Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`.
| Requirement | Design doc section |
|---|---|
| `perTenant: true` fan-out reading from `tenant.provider` | §5.4 |
| Provider ships in v1 with in-memory reference impl | §5.4 |
| Status filter (`active` default) | Implicit from §5.4 (fan-out should not include suspended tenants by default) |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| `AsyncIterable` over `Array` | Future-proofs against large tenant counts; an array forces full-set load, an iterable streams. Standard genesis pattern. |
| `runTenantProviderConformance` harness | Stage-5 deliverable (§13.4). |
## Verification approach
- `src/provider/provider.spec-conformance.test.ts` — interface shape; iterator semantics.
- `src/tenant-record/tenant-record.test.ts` — Zod accept/reject; `TenantId` brand preserved.
- `src/provider/status-filter.test.ts` — default excludes `suspended`/`archived`; `includeSuspended: true` surfaces them.
- `src/pagination/pagination.test.ts` — `pageSize` honored; `cursor` round-trips.
- `src/capability-id/capability-id.spec-conformance.test.ts` — `"tenant.provider"` in `CapabilityId`.
- `tests/dep-graph.test.ts` — deps: `@suaveplan/api-types`, `@suaveplan/error`, `@suaveplan/types`, `zod`.
## Migration impact
None — additive only.
## Impact
### Affected dependents
- (none) — [x] no action needed (reason: net-new; in-memory ref impl lands Stage 4)