An Arazzo workflow is a promise. It says “call this operation, take that value, pass it to the next step”. The API it describes lives in a different file, owned by a different team, changing on a different schedule.

So workflows rot. Somebody renames an operationId, nobody tells the workflow, and you find out in production.

Since v0.33, vacuum treats Arazzo as a first-class document type alongside OpenAPI, AsyncAPI and JSON Schema. It reads the workflow, opens every source it points at, and checks that the promise still holds.

vacuum lint workflows.yaml

There is no new command to learn. When vacuum lint sees an Arazzo document, it selects the built-in arazzo-recommended ruleset and gets to work.


Supported versions

vacuum supports Arazzo 1.0 and 1.1, as YAML or JSON.

Arazzo version Format
Arazzo 1.0 and 1.1 arazzo
Arazzo 1.0 arazzo1_0
Arazzo 1.1 arazzo1_1

Any other version is a tool error, and vacuum will say so.


What gets checked?

The heavy lifting is done by libopenapi-validator v0.16.0. It runs once per lint, and every finding comes back as a normal vacuum result, with a rule ID, a line number and a JSONPath.

Check What it proves
Structure The document is structurally valid Arazzo. arazzo-structure
Unique IDs Workflow, step and source IDs are unique. arazzo-duplicate-id
References Operations, workflows and steps being referenced actually exist. arazzo-reference
Parameters Parameters match the operation they are sent to. arazzo-parameter
Expressions Runtime expressions are valid where they are used. arazzo-expression
Selectors Selectors and replacement targets parse. arazzo-selector
Dependencies Workflow prerequisites exist and don’t loop. arazzo-dependency
Input schemas Workflow input schemas are valid JSON Schema. arazzo-input-schema
Source types Sources are the type the workflow says they are. arazzo-source-type

On top of that sit the authoring rules, the ones that nag you about descriptions, summaries and script tags. See all Arazzo rules for the complete list.

vacuum validates workflows. It does not run them. No requests are sent, no actions fire and no scripts execute.


Linked sources

This is the good bit.

A workflow lists its APIs under sourceDescriptions. Those sources can be OpenAPI, AsyncAPI, or another Arazzo document.

arazzo: 1.1.0
info:
  title: Read and publish
  version: '1.0'
  description: Use HTTP, event, and shared workflow sources.
  summary: Read and publish.
sourceDescriptions:
  - name: api
    url: api.yaml
    type: openapi
  - name: events
    url: events.yaml
    type: asyncapi
  - name: flows
    url: flows.yaml
    type: arazzo
workflows:
  - workflowId: run
    description: Read a pet and publish its event.
    summary: Process a pet.
    steps:
      - stepId: read
        description: Read a pet.
        operationId: $sourceDescriptions.api.read
      - stepId: publish
        description: Publish an event.
        operationId: $sourceDescriptions.events.publish
        action: send
      - stepId: remote
        description: Run the shared workflow.
        workflowId: $sourceDescriptions.flows.remote

vacuum loads all three and checks every step against the real thing. Rename read in api.yaml and you get this:

workflows.yaml:18:22  ✗  operation "read" does not exist in source "api"

Problems inside a linked Arazzo source are reported too, at the file and line where they were written. Add --snippets and vacuum prints the offending lines from that source, so you are never squinting at the wrong file.

vacuum lint workflows.yaml --details --snippets

Where does vacuum look?

Source Behavior
Relative url Resolved from the workflow file’s directory
--base ./specs Resolved from that directory (or URL) instead
http(s):// Fetched by default
--remote=false Remote sources are skipped, local files still load
--stdin No file to resolve from, so pass --base

Only local files and HTTP(S) sources are supported. The existing certificate, CA and TLS flags apply to source requests, exactly as they do for OpenAPI references.

cat workflows.yaml | vacuum spectral-report --stdin --stdout --base ./specs

When a check can’t finish

Some things can’t be proven by reading files. An XPath selector. A condition that only makes sense once a real response shows up. A remote source you told vacuum not to fetch.

I didn’t want vacuum to guess, and I didn’t want it to stay quiet either. So it tells you with arazzo-validation-incomplete, a warning pinned to the exact spot it could not check.

workflows.yaml:18:22  â–²  linked-target: source document is unavailable

A warning here does not mean the workflow is broken. It means “I couldn’t check this bit”.

If an unchecked workflow should fail your build, say so.

vacuum lint workflows.yaml --fail-severity warn

Exit codes

Code Meaning
0 No findings at or above the fail severity (error by default)
1 Findings at or above the fail severity
2 Input or tool error

A 2 means vacuum could not do the job at all. The workflow would not parse, the version is unsupported, the run timed out, or a source that lookup was allowed to fetch could not be found.


Rulesets

The default ruleset is arazzo-recommended, which is 16 rules.

extends: [[vacuum:arazzo, recommended]]

To run all 18 built-in Arazzo rules, use all, or pass --hard-mode on the command line.

extends: [[vacuum:arazzo, all]]

To start from nothing and switch on only what you want, use off.

extends: [[vacuum:arazzo, off]]
rules:
  arazzo-reference: true

vacuum:arazzo, arazzo-recommended and spectral:arazzo are interchangeable names for the same native ruleset.

You can generate ready-to-edit rulesets from the CLI.

vacuum generate-ruleset arazzo-recommended my-arazzo-rules

Learn more about the Arazzo recommended ruleset and the all Arazzo rules ruleset.


Custom Arazzo rules

Custom rules work the way they always have. Scope them with an Arazzo format so they stay out of your OpenAPI and AsyncAPI documents.

extends: [[vacuum:arazzo, recommended]]
rules:
  arazzo-validation-incomplete: error
  arazzo-info-summary: off
  workflow-title:
    description: Workflow documents need a title
    severity: warn
    formats: [arazzo]
    given: $.info
    then:
      field: title
      function: truthy

That ruleset promotes incomplete checks to errors, switches off a hint I don’t care about, and adds a rule of my own. Core functions, custom JavaScript functions and custom Go functions are all available.

vacuum lint workflows.yaml --ruleset arazzo-rules.yaml

Ignoring results

Ignore files and x-lint-ignore both work with Arazzo.

steps:
  - stepId: read
    description: Call the read operation.
    x-lint-ignore: [arazzo-reference]
    operationId: $sourceDescriptions.api.comingSoon

One thing to know. A finding that comes from a linked source is ignored in that source. An x-lint-ignore in your workflow can’t hide a problem that lives in somebody else’s file.


Reports and editors

Arazzo results flow through the same surfaces as every other vacuum result: console output, the dashboard, report, html-report, spectral-report compatible JSON and the language server.

Findings from linked sources keep their own file and line in every one of them. In an editor, they show up on the workflow’s source declaration and link through to the real line.

--original works as well. vacuum re-checks both source graphs, so an API that changed underneath an untouched workflow still produces a new finding.


Coming from Spectral?

spectral:arazzo is accepted as an alias, so existing rulesets load without edits.

It selects vacuum’s native rules. It does not run Spectral’s JavaScript functions, and it makes no promise of identical rule IDs or messages for the semantic checks. If you override those, move the overrides across.

Spectral check In vacuum
Unique IDs Workflow and step ID uniqueness is arazzo-duplicate-id
Prerequisites Workflow dependsOn checks are arazzo-dependency
Descriptions Description and summary rule IDs are unchanged

Run your existing custom rules and ignore entries against a generated report before you trust the migration.


What’s not here (yet)

Feature Arazzo support
Running workflows No. vacuum is a linter.
bundle No. It remains an OpenAPI-only command.
apply-overlay No. It remains an OpenAPI-only command.
vacuum docs No. Generated docs cover OpenAPI and AsyncAPI.
Breaking changes No. Findings are compared with --original, change summaries are OpenAPI-only.

Using it as a library

result := motor.ApplyRulesToRuleSet(&motor.RuleSetExecution{
    Spec:         workflowBytes,
    SpecFileName: "specs/workflows.yaml",
    AllowLookup:  true,
    RuleSet:      rulesets.BuildDefaultRuleSets().GenerateArazzoRecommendedRuleSet(),
})
defer result.ReleaseOwnedResources()
// Handle result.Errors before reading result.Results.

AllowLookup and Base control where sources are loaded from. Use ApplyRulesToRuleSetWithOptions to supply your own context, a total timeout, or a rule concurrency limit.

A ruleset with only custom rules never invokes the validator and never loads a source.