PermDock
CLI

doctor

Diagnose a PermDock installation: server-only imports in client entries, unknown references, ungranted permissions, stale catalogs, roles or tenants read from unverified claims, missing skills and the TypeScript version.

Status: planned Phase: 2

permdock doctor runs every check PermDock knows how to make about a repository and prints a report with a fix for each finding. It is the first command an agent runs after wiring PermDock and the last one before opening a pull request. Findings have stable codes so skills and CI can reference them.

Usage

permdock doctor                # all checks
permdock doctor --json         # report with $schema, for agents
permdock doctor --only imports,references
permdock doctor --fix          # apply safe fixes (install skills, regenerate the catalog)

Exit code 1 on any error, 0 otherwise; warnings only fail with --strict.

Checks

CodeCheckSeverity
PD001Server-only imports in client entrieserror
PD002Unknown permission referenceserror
PD003Permissions used but never grantedwarning
PD004Stale catalog or generated barrelerror
PD005Missing or outdated Agent Skillswarning
PD006Unsupported TypeScript versionerror
PD007Policy validation mode never with an HTTP, MCP or agent adapter presentwarning
PD008Reserved identifier used for a public exportwarning
PD009Duplicate permdock copies in the dependency treeerror
PD010Roles or tenant read from an unverified or user-editable claimerror
PD011Tenant read from an optional claim under a multi-tenant issuerwarning
PD012OpenAPI document or Overlay pinned to a draft revision the installed CLI no longer emitswarning

PD001: server-only imports in client entries

Policy modules, createPermDock from permdock/next and any file that imports from permdock/hono, permdock/mcp or a provider must never be reachable from a client bundle. doctor builds the import graph from collect.srcPath with oxc-parser, marks files with 'use client' or under known client roots (Vite src/, Expo app/) as client entries, and reports any path from a client entry to a server-only module. This is the permix issue #49 class of bug (server code leaking into client bundles), caught before the bundler does something surprising.

PD002: unknown permission references

Member accesses rooted at a definition export that do not resolve to a leaf (permissions.post.archive when archive is not an action), and findPermission(permissions, 'literal') calls with a literal that no key matches. These are type errors in TypeScript projects; doctor catches them in JavaScript files, MDX and generated code too.

PD003: used but ungranted

The same analysis as usage, reported here at warning level so a single doctor run covers it.

PD004: stale catalog

Runs collect --check in memory. A stale permissions.catalog.json or generated barrel is an error because downstream tools (OpenAPI emission, MCP descriptions, the docs) read the file, not the code.

PD005: skills

Checks that the wire-permdock and audit-permissions skills are installed for the agents configured in the repository (.agents/skills, .claude/skills, .cursor/skills) and that their version matches the installed permdock. --fix runs permdock skills install. See skills.

PD006: TypeScript version

Reads the resolved typescript version and compares it with the supported matrix (5.9, 6, 7). Warns on 7.0 when a tool that needs the compiler API is detected, because TS 7.0 does not ship a stable one. Also verifies that isolatedDeclarations is not required of the consumer: PermDock enables it for itself, apps do not have to.

PD007: validation mode

validate: 'never' is a documented footgun when untrusted input reaches can(). If the policy sets it and the repository imports an HTTP, MCP, AI SDK or Claude Agent adapter, doctor says so and links to Validation.

PD008: reserved identifiers

Public exports named dock, ability, a can that defines rather than checks, or $-prefixed members in modules that import permdock. Enforces the naming convention in userland code where a skill would otherwise learn the wrong names.

PD009: duplicate copies

Two versions of permdock in node_modules are not a correctness problem for references (identity is by key, 0008) but are for types and bundle size; reported with the two paths.

PD010: roles or tenant from an unverified claim

Reads the claims option of every subjectFromJwt and createJwtSubjectResolver call and the field accesses in definePolicy's subject function, and reports a roles or tenant source that is one of: email or anything derived from it (a .split('@') on email is the common form), name, preferred_username, picture, or a bucket the provider documents as user-editable (user_metadata, Clerk unsafeMetadata, Stytch untrusted_metadata, Stack Auth clientMetadata, Ory traits, Cognito custom:* attributes without a write restriction). These are the escalation paths in the threat model: the token is genuine, the claim is not authoritative. The fix names the server-set counterpart for the detected provider (app_metadata, publicMetadata, trusted_metadata, serverMetadata, metadata_admin) or, for roles, points at a database lookup in context (claim trust rules).

PD011: tenant from an optional claim

Fires when claims.tenant names a claim the issuer emits only for some accounts and the issuer is a multi-tenant one: Google's hd under accounts.google.com (absent for consumer accounts), Entra tid under the common or organizations endpoint (personal accounts arrive with the consumers tenant), Okta groups-derived tenants without a filter. A missing tenant yields a subject without one, which tenant-scoped conditions deny, so this is a warning rather than an error; it exists because the usual next step is a default tenant in the resolver, which is the bug. The fix is to compare the claim against onboarded tenants in the resolver, or to restrict the issuer (login.microsoftonline.com/<tenant>/v2.0 instead of common, the hd authorization parameter at Google) so the IdP filters first (single sign-on).

PD012: stale draft pin

Reads x-permdock-catalog.drafts from every OpenAPI document and Overlay permdock openapi has written in the repository (found through the openapi section of the config or the paths passed to --doc) and compares each pin with the revision the installed CLI emits (OpenAPI 3.3, ADR 0025). A mismatch means the document carries an older draft shape than a regeneration would produce, which is a warning here and an error in permdock openapi emit --check; the fix is to regenerate. Documents and Overlays without drafts (targets 3.1 and 3.2, Overlay 1.1) are not checked; a --overlay 1.2 Overlay carries drafts.overlay (OpenAPI Overlay).

Example report

permdock doctor

  ✖ PD001  app/(marketing)/pricing/page.tsx imports src/permdock/server.ts through src/lib/billing.ts
           fix: move the check into a Server Component or import from 'permdock/react'
  ✖ PD004  permissions.catalog.json is stale (post.publish added)
           fix: pnpm exec permdock collect
  ⚠ PD005  skill wire-permdock@0.1.0 installed, 0.2.0 available
           fix: pnpm exec permdock skills install

  2 errors, 1 warning

The Unicode markers are replaced with error / warn under --no-color or when stdout is not a TTY.

Open questions

  • Whether doctor should compare shipped permdock exports against the documented adapter pages once the docs app exists (a "docs drift" check for maintainers).
  • Detection of client roots for frameworks without a convention (plain Vite, Solid Start) may need a doctor.clientEntries config option.

On this page