Veto/docs
Meow Gateway

Workflow

The top-level object a capsule binds to. Tracks a single payment from draft through settlement.

A Workflow is the unit of work. One workflow = one "pay Acme invoice #47" — it holds together the capsule, the decision receipts, and the Meow-side settlement webhook.

States

draft → approved → consumed → settled | failed
         ↓           ↓
       cancelled   cancelled
  • draft — created, awaiting mint + HITL approval
  • approved — policy + approval cleared; capsule can mint
  • consumed — capsule consumed; upstream Meow invoked
  • settled — Meow webhook confirmed money moved
  • failed — Meow webhook reported terminal failure
  • cancelled — operator cancelled before consume

Each transition is validated by a deterministic edge check inside a SQLite transaction. Illegal transitions fail with workflow_invalid_transition — no silent state corruption.

Audit history

Every workflow carries an append-only history array:

[
  { "from": null, "to": "draft", "at": 1714579200000, "actor": "op_1" },
  { "from": "draft", "to": "approved", "at": 1714579800000, "actor": "op_compliance" },
  { "from": "approved", "to": "consumed", "at": 1714580100000, "actor": "agent_finance_bot" },
  { "from": "consumed", "to": "settled", "at": 1714583700000, "actor": "meow-webhook" }
]

The history is stored as JSON in the workflow row AND mirrored into decision receipts at each transition, so even if the workflow row is lost the audit trail survives on the chain.

Webhook-driven settlement

When Meow emits payment.settled, the gateway's /webhooks/meow handler:

  1. Verifies the HMAC-SHA256 signature
  2. Extracts workflow_id from the payload
  3. Calls WorkflowEngine.transition({ to: "settled" })
  4. On WorkflowTransitionError (race / replay), logs + returns 200 so Meow doesn't retry forever
  5. On WorkflowNotFoundError (misrouted webhook), logs + returns 200

The transition writes a new decision receipt with decision: "allow" and reason_code: "settled" so auditors see the full lifecycle on the chain.

Cancel

curl -X POST http://localhost:3005/v1/workflows/$WF_ID/transitions \
  -H "content-type: application/json" \
  -d '{ "to": "cancelled", "actor": "op_1", "reason": "vendor dispute" }'

Cancel works before consume. After consume the capsule has already moved money; the terminal state comes from Meow's settlement webhook.