Files
proxmox/hybx_routing_graph_data_model.md
defiQUG 6c5fdcfd62 chore: pnpm lockfile, info-defi-oracle-138 app, token-lists, OMNL discovery output
- Refresh pnpm-lock.yaml / workspace after prior merge
- Add Chain 138 info hub SPA (info-defi-oracle-138)
- Token list and validation script tweaks; path_b report; Hyperledger proxmox install notes
- HYBX implementation roadmap and routing graph data model

Note: transaction-composer is a nested git repo — convert to submodule before tracking.
Made-with: Cursor
2026-03-31 22:32:15 -07:00

436 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.