lock fra cron_book (:47) e cron_cuscino (:53): nessun flock, separazione solo temporale #36

Closed
opened 2026-09-27 13:12:15 +00:00 by Adriano · 3 comments
Owner

Revisione 2026-09-27 (goldrake): rilievo Opus 5.5, verifica avversariale fable. Sola lettura, nessun codice toccato.

Rilievo A11 [miglioria]

FILE: PG:scripts/cron_book.sh:37-42
COSA: nessun timeout ne' flock attorno a book_execute, a differenza di TradingBook/scripts/cron_ombra.sh (timeout -k 10 150). La durata di un giro e' limitata solo dai timeout delle singole chiamate (15s gateway, ccxt, fino a 200 pagine per asset).
SCENARIO: giro lento (misurato fino a 659 s il 26/06 19:00) -> sconfina nel :53 di cron_cuscino, che compra/vende USDE sullo stesso conto mentre book_execute sta ancora dimensionando; un giro appeso oltre l'ora si sovrapporrebbe al :47 successivo, con due processi che leggono la stessa posizione e mandano lo stesso delta (ordini doppi).
PROVA: VERIFICATO: parse di logs/cron_book.log (2296 giri): durata max 659 s, 5 giri > 100 s, tutti a fine giugno. Sovrapposizione mai osservata -> rischio PRESUNTO, non misurato.
PROPOSTA: flock -n /tmp/pg_book.lock timeout -k 10 600 uv run python scripts/live/book_execute.py --execute.

TEST: .venv/bin/python -m pytest -q tests/test_book_live.py tests/test_book_scale.py tests/test_cap_watermark.py tests/test_shadow_usde.py tests/test_live_shadow.py tests/test_book_resilienza_venue.py tests/test_book_staleness_gate.py tests/test_skh_feed_freshness.py tests/test_skh_live_book.py tests/test_book_cadenza.py -> 148 passed. Nessun test esercita rebalance_signed a taglia reale (tutti a $75 @ 60.000) -> A1 non e' coperto.
DUPLICAZIONE PG/TB: execution.py, livefeed.py, usde.py, config/live.json, trend_portfolio.py, skyhook.py, sleeves.py identici; book.py/shadow.py differiscono solo per impronta_feed/tp_pieno (TB); deribit.py 55 righe diverse (gateway). A1-A10 valgono identici per TB alla promozione.

COPERTURA: src/live/book.py, src/live/shadow.py, src/live/execution.py, src/live/deribit.py, src/live/livefeed.py, src/live/usde.py (1-150, intero), scripts/live/book_execute.py, scripts/cron_book.sh, config/live.json — per intero. Parziali: src/portfolio/sleeves.py:225-290,397-407; src/strategies/trend_portfolio.py (firme + current_target); src/live/tradesdb.py:1-40,180-224; tests/test_shadow_usde.py, tests/test_book_live.py (elenco test); CLAUDE.md §0-1, §3, §5 (grep).

Verifica fable A11 VERDETTO: CONFERMATO-RIDIMENSIONATO

GRAVITA': basso
PERCHE': scripts/cron_book.sh: nessun timeout/flock (letto). Durate misurate (start->done, 2296 giri): max 659 s (26/06 19:00), 387, 315, 263, 107 — tutte a fine giugno, quando il cron era a :00; con l'attuale :47 un giro da 659 s sconfinerebbe nel :53 di cron_cuscino (stesso conto) ma non nel :47 successivo. Sovrapposizione mai osservata: rischio PRESUNTO.
CORREZIONE: flock -n si'; il timeout va generoso (>=600 s) e con la consapevolezza che un SIGTERM a meta' ordine perde la riga di log (vedi A4): meglio flock senza kill, o timeout solo attorno alle fasi di lettura.

Rilievo B13 [miglioria]

FILE: pg:scripts/cron_cuscino.sh, pg:scripts/cron_book.sh
COSA: nessun flock fra cron_book (:47) e cron_cuscino (:53, puo' mandare ordini spot): la separazione e' solo temporale (6 min).
SCENARIO: book_execute che dura >6 min (misurato: 659s e 387s il 26/06) -> usde_convert modifica il saldo USDC mentre book_execute legge equity/posizioni; lettura non atomica USDC+USDE a meta' di un fill spot = equity sfasata di fino a 100 USDE (~2%) su cui book sizing e watermark ragionano.
PROVA: VERIFICATO: durate cron_book da logs/cron_book.log: 2296 giri, mediana 15s, >300s solo 3 volte (tutte giugno). Rischio basso ma reale; D5 dichiara solo il caso manuale.
PROPOSTA: flock -w 600 /tmp/pg-conto.lock attorno a book_execute e a cuscino_watch (e a usde_convert --esegui manuale), cosi' anche D5 si chiude.

COPERTURA: scripts/live/cuscino_watch.py, scripts/live/usde_watch.py, scripts/live/usde_convert.py, scripts/live/balance_watch.py, src/live/monitor_health.py, scripts/live/monitor_health.py (main), src/live/venue_watch.py (righe 59-393), scripts/live/venue_watch.py, src/live/venue_probe.py, src/live/scale_watch.py, scripts/live/fee_watch.py (40-269), scripts/live/edge_watch.py, src/live/usde.py, tutti scripts/cron_*.sh + crontab; parziali: src/live/book.py (_scala), src/live/deribit.py (DeribitRead, check_specs), CLAUDE.md §1/§5.17. Test: 156 passed (cuscino, usde, venue_watch, monitor_health, fee, edge, venue_probe). Nessun rilievo su balance_watch (formato ts fills compatibile, size gateway in USD verificata sui dati).

Verifica fable B13 VERDETTO: CONFERMATO

GRAVITA': basso
PERCHE': scripts/cron_book.sh e scripts/cron_cuscino.sh: nessun flock; separazione solo temporale :47/:53. awk su logs/cron_book.log: 2296 giri, >300s 3, >360s 2 (concorda con il revisore). Oggi cuscino_watch non ha mai lanciato una conversione (tentate 0), quindi il rischio e' teorico ma la barriera non esiste.
CORREZIONE: ok (flock -n o -w su un lock condiviso; con -w 600 in cron_cuscino attenzione: il giro :53 slitterebbe fin dentro il :47 successivo — meglio -n e saltare il giro registrandolo).

Revisione 2026-09-27 (goldrake): rilievo Opus 5.5, verifica avversariale fable. Sola lettura, nessun codice toccato. ### Rilievo A11 [miglioria] FILE: PG:scripts/cron_book.sh:37-42 COSA: nessun `timeout` ne' `flock` attorno a book_execute, a differenza di TradingBook/scripts/cron_ombra.sh (`timeout -k 10 150`). La durata di un giro e' limitata solo dai timeout delle singole chiamate (15s gateway, ccxt, fino a 200 pagine per asset). SCENARIO: giro lento (misurato fino a 659 s il 26/06 19:00) -> sconfina nel :53 di cron_cuscino, che compra/vende USDE sullo stesso conto mentre book_execute sta ancora dimensionando; un giro appeso oltre l'ora si sovrapporrebbe al :47 successivo, con due processi che leggono la stessa posizione e mandano lo stesso delta (ordini doppi). PROVA: VERIFICATO: parse di logs/cron_book.log (2296 giri): durata max 659 s, 5 giri > 100 s, tutti a fine giugno. Sovrapposizione mai osservata -> rischio PRESUNTO, non misurato. PROPOSTA: `flock -n /tmp/pg_book.lock timeout -k 10 600 uv run python scripts/live/book_execute.py --execute`. TEST: `.venv/bin/python -m pytest -q tests/test_book_live.py tests/test_book_scale.py tests/test_cap_watermark.py tests/test_shadow_usde.py tests/test_live_shadow.py tests/test_book_resilienza_venue.py tests/test_book_staleness_gate.py tests/test_skh_feed_freshness.py tests/test_skh_live_book.py tests/test_book_cadenza.py` -> 148 passed. Nessun test esercita rebalance_signed a taglia reale (tutti a $75 @ 60.000) -> A1 non e' coperto. DUPLICAZIONE PG/TB: execution.py, livefeed.py, usde.py, config/live.json, trend_portfolio.py, skyhook.py, sleeves.py identici; book.py/shadow.py differiscono solo per impronta_feed/tp_pieno (TB); deribit.py 55 righe diverse (gateway). A1-A10 valgono identici per TB alla promozione. COPERTURA: src/live/book.py, src/live/shadow.py, src/live/execution.py, src/live/deribit.py, src/live/livefeed.py, src/live/usde.py (1-150, intero), scripts/live/book_execute.py, scripts/cron_book.sh, config/live.json — per intero. Parziali: src/portfolio/sleeves.py:225-290,397-407; src/strategies/trend_portfolio.py (firme + current_target); src/live/tradesdb.py:1-40,180-224; tests/test_shadow_usde.py, tests/test_book_live.py (elenco test); CLAUDE.md §0-1, §3, §5 (grep). ### Verifica fable A11 VERDETTO: CONFERMATO-RIDIMENSIONATO GRAVITA': basso PERCHE': scripts/cron_book.sh: nessun timeout/flock (letto). Durate misurate (start->done, 2296 giri): max 659 s (26/06 19:00), 387, 315, 263, 107 — tutte a fine giugno, quando il cron era a :00; con l'attuale :47 un giro da 659 s sconfinerebbe nel :53 di cron_cuscino (stesso conto) ma non nel :47 successivo. Sovrapposizione mai osservata: rischio PRESUNTO. CORREZIONE: `flock -n` si'; il `timeout` va generoso (>=600 s) e con la consapevolezza che un SIGTERM a meta' ordine perde la riga di log (vedi A4): meglio flock senza kill, o timeout solo attorno alle fasi di lettura. ### Rilievo B13 [miglioria] FILE: pg:scripts/cron_cuscino.sh, pg:scripts/cron_book.sh COSA: nessun flock fra cron_book (:47) e cron_cuscino (:53, puo' mandare ordini spot): la separazione e' solo temporale (6 min). SCENARIO: book_execute che dura >6 min (misurato: 659s e 387s il 26/06) -> usde_convert modifica il saldo USDC mentre book_execute legge equity/posizioni; lettura non atomica USDC+USDE a meta' di un fill spot = equity sfasata di fino a 100 USDE (~2%) su cui book sizing e watermark ragionano. PROVA: VERIFICATO: durate cron_book da logs/cron_book.log: 2296 giri, mediana 15s, >300s solo 3 volte (tutte giugno). Rischio basso ma reale; D5 dichiara solo il caso manuale. PROPOSTA: `flock -w 600 /tmp/pg-conto.lock` attorno a book_execute e a cuscino_watch (e a usde_convert --esegui manuale), cosi' anche D5 si chiude. COPERTURA: scripts/live/cuscino_watch.py, scripts/live/usde_watch.py, scripts/live/usde_convert.py, scripts/live/balance_watch.py, src/live/monitor_health.py, scripts/live/monitor_health.py (main), src/live/venue_watch.py (righe 59-393), scripts/live/venue_watch.py, src/live/venue_probe.py, src/live/scale_watch.py, scripts/live/fee_watch.py (40-269), scripts/live/edge_watch.py, src/live/usde.py, tutti scripts/cron_*.sh + crontab; parziali: src/live/book.py (_scala), src/live/deribit.py (DeribitRead, check_specs), CLAUDE.md §1/§5.17. Test: 156 passed (cuscino, usde, venue_watch, monitor_health, fee, edge, venue_probe). Nessun rilievo su balance_watch (formato ts fills compatibile, size gateway in USD verificata sui dati). ### Verifica fable B13 VERDETTO: CONFERMATO GRAVITA': basso PERCHE': `scripts/cron_book.sh` e `scripts/cron_cuscino.sh`: nessun `flock`; separazione solo temporale :47/:53. `awk` su `logs/cron_book.log`: 2296 giri, >300s 3, >360s 2 (concorda con il revisore). Oggi cuscino_watch non ha mai lanciato una conversione (tentate 0), quindi il rischio e' teorico ma la barriera non esiste. CORREZIONE: ok (`flock -n` o `-w` su un lock condiviso; con `-w 600` in cron_cuscino attenzione: il giro :53 slitterebbe fin dentro il :47 successivo — meglio `-n` e saltare il giro registrandolo).
Adriano added the sviluppo label 2026-09-27 13:12:15 +00:00
Author
Owner

ANALISI (passo 1, Opus, 27/09/2026)

ANALISI U8 — gitea:Adriano/PythagorasGoal#30 (B6) + #36 (A11, B13)
CARTELLA: /home/adriano/lavoro/PG-u8 RAMO: lavoro/goldrake-u8 (da main 841d4c7)

#30
PERCHE': monitor_health gira solo alle 00:30 (cron_daily.sh). I sorveglianti orari hanno max_age 3h (cuscino_watch, venue_watch, collect_chain), quindi un sorvegliante fermo si scopre fino a 24h dopo.

  1. scripts/live/monitor_health.py — nuovo flag --solo-orari: controlla solo i monitor con max_age <= 6h (derivato dalla tabella in src/live/monitor_health.py, non da una lista di nomi cablata).
    • Con --solo-orari notifica solo gli alert NUOVI rispetto al marcatore data/live/monitor_health_detto.json: {"detti": [], "ts": ...}.
    • Chiave = nome monitor + stato, non il testo (il testo contiene l'eta', che cambia ogni ora).
    • Quando un monitor torna OK la sua chiave si toglie dal marcatore, cosi' un nuovo guasto riparte con un nuovo avviso.
    • Il marcatore si scrive SOLO se notify ha restituito True (stessa regola di fee_watch #29: niente "gia' detto" su un invio fallito).
    • Exit: 2 se ci sono alert (anche gia' detti), 0 altrimenti.
    • Il giro giornaliero senza flag resta identico: ogni giorno report completo e avviso.
    • Aggiornare USO e valida(... flag=("--quiet","--solo-orari")).
  2. scripts/cron_cuscino.sh — dopo cuscino_watch: uv run python scripts/live/monitor_health.py --quiet --solo-orari || true. Niente nuova riga di crontab.
  3. src/live/monitor_health.py — solo se serve esporre una funzione pura che filtra i monitor per max_age e calcola chiavi e nuovi. Una funzione pura e' preferibile: si testa.

#36
PERCHE': book_execute (:47) e cuscino_watch (:53, puo' mandare ordini spot USDE) sono separati solo dal tempo; giri fino a 659 s sono stati misurati.
4. scripts/cron_book.sh — solo la riga di book_execute diventa:
flock -n -E 75 logs/.conto.lock uv run python scripts/live/book_execute.py --execute; rc=$?
[ $rc -eq 75 ] && echo " book: SALTATO — conto occupato da un altro giro (logs/.conto.lock)"

  • L'exit code del wrapper oggi non conta (lo script e' dentro {} >> log); guarda se il resto dello script usa $? dopo book_execute e preservalo.
  • NIENTE timeout/kill: un SIGTERM a meta' ordine perde la riga di log (verifica fable A11).
  • venue_watch e trades_db restano fuori dal lock.
  1. scripts/cron_cuscino.sh — cuscino_watch dentro flock -n -E 75 logs/.conto.lock .... Se 75, riga " cuscino: SALTATO — conto occupato (logs/.conto.lock)". Mai -w: il giro scivolerebbe (verifica fable B13).
  2. scripts/live/usde_convert.py — solo documentazione: nel docstring d'uso, la conversione manuale --esegui si lancia con flock logs/.conto.lock uv run python scripts/live/usde_convert.py --esegui .... Controlla se CLAUDE.md del repo ha una riga d'uso di usde_convert --esegui: se si', aggiorna anche quella (una riga).
    Controlla se cuscino_watch lancia usde_convert come subprocess: se si', il lock e' gia' tenuto dal padre, quindi usde_convert NON deve prenderlo di nuovo (deadlock con -n: il figlio lo troverebbe occupato). Documentalo in un commento.

TEST

  • tests/test_monitor_health_orari.py: funzione pura su righe finte. Filtro per max_age; alert nuovo -> notifica; lo stesso alert al secondo giro -> nessuna notifica; ritorno OK -> chiave tolta; notify False -> marcatore non scritto. Monkeypatch di notify e del percorso del marcatore; niente Telegram.
  • Script shell: bash -n scripts/cron_book.sh scripts/cron_cuscino.sh. Prova il flock a mano con un lock tenuto da flock logs/.conto.lock sleep 5 & e un comando finto (NON book_execute), per vedere il rc 75. Riportalo in PROVA.
    Suite: cd /home/adriano/lavoro/PG-u8 && /opt/docker/PythagorasGoal/.venv/bin/python -m pytest -q -p no:cacheprovider. Baseline 1166 passed, 3 skipped.

NON TOCCARE: la crontab, cron_daily.sh (il giro giornaliero resta), book_execute.py, cuscino_watch.py (salvo lettura).
COMMIT: "monitor_health: giro orario dei sorveglianti da 3h con marcatore gia' detto (#30)" e "cron_book/cron_cuscino: flock -n sul conto, un giro occupato si salta e si scrive (#36)".

ANALISI (passo 1, Opus, 27/09/2026) ANALISI U8 — gitea:Adriano/PythagorasGoal#30 (B6) + #36 (A11, B13) CARTELLA: /home/adriano/lavoro/PG-u8 RAMO: lavoro/goldrake-u8 (da main 841d4c7) #30 PERCHE': monitor_health gira solo alle 00:30 (cron_daily.sh). I sorveglianti orari hanno max_age 3h (cuscino_watch, venue_watch, collect_chain), quindi un sorvegliante fermo si scopre fino a 24h dopo. 1. scripts/live/monitor_health.py — nuovo flag `--solo-orari`: controlla solo i monitor con max_age <= 6h (derivato dalla tabella in src/live/monitor_health.py, non da una lista di nomi cablata). - Con `--solo-orari` notifica solo gli alert NUOVI rispetto al marcatore data/live/monitor_health_detto.json: {"detti": [<chiave alert>], "ts": ...}. - Chiave = nome monitor + stato, non il testo (il testo contiene l'eta', che cambia ogni ora). - Quando un monitor torna OK la sua chiave si toglie dal marcatore, cosi' un nuovo guasto riparte con un nuovo avviso. - Il marcatore si scrive SOLO se notify ha restituito True (stessa regola di fee_watch #29: niente "gia' detto" su un invio fallito). - Exit: 2 se ci sono alert (anche gia' detti), 0 altrimenti. - Il giro giornaliero senza flag resta identico: ogni giorno report completo e avviso. - Aggiornare USO e `valida(... flag=("--quiet","--solo-orari"))`. 2. scripts/cron_cuscino.sh — dopo cuscino_watch: `uv run python scripts/live/monitor_health.py --quiet --solo-orari || true`. Niente nuova riga di crontab. 3. src/live/monitor_health.py — solo se serve esporre una funzione pura che filtra i monitor per max_age e calcola chiavi e nuovi. Una funzione pura e' preferibile: si testa. #36 PERCHE': book_execute (:47) e cuscino_watch (:53, puo' mandare ordini spot USDE) sono separati solo dal tempo; giri fino a 659 s sono stati misurati. 4. scripts/cron_book.sh — solo la riga di book_execute diventa: flock -n -E 75 logs/.conto.lock uv run python scripts/live/book_execute.py --execute; rc=$? [ $rc -eq 75 ] && echo " book: SALTATO — conto occupato da un altro giro (logs/.conto.lock)" - L'exit code del wrapper oggi non conta (lo script e' dentro {} >> log); guarda se il resto dello script usa $? dopo book_execute e preservalo. - NIENTE timeout/kill: un SIGTERM a meta' ordine perde la riga di log (verifica fable A11). - venue_watch e trades_db restano fuori dal lock. 5. scripts/cron_cuscino.sh — cuscino_watch dentro `flock -n -E 75 logs/.conto.lock ...`. Se 75, riga " cuscino: SALTATO — conto occupato (logs/.conto.lock)". Mai `-w`: il giro scivolerebbe (verifica fable B13). 6. scripts/live/usde_convert.py — solo documentazione: nel docstring d'uso, la conversione manuale `--esegui` si lancia con `flock logs/.conto.lock uv run python scripts/live/usde_convert.py --esegui ...`. Controlla se CLAUDE.md del repo ha una riga d'uso di usde_convert --esegui: se si', aggiorna anche quella (una riga). Controlla se cuscino_watch lancia usde_convert come subprocess: se si', il lock e' gia' tenuto dal padre, quindi usde_convert NON deve prenderlo di nuovo (deadlock con -n: il figlio lo troverebbe occupato). Documentalo in un commento. TEST - tests/test_monitor_health_orari.py: funzione pura su righe finte. Filtro per max_age; alert nuovo -> notifica; lo stesso alert al secondo giro -> nessuna notifica; ritorno OK -> chiave tolta; notify False -> marcatore non scritto. Monkeypatch di notify e del percorso del marcatore; niente Telegram. - Script shell: `bash -n scripts/cron_book.sh scripts/cron_cuscino.sh`. Prova il flock a mano con un lock tenuto da `flock logs/.conto.lock sleep 5 &` e un comando finto (NON book_execute), per vedere il rc 75. Riportalo in PROVA. Suite: `cd /home/adriano/lavoro/PG-u8 && /opt/docker/PythagorasGoal/.venv/bin/python -m pytest -q -p no:cacheprovider`. Baseline 1166 passed, 3 skipped. NON TOCCARE: la crontab, cron_daily.sh (il giro giornaliero resta), book_execute.py, cuscino_watch.py (salvo lettura). COMMIT: "monitor_health: giro orario dei sorveglianti da 3h con marcatore gia' detto (#30)" e "cron_book/cron_cuscino: flock -n sul conto, un giro occupato si salta e si scrive (#36)".
Author
Owner

ESITO: fatto sul ramo, non in produzione
RIF: gitea:Adriano/PythagorasGoal#36
FILE: commit ddcd377 su lavoro/goldrake (pushato, HEAD d34b0a8)
PROVA: /home/adriano/lavoro/PythagorasGoal pytest -q → 1220 passed, 3 skipped
MANCA: merge su main con ok di Adriano; poi issue chiudi --soluzione

ESITO: fatto sul ramo, non in produzione RIF: gitea:Adriano/PythagorasGoal#36 FILE: commit ddcd377 su lavoro/goldrake (pushato, HEAD d34b0a8) PROVA: /home/adriano/lavoro/PythagorasGoal pytest -q → 1220 passed, 3 skipped MANCA: merge su main con ok di Adriano; poi issue chiudi --soluzione
Author
Owner

Soluzione

cron_book.sh e cron_cuscino.sh: flock -n -E 75 logs/.conto.lock attorno a book_execute e cuscino_watch; rc 75 -> riga SALTATO nel log. Nessun timeout/kill. usde_convert manuale: docstring con flock. Commit ddcd377.
Su main d34b0a8 dal 27/09/2026 17:31Z, produzione aggiornata. Test 1220 passed.

**Soluzione** cron_book.sh e cron_cuscino.sh: flock -n -E 75 logs/.conto.lock attorno a book_execute e cuscino_watch; rc 75 -> riga SALTATO nel log. Nessun timeout/kill. usde_convert manuale: docstring con flock. Commit ddcd377. Su main d34b0a8 dal 27/09/2026 17:31Z, produzione aggiornata. Test 1220 passed.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Adriano/PythagorasGoal#36