Deterministic. Versioned.
Replayable.

Every Pegana alert is the output of a pure function over four inputs: the asset's intrinsic value, the executable market quote, threshold configuration from assets.toml, and the EWMA + hysteresis state at the moment of decision. The function lives in the open-source methodology Rust crate, versioned by semver, pinned to a git tag.

spread = 1 − market / intrinsic

Verify any alert.

Install the verifier from releases.pegana.xyz — a self-hosted mirror that ships a SHA-256-pinned binary plus a Sigstore attestation.

verify.shshell
# Install (curl|sh — reads SHA256SUMS, verifies, drops the
# binary in ~/.pegana/bin/, no daemon)
curl --proto '=https' --tlsv1.2 -LsSf \
  https://releases.pegana.xyz/pegana-replay-installer.sh | sh

# Verify a single alert
pegana-replay --alert-id <UUID>

# Verify the on-chain commitment too
pegana-replay --alert-id <UUID> --verify-onchain

Ships a pre-built binary for aarch64-apple-darwin; other platforms fall back to cargo install --git until the GA Sigstore fan-out. Source

A version has three states.

The engine refuses to start on a broken version — past receipts from broken versions stay queryable and replayable, but render a warning.

active

currently emitting receipts

The version the running engine is built against. Receipts produced by this version are trusted.

deprecated

superseded, still valid

A newer version is active. Receipts from this version remain valid and replayable.

broken

bug confirmed, engine refuses to run

A bug was found post-release. The engine refuses to start to prevent bad alerts. Past receipts are preserved with a warning.

The canon, hashed into every receipt.

Every receipt hashes the canonical assets.toml as part of its inputs. Replay still works against historical receipts — each receipt embeds the canonical TOML it was produced under.

63active assets across7classes
LST38Stable · Yield10Stablecoin8Stable · DN3Stable · CDP2Synth · Leverage1Stable · FX1
SymbolClassPeg targetVerified
jitoSOLLSTSOLon-chain
mSOLLSTSOLon-chain
bSOLLSTSOLon-chain
INFLSTSOLon-chain
bbSOLLSTSOLon-chain
JupSOLLSTSOLon-chain
USDCStablecoinUSDon-chain
USDTStablecoinUSDon-chain

Plus 2 symbols kept in the canon but currently inactive: BRZ, pbUSDC.

Methodology changelog.

Rendered at build time from crates/methodology/CHANGELOG.md.

v0.6.12026-07-08

Bug fix (PATCH — no API signature change, unlike 0.6.0). Follow-up to 0.6.0: apply_ewma_pure's overflow guard protects the EWMA arithmetic itself, but its output (discount_smooth) has no bound on magnitude beyond "didn't overflow that specific computation." Three downstream consumers — premium_sanity_violated, discount_sanity_violated, and state_for_bps_discount — all did unchecked discount.abs() * 10_000 (bps scaling), so an untrusted rederive() input crafted to produce a large-but-non-overflowing discount_smooth (a moderately large out-of-range alpha is enough) still panicked one step later. Found via a systematic grep for the same .abs() * Decimal::from(10_000...) pattern across the crate after shipping 0.6.0 — same bug class, three more sites.

premium_sanity_violated/discount_sanity_violated now treat an overflowing magnitude as violated (true) — consistent with their purpose: an implausible discount forces honest-dark UNKNOWN rather than a false verdict. state_for_bps_discount saturates to BlackSwan (not the 0/Pegged a naive parse-failure default would give) — unlike a CR ratio where larger is healthier, a larger discount is always worse, so overflow must map to the most alarming band. Zero verdict change on the 2025-10-10 backtest golden (123 receipts, re-baselined, no diff beyond version+hash) — unreachable in the live engine for the same reason as 0.6.0.

v0.6.02026-07-08

API change (MINOR), not a verdict change. apply_ewma_pure and state_for_cr used unchecked Decimal arithmetic, which rust_decimal panics on overflow rather than erroring — same bug class as the 0.4.0-era discount.rs fix, never applied to these two functions. apply_ewma_pure now returns Option<Decimal> (breaking signature change, hence MINOR); state_for_cr saturates to the healthiest band on overflow instead of panicking (signature unchanged). Unreachable in the live engine by construction — discount_raw is always |d| < 1 before reaching apply_ewma_pure, and by induction so is every prior EWMA output, so the overflow can only be triggered by out-of-range alpha/ewma_prev/hyusd_cr in an untrusted receipt fed to rederive (i.e. via pegana-replay's public verify CLI, not the engine's own pipeline). Every legitimate, in-range input produces the exact same result as before — only pathological inputs that previously crashed the process now return None/saturate instead. No existing receipt's recorded verdict changes.

v0.5.02026-07-01

Behavior change (MINOR). Intrinsic-sanity guard — discount_sanity_violated + NAV_DISCOUNT_SANITY_BPS = 3000. Mirror of the 0.4.0 NAV premium-sanity on the DISCOUNT side: a garbage-HIGH intrinsic (>30% discount on a direction-sensitive class) is a broken NAV anchor, published as honest-dark UNKNOWN instead of a false CRITICAL/BLACK_SWAN (JLP 2026-06-24: RR feed printed 21817 vs 3.41). Verdict-affecting only for >30%-discount garbage prints; provably inert on the live universe (max real direction-sensitive discount = 245 bps) and on the 2025-10-10 backtest. Not ADR-0024 (single-sample magnitude, not constancy). See ADR-0030.

v0.4.02026-06-15

Behavior change (MINOR) — the "validation follow-ups" cluster (ADR-0025). Additive publish-time honesty guards; no existing band thresholds change.

  • NAV-sanity premium cap — new premium_sanity_violated(class, discount) + NAV_PREMIUM_SANITY_BPS = 1000. A premium (negative discount) beyond 10% on a direction-sensitive class (LST, stable_yield) is not demand pressure — it means the NAV/intrinsic anchor is broken. The ADR-0021 premium→PEGGED carve-out would otherwise mask it as a confident PEGGED off a garbage anchor (sHYUSD: market ≈ +30% over a thin-Jupiter NAV print). The engine now publishes honest-dark UNKNOWN instead. Magnitude check on one smoothed sample — explicitly NOT the discount-constancy freeze detector rejected in ADR-0024. >6× margin over the widest legitimate premium observed across the 26 assets (INF ≈160 bps).
  • Confidence gate — an asset with NO Pyth feed anywhere (neither a pyth_spot/jupiter_usd-numeraire market nor a pyth_* intrinsic) has no independent oracle cross-check, so it can no longer assert high confidence: the engine's pyth_confidence_for_asset caps it at medium. Blast radius today is exactly sUSD (DexScreener ~$8k pool + custom rate); every other active asset has a Pyth dependency and is unchanged. Answers the validation grill "why does sUSD carry HIGH off a thin single source?". (Display-only — confidence is not a band threshold; no state-classification or calibration impact.)
  • BLACK_SWAN reachable from spreadstate_for_bps_discount gains a black_swan band, default 2 × critical (overridable via black_swan / black_swan_bps). Previously the bps path topped out at CRITICAL and only the CR path (hyUSD) could reach BLACKSWAN, so the publicly-advertised 5th state was unreachable for 25/26 assets — the validation "Four/Five states" honesty gap. Now a >2×-critical move (a USDC 4%+ break, a UST-style CRITICAL→BLACKSWAN cascade) is labelled terminal-grade. Re-labels only the most extreme moves (already CRITICAL today); nothing at/below critical changes. It auto-exits via normal hysteresis — the engine does NOT enforce a "never auto-exits / operator-reset" terminal state (no reset path exists to make that safe), and the CR-path black_swan already behaved this way. See ADR-0025.
v0.3.02026-06-04

Behavior change (MINOR). Activated immediately under the ADR-0009 critical-bug exception ("wrong alerts firing"): during the calibration window hyUSD flapped PEGGED↔DRIFT ~52× in two days as its collateral ratio oscillated around the 130% drift band (oracle jitter on the SOL-priced reserves clipping the threshold). The CR classification path carried neither the EWMA smoothing nor the magnitude deadband the spread path already had (ADR-0021).

  • CR magnitude hysteresis (Schmitt-trigger deadband) — new classify_cr_with_hysteresis. The CR analog of classify_with_hysteresis with the band inverted (for a collateral ratio, a LOWER value is worse). A worsening CR escalates at the normal threshold; a relaxation toward a looser state only commits once CR rises above threshold × (1 + deadband_pct) (engine default 2%). Why: time-hysteresis (transition.rs confirmup/decaydown) alone could not stop sustained oscillation at the boundary. Measured on the calibration window, the deadband cut hyUSD's transitions ~80% (52 → ~10) while keeping escalation immediate.

Receipt schema: v1 (unchanged). Verdicts change for some valid hyUSD inputs, hence the MINOR bump; replay stays deterministic via the recorded methodology_git_sha. See ADR-0023. v0.2.0 → deprecated (still valid for replay), superseded by 0.3.0.

v0.2.02026-06-01

Behavior change (MINOR). Activated immediately under the ADR-0009 critical-bug exception ("wrong alerts firing"): v0.1.0's symmetric classification was emitting two classes of false alerts during the calibration window — DRIFT↔PEGGED flapping when a smoothed discount sat near a threshold, and a 🚨 DRIFT on a benign LST premium (market above redemption value).

  • Magnitude hysteresis (Schmitt-trigger deadband) — new classify_with_hysteresis. Escalation still uses the normal threshold, but a relaxation toward a looser state only commits once the smoothed discount falls below threshold × (1 − deadband_pct) (engine default 25%). Time-hysteresis (transition.rs confirmup/decaydown) alone could not stop oscillation at the boundary. Why: JupSOL flapped DRIFT↔PEGGED around 60 bps (e.g. 84.7 → 48.8 bps); the deadband (exit 45 bps) holds the state through the dead zone.
  • LST premium carve-outis_direction_sensitive now includes AssetClass::Lst. A premium (market > intrinsic) normalizes to PEGGED, same as yield-bearing stables. Why: for an LST the risk signal is the discount (redemption stress, cf. stETH −7% in 2022, ezETH depeg); a premium is demand pressure, not stress. Discount-side classification is unchanged.

Receipt schema: v1 (unchanged). Verdicts change for some valid inputs, hence the MINOR bump; replay stays deterministic via the recorded methodology_git_sha. See ADR-0021. v0.1.0 → deprecated (still valid for replay), superseded by 0.2.0.

v0.1.02026-05-29
  • Initial public release.
  • Extracted pure functions from pegana-engine v0.1.0; no behavior change.
  • Provides: computediscount, applyewmapure, transitiondecide, stateforbps_discount*, stateforcr, isplausiblediscountsample, canonicalassetshash, canonicalreceipthash, methodologyversion, methodologygitsha.
  • Receipt schema: v1.

What this is not.

Not SOC 2 complianceNot a regulatory auditNot financial advice

Pegana's audit log is a verifiable computation log: anyone can prove which methodology produced which alert, given which inputs. Use it to verify our claims; don't substitute it for your own judgment.