# HYBX Routing Graph Data Model **Purpose:** Define the canonical **routing graph** used by the Compliance & Routing Sidecar (see [hybx_compliance_routing_sidecar_technical_plan.md](hybx_compliance_routing_sidecar_technical_plan.md)) for graph-based pathfinding, liquidity resolution, fee path construction, and jurisdiction-aware routing. This is the mathematical backbone for nostro pathfinding, multi-bank routing, liquidity selection, and failover as **alternate paths** on the same graph. **Audience:** Engineers implementing the Routing Engine, orchestration adapters, and registry services. **Non-goals:** Policy DSL grammar, liquidity registry service APIs, explanation-engine templates, and failover algorithms are specified elsewhere. --- ## 1. Purpose and scope The routing graph models the **operational payment network** relevant to a transaction: institutions, account corridors, liquidity pools, FX venues, settlement rails, fee agents, and beneficiary anchors, connected by **directed, weighted** relationships. - **In scope:** Vertex types, edge types, cost vectors, liquidity and fee metadata, jurisdiction overlays, serialization, versioning, and mapping from the Transaction Composer compile output. - **Out of scope:** Specific shortest-path or MOCO (multi-objective) solver implementations; this document defines **inputs** those solvers consume. The Transaction Composer UI produces a **design-time pipeline graph** (user intent). The sidecar may **project** that into one or more **routing subgraphs** and enrich them with edges and weights that are not drawn in the UI (e.g. alternate correspondents, backup rails). --- ## 2. Conceptual model Let **G = (V, E)** be a **directed graph** with: - **V**: typed vertices (routing nodes). - **E**: typed directed edges with metadata and costs. ### 2.1 Two layers | Layer | Meaning | Typical source of truth | |--------|---------|-------------------------| | **Topology** | Durable relationships: who may route to whom, which rails exist, static eligibility. | Reference data, contracts, registry | | **Snapshot** | Time-bound overlays: available liquidity, current fee quotes, latency estimates, risk adjustments for **this evaluation**. | Pool registry, market data, sidecar snapshot builder | At evaluation time, the engine consumes a **materialized view**: topology ∪ snapshot overlays. The JSON envelope below supports both. ### 2.2 Relationship to sidecar API The sidecar plan describes `POST /evaluate-transaction` with `transactionGraph` and responses including `routingPlan`. This document defines the **shape of the network graph** (and fragments thereof) that backs **routingPlan** generation—not the full HTTP schema. --- ## 3. Vertex (node) types Vertices are discriminated by `vertexKind`. All vertices share a common base; extensions are kind-specific. ### 3.1 Base fields (all kinds) | Field | Type | Required | Description | |--------|------|----------|-------------| | `id` | string | yes | Stable unique id in this graph (UUID or registry key). | | `vertexKind` | enum | yes | One of the kinds below. | | `displayName` | string | no | Human label. | | `jurisdiction` | string | yes | ISO-3166 alpha-2, or internal jurisdiction code (e.g. `ID-JK`). | | `identifiers` | object | no | `bic`, `lei`, `internalOrgId`, etc. | | `capabilities` | object | no | See below. | | `riskTier` | string \| number | no | Opaque tier for risk engine (e.g. `T1`–`T3`). | | `metadata` | object | no | Opaque extensibility. | **Capabilities object (optional, common pattern):** ```json { "currenciesAllowed": ["USD", "IDR"], "fxPermitted": true, "settlementTypes": ["RTGS", "ACH"], "maxSingleTransfer": { "amount": "1000000000", "currency": "USD" } } ``` ### 3.2 Vertex kind: `Institution` Legal or operational entity (bank, CB, PSP). Maps from composer participants and some operational nodes. ### 3.3 Vertex kind: `AccountCorridor` Abstract **nostro/vostro or nostro-like** relationship endpoint—not necessarily one ledger row. Used for pathfinding between institutions. - Typical use: attach liquidity and correspondent edges to corridors. ### 3.4 Vertex kind: `LiquidityPool` A **source of fungible liquidity** in a currency (or synthetic pool id). - Should align with sidecar liquidity sketch: currency, amount, provider, expiry (carried in snapshot overlay on the pool or on incident edges). ### 3.5 Vertex kind: `FxVenue` FX conversion capability (desk, LP, internal book). ### 3.6 Vertex kind: `SettlementRail` Logical settlement channel (RTGS system, chain leg, internal settlement batch). ### 3.7 Vertex kind: `FeeAgent` Optional dedicated vertex when fees are not folded into `Institution` (e.g. third-party fee collector). ### 3.8 Vertex kind: `BeneficiaryAnchor` Terminal or near-terminal node representing beneficiary credit location (could be institution + product, or a logical “credit to PT Parak at Bank Kanaya”). --- ## 4. Edge types Edges are **directed**: `(sourceId, targetId)`. Each edge has: | Field | Type | Required | Description | |--------|------|----------|-------------| | `id` | string | yes | Unique edge id. | | `sourceId` | string | yes | Vertex `id`. | | `targetId` | string | yes | Vertex `id`. | | `edgeKind` | enum | yes | See below. | | `costVector` | object | yes | Normalized costs (section 5). | | `jurisdictions` | string[] | no | Tags for crossing rules; default may inherit from endpoints. | | `validFrom` / `validTo` | string (ISO-8601) | no | Validity window for this edge. | | `policyRefs` | string[] | no | Opaque ids for future Policy DSL bindings. | | `liquidityRef` | string | no | Pool or line id when edge represents funding. | | `feeRef` | string | no | Link to fee schedule id. | | `metadata` | object | no | Extensibility. | ### 4.1 Edge kind: `correspondent` Bank-to-bank (or institution-to-institution) routing leg; may attach to `AccountCorridor` vertices or directly to `Institution` depending on model granularity. ### 4.2 Edge kind: `liquidity_link` Connects a pool to a corridor, venue, or institution **consumption** point. ### 4.3 Edge kind: `fx_quote` Connects currency/state A to B through an `FxVenue` (often modeled as two edges via the venue, or one bundled edge with pair metadata in `metadata`). ### 4.4 Edge kind: `fee_hop` Explicit fee accrual or pass-through segment (supports building a **fee propagation tree** as a subgraph). ### 4.5 Edge kind: `settlement_path` Connects to or from a `SettlementRail` or `BeneficiaryAnchor`. --- ## 5. Weights and multi-criteria cost The sidecar Routing Engine (see technical plan) uses a **weighted directed graph** with dimensions including **fee cost**, **latency**, and **liquidity availability**. ### 5.1 `costVector` (required on every edge) Recommended normalized fields (all optional numbers except at least one should be present for pathfinding): | Key | Meaning | Units / notes | |-----|---------|----------------| | `feeCost` | Expected monetary cost of traversing this edge | Normalized to a reference currency in snapshot builder, or raw with `feeCurrency` in `metadata` | | `latencyMs` | Expected processing time | Milliseconds or representative score | | `liquidityAvailability` | How “easy” it is to fund this hop | 0–1 score, or available notional on this hop | | `regulatoryPenalty` | Additive penalty from policy | Non-negative; 0 if none | | `reliability` | Historical success / health | 0–1, can be inverted by solver | Example: ```json "costVector": { "feeCost": 1250.5, "latencyMs": 800, "liquidityAvailability": 0.92, "regulatoryPenalty": 0, "reliability": 0.995 } ``` ### 5.2 Aggregation strategy (normative intent, not algorithm) Path cost is a **multi-criteria** problem: 1. **Hard constraints:** e.g. minimum liquidity availability, blocked jurisdictions, expired `validTo`. 2. **Scalarization:** weighted sum `w1*feeCost + w2*latencyMs + w3*(1-liquidityAvailability) + w4*regulatoryPenalty + w5*(1-reliability)` with configurable weights per product lane. 3. **Pareto / k-shortest:** optional second phase to present alternates for failover UX. Exact solver choice is implementation-defined; this document standardizes **what** is measured on each edge. --- ## 6. Liquidity metadata Aligned with the sidecar liquidity model (currency, amount, provider, expiry): **On `LiquidityPool` vertex or `liquidity_link` edge (snapshot):** | Field | Description | |--------|-------------| | `poolId` | Registry identifier | | `currency` | ISO-4217 | | `availableAmount` | Decimal string recommended | | `providerId` | Institution or LP id | | `expiresAt` | ISO-8601 | | `refreshTtlSeconds` | Optional hint for cache | --- ## 7. Fee metadata Fees may be attached to `fee_hop` edges or embedded in `correspondent` / `settlement_path` via `feeRef`. **Suggested fields (snapshot or static):** | Field | Description | |--------|-------------| | `feeModel` | `percent`, `flat`, `tiered`, `conditional` | | `bps` | Basis points if percent | | `flatAmount` | If flat | | `currency` | Fee currency | | `conditionRef` | Opaque id for conditional rules (Policy DSL later) | **Fee propagation tree:** Derived by taking the subgraph induced by `fee_hop` edges (and optionally fee-bearing segments), orienting edges in flow direction, and interpreting parent/child as **payer → collector → onward**. The routing graph remains the single source of truth; the tree is a **view**, not a second graph. --- ## 8. Jurisdiction overlays - **Node default:** Each vertex has a primary `jurisdiction`. - **Edge override:** `jurisdictions[]` on an edge marks **crossing** or **rule buckets** (e.g. `US-OFAC`, `EU-PII`). - **Transaction-level filter:** Evaluation request may supply `transactionJurisdictions` or `denyJurisdictionTags`; the pathfinder **prunes** edges violating hard policy (details in Policy DSL doc). Compliance outcomes (PASS / WARN / FAIL) are produced by the Compliance Engine; this model only supplies **tags and penalties** (`regulatoryPenalty`) consumed by routing. --- ## 9. Mapping from Transaction Composer The Transaction Composer ([transaction-composer/](transaction-composer/)) compiles UI graphs into `CompiledTransaction` with buckets: `participants`, `nostroAccounts`, `liquidity`, `fx`, `fees`, `settlement`, plus `topology`. **Composer is a pipeline; routing graph is a network.** The table below is a **projection guide**, not a 1:1 id equality (routing vertices may be created per registry lookup). | Composer source | Compiler bucket / `NodeKind` | Typical routing vertex kind(s) | Notes | |-----------------|--------------------------------|---------------------------------|--------| | Central / commercial / remittance bank | `participants` (`centralBank`, `commercialBank`, `remittanceInstitution`) | `Institution` | Map `institution` string to registry id; `participantRole` informs source vs beneficiary anchor. | | Nostro | `nostroAccounts` | `AccountCorridor`, `Institution` | Often one corridor per nostro relationship. | | Liquidity | `liquidity` (`liquidityProvider`) | `LiquidityPool`, `Institution`, `liquidity_link` | Pool may be resolved via Liquidity Pool Registry. | | FX | `fx` (`fxConversion`) | `FxVenue`, `fx_quote` edges | May expand to venue + pair legs. | | Fees | `fees` (`feeRouter`) | `FeeAgent` or `Institution`, `fee_hop` | Multiple fee nodes become multiple hops or a small fee subgraph. | | Settlement | `settlement` | `SettlementRail`, `BeneficiaryAnchor` | Beneficiary text may map to anchor + rail. | | Topology edges | `topology.edges` | Mixed `edgeKind` | Composer edges imply **ordering**; routing edges add **weights** and **alternates**. | **Important:** The composer `topology.orderedNodeIds` defines **design order**. The routing engine may introduce **parallel paths** (e.g. two correspondent edges) not present in the UI graph. --- ## 10. Serialization and versioning ### 10.1 JSON envelope ```json { "schemaVersion": "1.0.0", "graphId": "rg-2026-03-29-indonesia-demo", "effectiveAt": "2026-03-29T12:00:00Z", "vertices": [], "edges": [], "overlays": { "liquiditySnapshotId": "optional-registry-pointer", "feeScheduleSnapshotId": "optional", "notes": "optional" } } ``` ### 10.2 Stability rules - **Patch** (1.0.x): Add optional fields only; do not remove or rename required fields. - **Minor** (1.x.0): Add new `vertexKind` / `edgeKind` values; old consumers ignore unknown kinds if possible. - **Major** (x.0.0): Breaking renames or semantic changes. --- ## 11. Worked example (minimal) Scenario aligned with composer demo intent: **OMNL → BNI nostro/liquidity → USD/IDR FX → fees → settlement at Bank Kanaya; beneficiary PT Parak International.** Includes one **alternate** `correspondent` edge as a failover hint (not expanded into full failover logic). ```json { "schemaVersion": "1.0.0", "graphId": "example-omnl-bni-kanaya", "effectiveAt": "2026-03-29T12:00:00Z", "vertices": [ { "id": "v-omnl", "vertexKind": "Institution", "displayName": "OMNL", "jurisdiction": "ID", "identifiers": { "internalOrgId": "OMNL" }, "capabilities": { "currenciesAllowed": ["USD"], "fxPermitted": false } }, { "id": "v-bni", "vertexKind": "Institution", "displayName": "BNI", "jurisdiction": "ID", "identifiers": { "bic": "BNINIDJA" } }, { "id": "v-nostro-bni-usd", "vertexKind": "AccountCorridor", "displayName": "BNI USD Nostro corridor", "jurisdiction": "ID", "identifiers": { "internalOrgId": "BNI", "currency": "USD" } }, { "id": "v-pool-bni-usd", "vertexKind": "LiquidityPool", "displayName": "BNI USD pool", "jurisdiction": "ID", "metadata": { "currency": "USD", "availableAmount": "70000000000", "providerId": "v-bni", "expiresAt": "2026-03-29T18:00:00Z" } }, { "id": "v-fx-bni", "vertexKind": "FxVenue", "displayName": "BNI FX", "jurisdiction": "ID", "capabilities": { "currenciesAllowed": ["USD", "IDR"], "fxPermitted": true } }, { "id": "v-rail-id", "vertexKind": "SettlementRail", "displayName": "ID domestic settlement", "jurisdiction": "ID", "capabilities": { "settlementTypes": ["RTGS"] } }, { "id": "v-kanaya", "vertexKind": "Institution", "displayName": "Bank Kanaya", "jurisdiction": "ID" }, { "id": "v-parak", "vertexKind": "BeneficiaryAnchor", "displayName": "PT Parak International", "jurisdiction": "ID", "metadata": { "creditInstitutionId": "v-kanaya" } }, { "id": "v-alt-correspondent", "vertexKind": "Institution", "displayName": "Alternate correspondent (failover candidate)", "jurisdiction": "ID" } ], "edges": [ { "id": "e-omnl-nostro", "sourceId": "v-omnl", "targetId": "v-nostro-bni-usd", "edgeKind": "correspondent", "costVector": { "feeCost": 0, "latencyMs": 200, "liquidityAvailability": 1, "regulatoryPenalty": 0, "reliability": 0.999 } }, { "id": "e-nostro-pool", "sourceId": "v-nostro-bni-usd", "targetId": "v-pool-bni-usd", "edgeKind": "liquidity_link", "liquidityRef": "pool-bni-usd-001", "costVector": { "feeCost": 0, "latencyMs": 50, "liquidityAvailability": 0.95, "regulatoryPenalty": 0, "reliability": 0.998 } }, { "id": "e-pool-fx", "sourceId": "v-pool-bni-usd", "targetId": "v-fx-bni", "edgeKind": "liquidity_link", "costVector": { "feeCost": 0, "latencyMs": 100, "liquidityAvailability": 0.93, "regulatoryPenalty": 0, "reliability": 0.997 } }, { "id": "e-fx-rail", "sourceId": "v-fx-bni", "targetId": "v-rail-id", "edgeKind": "fx_quote", "metadata": { "pair": "USD/IDR", "rateRef": "DEMO-15000" }, "costVector": { "feeCost": 500, "latencyMs": 400, "liquidityAvailability": 0.9, "regulatoryPenalty": 0, "reliability": 0.996 } }, { "id": "e-fee-compliance", "sourceId": "v-rail-id", "targetId": "v-kanaya", "edgeKind": "fee_hop", "feeRef": "fee-reg-omnl-bni", "costVector": { "feeCost": 200, "latencyMs": 50, "liquidityAvailability": 1, "regulatoryPenalty": 0, "reliability": 0.999 } }, { "id": "e-settle-parak", "sourceId": "v-kanaya", "targetId": "v-parak", "edgeKind": "settlement_path", "costVector": { "feeCost": 0, "latencyMs": 300, "liquidityAvailability": 1, "regulatoryPenalty": 0, "reliability": 0.998 } }, { "id": "e-omnl-alt-nostro", "sourceId": "v-omnl", "targetId": "v-alt-correspondent", "edgeKind": "correspondent", "costVector": { "feeCost": 800, "latencyMs": 350, "liquidityAvailability": 0.7, "regulatoryPenalty": 0, "reliability": 0.99 }, "metadata": { "role": "failover_candidate" } } ], "overlays": { "liquiditySnapshotId": "demo-snapshot-001", "notes": "Demonstration only; ids and costs are illustrative." } } ``` --- ## 12. Related work (next documents) | Next artifact | What this routing graph doc provides | |---------------|--------------------------------------| | **Policy DSL specification** | `policyRefs` on edges, `regulatoryPenalty`, jurisdiction tags, and constraint hooks for hard rejects. | | **Liquidity pool registry model** | Canonical `poolId`, refresh TTL, and binding to `LiquidityPool` vertices / `liquidity_link` edges. | | **Decision explanation engine** | Path trace over `vertices`/`edges` with human-readable labels and cost breakdowns from `costVector`. | | **Failover routing strategy model** | k-shortest paths, edge_disjoint alternates, and scoring on top of this graph without redefining V/E types. | --- ## References - [hybx_compliance_routing_sidecar_technical_plan.md](hybx_compliance_routing_sidecar_technical_plan.md) — sidecar architecture, Routing Engine, API sketch. - [transaction-composer/src/types/nodeTypes.ts](transaction-composer/src/types/nodeTypes.ts) — `NodeKind`, `TransactionNodeData`. - [transaction-composer/src/orchestration/transactionCompiler.ts](transaction-composer/src/orchestration/transactionCompiler.ts) — `CompiledTransaction` buckets and topology.