# 2026-08-19 — Quattro 🚨 per una manutenzione annunciata, e cosa c'era dietro **Richiesta:** *"su Adp_VPS mi sono arrivati questi messaggi"* — quattro allarmi VENUE WATCH del 18/08, incollati senza domanda. La domanda vera era: e' successo qualcosa di grave? **No.** Ma il modo in cui l'ho scoperto ha rivelato tre difetti, e uno riguarda gli ordini. **Toccati:** `src/live/venue_watch.py`, `src/live/deribit.py` (tabella `_CONTRACT` + `check_specs`), `scripts/live/venue_watch.py`. **Test:** `tests/test_venue_watch.py` 23 → **35**; suite intera **618 verdi**. **Book, pesi, `config/live.json`, THRESHOLD_BPS, PERSIST_HOURS: INVARIATI.** --- ## 0. Cosa era successo davvero Manutenzione Deribit **annunciata il 14/08 per il 18/08 alle 09:00 UTC**, downtime dichiarato **15–30 minuti**. Il book si e' astenuto ai giri delle 09:07 e 10:07 UTC — `conto non leggibile (offline) -> stop, non eseguo a cieco` — e alle 11:07 eseguiva di nuovo. **Il sistema ha fatto la cosa giusta.** I 572 minuti di "feed SKH stantio" non erano una deriva nascosta: con la coda 5m non attaccabile il book e' ripiegato sul feed certificato, che e' di suo vecchio di ~9,5 ore. Prima (08:07) e dopo (11:07) diceva `fresco (0 min)`. Ma `venue_watch` ha visto la piattaforma bloccata fino alle **14:07 UTC**: la manutenzione ha sforato l'annuncio **di ore, non di minuti**. E ha mandato quattro 🚨 identici, **tutti dopo che il book aveva gia' ripreso**. ## 1. Perche' quattro messaggi uguali: l'allarme non aveva memoria Il rilevatore di dislocazione e' tarato con cura — 100 bps, 4 ore a segno costante, «zero falsi allarmi in 8 anni», controllo positivo su Bitfinex 2018-19 — e la macchina a stati `step()` allerta **una volta per streak** e poi tace. Accanto, il blocco piattaforma era un `if` secco senza stato: ```python if obs["platform_locked"]: report["alerts"].append("Deribit public/status: PIATTAFORMA BLOCCATA (locked=true)") ``` Nessuna persistenza, nessuna deduplica, nessuna nozione di manutenzione. Un rilevatore disciplinato con accanto uno che grida al lupo ogni ora, **e con lo stesso titolo**: *«PRIMO PASSO: prelievo di prova»*. Il costo non e' il fastidio: e' che un allarme massimo speso per un evento atteso e' un allarme che non verra' letto il giorno che e' vero — l'unico giorno che conta. Ora il lock passa da `lock_step()`, pura e testata, con gli stessi livelli degli altri stati: | condizione | esito | |---|---| | bloccata + manutenzione dichiarata, entro `MAINT_GRACE_HOURS` (2) | ⚠️ **una volta**, «attesa, non allarme» | | manutenzione che **sfora** la tolleranza | 🚨 **una volta**, «ha SFORATO 2h» | | bloccata **senza** manutenzione dichiarata | 🚨 subito, una volta | | rientrata | avviso di rientro, una volta | | `public/status` **illeggibile** | **niente**: le ore restano dove sono | Rigiocando il 18/08: quattro messaggi identici diventano **⚠️ → 🚨 (ha sforato) → rientrato**. Tre messaggi che dicono tre cose diverse. ⚠️ L'ultima riga della tabella e' quella che mi premeva: dichiarare «e' rientrato» perche' non si e' riusciti a *guardare* sarebbe la bugia peggiore possibile in questo file. `locked is None` non azzera niente e non annuncia niente. ⚠️ E la tolleranza non e' un'assoluzione: la manutenzione declassa l'allarme **solo dentro la finestra**, e oltre lo **rialza**. Il 18/08 e' la ragione per cui la regola non poteva essere «se e' manutenzione, stai zitto»: Deribit aveva annunciato mezz'ora ed e' rimasta bloccata per ore. Una manutenzione che dura sei volte l'annuncio **e' di nuovo una notizia.** ## 2. Il messaggio dichiarava un valore che non aveva letto Il parser accetta qualunque valore diverso da `false`/`none` — Deribit risponde anche `partial` — e il testo era cablato: `(locked=true)`. Il runbook al passo 2 manda a controllare *proprio quel campo*: mandarci qualcuno con in testa la stringa sbagliata e' peggio che non dirgliela. Adesso `platform_status()` restituisce il valore grezzo, il messaggio lo stampa e lo **stato lo salva**. Del 18/08 non e' piu' ricostruibile quale fosse: nessuno lo registrava. ## 3. Il difetto che poteva costare: le specifiche contratto Cercando altro, il confronto con gli annunci Deribit ha trovato una cosa che nessun allarme sorvegliava. Il 18/08 dopo le 09:00 UTC Deribit ha aggiornato **tick, contract size e minimum order size dei perpetual lineari USDC** (annuncio del 14/08). La tabella `_CONTRACT` di `deribit.py`, che e' quella con cui si costruiscono gli ordini, era **cablata a mano** e non se n'e' accorta: | | dichiarato | venue dal 18/08 | |---|---|---| | BTC tick | 0.5 | **0.1** | | ETH tick | 0.05 | **0.01** | | ETH min/step | 0.001 | **0.0001** | Nessun ordine e' stato rifiutato, e la ragione **non e' merito nostro**: i cambi erano *riduzioni*, e un valore piu' grosso resta conforme. Il costo effettivo era di granularita' — su ETH l'incremento minimo restava **$1.92** invece di **$0.19** su un conto da $597 (0,32% contro 0,03%). ⚠️ Il giorno che Deribit **alza** un minimo, la stessa cecita' fa **rifiutare gli ordini**, e non c'e' niente che lo dica prima. **Scelta di disegno: la tabella dichiarata resta l'autorita' per costruire un ordine; il venue diventa il controllore, non la fonte.** Prendere i valori dall'API dentro il percorso di esecuzione avrebbe reso l'ordine dipendente da come ha risposto una GET — cioe' non ricostruibile dopo. Invece `check_specs()` gira **nel venue_watch orario** (sola lettura, fuori dal percorso ordini) e confronta; la tabella si aggiorna a mano, di proposito, in git. Il confronto dichiara la **direzione**, che e' l'unica cosa che serve per decidere se correre: - `granularita'` — dichiarato piu' grosso: conforme, si perde precisione → ⚠️, si aggiorna con calma; - `rifiuto` — dichiarato piu' fine: **il venue rifiuta** → 🚨, si corregge subito. ⚠️ Uno strumento **non letto** non e' una divergenza e non viene contato come «combacia»: finisce in `non_letti`. Silenzio e uguaglianza non sono la stessa cosa, e solo una delle due e' rassicurante. Tabella aggiornata ai valori veri e verificata: `check_specs()` risponde `divergenze: []`, `non_letti: []`. Il floor `min_order_usd = $5` del book e' indipendente, quindi lo step piu' fine **non** apre la porta a ordini da 19 centesimi. ## 4. Il difetto piu' lento: la referenza non e' piu' indipendente `venue_watch` misura Deribit contro un consenso di referenze USD. Erano **due**: Coinbase e Bitstamp. Ma le comunicazioni del 12–18/08 dicono «Deribit **by Coinbase**», mettono il **Coinbase Index** come riferimento per una parte dei perpetual lineari, e instradano lo spot Deribit su **Coinbase Exchange**. Il rilevatore esiste per rispondere a «Deribit sta scollando dal mondo?». Se una delle due referenze e' **la casa madre**, uno shock di Coinbase muove Deribit e la referenza *insieme*, e lo scarto in bps resta piccolo **proprio nell'ora in cui dovrebbe aprirsi**. Con due referenze, il consenso indipendente si riduceva di fatto a Bitstamp. Aggiunta **Kraken** come terza. BTC ed ETH restano sul Deribit Index (hanno opzioni quotate), quindi oggi la contaminazione e' di *proprieta'*, non ancora di *calcolo*: si interviene finche' e' teorica. ⚠️ **Onesta' sulla taratura.** «Zero falsi allarmi in 8 anni» e' stato misurato con l'insieme di referenze di allora. `THRESHOLD_BPS` e `PERSIST_HOURS` non sono stati toccati, ma il consenso ora si calcola su tre serie invece di due, e **quel numero non e' stato ri-misurato**. Quello che si puo' affermare leggendo il codice e' la *direzione*: con tre referenze il consenso e' la **mediana** (robusta a un outlier) invece della media di due, e lo spread max-min si allarga → si va piu' spesso in **BLIND**, che e' lo stato morbido. Cioe' il modo in cui questa modifica puo' sbagliare e' **«allerta di meno»**, non «grida al lupo». Per il numero vero si rilancia `scripts/research/r0726_venue_tripwire.py` con la terza serie. Misurato subito dopo: BTC +3.0 bps, ETH +2.5 bps, **3 referenze**, spread fra referenze 1.5 e 0.7 bps. Le tre concordano strettamente: nessuna deriva verso BLIND nella pratica. ## 5. Cosa NON e' stato fatto, e resta aperto - **Il Rulebook del 12/08 non lo sorveglia nessuno.** Introduce ADL e perdita socializzata, *emergency powers in qualifying circumstances*, la disciplina dei conti dormienti, e la precisazione che Deribit ha *«limited administrative authority, rather than direct control»* sugli asset custoditi da terzi. Per un sistema il cui runbook dice «l'azione giusta e' prelevare», questa e' materia di rischio-venue pura — e il watcher guarda solo i prezzi. - **La taratura a tre referenze non e' ri-misurata** (vedi §4). - **Il verso della finestra di manutenzione e' euristico**: `MAINT_GRACE_HOURS = 2` non legge gli annunci, li **presume**. Un feed degli annunci Deribit renderebbe la tolleranza esatta invece che ragionevole.