Here is a run that looks fine. The Arazzo Specification has a login-then-call example: one step logs in, and the next sends Authorization: $steps.loginStep.outputs.sessionToken. Until recently, the Arazzo Runner sent that second request without the header. The run still reported completed (toolkit #279).
Nothing crashed. Nothing warned. A runner that fails is annoying. A runner that quietly does something the document didn’t say is worse, because you believe it.
If you write Arazzo workflows, or build the tools that run them, this post was written for you.
The Runner from UseArazzo, open source tooling for Arazzo, went on npm on October 22. In the two weeks after, I went through the Arazzo Specification rule by rule, looking for places where the Runner did something the document didn’t say. Here are four of the rules I found, what each one means for your workflows, and how to check it.
Does every parameter reach the request?
It has to. A step’s parameters are, in the words of the Step Object, parameters “that MUST be passed to an operation or workflow”. All three versions of Arazzo say so. None of them says “if the OpenAPI document declares them”.
So why does a header go missing? OpenAPI 3 describes authentication with security schemes, not parameters. It even ignores an Authorization header declared as a parameter. A tool that builds each request strictly from the OpenAPI operation has nowhere to put that header, so it drops it. That’s what the Runner did.
What it means for you: if a step sends a header the operation doesn’t declare, check that it actually goes out. A request log or an echo server tells you in a minute.
It cuts the other way too. A parameter declared on the workflow reaches every step’s request, not only the operations that declare it. So a parameter only some operations take belongs on those steps.
Can a step opt out of the workflow’s failure actions?
No. A workflow can declare failureActions (and successActions) for all of its steps. The Workflow Object says, in all three versions:
These failure actions can be overridden at the step level but cannot be removed there.
Here is a workflow where that matters:
workflows:
- workflowId: adoptPet
failureActions:
- name: stop
type: end
steps:
- stepId: getPet
operationId: getPetById
successCriteria:
- condition: $statusCode == 200
onFailure:
- name: refresh
type: goto
stepId: refreshToken
criteria:
- condition: $statusCode == 401
The step’s own action comes first, then the workflow’s. A 401 goes to refreshToken. A 500 doesn’t match refresh, falls through to stop, and the run ends there.
The tempting reading is that a step with its own onFailure replaces the workflow’s list. Then stop disappears for getPet, and onFailure: [] switches off every default. The Runner read it that way until toolkit #288.
What it means for you:
- Workflow failure actions are a floor. An unconditional
endon the workflow applies to every step, including steps with their ownonFailure. onFailure: []doesn’t opt out. It inherits everything.- To change an action for one step, reuse its name. A step action named
stopreplaces the workflow’sstop.
That last point had a catch. The spec never said what makes two actions “the same”. For parameters it does: “A unique parameter is defined by the combination of a name and in fields.” For actions there was no such sentence. The Runner matches on name, which is required and the only reading that makes both halves of the rule true.
A reading isn’t a rule, though. So I filed the gap upstream (OAI/Arazzo-Specification#619) and proposed the missing sentence the same day (#620). It was merged a day later, for the Arazzo 1.1.1 patch release:
A unique failure action is defined by its
name.
Until 1.1.1 is out, another runner may still read it differently, so check how the runner you use matches them.
Can a step pass without checking anything?
It can, if your runner is forgiving. Look at the first step here:
steps:
- stepId: getOrder
description: Fetch the order.
operationId: getOrder
successCriteria:
- $statusCode == 200
- stepId: getStatus
description: Fetch the order status.
operationId: getOrderStatus
successCriteria:
- context: $statusCode
condition: "^(2"
type: regex
The check under getOrder reads fine. But it’s a plain string. Arazzo wants a list of Criterion Objects, each with a condition field. A runner that skips entries it can’t read passes the step with no check at all. The Runner did that until toolkit #284. Now it refuses to start the workflow, before a single request goes out.
What it means for you: validate before you run. The CLI catches it, along with the regex under getStatus, which is missing its closing parenthesis:
$ npx @usearazzo/cli validate checks.arazzo.yaml
checks.arazzo.yaml
19:9-19:24 error 9050500 successCriteria must be an array of Criterion Objects
26:24-26:29 error 9100102 Criterion "condition" must be a valid regular expression when type is "regex".
✖ 2 problems (2 errors)
What happens when a check can’t be evaluated?
This rule is the odd one out. Here the Runner wasn’t too quiet. It was too loud.
The step fails. That’s new in Arazzo 1.1.0, in Evaluation Errors. A condition that can’t be evaluated, such as an invalid regex, “MUST evaluate to fail”. Implementations should also report why. Arazzo 1.0.0 and 1.0.1 left this open, and the Runner used to abort the whole run. Now it applies the 1.1.0 rule to every version (toolkit #301).
What it means for you: a broken regex or JSONPath doesn’t crash the run. It fails the step, and the step’s failure actions run. So if a step fails against a response that looks right, suspect the criterion before the API. The Runner records each criterion’s outcome in the run’s trace, with the reason it couldn’t be evaluated (toolkit #302). And the validation above would have caught it first.
What did fixing this take?
Seventeen releases in fourteen days, from October 23 to November 5. Four of them came on November 4. Each release carried one change, and went out the day it was merged. The changelog has one entry per release.
That pace is on purpose. When a release changes how your workflow runs, you know which rule changed, and you can pin the version before it.
The Validator and the Runner share one engine. So when they disagree about a document, that’s a bug. One turned up along the way: the Validator rejected in: querystring in Arazzo 1.1.0 documents, while the Runner ran it (toolkit #299).
And not every fix went into code. Where the spec itself was silent, the fix went upstream, like the action identity sentence above.
The Runner is still an alpha, and its API may change before 1.0.0. The next rules to land are the Arazzo 1.1.0 constructs it doesn’t run yet.
Closing words
So there it is. Four rules, and a checklist for your own workflows:
- Check that headers the operation doesn’t declare actually go out.
- Workflow failure actions apply to every step. Override one by reusing its name.
- Every
successCriteriaentry needs acondition. - A criterion that can’t be evaluated fails the step. Validate before you run.
To run your own workflows by these rules, install the Runner:
npm install @usearazzo/runner
The Runner page shows what it does, and the API reference covers what steps inherit and how runs fail in full. The source is in the arazzo-toolkit monorepo. If a workflow of yours runs differently than its document says, tell me in Discussions.