Lesson 27 · The Oracle Bridger · Discovery Internals

Inheriting oracle dependencies

A pure-Cypher pass that gives markets and vaults the oracles their assets depend on. ~12 min.

Builds on: L26 · L20 · L2 Anchor: Aave collateral, vault allocations New: transitive ORACLE_DEP New: graph-only enrichment

L26 stamped one kind of edge: token → ORACLE_DEP → oracle. But L20's model A oracle attack needed something L26 never produced — a market that "consumes" the oracle. A lending market doesn't declare its oracle dependency anywhere on-chain; it just holds a collateral token that has one. The Oracle Bridger is the small, elegant pass that makes that implicit dependency explicit in the graph — and it does so with no RPC at all, just Cypher.

Your anchor: exposure flows through what you hold
An Aave market listing WBTC as collateral is exposed to whatever prices WBTC — if WBTC's oracle lies, the market mis-liquidates. A MetaMorpho vault holding that market is exposed to the same oracle, one hop further out. You reason about this transitively without thinking. The Oracle Bridger writes that reasoning into the graph: if you depend on a thing, you inherit the things it depends on.

1 · The gap it fills

After L26, only tokens carry ORACLE_DEP edges. Yet L20's eligibility test for a model-A oracle cell was "a market that both consumes this oracle and holds T as collateral." Where does "consumes this oracle" come from for a market node? It has to be derived — propagated up from the collateral token to the market:

WBTC token
ORACLE_DEPfrom L26
WBTC oracle
WBTC token
LENDING_COLLATERAL
Aave market
⇢ bridger adds
ORACLE_DEP
WBTC oracle

The bridger runs periodically (default 30 min), mirroring the batch pipeline's bridge_lending_market_oracles() / bridge_vault_oracles(). It is pure Neo4j computation — no RPC calls — which sets it apart from the RPC-heavy probes of L25/L26. It's a graph transitive-closure step, nothing more.

2 · The three inheritance paths

Each path is one Cypher MATCH … MERGE. Here's Path A, the lending-market case, in full:

MATCH (token)-[:LENDING_COLLATERAL]->(market)
MATCH (token)-[:ORACLE_DEP]->(oracle)
WHERE NOT EXISTS { MATCH (market)-[:ORACLE_DEP]->(oracle) }   // skip if already linked
MERGE (market)-[r:ORACLE_DEP]->(oracle)
SET r.source = 'oracle_bridge:lending', r.category = 'DEPENDS_ON', r.subcategory = 'ORACLE_DEP'
PathInheritance rule (read right-to-left as "X inherits the oracle of Y")source tag
Aa lending market inherits the oracle of a token used as its LENDING_COLLATERALoracle_bridge:lending
Ba vault inherits the oracle of a token it holds via VAULT_ASSEToracle_bridge:vault_asset
Ca vault inherits the oracle of a market it funds via VAULT_ALLOCATIONoracle_bridge:vault_allocation
Two Cypher idioms worth keeping (you flagged Cypher as a gap)
WHERE NOT EXISTS { … } is an anti-join: create the edge only if the market doesn't already have it — so re-running the pass is a no-op once converged (idempotent, the L9 discipline). And the source stamp on every bridged edge is provenance: a downstream reader can tell a transitively-derived ORACLE_DEP from one a refresher stamped directly, which matters when you're debugging why a market shows a given oracle dependency.

3 · The subtle part: ordering inside one transaction

Look at Path C again: a vault inherits the oracle of a market it allocates to. But a market only got its oracle from Path A. So Path C depends on Path A having already run. The code packs all three statements into one graphwrite.Request, applied in order inside a single transaction:

// one Request, statements applied A → B → C in one tx
req := &graphwrite.Request{
    Source:  "oracle_bridger",
    IdemKey: fmt.Sprintf("oracle_bridger:%s:%d", graphID, now.Unix()/intervalSecs),  // per-cycle bucket
    Cypher:  &graphwrite.CypherOp{Statements: []CypherStatement{ pathA, pathB, pathC }},
}
Why one transaction, in order, matters
Paths B and C may MERGE edges that are transitively reachable only because Path A just created a market→oracle edge in the same cycle (vault → market → oracle). Running them as separate transactions could let a vault miss an oracle until the next cycle. Co-residing them in one ordered tx preserves the "each path observes the others' writes" semantics the batch pipeline had. The IdemKey is bucketed by the interval window (now.Unix() / intervalSecs), so a retried publish within the same window de-duplicates rather than double-writing.

4 · Why this completes L20

Recall L20 had two oracle attack models. L26 supplied the upstream for model B (NAV / value-defining oracles). The Oracle Bridger supplies the upstream for model A (DeFi-borrow oracles):

Both oracle paths now have a fully-traced upstream
Model B: L26 binds token→oracle from the NAV registry. Model A: L26 stamps the token→oracle edge, then the bridger propagates it to the markets and vaults that hold the token (Path A) or fund those markets (Paths B/C). The risk engine's oracle eligibility is built entirely from edges these two lessons produce.

Check yourself

1. What does the Oracle Bridger actually create?
2. How does the bridger differ operationally from the admin and NAV discovery probes?
3. Path A bridges a lending market to an oracle. The inheritance rule is…
4. What does WHERE NOT EXISTS { MATCH (market)-[:ORACLE_DEP]->(oracle) } achieve?
5. Why are Paths A, B, and C packed into one ordered transaction rather than run separately?
6. The r.source = 'oracle_bridge:lending' stamp on a bridged edge is there to…
7. How does this pass complete L20's model A (DeFi-borrow) oracle attack path?
8. The IdemKey is bucketed by now.Unix() / intervalSecs. The effect is that…
↳ Ask your teacher
Try: "What's the difference between VAULT_ASSET and VAULT_ALLOCATION?" · "Could the bridger create a cycle in ORACLE_DEP, and would that matter?" · "Show me how graphwrite applies a multi-statement Request in one tx." · "Why is this periodic instead of triggered when a LENDING_COLLATERAL edge appears?" · "How does exposure-BFS (L16) use these inherited ORACLE_DEP edges?"

What you can now do

Discovery subsystem — four lessons, and the oracle story is whole
L24 (flywheel) → L25 (admins + severity) → L26 (token↔NAV-feed binding) → L27 (propagating oracle deps to markets & vaults). Both of L20's oracle attack models now rest on edges you can trace to the exact pass that wrote them.

Grounded in: pkg/enrichment/oracle_bridger.go (OracleBridger 30-min cycle, pure-Neo4j no-RPC, three MATCH…MERGE paths A/B/C with WHERE NOT EXISTS guards + source/category/subcategory stamps, single ordered graphwrite.Request for transitive observe-each-other semantics, interval-bucketed IdemKey; mirrors batch bridge_lending_market_oracles()/bridge_vault_oracles()). Verify against source — the code is the truth.