PermDock
Decisions

0023: Compose with the OpenAPI toolchain, do not wrap it

Why PermDock reaches spec producers (next-openapi-gen, hono-openapi, @orpc/openapi), SDK generators (Hey API, Orval) and docs UIs (Scalar) through standard security fields and an Overlay rather than per-tool packages, why the same rule applies to every adjacent ecosystem, and what stays out of scope.

Status

Accepted, September 2026. Extends 0019: the x-permdock-* namespace, registered fallbacks and Overlay output stand; this decision fixes how that output meets the rest of the TypeScript OpenAPI toolchain and records the general rule for adjacent ecosystems.

The Overlay 1.2 bullet under Decision ("tracked, not emitted") is superseded by 0025, which builds the pinned 1.2 draft behind --overlay 1.2 with 1.1.0 as the default. The targetFormat field that bullet names is not in the v1.2-dev text. Everything else here stands.

Context

The docs described PermDock's OpenAPI output in isolation. The adapter page named four in-process hooks (hono-openapi, @hono/zod-openapi, @orpc/openapi, trpc-to-openapi) and nothing downstream. Three gaps followed:

  1. The Next.js adapter had no OpenAPI path. permdock/next is Phase 1 and Next.js route handlers have no built-in spec generator, so the most-used adapter could not produce a document PermDock could annotate. next-openapi-gen fills that role in the ecosystem: it scans route handlers in Next.js, TanStack Start, React Router, Remix, SvelteKit, Nuxt, Astro, Hono and Express, targets OpenAPI 3.0 to 3.2, scaffolds Scalar as the default docs UI, applies Overlay files before writing the spec, and compiles Arazzo workflows against the generated operationIds.
  2. SDK generators and docs UIs were never named. Hey API (@hey-api/openapi-ts), Orval and Redocly's generate-client turn a description into typed clients; Scalar renders it. All of them read standard security and securitySchemes. None of them knows PermDock, and none of them needs to if PermDock writes standard fields.
  3. The same question recurs for every neighbouring ecosystem. Auth providers, agent frameworks, sync engines, audit sinks, feature-flag SDKs: each could become an adapter, and each time the answer should be the same unless the tool exposes a hook that only an adapter can reach.

The OpenAPI ecosystem research maps the pipeline and the tools in it.

Decision

  1. Compose, do not wrap. PermDock is the authorization contribution to an OpenAPI description, delivered as standard security plus securitySchemes and x-permdock-* extensions. It does not generate routes, SDKs or documentation UIs, and it ships no package whose only purpose is to call another tool. No permdock/hey-api, permdock/scalar or next-openapi-gen adapter in Phase 2.
  2. The Overlay is the hand-off. The documented recipe for any project whose description is generated by a tool PermDock has no in-process hook for (Next.js first) is: generate the description, run permdock openapi emit --format overlay, apply the Overlay with the applier the project already has (next-openapi-gen overlay.apply, Redocly CLI join --overlay), then hand the applied description to SDK generators and docs UIs. --format document stays the CLI default for continuity (0019); the Overlay is the documented default whenever the description is generated.
  3. operationId is the join key. Overlay actions target operations by operationId, producers generate them, and permdock openapi emit --check fails on operations without one. A producer's own security annotations (next-openapi-gen @auth and authPresets, for example) must not be used on operations PermDock covers; PermDock owns security, the producer owns paths and schemas.
  4. Standard fields first, extensions second, other namespaces never. Consumers must work from security alone. x-permdock-* adds information for consumers that opt in (the importer, simulate, generated MCP servers). PermDock never writes into a registered namespace it does not own (x-scalar-*, x-stainless-*, x-readme, x-oai-* beyond the three registered names). One unregistered, widely rendered field is allowed as an opt-in docs hint: x-badges with name: "Approval required" on operations whose permission carries approval, off by default.
  5. Lint rules, not a linter. A ruleset for Spectral, Redocly and vacuum ships as a file in Phase 2: every operation with x-permdock-permissions has security; no x-oai-* outside the registered three; no remove on security in an Overlay. permdock openapi emit --check remains the authoritative gate.
  6. Bridges read the extension. OpenAPI-to-MCP generators (Orval, Scalar, Speakeasy and similar) produce MCP servers that know nothing about PermDock. The documented recipe binds each generated tool's permission to the operation's x-permdock-permissions; permdock openapi import is the reverse direction. This is how the OpenAPI and agent stories meet without a package.
  7. The general rule. An ecosystem is reached through a wire format or a recipe (OpenAPI, Overlay, AuthZEN, Standard Schema, JWT and JWKS, OCSF, Problem Details) unless the tool exposes a runtime hook that only in-process code can occupy (a protect middleware slot, a canUseTool callback, an ORM query builder). Auth providers beyond the four planned are recipes on authentication; agent frameworks beyond the five planned are a research table; sync engines and MongoDB are where targets decided at Phase 3; sinks and flag SDKs are recipes. The one new build artefact this decision adds is an unplugin-based collect hook so Vite-family frameworks get build-time collection before their Phase 4 adapters.

Consequences

  • The Next.js adapter has an OpenAPI story in Phase 2 without a new adapter: next-openapi-gen produces, PermDock overlays, next-openapi-gen applies and scaffolds Scalar.
  • Hey API, Orval, Redocly, Scalar and any other standard-compliant consumer work on day one and are listed as such; PermDock carries no maintenance burden for their release cycles.
  • Two producers writing security on one operation is the main failure mode of the recipe; the Next.js and Overlay pages warn against it and --check reports operations whose source already carries security PermDock would overwrite.
  • The x-badges hint is the only field outside x-permdock-* and the registered set, is opt-in, and is documented as a rendering hint with no semantics; the fixture rule from 0019 ("no other x-oai-* name") extends to "no other vendor namespace".
  • Later first-party plugins (a Hey API plugin emitting typed permissions maps, a Scalar operation plugin rendering x-permdock-*) are Phase 4 candidates only if recipes prove insufficient, and Scalar's plugin API must first support operation-level custom extensions; an upstream issue precedes any PermDock workaround.
  • Overlay 1.2 (reusable actions, targetFormat) is tracked, not emitted; next-openapi-gen already accepts 1.0 to 1.2, so PermDock can move when it needs a 1.2 feature.
  • Parser choice for permdock openapi is an implementation note recorded when the CLI lands: prefer an existing parser (@scalar/openapi-parser, @hey-api/spec-types or @readme/openapi-parser) over a hand-rolled AST so import and emit stay compatible with the tools in the pipeline.
  • The docs gain a runtime matrix, a provider recipes table, two research pages (agent frameworks, local-first sync) and watch-list rows (AsyncAPI Overlay, OCSF, SPIFFE, AG-UI) that all follow point 7 rather than adding entries to the adapter matrix.

Alternatives considered

  • A permdock/next-openapi adapter that drives next-openapi-gen. Rejected: it would couple a Phase 1 adapter to a third-party CLI's release cycle, duplicate the generator's configuration surface, and be obsolete the moment another producer is preferred. The Overlay recipe achieves the same result with two commands.
  • A Hey API plugin in Phase 2. Rejected for now: Hey API attaches auth from standard security already, so the plugin would add only typed comments or a permissions map. Recorded as Phase 4 at earliest, behind evidence that the recipe is insufficient.
  • Emitting x-scalar-* so Scalar renders approvals natively. Rejected: scalar is a registered namespace owned by Scalar; writing into it is the mistake 0019 rejected for x-oai-*. x-badges is unregistered and rendering-only, which is why it is acceptable as an opt-in hint.
  • Shipping an Overlay applier. Still deferred (0019): next-openapi-gen and Redocly CLI already apply Overlays in the pipelines PermDock targets, and Scalar lists it as roadmap.
  • One adapter per auth provider, agent framework and sink. Rejected by point 7: subjectFromJwt covers every JWKS issuer, most agent frameworks expose no hook beyond what decide and approval-required already serve, and a DecisionSink is a three-line interface. Adapters are reserved for tools with a slot only in-process code can fill.

On this page