# `factory/create-static-principal-resolver` — fixed-Principal resolver _(no submodules)_ ## Purpose Constructs a `SessionResolver` from `@suaveplan/api-auth` that returns the configured `Principal` verbatim on every request — no token decoding, no JWKS, no I/O. This is the workhorse adapter for plugin integration tests, the `api-plugin-example` fixture, and any environment where the goal is to exercise the downstream pipeline (RBAC gate, tenant resolver chain reading `principal.tenantId`, tenant-scoped DB) with a deterministic identity carried in config. The factory consumes options previously validated by the `options` module so construction is total: every misuse maps to either `AUTH_STATIC_PRINCIPAL.INVALID_OPTIONS` or `AUTH_STATIC_PRINCIPAL.INVALID_PRINCIPAL` and surfaces synchronously at construction time. The resolver itself never throws at request time. ## Features - Defensive clone on every `resolve()` — callers cannot mutate one returned Principal and observe the change on subsequent calls. - `applyTo(req)` predicate for arbitrary "return null for this request" rules. - `nullForPaths` suffix-glob list (`/webhooks/*`) and exact-match patterns for the most common public-route test setup. - Idempotent no-op `dispose()` matching the `SessionResolver` contract. - Returned objects are frozen (top-level + `scopes` + `claims`) to make attempted mutations fail loudly in strict mode. ## Usage: Basic ```ts import { createStaticPrincipalResolver } from "@suaveplan/api-auth-static-principal/resolver"; const resolver = createStaticPrincipalResolver({ principal: { id: "u1", scopes: ["billing:write"], tenantId: "acme", claims: { sub: "u1" }, }, }); const principal = await resolver.resolve( new Request("https://example.test/api/billing/invoices") ); // principal.id === "u1" ``` ## Usage: Advanced Combine `applyTo` and `nullForPaths` to model a mixed-route test surface: ```ts import { createStaticPrincipalResolver } from "@suaveplan/api-auth-static-principal/resolver"; const resolver = createStaticPrincipalResolver({ principal: { id: "u1", scopes: ["billing:write", "billing:read"], tenantId: "acme", claims: { sub: "u1" }, }, nullForPaths: ["/webhooks/*", "/healthz"], applyTo: (req) => !req.headers.has("x-skip-auth"), }); // /healthz and /webhooks/stripe resolve to null. // Requests with the `x-skip-auth` header resolve to null. // Everything else returns the configured Principal. ``` ## API Reference | Symbol | Kind | Description | | ----------------------------------- | -------- | ------------------------------------------------------------------ | | `createStaticPrincipalResolver(o)` | function | Returns a `SessionResolver` carrying `o.principal` on every match. | ## Implementation Notes The clone strategy is: deep-freeze a single template at construction, then clone-from-template per `resolve()`. The per-call clone is unavoidable — otherwise two callers each freezing the returned object would freeze each other's state. The per-call clone copies the top level plus `scopes` (array spread) and `claims` (object spread); deeper structures inside `claims` are not cloned. This matches the `Principal` contract: `claims` values are `unknown`, so the package can't responsibly deep-clone them. Callers passing mutable nested claims values are responsible for not mutating them in-place. `pathMatches` accepts only exact-match and a single suffix glob (`/foo/*`) on purpose — richer matching belongs in `applyTo`. The trailing slash is preserved when stripping the `*` so `/foo/*` matches `/foo/x` but not `/foox`. The pattern dialect is documented on the spec and exercised by the test file.