Naming
The PermDock naming convention, the use* and get* duality, reserved words, and what each adapter's createPermDock returns.
PermDock has one naming rule: the brand is the noun. The decision object is a PermDock, the variable is permdock, the factory is createPermDock, and the import path, not the identifier, says which framework you are in. Every package, docs page, skill, error message and example follows this page.
The three core names
| Name | Kind | Meaning |
|---|---|---|
PermDock | type | The immutable, request-scoped decision object returned by createPermDock. Methods: can, decide, assert, explain, filter, pick, where, simulate, snapshot, on, actions; tenancy: tenant, team (derived instances), derive (the same subject with other customRoles, approvalPolicies or relations), memberships, tenants, heldRoles, audiences, assignableRoles, assignablePermissions (read-only), decideRoleChange; graph: loadRelations, whoCan. Vocabulary trees: permissions, roles, plans. |
permdock | variable | The conventional name for a PermDock instance, and the npm package name. Also the CLI binary (permdock collect). |
createPermDock | function | Exported by core and by every server and agent adapter. Core returns a PermDock; adapters return a framework-shaped object. |
import { createPermDock } from "permdock"; // core: PermDock
import { createPermDock } from "permdock/next"; // Next.js: { getPermDock, getPermission, getSnapshot, requireAccess, PermDockProvider, permdockHandler }
import { createPermDock } from "permdock/hono"; // Hono: { permdock, protect }
import { createPermDock } from "permdock/mcp"; // MCP: { protectServer }There is no NextDock, HonoDock, createNextPermDock or createMcpPermDock. When two adapters are used in one file, alias at the import: import { createPermDock as createHonoPermDock } from 'permdock/hono'.
use* on the client, get* on the server
React reads permissions synchronously from a snapshot; servers resolve them asynchronously per request. PermDock follows the duality next-intl uses for useTranslations / getTranslations and useExtracted / getExtracted:
Client (permdock/react) | Server (createPermDock from permdock/next) | Returns |
|---|---|---|
usePermDock() | await getPermDock() | A PermDock (snapshot-backed on the client, full policy on the server) |
usePermission(permission, data?) | await getPermission(permission, data?) | { allowed, status } on the client; the same shape awaited on the server |
<Protected> | await requireAccess({ permission, data?, tenant? }) | Renders children or a fallback on the client; on the server, the granted Decision, or forbidden() / unauthorized() |
usePermissions([...], data?), useFilter(permission, rows) | (await getPermDock()).decide per reference, .filter | One entry per reference; the rows the subject may act on |
useTenant(), useMemberships(), useRoles(), useAssignableRoles(), useAssignablePermissions() | (await getPermDock()).tenants(), .memberships(), .heldRoles(), .assignableRoles(), .assignablePermissions() | The active tenant with switchTo; the membership list; Role[] held in a tenant; Role[] the subject may hand out; Permission[], the custom-role ceiling the subject may hand out |
useApproval(decision), useSubject() | the ApprovalStore and the subject on the server | Client-side request-access flow; the snapshot's principal, actor and delegation |
<PermDockProvider snapshot endpoint tenant> | <PermDockProvider> from the factory result | Client context; the server variant loads the snapshot once per request and serialises it |
The client hooks and <Protected> are direct exports with no factory: the permission reference carries all the types, so usePermission(permissions.post.update, post) is fully typed without a generic wrapper. Only the server side needs a factory, because only the server holds the policy. describe(decision) is a framework-free core export used by every UI adapter and by Problem Details (UI). Vue keeps the use* composable names. Svelte does not: it exports setPermDock / getPermDock and readable stores (permission, permissions, filter, tenant, …) because Svelte has no React-style hooks. Solid uses accessor functions. The UI parity table lists them.
Definition and policy vocabulary
| Word | Used for | Never used for |
|---|---|---|
definePermissions | Build the reference tree from resources and groups | Rules |
renamed, formerKeys, renamedFrom | The definePermissions option mapping former keys to current keys, the function that lists a leaf's former keys, and the catalog field that carries them | Keeping an old leaf alive; aliases, legacyKeys, deprecated |
defineRoles, definePlans | Build the typed Role and Plan trees (permdock.roles.admin, permdock.plans.pro) | Grants; Postgres roles |
Role, Plan | Frozen vocabulary leaves { key, on?, assignable, meta } and { key, meta }. A role named only by string becomes a synthesised Role leaf with assignable: false | The grant-list type; that is RoleBinding |
RoleMeta, audience | A role's meta: ActionMeta plus audience, the surface its holders use ('staff', 'portal', 'platform'); permdock.audiences() and snapshot.audiences list the distinct values of the roles held in the active tenant | A per-surface role list; a hard-coded role name in a layout |
min, max, transferOnly, assigns, for | Role ownership options: holders kept per scope instance, holders allowed, a holder count that only moves by transfer, the roles a holder may assign and revoke, the membership kinds (via) that may hold the role (ownership) | minHolders, canAssign, allowedVia; activation (reserved for time-boxed roles) |
decideRoleChange, RoleChange, RoleChangeDecision | permdock.decideRoleChange({ kind: 'assign' | 'revoke' | 'transfer', role, scope, id, within?, target, holders? }), or scope: 'global' with no id for a global role, and assignableRoles({ scope: 'global' }): the ownership rules for one change, answered as granted (with the authorising role) or denied with denials; the subject is always the actor | A write; an actor or subject taken from the change |
resource | One resource node: schema, id field, actions, collection, optional parent (which may be the resource itself), optional relations, optional version, optional restricted, optional disclosure | Anything without a schema-or-actions shape |
name (resource option) | The resource name leaves, tables, relations and AuthZEN types use when the last path segment collides | Changing the key; alias, table |
disclosure | The resource() option 'hide' | 'reveal' (default 'reveal'): with 'hide', a denied check on a loaded row answers 404 /not-found, the body a missing row gets, and the reason stays in audit | visibility, secret, a per-grant flag |
restricted, ResourceRestricted, stops | The resource() option naming a boolean column: a row where it is true is reached only by grants on itself, never by relations held on its ancestors or through its links; { field, stops } names the paths it closes (parent and link names) (relationships) | A deny; the reserved role(..., { restricted }) option |
FieldRelation, EdgeRelation, PrincipalRelation (edge, object, subject, expiresAt, principal, period, startsAt) | The three relation shapes on a resource: a row field holding the principal id ({ field, memberOf? } or a string), an edge table ({ edge, object?, subject?, expiresAt? }), and a principal column with an optional validity period ({ principal, period?: { startsAt?, expiresAt? } }) | A tuple store; a relation declared on a grant |
through, depth | relation(resource, name, { through: 'parent', depth }): follow the row's parent chain upward to resource, then its self-parent for up to depth hops (default 16, at most 32) | recursive, levels; walking a memberOf relation |
RelationSource, RelationChain, RelationHolder, memoryRelations, relations | The object-graph interface (ancestors, related), its answers, its in-process default over rows and edges, and the createPermDock option that takes it | A module-level cache; a decision |
loadRelations, whoCan, WhoCan, Holder, HoldingVia | await permdock.loadRelations(permission, rows) fills the instance's relation cache for an async source; await permdock.whoCan(permission, row) lists holders and how (role, relation, share) with complete | A grant; a partial list presented as complete |
related, RelatedCondition, RelationGrantee, RestrictedAncestors, restrictedAncestors, passRestricted | The condition node a graph relation compiles to, and the relation grantee type with through / depth | A hand-written where |
permdock_closure, permitted_<resource>_ids, permdock_closure_<resource>, permdock_restricted_<resource> | The closure table, the per-resource helper keyed by relation name and the trigger-maintained refresh function permdock rls generate emits for graph grants, and the helper listing the rows a closed link does not reach below | A table PermDock writes at runtime; service_role |
actions | The resource() option listing instance-level actions (can(permissions.post.update, post)), and the instance verb permdock.actions(permissions.post, post) returning one result per leaf of a resource | Type-level checks |
collection | Type-level actions (can(permissions.post.create)) | Instance checks |
crud, readable, writable | Option factories for resource(): conventional instance/collection split and default meta | Constructors; grants; a default of resource() |
mergePermissions, listPermissions, findPermission | Registry helpers; functions so they never collide with resource names | Methods on the tree |
parseCatalog, rowConditionKeys, catalogPath | permdock/catalog reads and freezes a permissions.catalog.json and lists its rowConditions: true keys; permdock/cli resolves the file path the way collect does | loadCatalog, readCatalog; a reader that returns null instead of throwing PermDockValidationError |
compileWhere, CompiledWhere | permdock/compile lowers a portable condition, for one subject, to the tree the ORM toWhere compilers render | A policy-time compile with no subject; RLS and PowerSync compile the condition themselves |
createServerKernel, ServerKernel, ServerKernelOptions, tenantScope, TenantScope, TenantOption | permdock/server: the kernel an HTTP adapter builds on. It hands out no instance, so it is not createPermDock. tenantScope resolves an adapter's tenant option against its framework context. | createPermDock for an adapter, which would return no instance |
isPermission | The structural guard for a permission leaf (string key, scope, resource, action and an object meta), which also accepts a leaf that crossed JSON and lost its non-enumerable kind | A registry lookup; identity stays by key |
definePolicy | Bind vocabulary, scopes, grants, principal, context, validate to a definition | Defining permissions |
role | A RoleBinding: named array of grants with an optional scope ({ on: <declared scope name> | resource reference, assignable }, typed against definePolicy({ scopes })), the ownership options min, max, transferOnly, assigns, for, exclusiveWith and a RoleMeta meta; activation and restricted are reserved; same-named bindings merge; first argument may be a Role leaf | Postgres roles; the only grantee |
to, anyone, authenticated, relation, plan, actor, assurance | Grantee selectors on allow / deny. anyone() is the only selector that evaluates for principal: null; an array in to means every selector must match | A second permission check; hasRole |
scopes | The definePolicy option declaring named scopes in order, each with its row key and an optional parent within ({ organization: { key: 'organization_id' }, customer: { key: 'customer_id', within: 'organization' } }) (named scopes) | Permission scopes (.scope is the colon string form) |
within | A scope's parent in definePolicy({ scopes }), and on a Membership the ids of its ancestor scopes | parent (that is the resource chain) |
scope, id, on | The two shapes of a Membership (an instance of a named scope, a resource role) and the on option of role | Hard-coded tenant and team kinds in core |
tenant, team | Aliases of the first and second declared scope (in role on, memberOf, and the { tenant, team } membership input form); principal.tenant is the active instance of the first scope | Scopes named after a provider's vocabulary when the policy's own names differ |
Membership, CustomRole, CustomRoleGrant | The wire types for a scoped role assignment, a tenant-defined role (includes declared roles, grants of declared permission keys, optional scope and id) and one of its { permission, effect? } entries | Classes; a condition, approval or limit on a custom-role grant; all are plain JSON |
customRoleClaim | Builds the compact memberships[].grants map (key, -key, @role per custom role name) that RLS helpers read in jwt mode with --custom-roles | A claim PermDock trusts without subjectFromJwt; a per-provider helper |
custom_role_permissions, custom_role_includes, permdock_ceiling, permdock_custom_keys | The SQL objects permdock rls generate --custom-roles emits next to role_permissions and the helpers | Tables PermDock writes at runtime; a way around the ceiling |
resolveCustomRole, validateCustomRole | The one resolver of a custom role against the ceiling of assignable declared roles ({ grants, dropped }), and its summary { ok, permissions, dropped } for a save action and permdock doctor PD023 (custom roles) | Widening a role; resolving a name a declared role already uses |
CustomRoleDrop, CustomRoleDropReason | Why a custom-role entry was left out: unknown-permission, outside-ceiling, condition-not-allowed, unknown-level (with level), or { role, reason: 'unknown-role' } for an include | Denial reasons; a drop is never a Decision |
meta.readOnly, meta.destructive, meta.idempotent | Action metadata the MCP adapters turn into readOnlyHint, destructiveHint and idempotentHint for tools whose author set no annotation | A decision input; tags: ['destructive'] |
meta.x, MetaValue | Application-owned JSON on an action's metadata; PermDock carries it and never reads it | A decision input; meta.custom, meta.extra |
x (definition option), AppData, AppDataSchema, DefinePermissionsOptions, VocabularyOptions, PolicyAppDataSchemas | One Standard Schema per kind of app data: definePermissions(…, { x: { permission, resource } }), defineRoles(…, { x }), definePlans(…, { x }), definePolicy(…, { x: { grant, membership } }); AppData is the default plain JSON object | A decision input; declare module augmentation; extensions, custom |
ResourceMeta, GrantMeta | A resource's { title?, description?, x? } and a grant's { description?, x? }; carried to decisions, events, snapshots and the catalog | Policy fingerprint input |
ResourceXTree, RESOURCE_X | The type definePermissions adds to a tree so getResource types meta.x; RESOURCE_X is a type-only key with no runtime value | A runtime property |
Membership.x | Application-owned JSON on a membership, from a trusted source; dropped with an on('auth') schema event when invalid; never in claims | A condition input; metadata, attrs (that is the token claim) |
obligations, AppObligation, ObligationInput | allow(p, { obligations }) and the { kind: 'app', name, detail? } entries it puts on a granted decision | A new kind per app need; obligations on a deny |
DenialDetails | The map from a denial reason to its typed detail (limit, inactive-grant, deny), read through Denial<R> | A free-form detail per reason |
onDenied (adapter option), OnDenied, OnDeniedEvent | The HTTP adapter hook that receives { decision, problem, request } for a refusal and returns Problem Details, a Response or nothing; it cannot grant | onForbidden, errorFormatter; a hook that turns a denial into a grant |
OnDeniedText, DeniedTextEvent | The MCP and AI SDK onDenied hook, which may replace only the refusal text | A hook that changes isError or structuredContent |
context (adapter option), RequestContext | The adapter hook that returns server-derived JSON merged into subject.context for the request | A subject, membership, tenant or actor source; locals, extra |
wrap, wrapPermDock, PermDockWrap, PermDockOverrides | The adapter option that wraps every request's instance after otel, and the helper that overrides methods and keeps tenant(), team() and derive() wrapped | A plugin system; middleware, decorate |
field, explain (protect options) | The per-route field check and decision trace passed to decide | A field mask; a trace on the wire |
defaults, PermDockDefaults | Provider-wide content for the <Protected> slots a component leaves out | fallbackComponent; a global mutable default |
useDescribe, getDescribe | describe with the provider's messages; getDescribe in Svelte | A second description format |
meta.manageRoles | On a Role or a permission leaf: holding the role, or being granted the permission, makes the full ceiling assignable instead of only what the subject holds | A grant; a bypass of RoleSource.assignable |
SnapshotAssignable | One { tenant, roles, permissions } entry of a snapshot's assignable list | A second snapshot format |
SnapshotScope | One { name, key, within?, resources? } entry of a snapshot's ordered scopes list | A tenant-and-team pair of keys |
Scope, ScopeDeclaration, PolicyScopesInput, PolicyScopes, ScopeNames | A declared scope { name, key, within? }, one definePolicy({ scopes }) entry { key, within? }, the whole option, Policy.scopes in order, and the names on accepts (declared names plus the tenant / team aliases) (named scopes) | A scope kind enum |
GrantScope, RoleScope | Where a grant applies ('global', a scope name, or { resource }) and what role(..., { on }) accepts | Hard-coded 'tenant' | 'team' |
MembershipSource, RoleSource | The two subject-input interfaces (membershipsFor, rolesFor / assignable) passed as memberships and customRoles to every createPermDock | Stores; PermDock never writes a membership or role |
SubjectResolver | The generic type of every subjectFrom* function: verified input in, Subject out, never throws | A class hierarchy |
memoryRoleSource | The in-process RoleSource over a static CustomRole[] | Production storage |
memoryMembershipSource | The in-process MembershipSource over memberships keyed by principal id | Production storage |
memorySnapshotSource | The in-process SnapshotSource over one snapshot; set replaces it and calls each subscriber | Production storage |
customRoleSource, CustomRoleReader, CustomRoleSourceOptions | A RoleSource over rolesOf(tenant), a read of every custom role of the tenant; read: 'held' skips the read when the subject holds only declared roles | A source that returns only the held roles; rolesForUser |
globalRoles, scope: 'global' | The RoleSource method returning platform custom roles, and the custom role scope that marks one | A tenant role without a tenant; platformRoles, systemRoles |
ApprovalPolicySource, ApprovalPolicy, memoryApprovalPolicies, approvalPolicies, validateApprovalPolicy, ApprovalPolicyProblem | Approval requirements kept as data, the entry shape, the in-process default, the createPermDock option, and the check that names why an entry does not load (unknown-permission, invalid, where-on-collection, stale-on-without-version); entries only add stages | Granting or relaxing an approval; approvalRules, workflows |
composeMemberships, claimsFirst | One MembershipSource over several (merged, de-duplicated, list and version combined); a source that keeps the verified token's memberships until the token says they were truncated. An array passed as memberships is composed | A store; a cache with its own TTL |
MemberEntry, list, version, claimsFirst | The optional MembershipSource members: every member of one scope instance ({ principal: { id }, membership }), the principal's authorization version, and the claims-first flag | members, epoch, revision |
EntitlementSource, entitlementsFor, entitlements, memoryEntitlementSource, fromStripeEntitlements, testEntitlementSource | The subject-input interface for plan names per tenant, its createPermDock option, the in-process default, the Stripe Entitlements reader over a structural client, and its conformance runner | A permdock/stripe entry; plans as an option name |
fresh, stale, stale-credentials, authzVersion, authz_ver | The definePolicy option listing permissions that need an up-to-date token, the subject flag, the denial reason, the permdock/supabase version reader and the claim it compares | sensitive, strict, stale-token |
onStale | The claimsFirst option for a token whose authorization version is behind: 'deny' (default) denies fresh permissions, 'reread' reads the memberships from the sources instead | A re-read on every request |
managedBy: 'idp', isExternallyManaged, externally-managed | A membership the identity provider owns, its predicate, and the decideRoleChange reason that refuses to change it | readonly, locked, scim: true |
fromSupabasePostgres, SupabasePostgres | ctx.postgres or ctx.postgresAdmin from @supabase/server's withPostgresClient as a SqlQuery, through its queryRaw | A Postgres client; a new connection |
fromTable, fromJunction, SqlQuery, SqlMembershipSource, RoleThrough, supabaseMembershipsBudget | The permdock/supabase SQL membership sources, the statement runner they take, their type, a role or user column read through another table, and the default claim budget in bytes | A query builder; an ORM adapter |
postgrestSources, subject_for, authz_version_for, members_of, SupabaseRpcClient, SupabaseRpcCaller, SubjectRecord | The permdock/supabase sources over PostgREST: one subject_for(p_user) call per user, generated next to the hook, gives memberships, customRoles(principal) and subject(userId) for a backend that only has supabase-js, authz_version_for(p_user) gives memberships.version without the full read, and members_of(p_scope, p_id) gives memberships.list; SupabaseRpcCaller is the client parameter, loose enough for a supabase-js client typed with a generated Database | A client-callable function; a subject from a request body |
RoleSourceFactory | (subject) => RoleSource | undefined, the per-subject form of every customRoles option, called once per instance with the resolved subject | A source that remembers the last principal it served |
permittedIds | The instances of a scope where the subject holds a permission with no row condition, minus denied ones, or with conditioned: true where a conditioned allow applies: the in-process mirror of permitted_<scope>_ids_by_permission; a listing, never a decision | A decision; a check of one row |
countHolders | The live holders of a role in one scope instance, from MembershipSource.list, for decideRoleChange's holders | A holder count from a second, unfiltered query |
operationPermissions, operationPermissionsFromOpenApi | One declaration of each API operation's permission, "METHOD /path/{param}" to a permission, read by an HTTP gate (forRequest) and by permdock/mcp's permissionFor (forOperation) | A regular-expression route table; a second mapping for the MCP tools |
oauthScopes (MCP tool config, protect options, an operation entry, withPermDock config), oauthScopesFor, oauthScopesForRequest, oauthScopesForOperation, operations, ScopeGuard | An operation's own OAuth scopes in place of those derived from its permission, narrowing which coarse scope reaches each MCP tool or REST route that shares a permission, or the only gate of a route that checks no permission (protect(null), ScopeGuard); operations on permdock/server reads them from operationPermissions | A second permission only to tell two tools apart |
supabaseApprovalStore, rls.approvals, approval_requests, permdock_approval_* | The generated approval store (a table and one function per ApprovalStore method, executable by no client role) and the permdock/supabase store over it | An approval table a client role can read or write |
rls.jsonSchema, approval_requests_body_schema | 'auto' | boolean: a pg_jsonschema check constraint that each approval body matches approval-request-v1.json | A remote $ref; format assertions |
APPROVAL_POLICY_UNAVAILABLE | The approval-policy-unavailable denial detail when an ApprovalPolicySource fails, as a constant | A second spelling of the detail |
supabaseClaims, SupabaseClaims, SupabaseMembershipClaim, SupabaseClaimsSchema, extend | The permdock/supabase Standard Schema of the hook's claim contract (loose, no validation library), its output types, and the method that merges an app's own claim schema over it | supabaseClaimsSchema, extendClaims; a strict object that rejects other claims |
oauthScopes, PolicyOAuthScope | Coarse OAuth scopes an authorization server issues (mcp:read), each covering permissions; expanded into the token delegation and named in scope challenges | scopeAliases, aliases, scopeMap |
ClientNames, clients, actor.client, to: { kind, client } | Stable names for OAuth clients whose ids differ per environment or come from dynamic registration: the subject resolver maps a verified client id to a name, and a policy delegation names the client instead of the id | aliases, clientAlias, a client id in the policy |
actorOf, delegationOf, SupabaseActor, SupabaseActorResult | The permdock/supabase rules that turn act / client_id into the acting party (oauth-client, support, impersonation, by act.kind) and scope into delegated scopes; { ok: false, reason: 'invalid-chain' } must deny | getActor, parseActor; returning null for a malformed chain; admin or impersonator as an actor kind |
anonymousSignIns | SupabaseSubjectOptions option: 'deny' maps a token with is_anonymous: true to the anonymous subject, as rls.anonymousSignIns does in RLS | allowAnonymous, a boolean |
liveSession | SupabaseSubjectOptions option and Subject field: true once the caller checked the session against the Auth server for this request; never read from claims | fresh, which fresh permissions already name; sessionValid |
supabaseClaimVectors | permdock/testing export: supabaseClaimFixtures as a supabase/sdk conformance vector file, { feature: "auth.session.get_claims", cases: [{ name, input, expected }] } | supabaseConformance, which names no feature; a JSON file outside the package |
requires | Supabase manifest field: { matrix, capabilities }, the capability-matrix release tag and the feature ids the setup depends on | capabilities at the top level, which reads as what PermDock offers |
pageApprovals, approvalPageSize, encodeApprovalCursor, decodeApprovalCursor, listAllApprovals, ApprovalCursorPosition | The list-paging helpers the built-in approval stores use, exported from permdock/approvals for a custom ApprovalStore (approvals) | A cursor format of the store's own; paginate, listAll |
{ subject: { session: { live: true } } } | The where form for a grant that needs a live session; normalised to { "op": "liveSession" } | A principal.sessionLive ref, which would read a claim |
Actor.readOnly | true narrows every policy delegation to that actor to its read-only permissions (readOnlyHint); a Supabase support session sets it from act.read_only | readonly, writable: false |
supabaseTenantClaim | The default claim (tenant_id) for the active tenant, shared by the hook, subjectFromSupabase and the RLS helpers | A per-app tenant name; a column name |
SupabaseHookManifest, SupabaseManifestMembership, SupabaseManifestRls, SupabaseManifestHelper, SupabaseManifestValue, SupabaseManifestColumn, supabaseHookManifestFixture, permdock supabase inspect, permdock.manifest.json | The version: 1 manifest of the generated hook and SQL helpers (helper names and signatures, tenant claim, budget, claims written, membership sources, RLS mode, deciding columns, marker majors), its types in permdock/supabase, its fixture in permdock/testing, and the file inspect --out writes | apiVersion; a second hook generator |
SupabaseManifestRole, SupabaseManifestThrough | A manifest membership's role: a column, fixed roles, or a column with the roles table (table, id, column) it references; a membership's user takes the same through | apiVersion; a second hook generator |
permdock supabase hook generate | The CLI command that compiles the SQL sources into the Custom Access Token Hook | permdock hook, permdock rls hook |
attrs (supabase.hook.attrs, the claim) | Allow-listed server-owned columns and app_metadata.<key> entries the hook writes for attribute conditions (principal.claims.attrs.<key>) | profile, user_metadata, claims as a config key |
supabase.hook.api | The PostgREST wrappers <prefix>subject_for, <prefix>members_of and <prefix>authz_version_for (default schema public, prefix permdock_) that hook generate writes for a backend reaching Postgres through the Data API; postgrestSources({ api }) takes the same setting. The one place the hook grants to service_role, and only when the option is set | A hand-written wrapper per function; a grant to authenticated |
supabase.hook.before | Schema-qualified functions (event jsonb) returns jsonb the generated hook calls first, in order; a result with error is the hook's answer | A second access token hook; a check that writes the claims PermDock owns |
supabase.hook.validate | Check the claims the hook wrote against supabase-claims-v1.json with pg_jsonschema and drop them all on a mismatch | Raising an Auth error on a bad claim; validating claims another package owns |
LimitStore, memoryLimitStore, limits | The quota counter for limit grants, its in-process default, and the createPermDock option (core, every HTTP and agent adapter) | Auto-installing a store; a thenable consume as granted |
GrantLimit, mode, alertAt | The limit grant option { count, per, mode?, alertAt? }: mode is 'hard' (default) or 'soft', alertAt a fraction of count in (0, 1] (limits) | A soft limit that grants when the store is unreachable |
Quota, quota | { remaining, resetsAt } on a granted decision under a limit; resetsAt is Unix seconds | A snapshot field; a denial |
LimitDetail | { count, window, resetsAt }, the detail of a limit denial: the grant's count, its window in seconds and its reset in Unix seconds; HTTP adapters render it as 429 with Retry-After, RateLimit and RateLimit-Policy | quota on a denied decision; a store read in the renderer |
not-entitled, requiredPlans, SnapshotNotEntitled | The denial reason when a held role's grant fails only on its plan grantee; requiredPlans(decision) lists the plan keys when every denial is not-entitled; describe kind upgrade with plans; 403 /not-entitled with plans; the snapshot's notEntitled entries { permission, role, to } | no-plan, upgrade-required, missing-entitlement; a 402 |
Obligation, obligations, over-limit, near-limit | What a granted decision says the caller owes, as { kind } objects; the kinds so far are over-limit (a soft limit past its count) and near-limit (usage reached alertAt) | A fourth outcome; an obligation on denied or approval-required |
pdp | The HTTP adapter and kernel option taking createPermDock from permdock/pdp, so protect decides delegated permissions remotely | Making the request-scoped instance async |
ProtectOptions, trusted | The third argument of every protect ({ trusted: true } skips validating what the loader returns) | A per-route validate mode |
scope (decide option) | The DecideOptions field naming a declared scope whose memberships alone answer a check (can(permission, undefined, { scope: 'organization' })); global roles still apply (scopes) | scope(...) as a derived instance, tenantOnly, firstScope |
request | The permdock/trpc and permdock/orpc option mapping a context to its Web Request, and the permdock/nest option mapping a non-HTTP ExecutionContext | Reading a subject from it directly |
problemFromError | The permdock/server function turning a thrown PermDock error into its Problem Details Response, undefined for anything else | An error class; a catch-all that maps non-PermDock errors |
ormParity, OrmParityScenario | The permdock/testing runner comparing filter() in memory with the rows a database returns for toWhere(where()), and its scenario shape | A database the runner opens itself |
testClientParity, ClientParityCase | The permdock/testing runner asserting that fromSnapshot never grants what the server denies, per membership and tenant, and its case shape | Deriving expectations from the snapshot itself |
testClientStore, ClientStoreFactory | The permdock/testing runner for client snapshot stores and the factory it calls (scenario testing) | A DOM or a framework renderer |
WebMcpToolCall | The { input, token } argument a permdock/webmcp handler receives | Positional input arguments |
listFields, requiredFields | toWhere options: scalar-list fields whose contains is element membership (Prisma, Kysely), and Prisma's non-nullable fields, where a null branch folds away | Inferring either from the Prisma client at runtime |
prismaModelFields, PrismaModelFields, model | The permdock/prisma function reading required and list fields from schema.prisma text or a DMMF datamodel, its result, and the toWhere / toPredicate option taking it | Loading the schema file itself |
toPredicate | The permdock/prisma compiler for Prisma 8 field-proxy predicates | A second toWhere overload |
checkRow, RowCheck | The single-row check of permdock/drizzle, permdock/kysely and permdock/prisma, and its { found: false } | { found: true, granted } result | A not-found outcome on Decision |
resolveRelated | The permdock/prisma step replacing related nodes in a where() result with ids read through a raw query | A compiler; it returns a WhereResult |
relations (compiler option), RelationsMapping | Where the graph tables live for Drizzle and Kysely toWhere and resolveRelated: tables and closure | The createPermDock relations source, which answers facts in process |
match, groups, includes, links | Relationship declarations: an edge table's fixed column filter, its group holders, relations implying this one, and named to-one references walked by through: [...] | Tuple rewrites; through naming a field |
MemberOfParent | A memberOf parents entry: a keyed { field, resource } or a bare field name | Walking a parent chain with a query |
WhereResult.subject | The non-enumerable subject a where() result carries so toWhere compiles memberOf without a second argument | A serialised field; it never reaches JSON |
testAuthZen, AuthZenVectors, authzenTodoPolicy, authzenTodoVectors | The permdock/testing AuthZEN interop runner, its vector layout (the interop harness files unchanged) and the interop Todo domain (authzenTodoPermissions, authzenTodoUsers, authzenTodoData) | Vendoring the official vectors |
testHttpAdapter, HttpMounted | The permdock/testing HTTP scenario runner and the value a mount returns (fetch, call, close) | A server the runner starts itself |
ArazzoSimulateInput, ArazzoPlan, arazzoFindings | The simulate({ arazzo, openapi }) argument and result, and the resolution-only helper the CLI uses | A permdock/arazzo package; a subject on the call |
webBotAuth, verifyWebBotAuth, discoverViaSignatureAgent | The HTTP createPermDock option for RFC 9421 signatures, the verifier it calls ((request) => verifyWebBotAuth(request, options), so an app without it bundles no verification) and its Signature-Agent directory lookup | A permdock/web-bot-auth package; trusting a host that is not on allow |
InvalidSignatureError | Thrown by permdock(request) when a claimed Web Bot Auth signature fails; protect returns the Problem Details response instead | Downgrading a failed signature to an anonymous actor |
allow, deny | The two grant constructors; both accept one reference or an array and a to grantee | Checking |
principal | The condition ref builder (principal.id) and the definePolicy option that maps a user to principal values. There is no subject ref builder; subject stays accepted as the definePolicy option name | The RFC 8693 Subject (that stays subject on the instance) |
context | The condition ref builder for values loaded by the definePolicy context function (context.teamIds) | Request-scoped state; the ctx a closure receives |
principal, actor, delegation | The three parts of a subject | Synonyms for user or role |
granted, denied, approval-required | The three Decision.outcome values | not-applicable, allow, deny |
where, check | Conditions on the current row and on the next row | Query building outside conditions |
sqlFunction | Named SQL function plus a portable twin the evaluator, filter, where and snapshots run; rls generate emits the call | A runtime database round-trip; a closure; inlining the function body as the only representation |
opaque | Imported SQL with a fingerprint and no twin; evaluates to false | A portable grant |
fields, pick | Schema keys on a grant and the redaction helper permdock.pick(permission, row) | CASL globs; a permission per field |
validFrom, validUntil, validity | The grant options bounding when a grant applies (RFC 3339 or Unix seconds), and the normalised { from?, until? } on Grant and a snapshot grant; an inactive allow denies with inactive-grant | expiresAt (that is a membership's end), notBefore, window, a clock operator in where |
group | The grant option naming a condition group in generated SQL: the grant key is <permission>#<group> instead of a positional #n | A positional key in hand-written SQL; a group per role |
requires | The allow option naming a permission, or a list of them, the subject must also hold through a role, globally or on the row's scope instance, for the grant to count | permission() as a grantee; a where on roles |
inherit(permission, { through }), RelationSource.row | The grantee that holds on a row when the subject holds permission on the row a link list or the parent points to, decided by every grant of that permission; row reads that row by id | from, via, a relation per way the target becomes readable |
key, scope | The dotted (post.update) and colon (post:update) string forms of a permission | Public API arguments |
subjectFrom<Provider> | Turning verified material into a Subject: subjectFromJwt, subjectFromIntrospection, subjectFromSupabase, subjectFromSupabaseSession, subjectFromClerk, subjectFromBetterAuth, subjectFromMcp, subjectFromCapability, subjectFromCiOidc, subjectFromApiKey; each provider mapper takes a schema option (authentication) | Verifying, logging in, or anything that can throw |
JwtPrincipal | The base principal type permdock/jwt exports (id from sub, issuer from iss, assurance, RFC 9068 roles and groups) | A session type; claims stay on principal.claims |
SupabasePrincipal | The base principal type permdock/supabase exports (id from sub, roles and tenant from hook claims, memberships, assurance.acr from aal) | A session type; user_metadata is never read |
supabaseRls, authorizeSql | RLS compile hints for permdock rls and the authorize() function SQL with an optional tenant parameter | A policy builder; it does not decide |
suspension (RlsSuspension, SupabaseSuspension, RlsSuspendedScope, SupabaseSuspendedScope, RlsSuspendedMembership, SupabaseSuspendedMembership) | The rls config key, supabaseRls and authorizeSql option naming the users and scope-instance tables (RlsActiveRow / SupabaseActiveRow: table, id, disabledAt, status, active) the generated helpers, authorize() and the token hook read to drop suspended rows, and per scope keep, the permissions a suspended instance's members still hold, which a membership carries as Membership.keep; memberships.keep is the same list for a membership whose table's disabledAt column is set (RlsMembershipTable, fromTable columns.disabledAt, fromJunction disabledAt) (RLS) | A createPermDock option; in process, suspension is a MembershipSource concern |
permdock_has, permitted_<scope>_ids, member_<scope>_ids, role_permissions | The SQL objects permdock rls generate emits: permdock_has(p_grant) for global roles, one permitted_<scope>_ids(p_grant) per declared scope (permitted_tenant_ids and permitted_team_ids for a policy that declares none), over the seeded role_permissions (role, permission, grant_key, scope, effect), and one membership-only member_<scope>_ids() per declared scope, plus member_<scope>_ids_for(p_user) on Supabase for a scope with a membership source, executable by supabase_auth_admin only, and in database mode permdock_has_for(p_user, p_grant) and permitted_<scope>_ids_for(p_user, p_grant), executable by no client role; permdock_has_permission(p_permission), permitted_<scope>_ids_by_permission(p_permission) and grant_keys(p_permission, p_scope, p_effect) take a permission key and answer with its unconditional allows minus any deny; a grant key is the permission key or permission#n (RLS) | Per-row checks; anything a client calls with another user's id; <scope>_ids_for_user or member_<scope>_ids(p_user) for the per-user helper; permitted_<scope>_ids() with no argument or <scope>_ids_for_member for the membership-only helper |
permdock_role_permissions, permdock_permission_keys | The role and catalog readers permdock rls generate emits: permdock_role_permissions(p_role, p_scope, p_tenant, p_scope_id) lists the permission keys a role holds with their effect, permdock_permission_keys() every declared key and permdock_permission_keys(p_scope) those some declared role is allowed on a scope, both executable by authenticated (RLS) | Hand-written joins over role_permissions; role_permission_keys or permission_keys |
permdock_trusted_role_permissions, rls.trustedReaders | The role reader for server code without the caller check, and the Postgres roles granted execute on it | A flag on the member-facing reader; service_role hard-coded |
permitted_<scope>_permission_keys, permitted_<scope>_permission_keys_for | Every permission key the caller (or a named user) holds on one scope instance, as the permission-key helpers answer each key, in one call (RLS) | A loop of permdock_has_permission calls; held_permissions |
permitted_<resource>_rows, permitted_<resource>_rows_for, rowHelpers | The per-resource row helper rls.rowHelpers turns on: the row ids the caller (or a named user) may act on with one permission, as the generated policies decide it (RLS) | permitted_<resource>_ids (that is the graph helper keyed by relation); allowed_rows |
rls.membershipSources | The fromTable / fromJunction sources the database-mode helpers read for a scope rls.memberships maps no table for; default supabase.hook.memberships | A second membership mapping per scope |
permdock_can_assign, permdock_can_assign_for, permdock_holders_<scope>, permdock_transfer_only_<scope> | The ownership objects permdock rls generate adds when a role declares a rule: permdock_can_assign(p_role, p_scope_id) for the application's policies on its membership tables (a null p_scope_id for a global role), permdock_can_assign_for(p_user, p_role, p_scope_id) for trusted SQL acting for a stored user in database mode, the deferred holder-count trigger (min, max) and the transfer-only statement triggers on each scope's membership table (ownership) | A trigger that writes; a check that trusts the caller's role claim over the membership table |
<table>_visible, <table>_visible_fields, permdock_key, --fields views, --revoke-columns, rls.fields, rls.revokeColumns | The field views permdock rls generate --fields views emits: a security_invoker view per table with field-limited read grants whose restricted columns are case when <permitted> then col end, and with --revoke-columns the owner-rights companion it joins on permdock_key while the table keeps only unrestricted columns readable; rls import reads them back as the fieldViews export, rlsParity({ fieldViews }) and rls verify compare them with pick, doctor PD030 flags columns still readable | Postgres column privileges per app role, a permission per field, or a view that reads past RLS |
secretKeys, SupabaseSecretKey | The permdock/supabase/middleware option mapping a named Supabase secret key (authKeyName from withSupabase) to a service principal with { id, tenant, roles, permissions }, as a service credential | A list of accepted keys; withSupabase's auth: 'secret:<name>' decides which keys verify |
withPermDock | The @supabase/middleware entry permdock/supabase/middleware's createPermDock returns: requires jwtClaims upstream, contributes ctx.permdock; withPermDock({ protect, data }) short-circuits with Problem Details | A PermDock naming pattern; with* is the pipeline's convention and appears elsewhere only as withSubject and withOtel |
createJwtSubjectResolver | The cached, reusable form of subjectFromJwt, one per issuer (JWT adapter) | A second createPermDock |
createClerkSubjectResolver | The subjectFromClerk resolver that caches each user's memberships: 'all' list for cache.ttl (Clerk adapter) | A token or session cache |
verifyDpopProof | The exported DPoP check permdock/jwt also runs when sender: 'dpop'; replay (a ReplayStore, memoryReplayStore() from permdock/jwt) refuses a reused proof jti | A second verifier; it only checks the proof |
TokenVerifier, TokenSigner | The two JOSE interfaces core declares as types (extension interfaces) | Anything that throws; anything that decides |
joseTokenVerifier, joseTokenSigner | The permdock/jwt implementations of the two interfaces over the optional jose peer | Exports of core |
verifier, signer | The option names for a TokenVerifier (subjectFromJwt, permdock/ssf) and a TokenSigner (permdock.snapshot, approvalsHandler, permdock/cloud) | jwt, jose, crypto as option names |
discovery, accept | The permdock/jwt options for OpenID Connect Discovery (an issuer URL that supplies jwks_uri and issuer) and for which token kind a resolver accepts ('access-token' default, 'id-token') | Guessing the token kind from its claims |
subjectFromIntrospection | An RFC 7662 or RFC 9767 introspection response in, Subject out | An HTTP client; the call is yours |
<provider>RoleSource | A provider's RoleSource implementation (betterAuthRoleSource) | A subject mapper |
authorizationProvider | permdock/better-supabase: better-supabase's authorization config value (AuthorizationProvider, apiVersion: 1), built from the manifest and catalog; approver sets canApprove | PermDock's own decision path |
bucketPolicy, topicPolicy | permdock/better-supabase: the access policy of defineBucket and defineTopic, mapping operations to Permission references | rls.storage and rls.realtime, which permdock rls generate writes |
apiKeyClaimOptions | permdock/better-supabase: the claim and tenantClaim options of better-supabase's apiKeyClaims() and apiKeyResolver(), from rls.apiKeys | Verifying a key |
subjectFromBetterSupabase | permdock/better-supabase: a Subject from better-supabase's AuthSession, with apiKeys for apiKey sessions and plans for the features claim | Verifying the session; better-supabase did that |
toolPolicy | permdock/better-supabase: the authorize and visible hooks of better-supabase's createMcp, for tools whose meta is a Permission | Tools on the official MCP SDK (permdock/mcp) |
credentialGuard | permdock/better-supabase: a better-supabase CredentialProvider whose token uses PermDock decides first | Storing or refreshing tokens |
describe | describe(decision) returning { kind, title, detail, alternatives } for tooltips and Problem Details | Logging; it is pure |
Snapshot | The JSON type of permdock.snapshot(); format major is the v field | A versioned type name (SnapshotV2); majors live in v |
parseSnapshot | Read snapshot JSON; rejects unknown majors and forbidden keys (__proto__, constructor, prototype) | Evaluating; it is a reader, not createPermDock |
fromSnapshot | Build a client PermDock from a Snapshot so portable grants evaluate locally | A second policy; closures stay on the server |
snapshotFor | The synchronous, deterministic snapshot builder for app-owned 'use cache: private' functions (snapshots) | A function that adds a cache directive, reads headers or awaits a source |
snapshotPromise | PermDockProvider prop on permdock/react: an unawaited Promise<Snapshot> from a Server Component; hooks answer pending until it resolves, or suspend through use() with suspend | Awaiting the snapshot in a layout |
cacheLifeFor | permdock/next: { stale } for cacheLife() from a snapshot's expiresAt and issuedAt, clamped to 30..300 seconds | A cache directive; the app calls cacheLife |
mayAccess | mayAccess(policy, user, permission, { tenant }): optimistic check for a proxy; false only when the declared roles provably lack the permission | A decision; the page still calls can / decide |
mayUse | mayUse(permdock, permission): whether some grant in the instance's snapshot could match for its subject, active tenant and delegation; the listing hint permdock/mcp, permdock/a2a and hosts with their own MCP server (better-supabase createMcp) use to filter tools | A decision; the call still runs can / decide |
approvalHeaders | { 'PermDock-Approval': token } for a retried mutation | Minting a token; it only wraps the one decide returned |
ApprovalStore, memoryApprovalStore, approvalsHandler, cancelApprovals | The pluggable store behind approval-required, its in-process default, the Fetch routes for approvers, and the helper that rejects pending requests for a session (permdock/approvals) | Deciding; a store never influences decide |
consume, consumedAt, consumeApproval, resumeDecision, storedApprovalToken | The atomic single-use step on ApprovalStore, the field it sets, and the helpers that apply a resume token, or find the stored one for a recomputed decision | used, redeemed, a consumed status |
ApprovalError, isApprovalError, assertApprover | The error (with a closed code list), its guard (matches by name and code, so a duplicated module copy still maps) and the approver check (by, escalation once open, no repeat) a custom ApprovalStore applies in resolve | Plain Error from a store, which approvalsHandler maps to 500 |
permdockApproval | The context key the ai-sdk and openai adapters read an explicit resume token from; every agent adapter also recomputes the token and finds the record in its store without it | approval, token (collide with application context) |
APPROVAL_META_KEY | 'dev.permdock/approval', the MCP request _meta key permdock/mcp reads a resume token from | A token in tool arguments (model-controlled) |
approval: 'human' or { by, distinct, staleOn, quorum, ttl, escalation } | Grant option that yields approval-required; by is a grantee selector; distinct (default true) refuses the principal as approver, and distinct: false is the explicit opt-out doctor PD024 reports; staleOn: 'resource-change' binds the approval to the row's version; quorum (default 1) is the number of distinct approvers; ttl is a duration that caps how long the request stays open; escalation: { after, to } widens the approvers after a duration | A fluent approver builder; allowSelf; approvers on the grant (that is the request's copy); threshold, minApprovals |
holder, anyOf, allOf, PermissionApprover, AnyOfApprover, approverPermissions, permdockFor, verdict.permissions | Approvers who hold a permission in the request tenant (custom roles included), an any-of group, an explicit all-of list, and the verdict facts that holder() is checked through | hasPermission(), oneOf, or() |
mode, stages, user, UserApprover, Approver | The approval mode ('any', 'all', 'sequential'), the ordered approver sets it takes, the approver for one named person, and the union of approver kinds | A workflow engine; steps, chain, levels, person |
approverRelations, approverRelationKey, relations (verdict) | The handler step that reads which relation approvers an approver satisfies, the key each one is stored under, and the verdict field that carries those keys to the store | Facts from a request body |
ApprovalEscalation, ApprovalSignature, approvals | The normalised escalation on a grant, one recorded approval { by, at }, and the request field that lists them oldest first | signatures, votes; a count without who gave it |
applyApprovalVerdict, approvalQuorum, escalationOpenAt | The helpers a custom ApprovalStore uses in resolve: the next request after a verdict (quorum and escalation applied), the approvers a request needs, and the instant its escalation opens | A store re-implementing the quorum rules |
vouchApproval, vouched | A verdict the application's own approval rules decided, recorded with the rule's name: it resolves the request at once without the approvers eligibility checks and keeps the separation checks | A verdict from a request body; a flag that also skips the actor and requester checks |
approver-repeated | The ApprovalError code for a principal who already approved the request; approvalsHandler maps it to 409 | duplicate-approval; counting the second approval |
version | The resource() option naming the row field (updatedAt, a revision) that a staleOn: 'resource-change' approval binds to; also on catalog resources | A schema version; the snapshot or catalog format major |
stale-approval | The denial reason for a resume token that approved an earlier version of the row | approval with a detail |
exclusiveWith | Role option listing roles that must not be held together; doctor PD018, separationConflicts and decideRoleChange (conflicting-role) | An evaluation deny |
DecisionSink, memorySink | The pluggable destination for on('decision') events and its in-process default; memorySink({ signer }) signs each write | Blocking a decision; sinks are fire-and-forget |
signDecisionBatch | Sign a batch of sink events as a permdock-decisions+jwt JWS | Feeding a signed batch back into decide; evidence only |
Capability, CapabilityInput, CapabilityRedeemer, parseCapability | The v1 share-link object { v, id, holder, on, roles, permissions?, redeemer?, once?, expiresAt }, the signCapability input (permission and resource references, not keys), who may redeem it ('anyone', 'signed-in', { user }, { scope, id }), and the strict own-property parser (link capabilities) | ShareLink, LinkToken, audience (that is the JWT aud) |
signCapability, capabilitySubject | Sign a capability as a permdock-capability+jwt; build the link subject a verified capability acts as | Issuing without a guard; a link that holds a scope or global role |
subjectFromCiOidc, CiOidcSubjectOptions, CiOidcPrincipal, CiOidcProvider | permdock/jwt: verify a CI job's OIDC token (provider: 'github' | 'gitlab' | 'buildkite', required audience, optional issuer and schema) and return a workload principal with repository, ref and environment; on('auth') source ci-oidc | A user principal from a CI token; a long-lived CI secret |
subjectFromCapability, CapabilitySubjectOptions, CapabilityFailureCause | permdock/jwt: verify a capability and return the link subject, never throwing; options issuer, audience, linkPolicy, revoked, replay, viewer; causes redeemer-mismatch, link-policy, capability-revoked, capability-replayed next to the token causes | Accepting a capability in subjectFromJwt; a capability as an access token |
LinkPrincipal, kind: 'link', via: 'link' | The principal a capability resolves to (its id is the link id, with the verified capability attached) and the via of its one resource membership | A user id; a principal a model or request body can name |
LinkPolicy, LinkPolicyViolation, linkPolicyViolation, linkPolicy | A scope instance's rules for links on its resources (maxLifetime, redeemers, once), the broken rule (lifetime, redeemer, once), the check, and the option on subjectFromCapability and signCapability that applies them | A rule that widens; a policy read from the token |
holder | 'link', or the reserved 'key' for a future stateless key the resolver refuses; API keys are opaque pdk_ keys, not capabilities | A third holder kind |
Credential, CredentialKind, CredentialPermission, parseCredential | The v1 record an API key stands for { v, id, kind, principal, tenant?, roles?, permissions, createdBy, createdAt, expiresAt?, name? }, 'user' or 'service', one { permission, ids? } entry, and the strict own-property parser (API keys) | ApiKey for the record; the secret or its hash in the record |
CredentialPrincipal, via: 'credential' | A principal resolved from a key, with the verified credential attached; the via of a service key's one tenant membership | A principal a request body can name |
credentialSubject, credentialDelegation | The subject a verified credential acts as (a user key's owner narrowed by delegation, a service key's service principal) and the delegation alone | Copying the owner's rights into the key |
decideCredential, CredentialRequest, CredentialPermissionInput, CredentialDecision, DecideCredentialOptions | Decide whether the subject may create a key: granted with the Credential to store, approval-required with a bound token (resume with approved), or denied; options settings, approved, now | Storing the key; a key that mints keys |
CredentialPolicy, CredentialPolicyViolation, credentialPolicyViolation | A tenant's API-key rules (maxTtl, kinds, approval, allowNoExpiry), the broken rule (kind, no-expiry, ttl) and the check | A rule that widens |
SettingsSource, TenantSettings, memorySettings, settings | Per-tenant settings (settingsFor(tenant) returns { credentials? }), its in-process default and the option on decideCredential and subjectFromApiKey | Settings that grant; a module-level settings object |
CredentialVerifier, apiKeyVerifier, memoryCredentials, verifier | { verify(key) → Credential | null }, the verifier over an application table (find(id) returns { credential, hash }), the optional touch(id, at) for lastUsedAt, the in-process store (issue, rotate, revoke, list, lastUsedAt) and the option naming a verifier | Verifying keys in core; scanning every row |
subjectFromApiKey, ApiKeySubjectOptions, ApiKeyFailureCause | permdock/server: a SubjectResolver for pdk_ keys, never throwing; options verifier, permissions, owner, revoked, settings, sink, sample; causes malformed, unknown-credential, invalid-claims, expired, credential-policy, credential-revoked, owner-unavailable | An API key in subjectFromJwt; a key as an actor |
generateApiKey, hashApiKey, parseApiKey, StoredCredential | A fresh pdk_<id>_<secret><checksum>, its base64url SHA-256, the { id, secret } split, and the { credential, hash } row a verifier reads | Storing the key itself |
CredentialEvent, credentialEvent, dev.permdock.credential | A credential sink event (created, used, rotated, revoked; used carries sample), its builder and CloudEvents type | A per-operation CloudEvents type |
exchangeCapability, ExchangeCapabilityOptions | permdock/supabase: a verified link subject in, a short-lived role: 'anon' Supabase access token with a capability claim out | service_role; a sub claim; a security-definer RPC per resource |
rls.anonymousSignIns | 'deny' keeps a Supabase token with is_anonymous: true out of every RLS branch but the anyone() grants' | A boolean; a separate role for anonymous sign-ins |
rls.anonExecute | true grants anon usage on the helper schema and execute on the helpers, for hand-written policies that apply to public or anon | A grant to service_role; a way to let anon read rows |
rls.tenants | 'active' (default) narrows the generated helpers and memberOf checks to the tenant claim when the token carries one; 'all' admits every tenant the subject is a member of, for apps whose tenant comes from the URL | A boolean such as narrowTenant; an organisation-slug header |
permdock_trusted_replace_custom_role_grants, permdock_trusted_rename_custom_role_grants, permdock_trusted_delete_custom_role_grants | The generated custom-role writes for a trusted caller (a migration, a job, a backend role granted execute): the definition checks without the caller checks; no client role may execute them | service_role grants in generated SQL; an admin or bypass flag on the member functions |
permdock_replace_global_roles | The generated rls.roles write that sets one user's global roles in one call, as the invoker so the assignment trigger still judges each row; execute for authenticated only | A security definer variant that skips the ceiling; replace_roles without the scope word |
rls.customRoleWrites.requires, manage-roles | The permissions (or 'manageRoles') a caller must hold before the generated custom-role writes run the hand-out check, and the hint when it holds none | A role name list; a check that only runs on save |
rls.customRoleWrites.roles, permdock_cascade_custom_role | The application's table of custom roles (table, key, tenant, scope, id, skip) and the generated trigger function that carries a role's grants through renames, moves and deletes of its row | A second store of custom role names; a trigger that writes without the write functions' checks for a signed-in caller |
rls.customRoles.from, --backfill-out, -- permdock:backfill v1 | The application's existing custom-role tables (permissions, includes) and the flag that writes the idempotent data migration copying them through the trusted replace function, with its marker line | import, adopt or sync for a one-off copy; a copy that overwrites a role edited since |
rls.triggers, rls.audit | Triggers the application owns on generated tables (name, when, events, level, function, args), and one call per generated table to an audit module that registers it by regclass | A permdock_ trigger name for an app trigger; a hand-written migration that has to run after the generated file |
rls.migrate.tables, refresh, --retire-out, -- permdock:retire v1 | The materialised permission tables rls migrate retires, the functions that refresh one, the flag that writes the drop migration once nothing reads them, and its marker line | cascade; drop, remove or cleanup for the flag |
rls.assignments, rls.assignments.ownRole, permdock_assignment_<table>, permdock_can_assign_custom_role, permdock_can_assign_custom_role_for | Assignment triggers on the membership tables, the global-roles table and listed tables such as invitations: a client role may write only the roles it may assign, by assigns or, for a custom role, by what it may hand out, and with ownRole: 'refuse' none on its own rows; other writers are trusted, and the _for form re-checks a stored user | A setting a client could flip to bypass the check; a trigger that trusts the role claim |
rls.ownershipTriggers | false, or { <scope>: false }, leaves out the holder-count and transfer-only triggers of min, max and transferOnly | A way to turn the rules off in decideRoleChange |
permdock_can_assign_any, permdock_can_assign_any_for | One assignment check for a declared or a custom role, (p_role, p_tenant, p_scope, p_scope_id), written when a role declares assigns; p_scope 'global' assigns a platform role, and the _for form takes the stored user first | A caller-side branch on whether a role is declared; a check that trusts the role claim |
rls.readOnlyActors | Restrictive policies, {table}_{op}_read_only_actors, that refuse writes from a token whose act.kind is a listed actor kind (true: support and impersonation) unless act.read_only is false | A permissive policy that widens access; a session flag a client could set |
rls.realtime.topics, permdock_realtime_<pattern>_<op> | Private Realtime channel policies on realtime.messages: a :-separated topic pattern with one {<scope>} segment, read (select) and write (insert) permission references | A policy keyed on the row's topic column; a public channel |
rls.storage.buckets, permdock_storage_<bucket>_<op> | Storage policies on storage.objects for one bucket: scope, folder (1-based, default 1), read, write (insert and update) and delete permission references | Owner-only policies on owner_id; a bucket-wide grant |
rls.apiKeys, permdock_api_key_allows(p_grant) | An API key passed in a claim (api_key by default) caps every allow at its scopes (permission keys), and a key with a tenant and no subject is a service principal holding roles (or serviceRoles) in that tenant | A wildcard scope; a key that widens its owner's rights; a key set by a client |
permdock_user_id() | The Supabase helper every generated helper, policy, field view and trigger reads the subject through: what auth.uid() returns, and null for an empty sub | auth.uid() in a policy a token with an empty sub reaches; a user id the caller passes |
permdock_session_live() | The Supabase helper a { subject: { session: { live: true } } } condition compiles to: the token's session_id is in auth.sessions for the caller and not past not_after | auth.uid() alone, which a revoked session's token still passes |
permdock_capability_ids, --capabilities, rls.capabilities | The RLS helper that reads the capability claim, and the flag and config key that emit it with one anon policy per resource-scoped grant | A policy that trusts a capability the server did not exchange |
store, sink | Options for an ApprovalStore and a DecisionSink; a SnapshotSource is the source option of permdock/react-native | Anything else; store is never a database handle |
policies | The option every adapter's createPermDock accepts for a PolicySource; read once per instance | A module-level policy the source swaps; a per-check fetch |
hostable | definePolicy option listing the permission subtrees or leaves hosted grants may touch; default none | hosted, editable, remote; a grant option |
PolicySource, memoryPolicySource, testPolicySource | The channel for hosted grants (current(), refresh()), its in-process default and its permdock/testing runner | A decision path; current() is synchronous and never fetches |
PolicyDocument, HostedGrant, parsePolicyDocument, mergeHostedGrants | The v: 1 hosted-grant document, one grant in it, its reader (rejects unknown majors and forbidden keys) and the pure merge createPermDock runs | PolicyDocumentV2; majors live in v |
HostedGrantRef, matched.hosted | { document, grant } on a grant and a Decision a hosted grant matched: the document fingerprint and the grant id | A role name; hosted grants still name declared roles |
hosted-grant-dropped, HostedGrantDropped | The on('error') value for a hosted grant that failed a merge rule; reason is not-hostable, unknown-permission, unknown-grantee, non-portable, weaker-approval or invalid | A denial reason; a dropped grant never reaches Decision |
CLOUD_EVENT_TYPES, CloudEventType, CatalogEventData | The closed CloudEvents type list (dev.permdock.decision, .approval, .directory, .membership, .catalog) and the catalog payload { kind, fingerprint, previous?, findings? } | A new type without a wire-format change |
CatalogFinding, CatalogFindingCode | One drift finding { code, permission, grant? }; code is permission-removed, not-hostable, grantee-removed or approval-tightened | Free-text finding lines |
coveredByDelegation | permdock: the delegation coverage check (scopes, RAR authorizationDetails, GNAP access) decide uses; returns undefined, no-delegation or not-delegated | isDelegated, checkScopes, a boolean that hides the reason |
delegations, DelegationInput, PolicyDelegation, DelegationTarget | The definePolicy option for standing delegations { from, to, permissions, validFrom?, validUntil? }, its input type, the normalised entry { from, to: { kind, id? }, permissions: string[], validity? } on policy.delegations, and the actor it is for | agents, grantsToActors; a grant with an actor grantee (that gives the actor access of its own) |
delegatedPermissions, delegated | permdock: the ceiling of permission keys the policy's delegations give the subject's actor right now (undefined when none applies), and the snapshot field carrying it sorted | delegationScopes (these are permission keys, not OAuth scopes); a field on the subject |
delegation-removed, delegation-narrowed | permdock diff breaking kinds for a policy delegation that disappeared or lost permission keys or validity | delegation-changed for a narrowing |
catalogFingerprint | permdock: the catalog v1 fingerprint (base64url SHA-256 of canonical JSON without generatedAt, generator, fingerprint and usages) the CLI writes and the Cloud recomputes | A hash of the pretty-printed file; a CLI-only helper |
WireDenial, WireDecision | permdock: a denial as it leaves the process ({ role, reason, to?, detail? }, detail a JSON value, never a closure cause or validation error) in decision events, Problem Details, MCP and WebMCP refusals and the decision endpoint; WireDecision is a Decision with those denials | SerializedDenial, PublicDenial; sending Denial as is |
toCsvRow, CSV_COLUMNS | permdock: one RFC 4180 CSV row for a decision event and the pinned header (time, principal, actor, tenant, permission, outcome, matched.role, via, denials.reason, token) | A configurable column list; a Cloud-only exporter |
toOcsf, OCSF_VERSION, OcsfAuthorizeSession | permdock: the pure projection of a decision event onto OCSF Authorize Session, pinned to one OCSF version | A second, Cloud-only projection |
verifyWebhook, parseCloudEvent, PermDockCloudEvent | permdock/cloud: verify a signed Cloud webhook delivery (never throws, no unsigned mode) and validate one CloudEvent | A shared-secret or unsigned webhook mode |
PermDockEnv | permdock/hono: the Env type the permdock() and protect() middleware declare, so c.var.permdock and c.var.permdockData are typed | A declare module augmentation of ContextVariableMap |
memberships, customRoles, tenant | The three tenancy options every adapter's createPermDock accepts: a MembershipSource, a RoleSource, and how the active tenant is resolved from the request | Reading a tenant from an unsigned header or a model argument |
cloud | cloud({ url, key, environment, verifier }) from permdock/cloud, returning approvals, sink, snapshots, policies, the environment URL as issuer and its jwks URL | A factory for a PermDock; there is no decide on it; an app audience option |
cloudEndpoints | cloudEndpoints({ url?, environment? }) from permdock/cloud: the environment URL (issuer) and JWK Set URL (jwks) with cloud()'s fallbacks, for building a verifier before cloud() | A network call; discovery |
scimHandler | The RFC 7644 Fetch handler from permdock/scim writing into a DirectoryStore (SCIM adapter) | A PermDock; it decides nothing and reads no tenant from a body |
DirectoryStore, memoryDirectoryStore | The repository interface SCIM provisioning writes to (users, groups, groupsFor), every method tenant-first, and its in-process default | A MembershipSource; the store is the write side, the source below is the read side |
directoryMembershipSource | directoryMembershipSource(store): the MembershipSource that turns synced groups into tenant memberships { tenant, roles, via: 'group:<id>' } and yields nothing for an inactive user | Reading display names; group ids only |
ReplayStore, memoryReplayStore | The pluggable jti replay store for permdock/ssf and its in-process default | A decision input; SETs and logout_tokens never reach decide |
connection, Connection, ConnectionOptions | The kernel and HTTP adapter method that opens a long-lived connection for a stream or socket, and its frozen result (permdock, signal, check, filter, close) | A mutable instance; stream, socket or subscribe as the kernel method name |
RevocationFeed, RevocationEvent, memoryRevocationFeed | The interface that tells open connections a subject changed (subscribe, revoke; events session-revoked and changed) and its in-process default | A decision input; a feed ends or revalidates, never grants |
revocations | The option carrying a RevocationFeed on the kernel, the HTTP adapters, permdock/ssf and permdock/scim | revocationFeed, feed |
PermDockRevokedError, RevokedCode | The reason of an aborted connection signal; code is session-revoked, expired, denied or subject-changed | A denial reason; a check after the abort is no-grant with detail: 'connection-revoked' |
socket, sse, SseOptions | permdock/hono helpers: socket(conn, events) wraps upgradeWebSocket events, sse(conn, stream, source, options) writes an async iterable to streamSSE | A with* or Dock name; helpers for other adapters |
ElysiaSocket, NestSocket | The structural socket types connection accepts in permdock/elysia and permdock/nest | An import of the framework's socket class |
ApprovalListQuery, ApprovalPage | The ApprovalStore.list argument (ApprovalListFilter plus limit and cursor) and its result (items, next) | An offset, or a page number |
ApprovalHint | { at?, hint? }: the approval option on the server kernel, HTTP adapters and permdock/terminal, emitted as the approval member of approval-required Problem Details | A terminal-only field, a URL built from the token by PermDock |
KeyringEntry, storage.keyring | The Entry shape from @napi-rs/keyring and the permdock/terminal storage option that takes it | A bundled keychain binding, keytar |
remotePdp | permdock/pdp helper that implements DecisionProvider over an AuthZEN evaluation URL | A local decide; unknown or unreachable remote answers deny |
openfga, spicedb, RelationMap | permdock/pdp presets that implement DecisionProvider over OpenFGA check / list-objects and the SpiceDB HTTP gateway CheckPermission / LookupResources, and their one-callback-per-permission tuple map | A tuple store, a model DSL or a per-vendor entry |
DecisionProvider.permitted | The optional listing member: the ids of one resource type a subject may act on, used by the PDP instance's filter and where | A grant; null denies every row |
PermDock-Approval | The HTTP request header carrying an approval token on a retried request | Anything else on the wire |
membershipEvent | Pure builder for a membership sink event | A Decision; it is audit only |
Strings appear only as .key and .scope on the wire, in audit events and in catalogs. The public API takes references.
The subjectFrom<Provider> pattern names the source of the material (a JWT, a Supabase claims object, a Clerk auth object, a Better Auth session, MCP authInfo), always returns a Subject (anonymous when the material cannot be trusted) and never throws, so every provider reads the same way at the call site: createPermDock(policy, subjectFromClerk(await auth())). See Authentication and PermDock.
Reserved words
These never appear as public identifiers, for the reasons given:
| Word | Why it is reserved |
|---|---|
dock | It reads oddly next to PermDock; only permdock is used for the instance. |
ability | CASL's noun; using it would imply CASL semantics (subject detection, manage/all) that PermDock does not have. |
can as a definer | CASL uses can both to define and to check rules, which its own cookbook documents as confusing (less-confusing-can-api). PermDock defines with allow / deny and checks with can. |
$-prefixed members | Kilpi prefixes every instance member with $ to avoid clashing with policy names in a Proxy tree. PermDock keeps methods on the instance and the tree separate, so no prefix is needed. |
Can, Guard, Access | Component names used by CASL and Kilpi; the PermDock gate is <Protected>. The only other component is <PermissionBoundary> in permdock/next/client, permdock/vue, permdock/svelte and permdock/solid: an error boundary for thrown PermDock errors, never a gate. |
org, organization, workspace, account, group as PermDock identifiers | Every provider picks a different noun for the same thing. PermDock uses tenant and team in its own API and lets the subjectFrom* mapper translate (Clerk organization to tenant, SCIM group to team). |
useOrganization, useTeam, TenantProvider | The tenant is a property of the one snapshot, not a second provider; useTenant and useMemberships read it. |
hasRole, isAdmin, roleGuard | Role checks in application code bypass the permission model; check a permission, and read roles only for display (useRoles) or assignment (useAssignableRoles, useAssignablePermissions). |
who | Reserved for a directory-backed "who may X this row" verb; not exported. |
SnapshotV2 | The format major lives in v; the type is Snapshot. |
scope(...) as a derived instance | Derived instances are tenant(id) and team(id), one method per scope kind. |
permdock.with(...) | with* names the Supabase middleware pipeline steps, withSubject and withOtel; an instance with other sources is permdock.derive({ customRoles, approvalPolicies, relations }). |
@permdock/cli, @permdock/testing, any @permdock/* package | permdock is the only published name, owned by the scaledockhq npm organisation. The CLI is its bin plus permdock/cli, permdock/unplugin and permdock/next/plugin; the runners are permdock/testing. One package keeps core, adapters, CLI and runners on the same version. |
permdock/cloud-auth | There is no such entry: PermDock Cloud identity is subjectFromJwt({ discovery }) against the Cloud issuer. |
What each adapter's createPermDock returns
| Import path | Returns | Notes |
|---|---|---|
permdock | PermDock | await createPermDock(policy, user, { tenant?, memberships?, customRoles?, actor?, delegation? }); sync when the policy declares no context and no async source |
permdock/next | { getPermDock, getPermission, getSnapshot, requireAccess, PermDockProvider, permdockHandler }, plus the standalone cacheLifeFor(snapshot, { min, max, now }), snapshotTag and snapshotHeaders shared with permdock/server | Options: subject, tenant, memberships, customRoles, store, sink, otel, wrap; never a cache directive (getSnapshot sets cacheLife and cacheTag inside the app's 'use cache: private' function, and its tags add to snapshotTag); requireAccess, not requirePermission or authorize, because it maps a decision to the forbidden() / unauthorized() interrupts |
permdock/next/client | PermissionBoundary (props denied, approval: a node or a PermissionBoundaryFallback function of the state) and usePermissionBoundary() returning { outcome, permission, token?, retry } | A 'use client' entry built on catchError from next/error; the boundary never decides, it renders a fallback for an error a check already threw |
permdock/server | { permdock, getSnapshot, protect, problem, openapi, permdockHandler }, plus the standalone problemFromError(error), snapshotHeaders(snapshot, { tags }), cacheLifeFor and snapshotTag | The Fetch kernel every HTTP adapter wraps. Options: subject, tenant, memberships, customRoles, store, sink, limits, pdp, otel, webBotAuth, actor, and InstanceOptions (approvalPolicies, relations, entitlements, policies) |
permdock/hono | { permdock, protect, permdockHandler } | permdock() is middleware; protect(permission, load?, options?) guards a route |
permdock/express, permdock/fastify, permdock/elysia, permdock/node | { permdock, protect, permdockHandler }, plus errorHandler and withPermDock(fn) (Express) and send (Node) | Same shape as Hono over the shared kernel |
permdock/nest | { PermDockModule, PermDockGuard, Protect, InjectPermDock, permdockHandler }, plus decorateMethod | PermDockModule.forRoot({ guard: 'global' }) registers PermDockGuard as APP_GUARD; permdockHandler({ path }) sets the controller path |
permdock/terminal | { permdock, protect, filterCommands, format, exitCode }; EX_OK, EX_USAGE, EX_TEMPFAIL, EX_NOPERM, EX_CONFIG; options yes and dryRun (--yes / -y, --dry-run), interactive: { confirm, typed } | For your own CLI; protect wraps a command action, filterCommands hides or annotates denied commands. Not the permdock binary or permdock/cli |
permdock/trpc | { permdock, protect, permdockHandler } | protect is procedure middleware; denials throw TRPCError |
permdock/orpc | { permdock, protect, permdockHandler }, permissionOf | protect is procedure middleware; denials throw ORPCError through the contract's declared constructor when it has one |
permdock/mcp | { protectServer } | protectServer(server).registerTool(name, { permission, data?, longRunning?, ... }, handler), plus registerResource and registerPrompt; protectServer(server, { enforce: 'procedure', permissionFor }) lists by permission and leaves the decision to the procedure; options: subject, requireAuthInfo, resource (RFC 8707), store, approval ({ at, hint }), stepUp ({ at }), requestState (an SDK createRequestStateCodec); also exports subjectFromMcp, APPROVAL_META_KEY, McpPrincipal |
permdock/ai-sdk | { toolApproval, capabilityMiddleware, needsApproval } | Options: subject, actor, tools |
permdock/claude-agent | { canUseTool, permissionRequestHook, permdock } | Options: subject, tools, store, mcpSources (trusted MCP server sources, default ['sdk']) |
permdock/eve | { approval, approvalFor, permdock } | approval is Eve's { request, response } pair for defineTool (EveApprovalPair, contexts EveApprovalContext and EveResponseContext, principals EvePrincipal); options: tools, store, approvers (EveApprovers); also exports subjectFromSession, actorFromSession, rolesOf |
permdock/openai | { needsApproval, guardTools, resolveInterruptions, permdock } | Options: subject, actor, tools, store |
permdock/authzen | { permdockHandler } | Serves evaluation, evaluations, search and .well-known |
permdock/ssf | { receiver } | RFC 8935 push and RFC 8936 poll receiver for CAEP events; receiver.logout for OIDC Back-Channel Logout |
permdock/pdp | { createPermDock } wrapping a local policy plus optional remotePdp | Opt-in AuthZEN client; fail-closed on unknown remote decisions |
permdock/convex | { withPermDock, snapshotQuery } | Convex query/mutation wrappers; no Convex SDK peer (structurally typed); options subject, tenant, actor |
permdock/scim | no createPermDock; exports scimHandler, memoryDirectoryStore, directoryMembershipSource and the DirectoryStore type | The handler is a Fetch handler you mount; the source is a value you pass as memberships to a factory |
permdock/a2a | { agentCard, extendedAgentCard, protectSkill, sign } | Options: skills |
permdock/otel | no factory; instrument / withOtel | Subscribes to on('decision'); optional @opentelemetry/api |
These entries deliberately do not export createPermDock:
permdock/reactandpermdock/react-nativeexportPermDockProvider,usePermDock,usePermission,usePermissions,useFilter,useTenant,useMemberships,useRoles,useAssignableRoles,useAssignablePermissions,useApproval,useSubject,useDescribeand<Protected>directly.permdock/react-nativeaddssubjectId(required,nullwhen signed out), the storage wrappersmemoryStorage,secureStoreStorageandmmkvStorage, the subscriptionsappStateForegroundandnetInfoOnline, the Expo Router guardsusePermissionGuardanduseSnapshotReady, and the snapshot sourceslocalSnapshotandpowersyncSource.permdock/vueexportspermdockPluginplus the same composable names and<Protected>.permdock/svelteandpermdock/solidkeep the same surface under each framework's idiom.permdock/better-auth,permdock/clerkandpermdock/supabaseexport theirsubjectFrom*mapper and, where the provider stores roles, a<provider>RoleSource; both are values you pass to a factory.permdock/webmcpexportsregisterTools(document.modelContext, group, { permdock })because it consumes a clientPermDockrather than creating one.permdock/drizzle,permdock/prismaandpermdock/kyselyexporttoWhere.permdock/prismaalso exportspermdockExtension,resolveRelated,toPredicateandprismaModelFields. All three also exportwithSubjectandcheckRow.permdock/jwtexportssubjectFromJwt,createJwtSubjectResolver,verifyDpopProof,joseTokenVerifierandjoseTokenSignerbecause it produces subjects, verifiers and signers for a factory, not an instance. Structurally typed providers (permdock/supabase,permdock/clerk,permdock/better-auth,permdock/convex,permdock/prisma,permdock/mcp,permdock/react-native) declare no peer dependency: they duck-type the SDK you already installed.permdock/supabase/middlewareis the exception on the Supabase side: it callsdefineMiddleware, so@supabase/middlewareis its optional peer, while@supabase/server'sJWTClaimsis still duck-typed.permdock/approvalsexportsmemoryApprovalStoreandapprovalsHandler, andpermdock/cloudexportscloud; both produce values you pass to a factory (store,sink,policies) or a client provider (source) rather than aPermDock.permdock/testing,permdock/testing/saasandpermdock/testing/saas/permissionsexport the test runners (describePolicy, thetest<Interface>runners,rlsParity,ormParity,testClientParity,testHttpAdapter,testAuthZen) and the shared SaaS domain. Test files import them; application entries never do, and Vitest is an optional peer.permdock/cliexportsdefineConfig(forpermdock.config.ts) andrun(thepermdockbinary as a function) plus the config types, andparseHookMarker/parseGrantsMarkerwith their result typesSupabaseHookMarker/SupabaseGrantsMarker. It is a build-time entry: no runtime entry imports it.- Build hooks:
permdock/next/pluginexportscreatePermDockPluginandpermdock/unpluginexportscreatePermDockUnplugin(Vite, Rollup, webpack, Rspack, esbuild through unplugin). Both runcollectat build time and never wire runtime API. Thecreateprefix and thePermDocknoun follow the factory rule; the suffix names the host (Pluginfor Next's config API,Unpluginfor the bundler family).
Names shared on purpose
PermDockmethods and snapshot fields share a name when the field is the method's result serialized:permdock.actions(resource)andsnapshot.actions,permdock.audiences()andsnapshot.audiences,permdock.snapshot()and asnapshotoption or prop,permdock.tenant(id)andprincipal.tenant. The method is on the instance; the field is the wire form a client hydrates from.- One name can be exported by several entries with that entry's meaning, like
createPermDock.PermDockPluginOptionsis the build plugin's options inpermdock/next/pluginandpermdock/unplugin, and the Vue app plugin's options inpermdock/vue. - Every adapter returns
<Adapter>PermDockfromcreatePermDockand takes<Adapter>PermDockOptions, rather than<Prefix><Thing>, so the adapter is the first word wherever both appear (recorded in the repository'sdocs/decisions/0019-adapter-suffix-and-no-dollar-members.md).
Spec names
PermDock's subject vocabulary is TypeScript, camelCase and typed; the specifications it consumes are JSON claims with short registered names. subject fixes the rule: where a spec name exists, PermDock either uses it verbatim or maps to it exactly once, in the table below and on subject; PermDock never coins a second spelling for a concept a specification already names.
| Specification name | Source | PermDock name | Rule |
|---|---|---|---|
sub | RFC 7519, OIDC Core | principal.id | Opaque string, compared byte for byte |
iss | RFC 7519, OIDC Core | principal.issuer | Always set by subjectFrom*; identity is issuer + id |
aud | RFC 7519 | audience option | Verified, not stored |
exp | RFC 7519 | subject.expiresAt, Membership.expiresAt, ApprovalRequest.expiresAt, snapshot expiresAt | NumericDate (Unix seconds) everywhere, the same unit as exp; the JWS envelope's exp copies it |
iat, nbf, jti | RFC 7519 | none on the subject; iat, jti on signed outputs | Verified, then dropped |
act | RFC 8693 | actor | Innermost act.sub is actor.id; the nesting is delegation.chain |
client_id, azp | RFC 9068, OIDC Core | actor.id when the client acts under a delegation; verified otherwise | actor.kind 'oauth-client' or 'mcp-client' |
Signature-Agent keyid | Web Bot Auth / RFC 9421 | actor.id when webBotAuth is set | actor.kind 'web-bot-auth' |
scope | RFC 6749, RFC 9068 | delegation.scopes; permission.scope | Space-separated string on the wire, array in TypeScript |
authorization_details | RFC 9396 | delegation.authorizationDetails; permission.authorizationDetails | One RAR type per permission |
access | RFC 9635, RFC 9767 | delegation.access | Objects and reference strings kept verbatim |
cnf (jkt, x5t#S256, jwk, kid) | RFC 7800, RFC 9449, RFC 8705 | binding with the same member names (reshaped) | FAPI's "sender-constrained" gives the field its name; the members are the cnf members so it round-trips |
acr, amr, auth_time | OIDC Core, RFC 8176 | principal.assurance.acr, .amr, .authTime | Provider aal maps into acr; RFC 8176 amr values include pwd, otp, swk, hwk, mfa |
verified_claims, verification, trust_framework, claims | OpenID Connect for Identity Assurance 1.0 | principal.assurance.verified[] with verification and claims kept under the spec's names | The collection is verified because assurance already scopes it; the fields inside keep the spec's snake case |
aal (aal1, aal2) | Supabase Auth JWT | principal.assurance.acr | Verified by getClaims(); aal2 is MFA |
fva / factorVerificationAge | Clerk session token | Recipe into principal.assurance; not mapped by subjectFromClerk | Second element >= 0 means a second factor was verified |
twoFactorEnabled | Better Auth user row | Recipe into principal.assurance; not mapped by subjectFromBetterAuth | A session exists only after the two-factor plugin verifies |
sid | OIDC Front- and Back-Channel Logout | subject.session | The join key for logout_token and CAEP events |
roles, groups, entitlements | RFC 9068 | principal.roles; principal.memberships (team from groups); principal.plans from entitlements | SCIM value only |
userName, externalId, active, displayName (SCIM User) | RFC 7643 | DirectoryUser.userName, .externalId, .active, .displayName verbatim | externalId is the IdP's identifier and id is the store's; active: false yields no memberships; displayName is never an identifier |
members[].value, displayName (SCIM Group) | RFC 7643 | DirectoryGroup.members[].value verbatim; via: 'group:<id>' on the membership | Group id is the group identifier; display and displayName are labels only |
urn:permdock:scim:schemas:extension:roles:1.0 (roles) | RFC 7643 section 3.3 extension | The Group extension carrying the declared role names for a group; read by directoryMembershipSource | Unknown or non-assignable role names are dropped, never invented |
type, source, subject (CloudEvents) | CloudEvents 1.0 | dev.permdock.decision, dev.permdock.approval, dev.permdock.directory, dev.permdock.membership, dev.permdock.catalog, dev.permdock.access.started / .ended / .revoked; the emitting service; the permission key, resource id or principal id | The envelope for every webhook and queue sink (wire formats) |
jwks_uri, issuer (Discovery) | OIDC Discovery, RFC 8414 | discovery option; jwks and issuer options | Discovery supplies both |
typ (at+jwt, JWT, logout+jwt, secevent+jwt) | RFC 9068, RFC 7519, OIDC, RFC 8417 | accept option; permdock-snapshot+jwt, permdock-approval+jwt, permdock-decisions+jwt, permdock-policy+jwt, permdock-capability+jwt on outputs | Explicit typing, RFC 8725 section 3.11 |
invalid_token, insufficient_scope, insufficient_user_authentication | RFC 6750, RFC 9470 | on('auth') reason invalid-token; Decision reasons not-delegated / no-delegation and insufficient-user-authentication | Hyphenated in PermDock, underscored on the wire; the HTTP adapter renders without a second table |
Subject, Resource, Action, Context | AuthZEN 1.0 | principal (+ actor, delegation, tenant under context), resource, action, context | The evaluation mapping on AuthZEN |
Files and folders
- The server factory lives in
src/permdock/server.tsin every example. The definition issrc/permissions.ts; the policy issrc/policy.ts; generated definitions aresrc/permissions.generated.ts. - Example apps are
apps/examples/<adapter>; the docs page is/docs/adapters/<adapter>; the source folder ispackages/permdock/src/<adapter>. The same short name is used in all three. - Errors are
PermDockDeniedError,PermDockApprovalRequiredErrorandPermDockValidationError. Problem DetailstypeURIs end in/denied,/approval-requiredand/validation. The first two carrydigest(React's name for the property that survives the Server Component boundary):PERMDOCK_DENIED;<permission>andPERMDOCK_APPROVAL_REQUIRED;<permission>;<token>, read withparsePermDockDigest.
Renamed before 0.1.0
| Old | New |
|---|---|
CreatePermDockOptions | PermDockOptions |
CreatePermDockPluginOptions | PermDockPluginOptions |
ServerPermDock.handler, AuthzenPermDock.handler | permdockHandler, like every other HTTP adapter |
ExpressPermDock.handler(fn) | withPermDock(fn), like permdock/convex |
SsfAdapter, SsfOptions | SsfPermDock, SsfPermDockOptions |
SupabaseMiddlewareOptions | SupabaseMiddlewarePermDockOptions |
A2AAgentCard, A2APermDock and the other A2A* types | A2aAgentCard, A2aPermDock, … (A2a like Ssf and Mcp) |
dock, pd, server, factory for the instance in examples | permdock; a cloud() result is permdockCloud |
Last updated on
Existing apps
Adopt PermDock in an app that already has permission keys, SQL helpers, tokens and stored custom roles, then move to its conventions one step at a time without breaking signed-in users.
Devtools
A docs-site panel for exploring decide and describe against a canned policy. Apps do not get a PermDock component beyond Protected.