128 lines
5.5 KiB
Markdown
128 lines
5.5 KiB
Markdown
<!-- markdownlint-disable MD024 -->
|
|
|
|
# Capability Spec Delta: api-auth-provider (api-auth-provider)
|
|
|
|
**Package**: `@suaveplan/api-auth-provider`
|
|
**Status**: PROPOSED
|
|
|
|
> Every requirement carries a `**Verified by:**` line.
|
|
|
|
## ADDED Requirements
|
|
|
|
### Requirement: AuthProviderAdapter contract
|
|
|
|
The package SHALL export `AuthProviderAdapter` interface: `pathPrefix: string` (default `/auth`), `routes(router: Router, ctx: PluginContext): void` to register provider routes, `lifecycle: { install, start, stop, uninstall }` hooks matching the generic plugin lifecycle. The adapter MUST be generic over `Caps extends CapabilityMap`.
|
|
|
|
#### Scenario: every documented field present
|
|
|
|
- **WHEN** `expectTypeOf<AuthProviderAdapter>()` is checked
|
|
- **THEN** `pathPrefix`, `routes`, and the four lifecycle hooks are declared
|
|
|
|
**Verified by:** `src/provider/provider.spec-conformance.test.ts::"AuthProviderAdapter declares pathPrefix routes lifecycle"`
|
|
|
|
---
|
|
|
|
### Requirement: `import z from "zod"` everywhere
|
|
|
|
Every Zod import MUST use the default form.
|
|
|
|
#### Scenario: no named import of z
|
|
|
|
- **WHEN** the test greps `import z` from `"zod"`
|
|
- **THEN** zero matches
|
|
|
|
**Verified by:** `tests/zod-import-form.test.ts::"every src file uses default-import z"`
|
|
|
|
---
|
|
|
|
### Requirement: Canonical provider route surface
|
|
|
|
The package SHALL export `PROVIDER_ROUTE_SURFACE` listing the canonical paths a provider OWNS (relative to `pathPrefix`): `/register`, `/login`, `/logout`, `/refresh`, `/oauth/:provider/start`, `/oauth/:provider/callback`, `/verify-email/:token`, `/forgot-password`, `/reset-password`. Providers MAY implement a subset; the host does not require every path.
|
|
|
|
#### Scenario: surface enumerates the documented paths
|
|
|
|
- **WHEN** the test inspects `PROVIDER_ROUTE_SURFACE`
|
|
- **THEN** every documented path is listed
|
|
|
|
**Verified by:** `src/route-surface/route-surface.test.ts::"canonical surface lists every documented path"`
|
|
|
|
---
|
|
|
|
### Requirement: Path-ownership registry
|
|
|
|
The package SHALL export `PathOwnership` — a runtime registry. `register(provider)` claims `provider.pathPrefix`. A second `register(provider2)` with a different provider MUST throw `AUTH_PROVIDER.MULTIPLE_PROVIDERS_REGISTERED`. Re-registering the SAME provider instance MUST be a no-op (idempotent).
|
|
|
|
#### Scenario: double registration with different provider rejected
|
|
|
|
- **WHEN** provider A is registered AND provider B is registered
|
|
- **THEN** the second call throws `AUTH_PROVIDER.MULTIPLE_PROVIDERS_REGISTERED`
|
|
|
|
#### Scenario: re-registering same provider is idempotent
|
|
|
|
- **WHEN** provider A is registered twice
|
|
- **THEN** the second call returns without throwing
|
|
|
|
**Verified by:** `src/path-ownership/path-ownership.test.ts::"double registration rejected"` AND `src/path-ownership/path-ownership.test.ts::"re-registering same provider is idempotent"`
|
|
|
|
---
|
|
|
|
### Requirement: Collision detector
|
|
|
|
The package SHALL export `detectAuthPathCollisions({ provider, plugins }): CollisionReport`. A plugin route whose prefix overlaps with `provider.pathPrefix` AND is not in `publicRoutes` MUST be reported with code `AUTH_PROVIDER.PATH_COLLISION` AND `details.plugin` AND `details.path`. Plugin routes outside the auth prefix MUST NOT be reported.
|
|
|
|
#### Scenario: overlap reported
|
|
|
|
- **WHEN** provider owns `/auth` AND plugin declares route `/auth/custom`
|
|
- **THEN** the report includes `{ code: "AUTH_PROVIDER.PATH_COLLISION", details: { plugin: ..., path: "/auth/custom" } }`
|
|
|
|
#### Scenario: non-overlap not reported
|
|
|
|
- **WHEN** provider owns `/auth` AND plugin declares route `/billing/invoices`
|
|
- **THEN** the report is empty
|
|
|
|
#### Scenario: configurable prefix shifts collision domain
|
|
|
|
- **WHEN** provider's `pathPrefix === "/myauth"` AND plugin declares route `/auth/x`
|
|
- **THEN** the report is empty AND a plugin route `/myauth/x` would be reported
|
|
|
|
**Verified by:** `src/collision-detector/collision-detector.test.ts::"overlap with auth prefix reported"` AND `src/collision-detector/collision-detector.test.ts::"non-overlap not reported"` AND `src/collision-detector/collision-detector.test.ts::"configurable prefix shifts collision domain"`
|
|
|
|
---
|
|
|
|
### Requirement: CapabilityId augmentation contributes "auth.provider"
|
|
|
|
The package SHALL augment the `CapabilityId` union to include `"auth.provider"`.
|
|
|
|
#### Scenario: auth.provider assignable to CapabilityId
|
|
|
|
- **WHEN** `expectTypeOf<"auth.provider">().toMatchTypeOf<CapabilityId>()` is checked
|
|
- **THEN** the assertion passes
|
|
|
|
**Verified by:** `src/capability-id/capability-id.spec-conformance.test.ts::"auth.provider is a valid CapabilityId after augmentation"`
|
|
|
|
---
|
|
|
|
### Requirement: Error codes registry frozen
|
|
|
|
The package SHALL export `AUTH_PROVIDER_CODES` containing exactly: `PATH_COLLISION`, `MULTIPLE_PROVIDERS_REGISTERED`, `PROVIDER_LIFECYCLE_FAILED`, `OAUTH_CALLBACK_INVALID_STATE`.
|
|
|
|
#### Scenario: every code reachable
|
|
|
|
- **WHEN** the test enumerates `AUTH_PROVIDER_CODES`
|
|
- **THEN** every code has a production throw site
|
|
|
|
**Verified by:** `src/error-codes/error-codes.spec-conformance.test.ts::"every AUTH_PROVIDER_CODES entry thrown by production path"`
|
|
|
|
---
|
|
|
|
### Requirement: Conformance harness for providers
|
|
|
|
The package SHALL export `runAuthProviderConformance({ factory, label })`. Every concrete provider in Stages 6/8 MUST pass it.
|
|
|
|
#### Scenario: harness covers path ownership, route surface, lifecycle, collision
|
|
|
|
- **WHEN** the harness runs against a stub provider
|
|
- **THEN** test categories exist for each documented behavior
|
|
|
|
**Verified by:** `src/testing/conformance.test.ts::"harness covers path ownership, route surface, lifecycle, collision"`
|