PermDock
Concepts

Operations

defineOperations binds each action an application exposes to one permission leaf, with a data loader, a results permission and risk meta. Agent tools, automations, chat commands and approval prompts read the same frozen table by name.

defineOperations(permissions, definitions) names the actions an application exposes and binds each to one permission. Agent tools, automations, chat commands and approval prompts read the same table instead of each keeping its own.

import { defineOperations } from "permdock";

export const operations = defineOperations(permissions, {
  sendInvoice: {
    permission: permissions.billing.invoice.submit,
    data: (input: { id: string }) => db.invoice.find(input.id),
    meta: {
      risk: "high",
      effects: ["send", "external"],
      title: "Send invoice",
    },
  },
  listInvoices: {
    permission: permissions.billing.invoice.read,
    results: permissions.billing.invoice.read,
  },
});

It returns a frozen OperationMap. Each entry is a frozen Operation { name, permission, data?, results?, meta }:

FieldMeaning
permissionThe leaf a caller must hold to run the operation
dataLoads the row the check runs on from the input; OperationInput<typeof operations.sendInvoice> is its parameter type
resultsThe leaf each result row is filtered by
meta{ risk?, effects?, title? }; {} when absent

risk is one of low, medium, high, critical, exported in that order as OPERATION_RISKS with the guard isOperationRisk. effects lists distinct values of read, write, delete, send, spend, external. A host reads meta to label, confirm or gate an operation; PermDock never decides on it.

defineOperations throws at definition time for:

  • a permission or results outside permissions (matched by key);
  • a data that is not a function;
  • meta outside those lists;
  • a name that is not 1 to 128 of A-Z a-z 0-9 _ . -, or that repeats another name case-insensitively.

Where operations are read

ReaderWhat it takes from an operation
AgentsdecideOperation(name, input, ctx) loads the row through data and decides permission; filterResults applies results
AutomationsAn automation lists the operations it runs; a run is narrowed to their permissions
Chatprotect(operation, handler) decides a chat command as its author
DeliveryapprovalPrompt(request, { operation }) puts meta.title and meta.risk in the approval email or card
Catalogpermdock collect writes { name, permission, results?, meta } with permission keys when permdock.config.ts names an operations module

With operations: "./src/operations.ts" in permdock.config.ts, permdock collect writes the map to the catalog as operations. Doctor PD067 reports an operation whose permission or results key the configured permissions module does not declare.

Why

  • One table per application. The same action reached from REST, MCP, an AI tool, an automation and a chat command otherwise gets a lookup table per entry point, and they drift apart.
  • An operation names a leaf. Operations are not a second permission vocabulary. Grants, snapshots, RLS and the catalog stay keyed by permission, and an operation only says which leaf an action needs and how to load its row.
  • MCP tool-name characters. An operation name becomes a tool name unchanged, so it uses the characters MCP 2025-11-25 recommends for tool names. Names that differ only in case are refused because chat commands and some tool hosts compare names case-insensitively.
  • Closed meta lists. A host can only gate on risk and effects if every operation uses the same words, so an unknown value throws instead of passing through.

Last updated on

On this page