Volley / Drift Check

Drift Check

Find out when your sequence diagram stopped being true.

Drift Check reads a recording of real HTTP traffic and tells you, arrow by arrow, where the diagram and the system disagree. Run it in the editor, from the CLI, or in CI on every pull request.

What it checks

Every request arrow whose text starts with a method and a path is matched to a request in the recording. Matching uses the method and each path segment. {id}, :id and <id> are placeholders, and two id-like segments match each other, because diagrams hold example ids.

Once a step is matched, its in: and out: payloads are turned into key paths and compared with what was really sent and received:

  • Drifted: a documented field was not sent, a JSON field changed type, or the reply came back with a different status code.
  • Missing: the diagram documents a request that never happened.
  • Skipped: an if branch whose status was not observed, such as the 400 path in a recording of a happy run.
  • Undocumented: a field or a whole request that the recording has and the diagram does not. These are reported. With --strict they fail too.

An example

This is the OAuth 2.0 client credentials diagram from the demo, checked against a recording of a billing run. The auth server stopped returning expires_in as a number and the service stopped sending scope.

client-credentials.volley
title: OAuth 2.0 client credentials
order: Service, Auth Server, API

Service -> Auth Server: POST /oauth2/token
in: Token request
  grant_type: client_credentials
  client_id: billing-worker
  client_secret: <redacted>
  scope: invoices:read

Auth Server --> Service: 200 OK
out: Access token
```json
{
  "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiaWxsaW5nLXdvcmtlciJ9.sig",
  "token_type": "Bearer",
  "expires_in": 900
}
```

Service -> API: GET /invoices?status=open
in: Headers
  Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

API --> Service: 200 OK
out: Invoices
```json
[{ "id": "inv_123", "total": 4200, "currency": "USD" }]
```
Output
$ volley verify client-credentials.volley billing-run.har
DRIFT   #1 POST /oauth2/token (line 4)
          1 documented field not sent.
          input scope: documented, not seen
DRIFT   #2 200 OK (line 11)
          1 field changed type, 1 undocumented field.
          output expires_in: was number, now string
          output scope: seen, not documented
ok      #3 GET /invoices?status=open (line 21)
ok      #4 200 OK (line 25)
          output [].due_at: seen, not documented

Recorded requests the diagram does not mention (1):
          POST api.example.com/invoices/inv_9001/reminders (202)

4 checked: 2 match, 2 drifted, 0 missing, 0 skipped. The diagram has drifted from the recording.

In the editor the same result shows up on the diagram itself: each arrow gets a mark and a click shows the field-level difference. "Update 3 steps" rewrites the token request and response to match the recording and adds the undocumented due_at field to the invoices reply, keeping the documented example values, and the recorded reminder request can be added in one click.

In the editor

  1. Open a diagram whose request arrows read like POST /oauth2/token.
  2. Record the flow in your browser's Network tab and choose "Save all as HAR".
  3. Drop the file on the editor and choose "Compare with this diagram", or use "Update from recording" under the diagram.
  4. Choose "Update N steps" to bring payloads and status codes in line with the recording, or edit them by hand. The comparison reruns as you type.

In CI

Most end-to-end test runners can save a HAR file. Playwright does it with one option on the browser context:

checkout.spec.ts
const context = await browser.newContext({
  recordHar: { path: "test-results/checkout.har", content: "embed" },
});

Then send the diagram and the recording to the hosted verify endpoint with an API key from Account, API keys. The response is the full report as JSON, and passed is false when anything drifted or went missing. This workflow fails the pull request when the docs no longer match:

.github/workflows/diagram-drift.yml
name: Diagram drift
on: [pull_request]

jobs:
  drift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Record traffic
        run: npx playwright test checkout.spec.ts

      - name: Check the diagram against the recording
        env:
          VOLLEY_KEY: ${{ secrets.VOLLEY_KEY }}
        run: |
          jq -n --rawfile source docs/checkout.volley \
                --slurpfile har test-results/checkout.har \
                '{source: $source, har: $har[0]}' > verify.json
          curl -s --fail-with-body https://volley.sh/api/v1/verify \
            -H "Authorization: Bearer $VOLLEY_KEY" \
            -H "Content-Type: application/json" \
            --data-binary @verify.json > report.json
          jq -r '.messages[] | "\(.status)\t#\(.index)\t\(.summary)"' report.json
          jq -e '.passed' report.json > /dev/null

The report looks like this, trimmed:

report.json
{
  "passed": false,
  "summary": { "checked": 4, "matched": 2, "drifted": 2, "missing": 0, "skipped": 0, "undocumented": 1 },
  "messages": [
    {
      "index": 2,
      "status": "drift",
      "fields": [{ "side": "output", "kind": "type", "path": "expires_in", "expected": "number", "observed": "string" }],
      "summary": "1 field changed type, 1 undocumented field."
    }
  ],
  "undocumented": [{ "method": "POST", "host": "api.example.com", "path": "/invoices/inv_9001/reminders", "status": 202 }]
}

The CLI

The same check is volley verify <diagram> <har>. It prints the report, exits 1 on drift, and takes --strict to fail on undocumented requests, --json for the raw report, --host <substring> to look at one service and --all to include static assets. The CLI ships with Volley's source and is not published to npm yet, so in CI the hosted endpoint above is the way to run it today.

From an agent

The remote MCP server has a verify_diagram tool. Hand your agent the diagram and a recording and it gets the same report, then can fix the diagram itself.

Questions

Does Drift Check send any requests?

No. Volley never calls your APIs. It only reads a recording you already have.

Why are field values not compared?

Diagrams hold example values, such as a sample token or order id. Comparing them would flag every step. Drift Check compares field names, JSON types and status codes, which is what breaks clients.

Which messages are checked?

Solid and bold arrows whose text starts with an HTTP method and a path, like GET /orders/{id}. Dashed replies are checked by the status code at the start of their text. Everything else is left alone.

Is there a size limit?

The hosted API takes diagrams up to 256 KB and recordings up to 8 MB. In the editor the check runs in your browser.

See it catch a real drift.

The demo loads a diagram and a recording that disagree in two places.