Files
suaveplan/openspec/archive/2026-05-25-api-auth-provider/specs/api-auth-provider/spec.md
2026-05-27 23:29:53 +01:00

5.5 KiB

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"