Repository navigation
Epic: Deliver event-driven Morpho vault governance monitoring #437
Description
Goal
Deliver Morpho Vault V1/V2 governance alerts within a few seconds of block inclusion. Use the continuous Envio subscriber proven by the timelock pilot, while retaining the existing daily state-based Morpho jobs as a low-cost completeness and semantic audit.
Initial latency target: at most five seconds p95 from observed block inclusion to durable event acceptance. Record block inclusion, Envio detection, durable acceptance, alert enqueue, and Telegram delivery separately. The WebSocket path is near-real-time transport; HTTP reconciliation and daily state snapshots remain the recovery and correctness layers.
Architecture
Blockchain governance event
-> Envio indexes event
-> continuous worker in yearn/monitoring
-> durable event inbox and deterministic Morpho lifecycle rules
-> independent alert delivery job
-> existing Morpho Telegram routing
Envio HTTP reconciliation
-> the same durable inbox
Existing daily Morpho state monitors
-> parity/divergence audit
- Envio owns event indexing and provenance; monitoring owns scope, lifecycle rules, alert state, recovery, grouping, and delivery.
- The monitoring worker opens outbound GraphQL WebSocket subscription(s). No alert policy or Telegram delivery belongs in Envio handlers.
- Keep bounded, paginated HTTP reconciliation while connected. Persist independent per-chain progress and deduplicate event occurrences and alert requests.
- Reuse one supervised process where practical. Share chain-head reads and an HTTP session so the fallback does not multiply TCP/TLS traffic.
- Keep Telegram outside ingestion. A slow or failed send must not block event intake, and delivery state must survive restarts.
- Observe events at first inclusion. Record changed, removed, and restored rows after reorgs; define correction behavior explicitly.
- Agree startup, historical replay, and recovered-during-outage policies before enabling alerts. A historical baseline must not emit old governance alerts, while genuinely new events recovered after downtime must still be considered.
- Preserve current per-vault grouping and severities. Recovered lifecycle batches must be evaluated in order so a Submit followed by Accept/Revoke during an outage is not reported as still pending.
Morpho scope
Use the explicit vault allowlists in protocols/morpho/config.py, currently covering Vault V1/V2 deployments on Mainnet, Base, HyperEVM, Robinhood Chain, and Katana.
Vault V1
Preserve the current protocols/morpho/governance.py coverage:
- pending supply-cap changes
- pending market removals
- pending timelock changes
- pending guardian changes
- the corresponding submit, revoke, and accept/set lifecycle events needed to reconstruct current state
Vault V2
Preserve the current protocols/morpho/governance_v2.py coverage:
- pending timelocked operations: Submit, Accept, and Revoke
- owner and curator changes
- sentinel and allocator membership changes
- adapter additions and removals
Out of scope for this epic:
- Morpho market-risk, allocation, liquidity, and bad-debt monitoring
MorphoMarketV1AdapterV2internal timelocks, which the current governance monitor explicitly does not cover- automated onchain execution in response to governance events
The daily V1/V2 governance jobs remain enabled during the rollout and report divergence between indexed event-derived state and the authoritative state snapshots. They should not be removed merely because live delivery is enabled.
Pilot and hosting decision
The timelock implementation landed in PR #442. It provides the reusable graphql-transport-ws client, durable SQLite inbox, per-chain checkpoints, HTTP reconciliation, provenance, reorg tracking, health reporting, and systemd supervision.
A read-only probe on 2026-10-24 against the configured Envio deployment:
- negotiated
graphql-transport-ws - answered Envio pings before and after
connection_ack - received a five-row
TimelockEventsubscription snapshot with no GraphQL errors - received HTTP 200 and the same five rows from the equivalent HTTP query
- matched WebSocket and HTTP event IDs exactly
- completed the WebSocket handshake and first snapshot in 0.343 seconds
This proves connectivity and snapshot consistency, not block-to-delivery latency or long-running recovery behavior.
Envio fully supports hosted WebSockets only on Dedicated; other plans expose them with reduced support and recommend staying below ten concurrent connections. The production decision must use the timelock pilot's live coverage/latency report and keep HTTP reconciliation regardless of hosting choice.
The WebSocket itself is not the expected resource constraint. A separate Morpho connection would keep the deployment well below the documented connection caution. At a 60-second cadence, HTTP reconciliation dominates traffic and request count; implementation should reuse the existing worker, share metadata queries, and keep persistent HTTP connections.
Implementation checklist
- 1. Run a timelock event subscriber alongside the hourly monitor — implemented by PR #442
- 2.
Move timelock alert delivery to the continuous worker— not planned. Every monitored timelock delays operations by at least 24h, so the scheduledtimelock-alertstask stays the timelock sender, now every 30 minutes (#454). The timelock stream stays an observation pilot that feeds the transport/hosting decision in Observe and validate Morpho event delivery before production cutover #450. - 3. Add Morpho Vault V1/V2 governance event entities, handlers, configured addresses, and required chains to
yearn/yearn-envio— merged in yearn-envio#71; deployment and backfill are a prerequisite of Observe and validate Morpho event delivery before production cutover #450, not deployed yet (the configured endpoint has noMorphoGovernanceEvent, HyperEVM, or Robinhood data as of 2026-10-26). AdapterburnSharesindexing (yearn-envio#72) was closed unmerged. - 4. Generalize the monitoring subscriber/inbox for named event streams while retaining per-chain checkpoints, provenance, reorg handling, health, and reports — merged in PR #452 (adds the observation-only
morphostream, off by default) - 5. Implement deterministic Morpho event lifecycle mapping, existing severity parity, per-vault grouping, and asynchronous Telegram delivery — in review: PR #453 (on-chain-confirmed decisions using the daily jobs' checks; observe by default, shadow to the errors chat; production delivery left to Observe and validate Morpho event delivery before production cutover #450)
- 6. Compare event-derived state and alerts with both existing daily Morpho governance monitors; expose divergence and delivery health
- 7. Run live observation with alerts disabled, then shadow delivery, before making the continuous worker the production Morpho sender
Create focused issues for steps 3–7 before implementation, with the indexer work deployable independently from the monitoring consumer.
Delivery order
Complete the timelock production transport decision and alert migration.Not planned: timelock alerts stay scheduled (every 30 minutes); see step 2 above.- Define the normalized Morpho event schema and exact V1/V2 event signatures.
- Add Envio coverage and backfill from explicit per-chain deployment blocks. Do not alert from historical backfill.
- Generalize the worker and implement Morpho lifecycle rules against fixtures and indexed history.
- Run WebSocket and HTTP recovery tests, then compare live output with the daily state monitors.
- Enable shadow delivery, validate grouping/severity/parity, and only then switch the production Morpho sender.
Epic acceptance criteria
- Timelock and Morpho streams run in one supervised process without one stream blocking the other; only the Morpho stream delivers alerts (timelock alerts stay scheduled).
- Every configured Morpho vault and every in-scope governance event has explicit indexed coverage.
- WebSocket delivery meets the five-second p95 durable-acceptance target, measured from included block time; HTTP recovery and Telegram delivery latency are reported separately.
- Disconnects, restarts, backlog recovery, duplicate snapshots, equal timestamps, late indexing, and reorgs do not silently lose events or duplicate completed alerts.
- Submit/Accept/Revoke and submit/revoke/set sequences recover correctly across outages; already-resolved actions are not presented as pending.
- Alert content, severity, routing, and per-vault grouping match the current V1/V2 governance monitors.
- Historical backfill cannot send unintended alerts. New events indexed during downtime remain eligible after restart.
- WebSocket and HTTP coverage, outages, reconciliation lag, state divergence, pending delivery, and delivery outcomes are observable.
- The existing daily state monitors stay enabled until event-derived state demonstrates parity; any later removal requires a separate decision.
- Relevant tests, monitoring metadata, protocol documentation, service supervision, and runbooks ship with each implementation issue.
Related work
- PR #442: continuous timelock subscriber and reusable transport/recovery foundation.
- Issue #439: move timelock alert delivery to the continuous worker.
- Issue #302 and PR #307: oracle-event consumer work; reuse suitable transport components without expanding this epic into oracle policy.
- Envio WebSocket documentation.
Activity
[-]Epic: Deliver event-driven infiniFi emergency monitoring[/-][+]Epic: Deliver event-driven Morpho vault governance monitoring[/+]gjwang commentedon Oct 25, 2026
For the HTTP recovery tests in steps 4–5, there is a fail-closed case worth preserving when generalizing the worker. On
8cb745ff9ff35970ea21ad9a8b861c869916b22b, a missing/null event collection is treated as a complete empty range.I reproduced this offline with the stored
envio.py,reconcile.pyand real SQLiteEventInboxcode (original AST, external import wiring stubbed, no production HTTP adapter): an active event at block 12, checkpoint 15, overlap 5 and processed head 20. Each of{},{"data":null},{"data":{}}and{"data":{"TimelockEvent":null}}returned normally, marked that eventremoved, and advanced the checkpoint to 20. In all four cases, the expected rejection did not occur.The separate controls behaved as expected: an explicit
TimelockEvent: []completed the empty range, a valid event stayed active, and a nonempty GraphQLerrorsarray raisedEnvioQueryErrorwithout changing event status or checkpoint.The boundary is
_data/fetch_range: theor {}/or []fallbacks let a malformed response reachmark_missingand checkpoint advancement. Requiring the expected data object and event list before reconciliation would preserve the checkpoint on malformed responses while retaining valid empty-array behavior.This was a synthetic response test, with no network or alert sends; I have not observed these payloads from the live Envio deployment. It is a recovery-hardening finding, not evidence of a production outage.
feat(morpho): live governance decisions from indexed events
fix(subscriber): fail closed on malformed Envio responses