Arazzo workflows
How an Arazzo 1.1.0 workflow is an agent plan, and how permdock simulate pre-flights every step by resolving its operationId to the operation's x-permdock-permissions and returning one Decision per step before anything executes.
What it is
The Arazzo Specification 1.1.0 (released 17 May 2026, announcement) describes workflows: sequences of API calls with dependencies between them, inputs, outputs and success criteria. An Arazzo document has:
| Field | Meaning |
|---|---|
arazzo | The Arazzo specification version, 1.1.0 |
info | Title and version of the workflow document |
sourceDescriptions | The API descriptions the workflows call into: type: openapi, type: arazzo (another workflow document) and, since 1.1, type: asyncapi |
workflows | Named workflows, each with inputs, ordered steps, outputs and success criteria |
steps[] | Each step points at one operationId or operationPath in a source description, or at another workflowId; it declares parameters, requestBody, successCriteria, outputs and what to do on success or failure |
1.1 adds AsyncAPI sources (send / receive style steps with correlation), typed parameters on actions that call another workflowId, and better data selection (Selector Object, JSONPath). 1.0.x documents remain valid.
Why it matters for PermDock
An Arazzo workflow is a plan: an ordered list of operations somebody intends to call, with the data flowing between them. That is exactly what an agent produces before it acts, and exactly what permdock.simulate was designed to evaluate (decisions, for AI agents). Today simulate takes an array of [permission, data] pairs the caller assembles by hand. An Arazzo document assembles them for free: each step names an operationId, the referenced OpenAPI description carries x-permdock-permissions on that operation (OpenAPI), and the permission keys resolve to leaves in the catalog.
The result is a pre-flight for a whole workflow, not a single call: before an agent (or a workflow runner, or a CI job) executes step one, PermDock reports which steps would be granted, which denied and with what alternatives, and which would stop at approval-required, so the approval can be requested up-front instead of halfway through a plan with side effects already committed. This is the OWASP ASI02 "bound the plan before the first side effect" control applied to a standard workflow format (OWASP agentic).
How PermDock uses it
A workflow is an agent plan
import { readFile } from "node:fs/promises";
import { PermDockDeniedError } from "permdock";
import { createPermDock } from "permdock/node";
import { policy } from "./policy";
const arazzo = JSON.parse(
await readFile("./workflows/publish-post.arazzo.json", "utf8"),
);
const openapi = JSON.parse(await readFile("./openapi.json", "utf8")); // after the Overlay is applied
const permdock = await createPermDock(policy, user);
const plan = permdock.simulate({ arazzo, openapi, workflowId: "publishPost" });
// {
// workflowId: 'publishPost',
// outcome: 'approval-required', // worst outcome across steps: denied > approval-required > granted
// steps: [
// { stepId: 'loadDraft', operationId: 'getPost', permissions: [permissions.post.read], decision: { outcome: 'granted', ... } },
// { stepId: 'editDraft', operationId: 'updatePost', permissions: [permissions.post.update], decision: { outcome: 'granted', ... } },
// { stepId: 'publish', operationId: 'publishPost', permissions: [permissions.post.publish], decision: { outcome: 'approval-required', reason: 'human', ... } },
// ],
// }
const blocked = plan.steps.filter((s) => s.decision.outcome !== "granted");
if (blocked.some((s) => s.decision.outcome === "denied"))
throw new PermDockDeniedError(blocked[0].decision);
for (const step of blocked) await requestApproval(step); // approval-required steps, before step one runsWhat simulate({ arazzo, openapi }) does, step by step (subject is the instance's, Arazzo):
- Validates the Arazzo document against the fields the 1.0 and 1.1 schemas require: an
arazzoversion of1.0.xor1.1.x,info, at least one source description with anameandurl, at least one workflow, and at least one step per workflow. Each step needs astepIdthat is unique in its workflow and exactly one ofoperationId,operationPathorworkflowId, and each parameter needs anameand avalueor a resolvable$components.parametersreference. A document that fails any of these produces onedocumentstep denied with reasonvalidation. Theopenapiargument supplies the resolved description for thetype: openapisources: one description, or a map keyed by sourcenamefor a workflow with several. - For each step of the selected workflow, resolves
operationId(oroperationPath) to an operation in the description and reads itsx-permdock-permissions. A qualified$sourceDescriptions.<name>.<operationId>and anoperationPathof the form{$sourceDescriptions.<name>.url}#/paths/...select their source by name. A plainoperationIdmust match exactly one source; one that matches several isundocumented, as the spec requires the qualified form there. Steps that reference a localworkflowIdare expanded recursively, with the calling step'sparametersas the nested workflow'sinputs; cycles and unknown workflows are avalidationerror, and aworkflowIdin another Arazzo document isunsupported. A nested step's result carries theworkflowIdthat owns it, so equalstepIds in different workflows stay separate. - Each permission key is looked up with
findPermissionin the catalog. An operation with nox-permdock-permissionsproduces a step withpermissions: []anddecision.outcome: 'denied'with reasonundocumented; a workflow cannot be cleared through a hole in the description (fail closed). - For
actionspermissions (those that take an instance), the workflowinputs, the workflow-levelparametersand the step'sparameters(the step's win on a name clash) build the resource data. A$inputs.<path>value is read from the inputs. Any other runtime expression, such as an earlier step's$steps.<id>.outputs, or an input that is absent, cannot be known ahead of time: the decision is evaluated without that value and markedprovisional: true, meaningprotectwill decide again at execution time with the real row. - All
[permission, data]pairs are evaluated with thesimulatebatch semantics: noon('decision')events per step, no approval tokens issued, no quota consumed. Onesimulateevent is emitted for the plan, carrying the worst step decision andcountsof granted, denied and approval-required checks. - The result lists one
Decisionper step in workflow order plus the worstoutcomeacross steps.
Where the documents come from
simulate reads the applied description: the producer's output with PermDock's Overlay merged, not the source before it. Without the Overlay no operation carries x-permdock-permissions and every step is denied with reason undocumented (OpenAPI Overlay). Arazzo documents themselves come from wherever the team writes them; two producers worth naming because they share the operationId join key with PermDock:
- next-openapi-gen compiles an
arazzoblock into workflow files after the spec write, against the generatedoperationIds, and applies Overlay files first, so its output is already the applied description. - Redocly CLI's
generate-arazzodrafts a workflow from an OpenAPI description andrespectexecutes one (commands);simulatesits between the two as the authorization pre-flight.
PermDock composes with these rather than wrapping them; none is a dependency.
Surfacing approval-required steps
An approval-required step tells the caller that executing it will stop for a human (or a policy-defined approver) unless approval is obtained first. Because simulate issues no tokens, the caller uses the step list to request approval before running the workflow; the approval flow and the binding of an approval token to permission key, resource id, subject and actor are described on approvals. There is no plan-level token: each approval-required step yields its own approval request, a reviewer may approve them together, and each step is still re-checked at execution. The step's decision carries what to ask for.
permdock arazzo check
A CLI command that runs the resolution half of simulate without a subject (CLI: arazzo):
permdock arazzo check --doc workflows/publish-post.arazzo.json --openapi openapi.jsonIt fails (exit 1) when a step's operationId does not exist in the referenced description, when the operation has no x-permdock-permissions, or when a key does not exist in the catalog. It is the workflow counterpart of permdock openapi --check: the document says what each call requires, and the workflow must only call documented operations.
Out of scope
- AsyncAPI steps. Arazzo 1.1 allows
sourceDescriptionsoftype: asyncapiand send / receive style steps (anoperationIdin an AsyncAPI source, or achannelPath). PermDock has no AsyncAPI adapter and does not emitx-permdock-permissionsinto AsyncAPI documents, so those steps are reported asunsupportedand the plan's outcome isdenied. - Workflows in other Arazzo documents. A step whose
workflowIdpoints into atype: arazzosource isunsupported; resolve it into the document first. - Executing workflows. PermDock pre-flights and guards; it does not run steps. Runners keep calling the API, where
protectdecides for real. - Import from Arazzo. Generating permission definitions from a workflow document is not planned; permissions come from the OpenAPI description or the catalog (CLI: openapi).
Mapping table
| Arazzo concept | PermDock concept |
|---|---|
| Workflow | A plan passed to simulate({ arazzo, openapi }) on the instance |
Step with operationId / operationPath | One [permission, data] entry per key in the operation's x-permdock-permissions |
Step with workflowId | Expanded into that workflow's steps, its parameters becoming that workflow's inputs |
sourceDescriptions[type=openapi] | The openapi argument (one description or a map by source name) |
sourceDescriptions[type=asyncapi] | Unsupported until an AsyncAPI adapter exists; step denied |
inputs, workflow and step parameters, $inputs.* | Resource data for instance-level permissions |
outputs feeding a later step | provisional: true decision; re-decided by protect at execution |
successCriteria | Not evaluated; PermDock decides authorization, not success |
Operation without x-permdock-permissions | denied, reason undocumented (fail closed) |
| Whole workflow | outcome = worst step outcome; one simulate audit event |
Sources
- Arazzo Specification 1.1.0.
- Arazzo announcement, OpenAPI Initiative.
- Standards watch list: the "workflow-step permission requirements" item.
- OpenAPI ecosystem research: the producers that emit Arazzo files and the applied-description rule.
Related
- Decisions:
simulatesemantics. - OpenAPI and OpenAPI Overlay: where
x-permdock-permissionscomes from. - OpenAPI registries: the extension namespace.
- Approvals: what happens to
approval-requiredsteps. - OWASP agentic: plan bounding as an ASI02 control.
- AuthZEN: the boxcar wire form
simulatealready uses. - For AI agents.
A provisional: true step is not a final grant. The agent must not treat it as cleared; protect decides again with the real row.
Last updated on
OpenAPI Overlay
How permdock openapi --format overlay emits an Overlay 1.1.0 document (or, behind --overlay 1.2, the pinned Overlay 1.2 draft with reusable actions) that adds security, securitySchemes and x-permdock-* fields to an OpenAPI description without mutating it, how to apply and check it in CI, and why the Overlay never removes security.
OpenAPI registries
The OpenAPI Initiative registries, the x-permdock- namespace PermDock emits, which registered x-oai-* extensions it reuses, why it does not use x-agent-trust, the JWS typ values and media types PermDock signs with, and the rule for never inventing names in someone else's namespace.