SEQUENCE

What actually happens when someone clicks Pay

One request or job, end to end, in the order it happens. Who calls whom, what they send, who waits for an answer and who fires and forgets. The branches are drawn where they split, so the failure path sits next to the happy one instead of in someone's head.

> what happens when a courier uploads their scans?
wrote scan-upload.sequence.json · scan-upload.sequence.html
 
# the skill follows one handler, then renders like any other
$ vibex validate docs/scan-upload.sequence.json
$ vibex render docs/scan-upload.sequence.json --open

An offline batch, end to end

A courier's handheld uploads a batch of scans it took offline. Each scan is written once, published to Kafka, applied by Dispatch, and a delivery ends in an SMS. From the Relay showcase.

scan-upload.sequence.html · 8 participants · 15 messages · loop, alt, optFull screen ↗

Click a message for what is sent and where it is in the code. Click a participant to light up everything it sends and receives.

What it draws

Every field in the spec, and where it shows up.

PARTICIPANTS

kind
Drawn left to right in the order you list them, each with its own shape: a pill for a person, a cylinder for a database.
personservicedatabasequeueexternal
id · label
Reuse the C4 element ID, so the same service has one name in every diagram.
technology
The second line in the box: Go, Kafka, PostgreSQL 15.

MESSAGES

kind
sync waits for an answer: a filled arrow, and an activation bar on the receiver until it replies. async is fire and forget: an open arrow. reply is the answer: dashed, and it closes the bar.
syncasyncreply
label · detail
What is sent, POST /v1/scans, with a smaller second line for the payload or the condition. Steps are numbered for you.
failure
An error path, drawn red: 402 Payment Required.
from = to
A call to itself, drawn as a loop on its own lifeline.

FRAGMENTS AND NOTES

fragment
A frame around the steps it holds, labelled with its condition. alt and par take else branches, and each branch starts from the calls that were in flight when the frame began. Frames nest.
altoptloopparbreak
note
A sticky note beside one participant, or across two.

Where it reads from

A flow lives in the code that runs it, so the skill picks one entry point and follows it:

ENTRY

The controller, resolver or consumer that receives the first message.

CALLS

Service methods, HTTP and gRPC clients, and repository writes, in the order the handler makes them.

EVENTS

publish, emit and queue sends, drawn async, then the consumers that pick them up.

BRANCHES

if / else around calls become alt. Loops over a batch become loop. Each message keeps a source link.

One flow per diagram. A sequence shows one path through the system, not all of them. For which services call which at all, use the System view; for the states a record moves through, use a lifecycle.

The spec you keep

{
  "schema_version": 1,
  "diagram_type": "sequence",
  "meta": { "title": "Checkout", "trigger": "The buyer presses Pay" },
  "participants": [
    { "id": "buyer", "label": "Buyer", "kind": "person" },
    { "id": "orders", "label": "Order API" },
    { "id": "stripe", "label": "Stripe", "kind": "external" }
  ],
  "steps": [
    { "from": "buyer", "to": "orders", "label": "POST /orders" },
    { "from": "orders", "to": "stripe", "label": "create PaymentIntent" },
    { "fragment": "alt", "label": "card accepted",
      "steps": [
        { "from": "orders", "to": "buyer", "label": "201 Created",
          "kind": "reply" }
      ],
      "else": [{ "label": "card declined", "steps": [
        { "from": "orders", "to": "buyer", "label": "402",
          "kind": "reply", "failure": true }
      ] }]
    }
  ]
}

Name the flow by what starts it, in meta.trigger: “the buyer presses Pay”. List participants in the order the call travels.

Steps read top to bottom, exactly as they happen. Put a branch in an alt rather than a second diagram, so the declined card is drawn next to the accepted one.

The full contract is schemas/sequence.schema.json.

What validate flags

$ vibex validate docs/checkout.sequence.json
error dangling-ref steps[1].to "payments" is not a participant id (known ids: buyer, bff, orders, audit)
warning label steps[1] (bff -> payments) has no label; say what is sent
warning else steps[2] is "loop"; only alt and par have else branches
warning unused participant "audit" sends and receives nothing
1 error(s), 3 warning(s)

Errors

Exit 1. Each one names the step.

  • dangling-refa message or note naming a participant that is not listed
  • typea step that is not a message, a note or a fragment
  • duplicate-idtwo participants or messages sharing an ID

Warnings

Printed, never blocking.

  • labela message that does not say what is sent
  • unuseda participant that sends and receives nothing
  • elseelse branches on a fragment that cannot have them
  • fragment-labela frame with no condition
  • sizemore than 40 messages or 10 participants. Split at a hand-off

The sentences it writes

A docs section can ask for sequence.messages: one sentence per step, carrying the frame it sits in, because “only when” is the part of a flow people get wrong. From the Relay build:

Step 5: Scan ingest notifies Parcel events without waiting. Message: parcel.scanned. Repeated: for each scan in the batch. Only when: a scan we have not seen.sequence.messages · scan-upload.sequence
Step 3: Scan ingest calls Relay DB and waits for the answer. Message: insert if new. Detail: unique index on idempotency_key. Repeated: for each scan in the batch.sequence.messages · scan-upload.sequence

What it connects to

C4

The same boxes, in order

C4 shows that two containers talk. A sequence shows when, and what they say. Share the IDs and they read as one system.

C4 →
MERMAID

Paste it into a README

Every render writes a .mmd beside the HTML: a Mermaid sequenceDiagram with numbering, branches and notes, for GitHub, GitLab and Notion.

DASHBOARD

Flows, next to the rest

Each sequence is an entry under Flows, beside the tables it writes and the routes it calls.

Dashboard →