{
  "docVersion": 1,
  "id": "template-digital-wallet-ledger",
  "name": "Digital wallet — double-entry ledger",
  "purpose": "Wallet backend where the append-only double-entry journal is the source of truth, balances are materialized views, and transfers are effectively-once under retries.",
  "nodes": {
    "app": {
      "type": "mobile",
      "label": "Wallet App",
      "x": 20,
      "y": 240,
      "runtime": "transfer £20 to Asha"
    },
    "api": {
      "type": "k8s",
      "label": "Wallet API",
      "x": 280,
      "y": 240,
      "runtime": "idempotency-key dedupe",
      "color": "blue",
      "patterns": [
        "stateless",
        "autoscale"
      ],
      "notes": "Validates and dedupes transfer commands; a retried request returns the original posting, never a second one."
    },
    "ledger": {
      "type": "worker",
      "label": "Ledger Service",
      "x": 540,
      "y": 240,
      "runtime": "posts double entries",
      "color": "green",
      "notes": "The ONLY writer to the journal. One transfer = one atomic posting: debit sender, credit receiver."
    },
    "journal": {
      "type": "ledger",
      "label": "Journal",
      "x": 810,
      "y": 110,
      "runtime": "append-only · immutable",
      "patterns": [
        "backup",
        "replicated"
      ],
      "notes": "Every posting is a permanent fact keyed by posting id. Corrections are reversing entries — history is never edited."
    },
    "bus": {
      "type": "stream",
      "label": "PostingCommitted",
      "x": 810,
      "y": 380,
      "runtime": "at-least-once"
    },
    "projector": {
      "type": "worker",
      "label": "Balance Projector",
      "x": 1070,
      "y": 380,
      "runtime": "folds postings → balances"
    },
    "balances": {
      "type": "keyvalue",
      "label": "Balances View",
      "x": 540,
      "y": 520,
      "runtime": "rebuildable projection",
      "notes": "Fast reads only. Lose it? Replay the journal and it comes back identical."
    },
    "recon": {
      "type": "cron",
      "label": "Trial Balance",
      "x": 1070,
      "y": 110,
      "runtime": "nightly: Σdebits = Σcredits",
      "color": "amber"
    }
  },
  "edges": {
    "e1": {
      "from": "app",
      "to": "api",
      "fromSide": "right",
      "toSide": "left",
      "label": "transfer · Idempotency-Key",
      "animated": true,
      "patterns": [
        "idempotent",
        "timeout"
      ]
    },
    "e2": {
      "from": "api",
      "to": "ledger",
      "fromSide": "right",
      "toSide": "left",
      "label": "post(debit A, credit B)",
      "animated": true
    },
    "e3": {
      "from": "ledger",
      "to": "journal",
      "fromSide": "right",
      "toSide": "left",
      "label": "append posting",
      "animated": true
    },
    "e4": {
      "from": "journal",
      "to": "bus",
      "fromSide": "bottom",
      "toSide": "top",
      "label": "emit committed postings",
      "style": "dashed"
    },
    "e5": {
      "from": "bus",
      "to": "projector",
      "fromSide": "right",
      "toSide": "left",
      "style": "dashed",
      "patterns": [
        "dedup",
        "ordered"
      ]
    },
    "e6": {
      "from": "projector",
      "to": "balances",
      "fromSide": "left",
      "toSide": "right",
      "label": "update view",
      "style": "dashed"
    },
    "e7": {
      "from": "api",
      "to": "balances",
      "fromSide": "bottom",
      "toSide": "top",
      "label": "GET balance — reads the view, never the journal",
      "patterns": [
        "cache-aside"
      ]
    },
    "e8": {
      "from": "recon",
      "to": "journal",
      "fromSide": "left",
      "toSide": "right",
      "label": "assert books balance",
      "style": "dashed"
    }
  },
  "boxes": {
    "b1": {
      "x": 770,
      "y": 20,
      "w": 560,
      "h": 560,
      "label": "source of truth — everything else is projection",
      "color": "purple",
      "opacity": 0.04
    }
  },
  "texts": {
    "t1": {
      "x": 20,
      "y": 450,
      "text": "why money can't vanish:\n1. debit + credit commit in ONE posting\n2. balances derive from postings — no drift\n3. retry replays the same posting id → no-op",
      "color": "green",
      "size": 12
    }
  },
  "meta": {
    "createdAt": "2026-07-13T09:00:00.000Z",
    "modifiedAt": "2026-07-13T09:00:00.000Z"
  }
}