Files
proxmox/hybx_routing_graph_data_model.md

436 lines
18 KiB
Markdown
Raw Normal View History

# 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 | 01 score, or available notional on this hop |
| `regulatoryPenalty` | Additive penalty from policy | Non-negative; 0 if none |
| `reliability` | Historical success / health | 01, 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.