# `path-ownership` — single-provider registry _(no submodules)_ ## Purpose `path-ownership` is the runtime registry the host consults to enforce the "exactly one auth provider per deployment" invariant from design §2.3. A provider claims its `pathPrefix` at install time; any subsequent registration with a different provider id throws `MULTIPLE_PROVIDERS_REGISTERED`. The registry is deliberately not a module-global singleton — each host instance constructs its own `PathOwnership`, which keeps multi-tenant test runners and hot-reload scenarios deterministic. ## Features - `PathOwnership` class with `register`, `release`, `current`, `hasProvider`. - Injectable `clock` — defaults to `Date.now`, used for the `claimedAt` timestamp on the recorded claim. - Idempotent registration — re-registering the same instance, or a different instance with the same provider id, returns the existing claim. - Strict rejection of a different provider id with `AUTH_PROVIDER.MULTIPLE_PROVIDERS_REGISTERED`, including a structured `context` payload (`incomingProviderId`, `incumbentProviderId`, `prefix`) suitable for diagnostic logs. - `release()` drops the current claim — used by host shutdown and the blue-green hot-reload sequence to swap providers atomically. ## Usage: Basic ```ts import { PathOwnership } from "@suaveplan/api-auth-provider/path-ownership"; const ownership = new PathOwnership(); ownership.register(myProvider); ``` ## Usage: Advanced Tests inject a deterministic clock so the recorded `claimedAt` is stable across runs: ```ts import { PathOwnership } from "@suaveplan/api-auth-provider/path-ownership"; const ownership = new PathOwnership({ clock: () => 1_000_000 }); const claim = ownership.register(myProvider); // claim.claimedAt === 1_000_000 // Hot-reload: release before swapping in the new provider. ownership.release(); ownership.register(replacementProvider); ``` ## API Reference - `class PathOwnership` — runtime registry. - `constructor(options?: PathOwnershipOptions)` - `register(provider): PathOwnershipClaim` - `release(): void` - `current(): ProviderClaim | null` - `hasProvider(): boolean` - `interface PathOwnershipOptions { clock? }` - `type OwnershipClock = () => number` - `interface PathOwnershipClaim { providerId; prefix; claimedAt }` - `interface ProviderClaim { provider; claim }` ## Implementation Notes The registry stores both the `AuthProviderAdapter` instance and a flat `PathOwnershipClaim` record. Two layers exist because consumers fall into two camps: hosts that need the live provider for lifecycle dispatch, and diagnostic tooling that only needs the metadata. Idempotency is decided by identity first, id second — so a host that constructs a fresh provider instance per hot-reload still cannot accidentally register two providers with the same id. The `MULTIPLE_PROVIDERS_REGISTERED` error carries enough context for operators to diagnose double-installation in a misconfigured multi-process deployment without consulting the host logs. The class uses a private field rather than a module-level variable so multiple registries can coexist in the same process — essential for runner-agnostic test suites and multi-tenant in-process deployments.