feat(3jane): redefine junior-buffer alert around at-risk credit coverage - #324
Closed
spalen0 wants to merge 1 commit into
Closed
feat(3jane): redefine junior-buffer alert around at-risk credit coverage#324spalen0 wants to merge 1 commit into
spalen0 wants to merge 1 commit into
Conversation
The 3Jane Junior Buffer Low alert was noisy because it measured sUSD3 against the entire borrow book, which is a structural design constant (~10-15% first-loss tranche) and carries no information about a *change* in risk. This replaces it with a tiered, real-signal coverage check. New monitors (read from existing Envio snapshot list - no double-fetch): * **Junior Tranche Coverage** - sUSD3 backing / severity-weighted at-risk credit. HIGH below 2.0x. The sUSD3 stakers' risk view and the leading indicator that sUSD3 PPS is about to drop. * **Senior Tranche Coverage** - (Insurance Fund + sUSD3) / severity- weighted at-risk credit. HIGH below 1.5x, CRITICAL below 1.0x. The USD3 holders' risk view; CRITICAL also triggers the emergency withdrawal dispatch already wired to 3jane. * **At-risk exposure aggregator** - sum(ending_balance * weight(bucket)) across all Delinquent/Default borrowers. Weights map the existing default_bucket values to an expected-loss proxy: default=1.0, 1d=0.9, 3d=0.7, 7d=0.5, 14d=0.3, delinquent=0.3. Also exposes largest_borrower_exposure for a future concentration check. With no impaired borrowers the coverage alerts are silent regardless of the sUSD3/whole-book ratio, eliminating the original false alarm. When Envio is unreachable the coverage checks are skipped and only the demoted structural check runs; no alerts on missing data. The original whole-book ratio becomes a LOW-severity informational 'Junior Buffer Drifting' check that fires only on a structural floor breach at 8% or a sharp drop of >=3pp versus a cached trailing baseline - keeping the leverage-drift signal without paging on the steady state. Deduplication, type annotations, docs, and monitoring.yaml are all updated. Closes #323.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the noisy "3Jane Junior Buffer Low" alert (which measured sUSD3 against the entire borrow book — a structural design constant, not a risk signal) with tiered, real-signal coverage checks based on severity-weighted at-risk credit.
Closes #323.
New monitors
sUSD3 / weighted at-risk). HIGH when the sUSD3 stakers alone cannot absorb the impaired book (< 2.0x). The leading indicator that sUSD3 PPS is about to drop.(Insurance + sUSD3) / weighted at-risk). HIGH < 1.5x, CRITICAL < 1.0x. The USD3 holders' risk view. CRITICAL also dispatches the emergency-withdrawal hook (3jane is inDISPATCHABLE_PROTOCOLS).At-risk exposure aggregation
New
AtRiskExposuredataclass +compute_at_risk_exposure()helper. Sumsending_balance * weight(default_bucket)across all Delinquent/Default borrowers from the existingBorrowerRepaymentSnapshotlist. Weights are an expected-loss proxy (default=1.0, 1d=0.9, 3d=0.7, 7d=0.5, 14d=0.3, delinquent=0.3) and are module-level constants so they're easy to tune against historical cure/charge-off rates. Also exposeslargest_borrower_exposurefor a future concentration check (one whale defaulting can blow the buffer even if the aggregate looks fine).Edge cases handled
ENVIO_GRAPHQL_URLunset / request failed / GraphQL errors):load_borrower_default_watch_snapshots_from_envio()now returnsNoneto signal data unavailable; coverage checks are skipped and only the demoted structural check runs. No coverage alert on missing data.main()and shared with the default-watch and coverage checks, so we never double-fetch from Envio.should_alert_value_dropwith new cache keys (3JANE_JUNIOR_COVERAGE_ALERTED,3JANE_SENIOR_COVERAGE_ALERTED). Senior coverage escalates HIGH → CRITICAL on the same run when the value drops further; recovery above the 1.5x threshold re-arms.ending_balance_raw(with fallback toamount_due_rawwhen 0), as recommended in the issue.Acceptance criteria
ruff,mypy, and tests pass (658 passed, 2 skipped; 0 new mypy errors).Test coverage
16 new tests cover:
at_risk <= 0clears both coverage alertsExisting tests updated to reflect the new demoted LOW-severity behaviour (no longer alerts at 12.5% design value).
🤖 Generated with Claude Code