Correzioni di main da portare in stage (mdm_installation)¶
Analisi del 29/10/2026 dei 42 commit presenti solo su origin/main (dal punto comune c8600313 del 02/02/2026 fino a 531e6552 del 19/10/2026), confrontati con origin/stage (d02dbaee, 3.9.0). Al 30/10/2026 i quattro comportamenti e l'audio sono committati su origin/stage (da f427d22e a ebde226c); origin/main è ancora a 531e6552, quindi non ci sono nuove correzioni da riportare. Riguarda solo mdm_installation: nel midserver la prossima versione è refactor/v1, e il wrapper ha main e stage allineati (stesso commit 3488c76, 3.5.0).
La linea stage è stata riscritta (FastAPI, configurazione in SQLite, cluster), quindi i commit non si possono riportare con un cherry-pick: per ognuno si è verificato se stage ha già lo stesso comportamento.
Risultato¶
- 18 commit cambiano solo versione, tag o changelog: non servono.
- Già presenti in
stage, scritti in altro modo: Frigate 0.17.1 e memoria condivisa 2 GB, MQTT che si riconnette da solo e non blocca l'avvio, sessione SQLite per thread, stato online delle telecamere, salute delle telecamere dal wrapper, audio nel live. - Da portare: quattro comportamenti, tutti P1, perché riguardano carico del box, stabilità e lo stato inserito/disinserito letto dalla centrale. Tutti e quattro sono fatti (vedi Cosa dobbiamo fare); restano
/heartbeat, il segreto di produzione e le prove in laboratorio.
Da portare in stage¶
| # | Comportamento di main |
Commit | Situazione in stage |
Effetto se manca |
|---|---|---|---|---|
| 1 | Sincronizzazione telecamere leggera (3.1.6): telecamere elaborate in parallelo (massimo 8 thread); snapshot preso solo nei cicli del controllo anti-manomissione, uno ogni 5; controllo su immagine ridotta a 320 px con differenza tra fotogrammi invece di SIFT. Cos'è e perché nessuno dei due algoritmi è affidabile: anti-manomissione | e28dc762 |
camera_manager.py prende gli snapshot uno alla volta; camera_model.py usa SIFT su immagini a piena risoluzione a ogni ciclo |
CPU alta e cicli lenti sui box con molte telecamere: stato in ritardo, possibile impatto sul rilevamento |
| 2 | Database che non si blocca dopo un errore (3.2.4, 3.2.7): rollback() quando un commit() fallisce; sessioni rilasciate dopo ogni richiesta e ogni ciclo di monitoraggio |
337fe08d, e24b47d2 |
scoped_session c'è, ma 24 commit() senza rollback() (13 nei repository, 5 in error_log_handler.py, 6 in log_repository.py; solo la migrazione fa rollback); nessun session.remove() né per le richieste FastAPI né per i thread |
verificato il 29/10 con SQLite bloccato da un'altra scrittura: (1) dopo il primo commit fallito ogni operazione di quel thread dà PendingRollbackError, anche a lock liberato, fino al riavvio: la coda degli eventi (event_queue_service.py) smette di reinviare; (2) ErrorLogs.log_exception, chiamato dentro gli except di InstallationMonitoring.run, solleva a sua volta e il thread di monitoraggio muore senza che nessuno lo riavvii: niente più sync_to_server, stato telecamere e anti-manomissione. Cause realistiche: disco pieno, lock oltre i 5 s |
| 3 | /status veloce anche con il detector giù (3.2.6, 3.2.7, 3.2.9): stato in cache per 2 s con un solo calcolo alla volta (gli altri ricevono l'ultimo valore); se il wrapper non risponde, niente nuove chiamate per 10 s; in main il client HTTP non ha retry |
8f29f6d3, e24b47d2, 413c6e40 |
nessuna cache; detector_info fa 3 chiamate (stato di inserimento e riepilogo di salute dal wrapper, statistiche da Frigate) e non ricorda i fallimenti; il client HTTP della stage ha Retry(total=10, backoff_factor=0.1) anche sugli errori di connessione (aggiunto per l'invio delle foto, a0d212f0); /status è async ma calcola lo stato in modo sincrono e blocca il loop di FastAPI; il ciclo di monitoraggio calcola lo stato due volte |
misurato il 29/10: una chiamata verso un servizio giù fa 11 tentativi e 102 s di pause di retry. Ne segue che un /status con il wrapper giù dura circa 3,5 minuti (circa 5 con anche Frigate giù; stima dalle 2 o 3 chiamate, non misurata su un box) e per tutto quel tempo il box non risponde ad altre richieste HTTP (comandi /api, allarmi GANZ). La differenza non è voluta: la stage ha già una chiamata senza retry (_request_no_retry) per il controllo di salute del GANZ, ma non per Frigate |
| 4 | Stato inserito/disinserito senza buchi (3.2.9): /status riporta l'ultimo stato noto finché il detector non risponde; dopo 60 s risponde 503 dicendo che il dato non è fresco; online del detector viene sempre dalla stessa fonte |
c7c11ff2, a2968dc8, 9903cd90, ec843161, 413c6e40 |
lo stato è salvato nel box (system_state) e riapplicato al detector all'avvio, quindi la parte "ricordarlo dopo un riavvio" c'è già. Però /status legge disarm dal wrapper in tempo reale: diventa null a ogni riavvio del wrapper non chiesto via MQTT. Inoltre online cambia fonte a seconda di chi risponde (frigate_detector.py, detector_info) |
chi interroga /status legge null come inserito e agisce di conseguenza (motivo della 3.2.9); con il detector giù da tempo nessuno lo capisce dalla risposta |
Verificato con il punto 4: all'avvio stage chiamava detector.arm() o disarm() senza gestire l'errore (alarm_system_manager.py, _init_detector). Con il detector che non risponde l'avvio falliva e il processo ripartiva in ciclo (restart: always), senza MQTT, API né allarmi. Corretto nel punto 4.
Già presenti o non necessari¶
| Commit | Cosa fa in main |
In stage |
|---|---|---|
11654387, 226c72c6 |
memoria condivisa 2 GB, Frigate 0.17.1 | presenti in infra/compose/docker-compose.yml |
596cf46e |
MQTT con connect_async e riconnessione |
presente (app/core/mqtt/base_mqtt.py: connect_async, reconnect_delay_set, nuova connessione dopo una disconnessione) |
337fe08d (in parte) |
sessione SQLite per thread (scoped_session) |
presente (app/core/db/sqllite.py); manca il rollback (punto 2) |
a9964f3b |
aggiornare online delle telecamere, log delle modifiche |
online aggiornato (update_camera); i log di debug non servono |
4e3c9033, 9354a294, 3de77efc |
supporto wrapper 3.0.7, salute delle telecamere dal wrapper, modelli tipizzati | presenti (riepilogo di salute, heartbeat, modelli del detector); da confermare nei test |
12fcd89a, fd44620b, 95f376be |
spostamento dei modelli del database | non serve: struttura diversa |
238b11d5, c0e1ced3 |
log degli errori di /status e dei comandi di disinserimento |
utile ma minore: stage usa già logger; da controllare quando si fa il punto 4 |
531e6552 |
audio nel live con -map 0:a? e AAC |
stage ha l'audio, ma con un probe in più a ogni avvio; l'approccio di main (audio facoltativo, nessun probe) va adottato in MDM-039 |
6d3d0b07, 31764c60 |
file .env del box versionato con indirizzi e credenziali; "Set VPN" porta midserver e MQTT su 172.16.16.253 (VPN) |
in stage questi valori vengono dal segreto AWS: il segreto di produzione deve usare gli stessi indirizzi via VPN (M27) |
413c6e40 (in parte) |
aggiunge per errore il riferimento al sottomodulo external/infra-lib senza .gitmodules |
innocuo; sparisce con l'unione in main |
| 18 commit di versione, tag e changelog | — | non servono |
Prove sul box di test GANZ (30/10)¶
Codice della stage copiato nel container internal_server di MDMTESTGANZ1 (backup dei file sostituiti in ~/internal_server_backup sul box), GANZ reso irraggiungibile bloccando il traffico dal box con iptables, sempre con un timer di sistema che toglieva il blocco.
| Prova | Risultato |
|---|---|
/status a regime |
0,26 s, poi 1–29 ms dalla cache (prima 1,3 s); disarm con "source": "detector"; arm_state sano |
| GANZ irraggiungibile 150 s | /status in 5–10 s invece di minuti; il server resta reattivo (/get_mac 0,00 s durante il calcolo); detector fault dopo circa 45 s; nessun falso evento arm_state; rientro entro 10 s |
| Avvio con il GANZ irraggiungibile | prima della correzione: crash in ciclo per 4,5 minuti (i controlli delle telecamere venivano creati all'avvio chiedendo l'elenco al detector). Dopo: API su in 26 s, nessun riavvio, /status 200 in 10 s con disarm dallo stato salvato; allo sblocco stato riapplicato e controlli delle telecamere avviati entro 40 s |
| Divergenza (GANZ inserito direttamente, box disinserito) | prima della correzione non rilevabile: il box teneva una copia dello stato del GANZ (DISARMSTATUS). Dopo: MALFUNCTION arm_state 3 s dopo, FUNCTION_RECOVERED 2 s dopo il riallineamento, entrambi consegnati via MQTT |
| Audio del live | trovato e convertito sulla telecamera vera; ascoltato dal pannello dev |
Trovati e lasciati aperti:
- ✅ corretto lo stesso giorno (commit ebde226c): senza notification provider configurato (il provider è il canale di notifica del box, oggi solo Telegram, che verrà tolto; al midserver gli eventi arrivano sempre via MQTT, che non cambia) ogni evento restava in coda per sempre e la coda, che si ferma al primo evento incompleto, non arrivava mai agli eventi successivi. Ora "nessun provider" vuol dire "niente da consegnare"; sul box i 14 eventi in coda si sono completati al primo giro;
- il primo calcolo di /status dopo un riavvio con il GANZ giù dura circa 10 s, e chi chiama nel frattempo aspetta (la cache non ha ancora un valore da dare).
Soluzione ideale¶
Prima di unire stage in main, la nuova versione fa almeno tutto quello che la 3.2.9 fa già in produzione, così l'aggiornamento non riporta indietro nessun comportamento. Ogni correzione futura su main viene riportata subito su stage, con un test (regola già in versioni-e-branch.md).
Per il punto 4 la soluzione naturale nella stage è usare lo stato che il box possiede già:
- /status riporta lo stato da system_state e indica a parte se il detector lo ha confermato e da quanto tempo non risponde;
- un campo assente non deve mai voler dire "inserito".
Cosa dobbiamo fare¶
| # | Passo | Ticket |
|---|---|---|
| 1 | ✅ Fatto il 29/10 sulla stage (commit f427d22e): la sincronizzazione è una sola chiamata al detector, senza snapshot; il controllo anti-manomissione (SIFT e luminosità) è sostituito dal nuovo rilevamento (MDM-082, stesso giorno), con snapshot in parallelo solo nel ciclo di monitoraggio |
MDM-030, MDM-082 |
| 2 | ✅ Fatto il 29/10 sulla stage (commit b9dd8510): ogni commit() passa da commit_or_rollback (app/core/db/sqllite.py); ErrorLogs non solleva mai e, se il database non risponde, scrive nel log dell'applicazione; il ciclo di monitoraggio sopravvive a qualsiasi errore; sessione rilasciata dopo ogni ciclo di monitoraggio, ogni giro della coda degli eventi e ogni richiesta HTTP quando non ce ne sono altre in corso. Test con un database SQLite bloccato davvero (lockable_db); suite 692 verdi. Le route sono tutte async; la dipendenza di autenticazione verify_credentials è sincrona, gira nel pool di thread e rilascia la sessione da sola: senza, ogni thread teneva occupata una connessione e con più di 15 richieste /api contemporanee le altre aspettavano 30 s e fallivano (riprodotto) |
MDM-030 |
| 3 | ✅ Fatto il 29/10 sulla stage (commit d567c9e7; nomi definitivi dei campi e GANZ in ebde226c). /status veloce con il detector giù, senza mai presentare un dato vecchio come attuale: (a) chiamate di controllo verso wrapper, Frigate e GANZ con un solo tentativo (get_no_retry); (b) un servizio che non ha risposto non viene richiamato per 10 s (Reachability): in quel tempo risulta non raggiungibile, con unreachable_reason e unreachable_seconds (nomi definitivi dal 30/10, validi anche per il GANZ), e i campi letti da lui restano vuoti; (c) cache di 2 s con un solo calcolo alla volta per /status (InstallationStateCache), last_update all'ora del calcolo; sync_to_server e le azioni leggono lo stato fresco; (d) /status calcolato nel pool di thread; (e) un solo calcolo per ciclo di monitoraggio; (f) tolto cache_disarm. Misura con wrapper e Frigate giù: da oltre 240 s a 4,1 s su Windows (2 connessioni rifiutate da circa 2 s; sui box Linux quasi immediate), 0 s durante la pausa. Suite 706 verdi. Resta da fare: /heartbeat è anch'essa async e fa lavoro bloccante (sincronizzazione telecamere e sync_to_server, il cui client verso il midserver ha ancora i 10 retry); il valore di disarm con il wrapper giù si decide al punto 4 |
MDM-030 |
| 4 | ✅ Fatto il 29/10 sulla stage (commit a7ba5da9; controllo arm_state in d65178d2; GANZ in ebde226c). (a) disarm mai vuoto: con il detector illeggibile vale lo stato salvato dal box (system_state), dichiarato con "source": "system_state", system_state e last_updated, sia in status (letto per Frigate) sia in is_disarmed (letto dal pannello per il GANZ); la lettura del detector porta "source": "detector". Verificato che midserver v3 e qAlarm leggono disarm mancante come inserito (_is_disarmed); i campi in più vengono ignorati, quindi niente da cambiare lì. (b) 503 con lo stato nel corpo dopo 60 s senza raggiungere il wrapper (valore di main); un wrapper che risponde con un errore HTTP è raggiungibile e non conta. (c) online sempre dalle statistiche di Frigate; wrapper_is_online vero quando il wrapper risponde. (d) avvio che non fallisce con il detector giù: lo stato salvato viene riapplicato dal ciclo di monitoraggio quando il detector risponde, salvo un comando arrivato prima. Suite 717 verdi. (e) divergenza tra detector e system_state: controllo di salute arm_state che manda MALFUNCTION dopo 3 controlli di fila (promemoria ogni ora, FUNCTION_RECOVERED al riallineamento), senza correggere da solo il detector (lo stato salvato può essere quello sbagliato) e senza bloccare l'inserimento; qAlarm lo mostra come "Stato di inserimento non allineato" in rosso. Suite 730 verdi. Resta: online resta true durante un riavvio di Frigate chiesto dal box (comportamento voluto e testato, segnalato da lifecycle_states); ~~con il GANZ non c'è conteggio dell'interruzione, quindi niente 503~~ corretto il 30/10: unreachable_seconds e il 503 dopo 60 s valgono anche per il GANZ (verificato sul box di test), e il midserver conserva i due campi |
MDM-030 |
| 5 | Segreto AWS di produzione con gli indirizzi via VPN usati oggi dai box (172.16.16.253 per midserver e MQTT) |
MDM-031 (M27) |
| 6 | ✅ Fatto il 30/10 sulla stage (commit 8665f4a6): audio facoltativo (-map 0:a?, AAC 128 kb/s a 48 kHz come in main) senza ffprobe a ogni avvio; audio_supported, usato dal pannello per l'icona e l'avviso, viene dall'elenco dei flussi in ingresso che ffmpeg stampa all'avvio (InputStreamReader). Verificato con ffmpeg 8 su file locali con e senza audio e, il 30/10, sulla telecamera del box di test GANZ (audio G.711 trovato e convertito); resta da vedere l'audio nel pannello con un live vero. Suite 732 verdi. Il probe UDP/TCP resta (MDM-039) |
MDM-039 |
| 7 | Confermare nei test di laboratorio i comportamenti dichiarati "già presenti" | MDM-081 |
Nota di compatibilità: il corpo di /status cambia formato tra le due linee. Per esempio lo stato è in detector_info.disarm nella 3.2.x e in status.detector.disarm nella stage. Chi lo legge (sistema vecchio, script di censimento) va verificato prima del passaggio, insieme alla matrice di compatibilità (versioni-e-branch.md).