> ## Documentation Index
> Fetch the complete documentation index at: https://xoxno.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Oracle disruption runbook

> Contingency actions when a price feed goes stale, returns no data, disagrees with its partner leg, or is budgeted against the wrong feed nature.

What to do when the protocol stops pricing an asset. **Read first:** [Oracles](/docs/stellar-lending/dev/oracles).

The protocol fails closed on price. When a feed cannot be trusted, the affected flows revert rather than act on a doubtful number. An asset that stops pricing is the system working as designed, not a solvency event. Your job in an oracle disruption is to restore a trustworthy price, not to force a price through.

## What fails closed, and what keeps working

| Flow                                                          | Behavior when a price is unavailable                                            |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `supply`, `withdraw`, `borrow`, `repay` on the affected asset | Reverts.                                                                        |
| `liquidate` touching the affected asset                       | Reverts. Positions cannot be liquidated on a price the protocol does not trust. |
| Markets that do not reference the affected asset              | Unaffected.                                                                     |
| Existing balances, indexes, and debt                          | Untouched. Nothing is written when a read fails.                                |

No user funds move during an oracle disruption. The exposure is that liquidations pause on the affected markets, so a position that becomes unhealthy while the feed is down cannot be liquidated until pricing is restored.

## Triage

Identify which of these you have before changing anything.

| Symptom                                        | Error                             | Likely cause                                                                 |
| ---------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------- |
| One asset stops pricing, feed timestamp is old | `#206 PriceFeedStale`             | Feed age exceeds its budget, or a leg-age spread bound is tripped.           |
| Legs disagree beyond the outer band            | `#205 UnsafePriceNotAllowed`      | Genuine market dislocation, or one leg is wrong.                             |
| Price is outside the configured band           | `SanityBoundViolated`             | Sanity bounds set too tight, or a real price move past them.                 |
| Self-hosted adapter returns nothing            | `NoDataForFeed` (adapter error 7) | Signer quorum not met inside the aggregation window.                         |
| An asset with a healthy own feed still fails   | Nested error from a reference     | A reference leg it depends on is failing. See [Blast radius](#blast-radius). |

## Feed nature and staleness budgets

This is the subtlest failure in the system and the one most likely to recur when listing a new asset.

A feed has a `FeedNature` of either `Market` or `Fundamental`, and that choice changes how staleness is judged.

* A **push feed triggered by deviation** only publishes when the price moves past a threshold. If the price is flat, no push is due, and the feed legitimately ages. **Age is not a proxy for error on a deviation feed.** A stale value means the price has not moved.
* A **heartbeat feed** publishes on a fixed cadence. Age past that cadence genuinely indicates a problem.

Two bounds apply, and both must fit the feed:

1. **The leg's own budget**, `max_stale_seconds`. Must cover the feed's slowest legitimate publication interval.
2. **The leg-age spread bound**, `MAX_LEG_AGE_SPREAD_SECONDS` (3600s). This applies **only when both legs are `Market` nature**. It exists because the blend weights both legs equally, so a market leg far older than its market partner would drag the midpoint while each still satisfies its own budget. A `Fundamental` leg is exempt, because it prices a slow-moving quantity and its own bound is the intended budget.

Raising `max_stale_seconds` alone does not fix a slow feed paired with a fast one. If both legs are `Market`, the 3600s spread bound still applies, and a 12-hour-heartbeat feed can never stay within an hour of a five-minute Reflector leg. Marking the slow leg `Fundamental` is what turns the spread bound off.

<Warning>
  When listing an asset, confirm the publication model of every leg before setting its budget. A deviation-triggered or long-heartbeat feed budgeted as `Market` with a short window will price correctly until the market goes quiet, then fail closed exactly when nothing is wrong.
</Warning>

Keep the leg budget below the reference's own `max_price_stale_seconds` so the leg budget binds first and the failure points at the leg that caused it.

## Blast radius

`compose()` reads the first source, and propagates a nested error before it reads the second. A failing reference therefore takes every dependent asset with it, even when those assets have a healthy standalone leg of their own, because the second source is never reached.

When several assets fail together, look for a shared reference rather than diagnosing each asset separately. Repair the reference and the dependents recover.

After changing an oracle, `set_oracle` re-probes dependents. A clean re-probe across all dependents is your confirmation that the repair reached them.

## Repair

<Steps>
  <Step title="Confirm the failure class">
    Read the error code and the feed timestamps for both legs. Distinguish a feed that is genuinely down from a budget that does not fit the feed.
  </Step>

  <Step title="Decide whether the price is wrong or merely old">
    If the feed is publishing correctly and the value is simply old because the price has not moved, this is a configuration fault. If the feed has stopped publishing, this is a provider fault.
  </Step>

  <Step title="For a configuration fault, correct the leg definition">
    Set the nature and `max_stale_seconds` to match the feed's real publication model. Submit through governance as an oracle update.
  </Step>

  <Step title="For a provider fault, wait or fail over">
    The protocol is already refusing to price, which is the safe state. Restore the provider, or move the asset to a different source through governance.
  </Step>

  <Step title="Verify the dependents">
    Confirm `set_oracle` re-probed every dependent without reverting, then read a price for each affected market.
  </Step>
</Steps>

## Do not

* **Do not widen sanity bounds or tolerance bands to clear a stale error.** Staleness and deviation are separate checks; widening bands does not fix age, and it removes a guard you will want during a real dislocation.
* **Do not unpause or force a price to unblock liquidations.** A liquidation executed on an untrusted price is worse than a delayed liquidation.
* **Do not treat one asset's failure as isolated** until you have checked whether a shared reference is the cause.
