# `collision-detector` — install-time auth-path collision check _(no submodules)_ ## Purpose `collision-detector` is the install-time guard that prevents a plugin from accidentally squatting on the auth-provider's owned path prefix. It walks every plugin route, segment-aligns its path against the provider's `pathPrefix`, and emits `AUTH_PROVIDER.PATH_COLLISION` entries for every overlap that is not on the plugin's `publicRoutes` allowlist or marked `isPublic` on the per-route descriptor. Returning a report instead of throwing keeps callers in control of how to surface aggregated failures — typically the host turns a non-empty report into a single error listing every offender. ## Features - Segment-aware containment — `/auth` matches `/auth` and `/auth/login` but NOT `/authoritative`. - Configurable prefix — a deployment that mounts the provider under `/myauth` shifts the collision domain accordingly. - Per-route `isPublic` opt-out for plugin routes that augment the auth surface (e.g. a custom `/auth/healthz`). - Plugin-level `publicRoutes` allowlist — identical effect, declared once per plugin manifest. - Aggregating reporter — every offender is returned in one frozen report. - Trailing-slash normalisation — `/auth` and `/auth/` are treated as equal. ## Usage: Basic ```ts import { detectAuthPathCollisions } from "@suaveplan/api-auth-provider/collision-detector"; const report = detectAuthPathCollisions({ provider, plugins }); if (report.hasCollisions) { throw new Error(`Auth-path collisions: ${report.entries.length}`); } ``` ## Usage: Advanced Aggregating a problem-detail response across every offender: ```ts import { detectAuthPathCollisions, type CollisionReportEntry, } from "@suaveplan/api-auth-provider/collision-detector"; const report = detectAuthPathCollisions({ provider, plugins }); if (report.hasCollisions) { const offenders = report.entries.map( (e: CollisionReportEntry) => ({ plugin: e.details.plugin, path: e.details.path }) ); return new Response( JSON.stringify({ type: "auth-collision", offenders }), { status: 409 } ); } ``` ## API Reference - `function detectAuthPathCollisions(input: DetectCollisionsInput): CollisionReport` - `function isInsidePrefix(routePath: string, prefix: string): boolean` - `function normalisePrefix(prefix: string): string` - `interface DetectCollisionsInput { provider; plugins }` - `interface PluginDescriptor { name; routes; publicRoutes? }` - `interface PluginRouteDescriptor { path; method?; isPublic? }` - `interface CollisionReport { entries; hasCollisions }` - `interface CollisionReportEntry { code; message; details }` ## Implementation Notes The detector returns a non-throwing report so the host can decide whether a collision is fatal or merely loggable — empirically a deployment running an older plugin against a newer provider needs the choice. Segment-aware matching is essential: a naïve `startsWith` would falsely flag `/authoritative` (or worse, `/auth-cache`) when the provider owns `/auth`. The root prefix `"/"` is short-circuited to "owns nothing" so a deployment that mistakenly configures the auth provider at the root does not blow up the whole route table; the path-prefix validator catches that mis-configuration at construction time. Two opt-out paths (per-route `isPublic` and plugin `publicRoutes`) coexist because manifests historically use one or the other and supporting both keeps migration tractable.