sei sorveglianti registrano i giri e stanno in monitor_health (Fixes #16)

src/live/giri.registra_giro: una riga jsonl {ts ms, campi} dopo una misura completata,
mai un'eccezione. edge_watch, fee_watch, scale_watch, vrp_f_watch, venue_news (cron_daily)
e venue_watch (cron_book, orario) scrivono data/<dir>/runs.jsonl; niente riga senza misura.
monitor_health: sei MonitorSpec (30h i giornalieri, 3h venue_watch), open_labeled=False.
Stessa riparazione di cc01 (#10, #12), generalizzata. Primo giro notturno: NUOVO, non allerta.
Test: incrocio P1 percorso/RUNS, misura -> una riga, non-misura -> nessuna. Suite: 1098 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-21 08:48:07 +00:00
parent e4efdaaa53
commit 1a7f670966
11 changed files with 425 additions and 1 deletions
+35
View File
@@ -0,0 +1,35 @@
"""registra_giro — il giro che un sorvegliante ha misurato, come riga jsonl (2026-09-21, #16).
PERCHE' ESISTE. `cc01_regime_watch` e' morto 14 notti di fila (#10) senza che nessuno lo vedesse,
perche' non scriveva niente da sorvegliare: `monitor_health` guarda solo le serie che i monitor
scrivono, e un monitor silenzioso non e' distinguibile da uno che dice "tutto ok". La riparazione
di cc01 (`data/cc01_regime/runs.jsonl`, commit 31f5915) e' il modello: qui si generalizza a ogni
sorvegliante, cosi' un monitor fermo diventa FERMO in `monitor_health`, non invisibile.
Un registro che fallisce non deve cambiare l'esito del sorvegliante che lo scrive: `registra_giro`
non solleva mai, e su errore lo dice su stderr e ritorna False.
"""
from __future__ import annotations
import json
import sys
import time
from pathlib import Path
def registra_giro(path: Path, **campi) -> bool:
"""Appende `{"ts": <ms epoch>, **campi}` a `path` (jsonl, una riga). Crea la cartella se manca.
Ritorna True se la riga e' stata scritta, False su OSError (disco pieno, permessi, percorso
invalido...) dopo aver scritto il motivo su stderr. Non solleva mai: la misura del
sorvegliante e' gia' fatta, e un registro che fallisce non deve buttarla via.
"""
riga = {"ts": int(time.time() * 1000), **campi}
try:
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("a", encoding="utf-8") as fh:
fh.write(json.dumps(riga, default=str) + "\n")
return True
except OSError as e:
print(f"registra_giro: non scrivo {path}: {type(e).__name__}: {e}", file=sys.stderr)
return False
+23
View File
@@ -105,6 +105,29 @@ MONITORS: tuple[MonitorSpec, ...] = (
MonitorSpec("cc01_regime_watch", "cc01_regime", "runs.jsonl", 24.0, max_age_h=30.0,
gate="trigger di regime CC01: WARN>=10% / ALERT>=15% funding 30g (riapre il dossier)",
open_labeled=False),
# I sei sorveglianti che generalizzano la riparazione di cc01 (#16): ognuno registra il
# proprio giro in `data/<dir>/runs.jsonl` (src.live.giri.registra_giro), timbri di
# ESECUZIONE come cc01 -> open_labeled=False. cron_daily.sh (GIORNALIERO) per i primi
# cinque: max_age 30h = un giro mancato + margine.
MonitorSpec("edge_watch", "edge_watch", "runs.jsonl", 24.0, max_age_h=30.0,
gate="kill del libro live (Sharpe 36m < -0,5 / protezione TP01)",
open_labeled=False),
MonitorSpec("fee_watch", "fee_watch", "runs.jsonl", 24.0, max_age_h=30.0,
gate="regola fee §3 decisa in anticipo",
open_labeled=False),
MonitorSpec("scale_watch", "scale_watch", "runs.jsonl", 24.0, max_age_h=30.0,
gate="GATE SCALA-01 criterio A2",
open_labeled=False),
MonitorSpec("vrp_f_watch", "vrp_f_watch", "runs.jsonl", 24.0, max_age_h=30.0,
gate="criteri (a)(b) §5.18",
open_labeled=False),
MonitorSpec("venue_news", "venue_news", "runs.jsonl", 24.0, max_age_h=30.0,
gate="annunci Deribit, debito 8",
open_labeled=False),
# cron_book.sh: gira OGNI ORA accanto al libro vivo. max_age 3h = due giri persi.
MonitorSpec("venue_watch", "venue_watch", "runs.jsonl", 1.0, max_age_h=3.0,
gate="tripwire di venue orario",
open_labeled=False),
)