For AI agents
How a coding agent should install, wire, check and audit PermDock, and how PermDock's errors and denials are written for models.
PermDock's second audience is the coding agent working in a TypeScript repository. Every page, skill, error message and CLI report is written so that an agent can wire PermDock end-to-end, verify its work and explain a denial without reading library source. This page is the entry point for that agent. Nothing here is implemented yet (Phase 0); the roadmap says when each piece lands.
Start here
npx skills add ScaleDockHQ/PermDockThis installs two Agent Skills into the folders your agent reads (.agents/skills/, .claude/skills/, .cursor/skills/):
wire-permdock: detect the framework and validator, createsrc/permissions.ts,src/policy.tsand the factory file, add the first check and the first UI guard, run the CLI checks, add CI steps.audit-permissions: review an existing installation for ungranted or unused permissions, missing approvals on destructive agent-reachable actions, closures that could be portable, and the OWASP Agentic Top 10 mapping.
The same skills ship inside the permdock npm package and can be installed with permdock skills install, so the skill version always matches the library version. See skills.
What the repository gives you
| Artifact | Where | Use it for |
|---|---|---|
AGENTS.md (CLAUDE.md is a symlink) | repository root | Maintainer guide: layout, commands, invariants, "when you change X also update Y" |
llms.txt, llms-full.txt | docs site root | Index and full text of the docs for context loading |
.md per page | append .md to any docs URL | Read one page as Markdown without HTML |
| Docs MCP server | docs site route (Phase 4) | Search and read docs from inside an MCP-capable agent |
permissions.catalog.json and its JSON Schema | app repository, permdock catalog --format schema | Know which permissions exist, their arity, scopes and metadata |
permdock doctor --json | app repository | Machine-readable findings with stable codes and fixes |
| Example apps | apps/examples/<adapter> | Working code for every adapter, exercised by e2e tests |
Standards for these artifacts are on the Agent docs standards page.
The shortest wiring recipe
Three files and one check. Import paths and identifiers follow the naming convention; do not invent framework-prefixed names.
// src/permissions.ts — importable everywhere; no rules, no secrets
import { definePermissions, resource } from 'permdock'
import { Post } from './schemas' // any Standard Schema validator
export const permissions = definePermissions({
post: resource(Post, {
id: 'id',
actions: ['read', 'update', 'delete'],
collection: ['create', 'list'],
}),
})// src/policy.ts — server-only
import { definePolicy, role, allow, subject } from 'permdock'
import { permissions } from './permissions'
const member = role('member', [
allow(permissions.post.read),
allow(permissions.post.list),
allow(permissions.post.create),
allow(permissions.post.update, { where: { authorId: subject.id } }),
allow(permissions.post.delete, { where: { authorId: subject.id }, approval: 'human' }),
])
export const policy = definePolicy(permissions, {
roles: [member],
subject: (user: User | null) => user && { id: user.id, roles: user.roles },
})// src/permdock/server.ts — the explicit factory; same pattern for permdock/hono, permdock/mcp, permdock/ai-sdk
import { createPermDock } from 'permdock/next'
import { policy } from '../policy'
export const { getPermDock, getPermission, PermDockProvider, permdockHandler } = createPermDock(policy, {
subject: async () => getUser(await cookies()),
})Then, in a Server Component or Server Action:
const permdock = await getPermDock()
permdock.assert(permissions.post.update, post)And in a client component:
import { usePermission } from 'permdock/react'
const { allowed, status } = usePermission(permissions.post.update, post)Finish with pnpm exec permdock doctor and add permdock collect --check and permdock usage --strict to CI. Full versions: quick start, Next.js adapter, MCP adapter, AI SDK adapter.
The shortest OpenAPI recipe
If the project already generates an OpenAPI description, do not write security by hand and do not add a PermDock wrapper around the generator. Emit PermDock's Overlay and let the existing pipeline apply it:
permdock openapi emit --doc public/openapi.json --format overlay --out permdock.overlay.jsonFor Next.js and the other frameworks next-openapi-gen scans, add overlay: { apply: ['./permdock.overlay.json'] } to openapi-gen.config.ts; for Redocly pipelines, redocly join openapi.json --overlay permdock.overlay.json -o dist/openapi.json. Hey API, Orval, Scalar and any OpenAPI-to-MCP bridge then read the applied description with no PermDock code. Rules: every operation needs an operationId; do not also declare security through the producer (@auth, authPresets) on operations PermDock covers; add permdock openapi emit --format overlay --check to CI. Details: OpenAPI adapter, ADR 0023.
Invariants an agent must keep
- Permissions are references (
permissions.post.update), never strings, in every public API. Strings only appear as.keyand.scopeat boundaries, converted back withfindPermission. - Instance actions take the row; collection actions do not. The type checker enforces it; do not cast around it.
- The policy module and any
createPermDockcall are server-only. Never import them from a'use client'file;permdock doctor(PD001) will catch it, the bundler may not. - Never trust a model-supplied subject. The subject comes from the session or token through the adapter; tool arguments are validated at the boundary and never decide who the caller is.
- Prefer portable
whereconditions to closures so snapshots, RLS and SQL compilers cover the rule. - A
Decisionisgranted,deniedorapproval-required. Never introducenot-applicableor map an unknown outcome to approval. - Put
approval: 'human'on destructive actions reachable by agents (delete,publish, payments).
How errors and denials are written for models
PermDock assumes the reader of a denial may be a language model deciding what to do next, so denials carry structure rather than prose:
decide()returns{ outcome: 'denied', denials: [{ role, reason }], alternatives: [...] }.reasonis the policy author's string;alternativeslists permissions on the same resource this subject does hold, so the model can pick a permitted action instead of retrying the same one.approval-requiredcarries the grant, a reason and atoken. Adapters turn it into AI SDKuser-approval,WorkflowAgentneedsApproval, MCP elicitation or an HTTP 403 with the.../approval-requiredproblem type; the token must be echoed back when the approval resumes.- MCP refusals return
isError: truewith theDecisioninstructuredContent, and missing delegation produces ascopeChallengenaming the exact scope (post:delete) to request. - HTTP adapters emit RFC 9457
application/problem+jsonwithtype,title,permission,denialsandalternatives; see Errors and Problem Details. PermDockValidationErrornames the resource and the schema issues;PermDockDeniedErrorandPermDockApprovalRequiredErrorcarry theDecision.- CLI reports use stable codes (
PD001...) with a one-line fix each, and--jsonoutput carries a$schema. simulate([[permission, data], ...])lets an agent pre-flight a whole plan and read every decision before acting; it is the in-process form of AuthZENevaluations.
Reading the docs
- Concepts first: Permissions, Policies, Decisions, Subject, Authentication.
- One adapter page per framework, each with the same sections (purpose, API, lifecycle, validation, denials, example app, standards, open questions): Adapters.
- Decision records explain why a constraint exists; cite them instead of relitigating: 0003, 0006, 0013.
- The
StatusandPhaselines under a page's frontmatter say whether the thing exists yet. In Phase 0, nothing does.
Security framing for agent code
PermDock's agent features map onto the OWASP Top 10 for Agentic Applications: per-tool permissions with approval: 'human' gates and boundary validation address ASI02 Tool Misuse; the two-principal subject and delegation intersection address ASI03 Identity and Privilege Abuse. When writing tool handlers, wire the permission on the tool (permission in registerTool or the tools map) rather than checking inside the handler, so list_tools and capability middleware can hide what the caller may not do.