--- name: project-gemello-nave description: "Gemello-nave: esperto a mandato esteso su Nave, sveglia a freddo da un demone meccanico su eventi mailbox, agisce entro un perimetro ristretto (check_job_health/logbook/gmail-check/gate-watch) e riporta ad Adrian (mai a Mauro direttamente). Handoff completo su tutti e quattro i perimetri dall'11/09/2026." metadata: type: progetto --- # Gemello-nave ## Cos'è (architettura decisa) Un esperto (stesso chassis di `expert-domotica`/`expert-data`/`expert-flussi`: cold-start via `claude -p`, nessun processo always-on, nessun contesto che sopravvive tra un'invocazione e l'altra) con un mandato in più: quando un **demone meccanico esterno** (systemd/cron, poll ogni 1-2 min su `scripts/slave-mailbox/mailbox.log`, zero LLM finché non trova qualcosa) rileva un evento nuovo, invoca `gemello-nave` a freddo, che legge il proprio stato/log per continuità, decide, agisce entro il proprio perimetro, scrive l'esito e chiude. Nessun `Monitor` armato da lei, nessuna sveglia event-driven diretta (scartata: documentata come fragile nel prodotto Claude Code stesso, non solo un'incognita nostra — issue GitHub reali su notifiche perse a sessioni idle, task orfani, "daemon mode" ancora una feature request aperta). **Perché serve, anche se Adrian è sempre attivo h24 su Nave** (chiarito 11/09, non un cambio di premessa — Mauro: "è sempre stato così"): non è una questione di copertura, è **liberare la sessione interattiva Adrian dal rumore operativo di routine**. Ogni evento gestito a mano nella conversazione con Mauro si accumula nel suo contesto (osservato in diretta l'11/09: decine di eventi piccoli hanno gonfiato la sessione); `gemello-nave` a freddo parte con un contesto minimo, costa meno per evento, e lascia la sessione con Mauro libera per lavoro strategico invece che sommersa di triage. Coerente con la visione a regime: Adrian pensa/decide con Mauro, gemello-nave fa l'operatività di routine. ## Decisioni chiuse - **Account**: condiviso con la sessione interattiva Adrian, non un secondo account come `gemello-ufficio`. `SendMessage`/`ListAgents` funzionerebbero nativamente se mai servissero, ma non sono il canale scelto per il riporto (vedi sotto). - **Collocazione — DEFINITIVA (11/09), a livello root**: `gemello-nave/`, sorella di `PYTHON/` (non annidata sotto `scripts/slave-sentinels/`). Due passaggi in giornata: prima dentro `scripts/slave-sentinels/gemello-nave/` (stessa convenzione degli esperti, coerente quando il mandato era "un esperto con un trigger in più"), poi spostata a livello root dopo che il mandato è cresciuto ben oltre quello di un esperto (conoscenza tecnica migrata, visione "Adrian riceve input dai gemelli") — domanda diretta di Mauro ("non dovrebbe stare più in alto, come `PYTHON/` per `gemello-ufficio`?") che ha corretto la collocazione precedente. `PYTHON/` è a `gemello-ufficio` ciò che `gemello-nave/` è a `gemello-nave` stesso: pari livello, stessa logica. **Regola di pulizia (Mauro, 11/09)**: tutto ciò che è a uso esclusivo di `gemello-nave` vive dentro questa cartella (demone di trigger incluso — prima versione in `scripts/servizio-gemello-nave-watch/`, poi spostata dentro `gemello-nave/` stessa, poi tutta la cartella spostata a root). Corollario per il futuro: man mano che una responsabilità passa da Adrian a `gemello-nave` (fine fase shadow su un perimetro), le tracce corrispondenti nella cartella/competenza di Adrian vanno **eliminate**, non lasciate come duplicato. - **Struttura di memoria**: schema a tre livelli, non log flat — `CLAUDE.md` di ruolo (statico) + `MEMORY.md` (sintesi corta, riscritta ogni volta, "cosa conta adesso") + `archivio/` (hard disk, stessa struttura foldered-con-hub di `archivio/` per Adrian, alla scala di gemello-nave — vedi "Coerenza con la struttura di Adrian" sotto). - **(11/09, coerenza con la struttura di Adrian, Mauro)**: `log.md` e `conoscenza-nave.md` erano inizialmente due file sciolti nella cartella `gemello-nave/`. Mauro ha chiesto esplicitamente di impostare comunque la stessa struttura di `archivio/` di Adrian (cartella + hub `_i_archivio.md`) per coerenza, anche se alla scala attuale (due soli file, ciascuno già piccolo) non ce n'era ancora un bisogno stringente di per sé — spostati in `gemello-nave/archivio/` con hub dedicato, tutti i riferimenti sparsi nell'ecosistema aggiornati nello stesso passaggio (`ADRIAN.md`, `memory/MEMORY.md`, vari file di `archivio/`). Precedente: la scala da sola non giustificava ancora lo split, ma la coerenza strutturale con Adrian ha vinto come criterio a sé. - **(11/09, stessa richiesta, seconda applicazione)**: Mauro ha poi notato che lo stesso principio vale per il demone di trigger — `watch.py`/`PROTOCOLLO.md`/`watch.log`/`trigger.offset` erano sciolti nella root di `gemello-nave/` insieme al "cervello" (`CLAUDE.md`/`MEMORY.md`/`archivio/`), mentre `logbook/` (altro componente meccanico) aveva già una cartella propria — incoerenza interna, non solo con Adrian. Spostati in `gemello-nave/trigger/`: crontab aggiornato (nuovo path di `watch.py`/`watch.log`), `OFFSET_FILE` in `watch.py` reso relativo allo script (`Path(__file__) .parent`, come già `LOG`) invece che fisso su `gemello-nave/`, tutti i riferimenti testuali aggiornati (`gemello-nave/CLAUDE.md`, `ADRIAN.md`, `memory/MEMORY.md`, questo canvas). - **Canale di riporto verso Adrian: mailbox, non `SendMessage`** — anche con account condiviso, `SendMessage` raggiunge solo una sessione viva in quel preciso istante; la mailbox è durevole (scritta su disco, letta quando una sessione Adrian la legge) e riusa lo stesso `Monitor` già armato oggi. Costo accettato consapevolmente (Mauro, 11/09): un doppio passaggio (gemello-nave scrive → Adrian legge e decide) invece di una notifica istantanea. - **Mai un contatto diretto con Mauro, `PushNotification` inclusa** — principio costituzionale fermo, applicato con una barriera tecnica reale (non solo istruzione testuale): `.claude/ settings.json` locale con `permissions.deny: ["PushNotification"]` — verificato che il tool sparisce dallo schema disponibile, non solo bloccato a runtime. - **Perimetro**: eventi `check_job_health:`, `logbook:` e `gmail-check:` in mailbox (`logbook:` aggiunto l'11/09 quando `logbook/` è stata spostata dentro `gemello-nave/`, vedi sotto; `gmail-check:` aggiunto lo stesso giorno, secondo candidato di delega scelto da Mauro dopo il primo giorno di dati incoraggianti sul perimetro `check_job_health:`, in shadow mode parallela come il primo). Classifica routine vs degno di attenzione, segnala in mailbox solo se degno di attenzione. Nessuna scrittura fuori dai propri file, nessun lancio di slave/esperti. - **(11/09) Estensione a `gmail-check:` — due problemi trovati e corretti prima di attivarla**: 1. I criteri di importanza (whitelist/blacklist/fascia grigia) vivevano solo nel `CLAUDE.md` di `sentinella-gmail-personale`, cancellato l'8/9 nello smantellamento senza migrarli — un riferimento morto in `scripts/servizio-gmail-check/PROTOCOLLO.md` (oggi `gemello-nave/gmail-check/PROTOCOLLO.md`, vedi sotto) e una voce sbagliata in `memory/MEMORY.md` (diceva fossero "già in PROTOCOLLO.md", falso) lo nascondevano. Recuperati dalla storia git (commit `596c5a8~1`) e migrati in `gemello-nave/archivio/criteri-gmail.md` prima che andassero persi per sempre — solo il contenuto di merito, non il protocollo Monitor/tick della vecchia sentinella persistente, che non si applica più al meccanismo attuale (evento singolo via mailbox, non ciclo continuo). Aggiunta esplicita nel nuovo file: senza accesso al corpo del messaggio (limite reale rispetto alla sentinella originale, che poteva leggerlo), il default in fascia grigia è segnalare, non tacere per assunzione ottimistica. 2. Il filtro di `watch.py` operava riga per riga sulla stringa del prefisso — corretto per eventi mono-riga (`check_job_health:`, `logbook:`) ma `gmail-check:` scrive eventi multi-riga (intestazione + una riga per ogni mittente), e solo l'intestazione contiene il prefisso. Con la logica precedente le righe di continuazione sarebbero state scartate, lasciando a `gemello-nave` solo "N messaggi nuovi:" senza sapere di chi — inutile per classificare. Fix: il filtro ora riconosce l'inizio di ogni riga mailbox (timestamp ISO8601) e tiene per blocchi, non più riga per riga — una riga di continuazione (che non inizia con un timestamp) resta nel blocco del suo evento. Trovato testando la logica prima di attivare il filtro, non in produzione. 3. **(a valle, su richiesta esplicita di Mauro) Spostamento fisico di `servizio-gmail-check`**: da `scripts/servizio-gmail-check/` a `gemello-nave/gmail-check/` — stesso principio "possesso fisico implica possesso logico" già applicato a `logbook/` (vedi sotto), esteso qui: da quando `gemello-nave` classifica anche `gmail-check:`, il servizio meccanico che genera quegli eventi vive dentro la sua cartella. Comporta un servizio **systemd** (non crontab come `trigger/`), quindi l'unit installata (`/etc/systemd/system/servizio-gmail-check.service`) andava aggiornata e il servizio riavviato. **Scoperta tecnica utile**: il blocco assoluto del classificatore Auto Mode su comandi systemd riguarda specificamente `systemctl enable` (registrazione permanente al boot) — `systemctl daemon-reload`/`restart` su un servizio già installato/abilitato **non** sono bloccati, eseguiti senza problemi. Il servizio restava già `enabled` da prima, quindi non è mai stato necessario un `enable` nuovo — non è chiaro se quello specifico sarebbe passato, non testato. Verificato `active (running)` dal nuovo path subito dopo il riavvio. - **Principio "possesso fisico implica possesso logico" (Mauro, 11/09)**: quando una cartella entra nella competenza di `gemello-nave`, il flusso reale deve seguirla per intero — non basta spostare i file se poi Adrian continua a leggerli/interpretarli direttamente altrove. Caso reale che ha reso il principio esplicito: `logbook/` spostata dentro `gemello-nave/` ma il suo alert restava letto direttamente dall'hook `session-start-reminder.sh` di Adrian, bypassando del tutto `gemello-nave` — corretto nella stessa sessione: `collect_logs.py` ora scrive in mailbox (sender `logbook:`), il perimetro di `gemello-nave` esteso di conseguenza, l'hook non legge più `logbook/` direttamente. Applicare questo principio per ogni futuro spostamento di cartella. - **Metodo di transizione: pezzo per pezzo, mai un salto secco** (Mauro, 11/09). Fase attuale ("shadow mode"): Adrian continua a triagare gli eventi `check_job_health` come sempre; `gemello-nave` elabora in parallelo gli stessi eventi per verifica. Solo dopo aver controllato che il suo giudizio regge su casi reali, Adrian smette di occuparsene lui stesso. Stesso schema per ogni futuro allargamento di perimetro. ## Stato attuale (11/09/2026) Tutto dentro `gemello-nave/`: `CLAUDE.md`/`MEMORY.md`/`archivio/` (`log.md` + `conoscenza-nave.md`, spostati qui l'11/09 per coerenza con la struttura di Adrian — vedi sopra), `.claude/settings.json` (deny `PushNotification`), `trigger/` (`watch.py` + `PROTOCOLLO.md` + `watch.log` + `trigger.offset`, cartella propria dall'11/09 — stessa logica di `logbook/`, vedi sopra; demone Python puro, loop persistente interno 90s tenuto solo per test manuali, meccanismo di produzione è `--once` da crontab). **Installato e attivo via crontab utente** (`*/2 * * * * ... trigger/watch.py --once`), non systemd: il piano originale (servizio `Type=simple` con loop persistente, `Restart=on-failure`) richiedeva `sudo systemctl enable`, bloccato in modo assoluto dal classificatore Auto Mode di Claude Code (azione di sistema con permessi elevati, nessun prompt interattivo da approvare anche con via libera esplicita di Mauro). Il crontab utente (nessun sudo, stesso meccanismo già in uso per `check_job_health.py`) ottiene lo stesso risultato pratico — `watch.py` ha un flag `--once` per l'invocazione singola da cron, oltre al vecchio loop persistente tenuto per test manuali. **Memoria allineata al modello EPROM/RAM/hard disk** (richiesto da Mauro, verificato 11/09): `CLAUDE.md` = EPROM, `MEMORY.md` = RAM (letto per primo a ogni invocazione, istruzione ora esplicita), `log.md` = hard disk (consultato a pezzi solo su necessità, mai riletto per intero di default). Corretta un'assenza: la prima versione elencava i tre file ma non ne imponeva l'ordine di lettura — dopo il fix, un test ha mostrato piena coerenza di giudizio tra invocazioni diverse sullo stesso pattern (prima capitava un'incoerenza, vedi sotto). **Un errore di design trovato e corretto in corsa**: la prima versione del `CLAUDE.md` dava a `gemello-nave` il tool `PushNotification` — violazione diretta della regola "mai contatto diretto con Mauro". Corretto con la barriera tecnica sopra. **Osservato nei test**: classificazione routine/degno-di-attenzione corretta sui casi provati; un caso limite (evento sintetico non marcato "TEST") gestito con cautela ragionevole la prima volta ma con un giudizio incoerente la seconda — poi autocorretto controllando il proprio log. Segnale positivo (si autocorregge) ma anche prova che **il giudizio non è ancora perfettamente riproducibile tra un'invocazione cold-start e l'altra** — da tenere d'occhio in fase shadow. Un evento reale intercettato durante i test: `archivio_raw_conversazioni`/`check_doppi_gemma` hanno saltato l'intero ciclo di ieri (10/09), non solo "non ancora scaduto" — coerente con lo scheduler `gate-ufficio` già fermo prima delle 18:00 di ieri, non solo da quell'ora in poi. **Primo evento reale gestito (non di test)**: il fallimento vero del job `backup` delle 08:30 (già diagnosticato separatamente: drive di rete non montati sul PC ufficio) — classificato correttamente "degno di attenzione", segnalato in mailbox. Prima prova che il meccanismo intero (demone → cold-start → classificazione → segnalazione) regge su un caso reale, non solo su eventi finti iniettati a mano. **Due bug trovati e corretti subito dopo l'installazione (11/09)**: 1. **Crontab duplicato**: il primo tentativo di correggere il path (relativo → assoluto) ha aggiunto una seconda riga invece di sostituire la prima — il filtro `grep -v` usato per rimuovere la vecchia riga cercava la stringa `gemello-nave-watch`, mai presente nel comando reale (`gemello-nave/watch.py`, cartella e file separati). Stessa classe di bug del doppio `Monitor` di stamattina (verifica insufficiente prima di aggiungere una schedulazione). La entry col path relativo comunque falliva silenziosamente (nessuna directory `scripts/` sotto `$HOME`), quindi nessun doppio costo LLM reale — ma il rischio era concreto. Corretto: una sola entry, path assoluti ovunque. 2. **`log()` scriveva ogni riga due volte**: stampava sia su stdout sia su file — pattern copiato da `servizio-gate-watch` (dove aveva senso: stdout va a `journald` via systemd, il file è una copia separata). Con l'invocazione da crontab (`--once >> watch.log 2>&1`) stdout e file sono la stessa destinazione, quindi ogni riga risultava duplicata. Corretto: solo scrittura su file. **Non ancora fatto**: tabella di autonomia formale, osservazione prolungata su eventi reali (i due bug sopra corretti nei primi minuti, nessun ciclo pulito ancora osservato per un periodo lungo). **Bilancio shadow-mode giorno 1 (11/09, mattinata)**: confronto diretto tra il giudizio di `gemello-nave` e la lettura indipendente di Adrian sugli stessi eventi reali (job Ufficio saltati, fallimento backup 08:30, tre probe di test) — **coincidenza totale**, incluso l'errore di coerenza sul secondo probe che si è autocorretta da sola consultando il proprio `log.md`. In più, ha un comportamento di dedup più disciplinato di quanto Adrian faccia a mano: non ri-segnala in mailbox un problema il cui gap cresce solo per il tempo trascorso, aspetta che scada davvero un nuovo ciclo cron prima di riscrivere (osservato su `check_doppi_gemma`, prossimo ciclo naturale 11/09 11:30 — banco di prova in tempo reale per un secondo ciclo saltato di fila). Primo giorno di dati, non ancora sufficiente per un handoff (vedi "Metodo di transizione" sopra), ma segnale iniziale forte. ## Handoff completo su tutti e tre i perimetri (11/09, stesso giorno della fase shadow) Il piano originale sotto ("Prossimi passi", 3 gradini: shadow → handoff singolo perimetro dopo verifica → allargamento perimetro dopo altra verifica) è stato **scavalcato di proposito da Mauro**, non abbandonato per trascuratezza — decisione esplicita, non una deriva. Sequenza reale della stessa giornata: 1. Mattinata: fase shadow su `check_job_health:`/`logbook:`, un giorno di dati, coincidenza totale col giudizio di Adrian (vedi "Bilancio shadow-mode giorno 1" sopra). 2. Pomeriggio: Mauro chiede di aggiungere `gmail-check:` come **secondo** perimetro, ancora in shadow (stesso metodo) — non ancora un handoff. 3. Subito dopo, stesso pomeriggio: Mauro chiede lo spostamento fisico di `servizio-gmail-check` dentro `gemello-nave/gmail-check/` per coerenza fisica/logica (vedi sopra). 4. Immediatamente dopo: Mauro chiede l'**handoff completo su tutti e tre i perimetri insieme** (`check_job_health:`, `logbook:`, `gmail-check:`) — "spegnimento logico e fisico" lato Adrian. Non solo `gmail-check:` (che non aveva ancora un solo giorno di shadow, zero dati reali), anche `check_job_health:`/`logbook:` nonostante il metodo dichiarato fosse "osservazione prolungata prima di lasciare il compito per intero". Eseguito lo stesso turno: `gemello-nave/CLAUDE.md` aggiornato (nessuna menzione di shadow rimasta sui tre perimetri), la checklist di avvio sessione di Adrian (`avvio-sessione/SKILL.md`, passi 6-8) non legge più le righe grezze `check_job_health:`/`logbook:`/`gmail-check:` né le applica — solo `gemello-ufficio:` e le segnalazioni `gemello-nave:`. Il `Monitor` già armato in sessione è stato fermato e riarmato con il filtro nuovo per rendere lo spegnimento reale subito, non solo documentato per la prossima sessione. 5. **Bug trovato mentre si aggiornava il filtro del Monitor/checklist**: stessa classe di errore già corretta in `watch.py` per `gmail-check:` (filtro riga-per-riga invece che a blocchi, righe di continuazione multi-riga scartate/lasciate come frammenti orfani) — corretto nello stesso passaggio con un filtro `awk` a blocchi, sia nel Monitor live sia nella lettura dell'arretrato documentata in `avvio-sessione/SKILL.md`. **Implicazione pratica per sessioni future**: nessun doppio controllo umano/Adrian dietro `gemello-nave` su questi tre perimetri — se il suo giudizio sbaglia (silenzio su qualcosa di importante, o rumore eccessivo), nessuno se ne accorge finché non emerge altrove. Vale la pena osservare i primi giorni reali con più attenzione del solito, soprattutto su `gmail-check:` e `logbook:` che non hanno ancora un solo precedente osservato. ## Quarto perimetro: `gate-watch:` (11/09, stessa giornata) Mauro chiede esplicitamente un nuovo perimetro da aggiungere. Prima di proporne uno, verifica sul campo (grep sender reali in `mailbox.log`, non solo l'elenco teorico in `ADRIAN.md`) quali servizi meccanici scrivono ancora eventi che arrivano ad Adrian senza passare da `gemello-nave` — scartati `check_dropbox_health` (competenza `gemello-ufficio`, infrastruttura Ufficio, principio "possesso fisico implica possesso logico" applicato al contrario), `servizio-madre-watch` (oggi non riporta nulla ad Adrian, niente da migrare) e `servizio-schedulazioni` (di fatto defunto, nessun cron/systemd attivo, ultimo evento mailbox 03/09). Candidato scelto: **`servizio-gate-watch`** — stesso dominio di `check_job_health:` (fallimenti job gate-ufficio) ma canale diverso: oggi la sua slave one-shot dedicata legge il log del job fallito, spiega la causa e riporta via `SendMessage` **diretto** ad Adrian, bypassando mailbox e quindi `gemello-nave`. **Domanda di Mauro prima di procedere**: lo spostamento introduce un doppio passaggio per i messaggi di `gemello-ufficio`? No — canali indipendenti, chiarito esplicitamente: `gemello-ufficio` scrive già oggi in mailbox (sender `gemello-ufficio:`), un solo passaggio, invariato. `servizio-gate-watch` è un demone Nave puro (legge i log di gate-ufficio sincronizzati via Dropbox, non passa mai dalla sessione `gemello-ufficio`) — lo spostamento aggiunge un hop **solo** al suo canale (mailbox → classificazione `gemello-nave` → mailbox, invece di `SendMessage` istantaneo), stesso tradeoff già accettato per il canale di riporto generale di `gemello-nave` (mailbox invece di notifica istantanea). **Seconda domanda di Mauro**: `servizio-gate-watch` e `check_job_health` sono unificabili? No — condizioni di errore complementari e mutuamente esclusive, lo dicono i loro stessi `PROTOCOLLO.md`: `check_job_health` (cron/schedule-based) rileva un job **mai partito**; `servizio-gate-watch` (reattivo sui log in tempo reale) rileva un job **partito e fallito**, e solo lui legge il log per spiegare la causa. Un solo script dovrebbe comunque implementare due logiche distinte al suo interno — nessun guadagno reale di semplicità, si perde la separazione pulita "meccanico puro" vs "meccanico + LLM solo quando serve". Restano due servizi distinti. **Migrazione eseguita (fisica + logica)**: 1. `git mv scripts/servizio-gate-watch gemello-nave/gate-watch` (stesso principio "possesso fisico implica possesso logico" già applicato a `logbook/`/`gmail-check/`). 2. `watch.py`: rimossa `launch_failure_slave`/`avvia_slave.sh` — sul fallimento ora scrive l'evento grezzo (job, exit_code, duration, path del log, ultime warning) in mailbox, sender `gate-watch`, stesso schema a blocchi già in uso per `gmail-check:` (righe di continuazione senza timestamp restano nel blocco). **Semplificazione reale, non solo uno spostamento**: `gemello-nave` è già invocata a freddo sullo stesso evento dal demone di trigger e ha accesso pieno agli strumenti — può leggere il log da sola, niente più bisogno di una slave separata dedicata solo a quello. Un passaggio in meno nel meccanismo complessivo, non uno in più. 3. `servizio-gate-watch.service`: `ExecStart` aggiornato al nuovo path, ricopiato in `/etc/systemd/system/`, `daemon-reload` + `restart` (non un `enable` nuovo — il servizio era già abilitato, non ha toccato il blocco assoluto di Auto Mode su `systemctl enable`, stessa scoperta già fatta per `servizio-gmail-check`). Verificato `active (running)` dal nuovo path. 4. `gemello-nave/trigger/watch.py`: `FILTERS` esteso con `"gate-watch:"`. 5. `gemello-nave/CLAUDE.md`: perimetro esteso a quattro, criteri di classificazione per `gate-watch:` aggiunti (leggere il log al path indicato, quasi sempre degno di attenzione — a differenza di `check_job_health:` non esiste una soglia "non ancora scaduto" per un job già fallito; eccezione nota: `exit_code=3` lock Excel su `backup` va comunque segnalato, non silenziato — Mauro l'ha deciso esplicitamente, il lock è informazione voluta su chi sta lavorando sul file). Handoff diretto, nessuna fase shadow (stesso schema scelto ieri per `gmail-check:`/`check_job_health:`/`logbook:`). 6. `gate-watch/PROTOCOLLO.md` riscritto: non più istruzioni per una slave one-shot, solo descrizione meccanica di cosa fa `watch.py` (stesso stile di `trigger/PROTOCOLLO.md`). 7. `avvio-sessione/SKILL.md` (passi 6/8): filtro `awk` esteso con `gate-watch` tra i prefissi grezzi esclusi dalla lettura diretta di Adrian, testo dei due passi aggiornato. 8. `Monitor` mailbox già armato in sessione: fermato (`kill` sul PID, confermato dal task-notification "exit 144") e riarmato con lo stesso filtro esteso — spegnimento reale subito, non solo documentato per la prossima sessione (stesso schema già seguito ieri per l'handoff dei primi tre perimetri). **Non ancora fatto**: nessun evento reale osservato su `gate-watch:` (zero precedenti, come già `gmail-check:`/`logbook:` ieri) — da tenere d'occhio nei prossimi giorni, in particolare se `gemello-nave` legge correttamente il log e riconosce il pattern noto `exit_code=3`/lock Excel come "segnala comunque" invece di silenziarlo per errore. ## Spostamento fisico di `servizio-inbox-watch`/`servizio-madre-watch` (11/09, stessa giornata) Mauro, dopo l'aggiunta di `gate-watch:`: "le parti fisiche io comunque le sposterei sotto gemello-nave anche se non riportano nulla" — principio diverso da "possesso fisico implica possesso logico" (quello vale quando la cartella *entra* nel perimetro di giudizio): qui è puro raggruppamento — tutti i servizi meccanici Nave-side in un solo posto, indipendentemente dal fatto che generino o no eventi per `gemello-nave`. Rimanevano in `scripts/` solo `servizio-inbox-watch` (sorveglianza `inbox-documenti/`, autonomo, non scrive mai in mailbox verso Adrian/`gemello-nave`) e `servizio-madre-watch` (ping periodico a `gemello-ufficio`, idem) — entrambi spostati: 1. `git mv scripts/servizio-inbox-watch gemello-nave/inbox-watch`, `git mv scripts/servizio-madre-watch gemello-nave/madre-watch`. 2. Path interni aggiornati (`watch.py`, `PROTOCOLLO.md`, `.service` — riferimenti testuali al vecchio path `scripts/servizio-*-watch`). 3. Unit systemd reinstallate dal nuovo `ExecStart`, `daemon-reload` + `restart` per entrambe — verificato `active (running)` dal nuovo path per tutti e due. 4. `gemello-nave/CLAUDE.md`: aggiunta una voce esplicita che le distingue dalle cartelle-perimetro (`trigger/`, `gmail-check/`, `gate-watch/`) — **non** fanno parte del suo giudizio, sono solo co-locate, per evitare che un'invocazione futura le confonda con eventi da classificare. 5. `ADRIAN.md`: riga ecosistema aggiornata, nota anche `servizio-schedulazioni` (rimasto in `scripts/`, non toccato) risulta di fatto defunto — non ripulito in questa sessione, solo segnalato. Nessun cambio di logica in nessuno dei due — solo spostamento fisico + aggiornamento path. ## Spostamento fisico di `flotta-diagnostics` (11/09, terza richiesta della stessa giornata) Mauro chiede se si può spostare anche `flotta-diagnostics/` (check settimanale hardware/docker/rete/backup/logbook, `sentinel.py`, cron domenica 06:00) — stesso principio di `inbox-watch`/`madre-watch`, nessun evento verso `gemello-nave`. Spostata con `git mv flotta-diagnostics gemello-nave/flotta-diagnostics`. **Bug reale trovato e corretto prima di lasciarla così**: `logbook_health.py` calcolava `LOGBOOK_DIR` risalendo due livelli da sé stesso e ridiscendendo in `gemello-nave/logbook` — corretto quando `flotta-diagnostics` era diretta sorella di `gemello-nave` a livello root. Dopo lo spostamento, `flotta-diagnostics` è diventata sorella di `logbook/` **dentro** `gemello-nave/`, non più sorella di `gemello-nave` stesso — il path risultava `gemello-nave/gemello-nave/logbook`, inesistente. Il check `logbook` di `sentinel.py` avrebbe fallito silenziosamente (errore catturato e riportato come payload, non un crash) dal prossimo giro domenicale in poi, senza che nessuno se ne accorgesse subito. Corretto risalendo di un livello solo. Verificato con un run reale di `sentinel.py` dal nuovo path (`python3 sentinel.py`, non via `run_sentinel.sh` — quel wrapper cerca un `.venv` a un livello sbagliato da prima di questo spostamento, non lo tocca l'invocazione reale via cron che usa `python3` di sistema): exit 0, tutti e sei i check completati, incluso `logbook` ora `status: OK`. Aggiornati anche: crontab utente (`cd` al nuovo path), `archivio/_secrets/config.md` e `archivio/_secrets/RECOVERY.md` (fuori git, snapshot crontab completo rinfrescato mentre c'ero — erano già disallineati da prima su altri job non toccati da questa sessione, non solo su `flotta-diagnostics`). ## Bug reale trovato cercando altro da spostare: migrazione `gmail-check` incompleta (11/09) Mauro chiede di cercare altro da consolidare. Scansione di `scripts/` per residui: trovata `scripts/gmail-check/` ancora presente con `.env` (credenziali OAuth vere), `gmail_check.py`, `oauth_bootstrap.py`, `.venv` — nonostante lo spostamento dell'11/09 mattina avesse dichiarato "spostato in `gemello-nave/gmail-check/`". In realtà si era spostato solo l'orchestratore (`watch.py`), che chiamava (`subprocess`, path assoluto hardcoded) il checker vero rimasto indietro in `scripts/gmail-check/gmail_check.py` — migrazione a metà, non notata prima perché il servizio continuava a funzionare (il path assoluto restava valido). Corretto: 1. Spostati dentro `gemello-nave/gmail-check/`: `gmail_check.py`, `oauth_bootstrap.py`, `requirements.txt` (git mv), `.env`/`.env.example`/`.venv` (mv, non tracciati da git). 2. Cancellato `scripts/gmail-check/PROTOCOLLO.md` — copia vecchia, superata da quella già riscritta in `gemello-nave/gmail-check/` l'11/09 mattina (mai stata sincronizzata dopo il primo spostamento parziale). 3. `watch.py`: `CHECKER`/`CHECKER_VENV_PY`/`STATE_FILE` ora relativi a `Path(__file__).parent` invece che a un path assoluto `scripts/gmail-check/` — stesso principio già in uso per gli altri demoni (`gate-watch/`, `trigger/`). `gmail_check.py`/`oauth_bootstrap.py` non hanno richiesto fix: il loro `.env` era già relativo a se stessi (`Path(__file__).parent / ".env"`). 4. **Verificato con un'invocazione reale** (non solo `py_compile`): `gmail_check.py --since ` dal nuovo path, via il `.venv` spostato insieme — exit 0, nessun messaggio (atteso). A differenza di `backup_avamposto.py` (SSH verso un VPS di produzione, non eseguito live), questo è un check di sola lettura via API Gmail, già eseguito automaticamente ogni 20 minuti dal servizio — nessun rischio aggiuntivo a farlo girare una volta in più per verifica. 5. Servizio riavviato, verificato `active (running)` e un tick reale completato dal nuovo path nel `watch.log`. 6. `gemello-nave/archivio/conoscenza-nave.md` aggiornato (path credenziali). **Lezione**: quando uno spostamento dichiara "fatto", vale la pena un grep esplicito per il nome della cartella vecchia in tutto il resto del codice (non solo verificare che il servizio riparta) — un simlink logico rotto a metà (orchestratore spostato, worker no) può restare invisibile a lungo se il path assoluto hardcoded continua a funzionare. ## Continuando la scansione: `monitor-consumo-istanze` spostato, `archivio-raw-conversazioni` NO Stesso giro di scansione di `scripts/` per altri residui Nave-side. Due candidati trovati: - **`monitor-consumo-istanze/`** (report token per istanza, cron 05:58) — spostato in `gemello-nave/monitor-consumo-istanze/`. Path self-relativo (`HERE = Path(__file__).parent`), nessun fix di codice necessario. Riferimenti esterni corretti: crontab utente, hook `session-start-reminder.sh` (il consumer più importante — leggeva `report_latest.txt` per iniettare l'alert nel promemoria di avvio sessione), `ADRIAN.md`, `conoscenza-nave.md`, `RECOVERY.md`. Verificato con un run reale (`python3 report.py`), exit 0. - **`archivio-raw-conversazioni/`** (copia meccanica trascritti `.jsonl` prima del cleanup a 30gg del CLI, cron 05:55) — **spostato per errore, poi annullato nella stessa sessione**. Il proprio docstring dello script lo dichiara esplicitamente: "stesso identico script gira invariato su Nave e Ufficio" — costruito il 05/09 apposta per girare su entrambe le macchine (Nave: cron; Ufficio: job `archivio_raw_conversazioni` in `schedule.json`/`scheduler_locale.py` di `gate-ufficio`, vedi `project_archivio_raw_conversazioni.md`). Spostarlo sotto `gemello-nave/` lo avrebbe rinominato anche nella copia sincronizzata via Dropbox su Ufficio, rompendo silenziosamente il suo job schedulato lì (path che io non posso correggere da Nave, competenza `gemello-ufficio`). **Criterio corretto, non solo "è meccanico e Nave-side"**: deve anche essere **esclusivo** di Nave, non condiviso con Ufficio — `git mv` annullato con un secondo `git mv` di ritorno prima di qualunque commit, crontab ripristinato al path originale. Nessun danno reale (mai committato). Aggiornato `gemello-nave/CLAUDE.md` per rendere esplicito questo criterio più preciso, non solo "raggruppare i servizi meccanici di Nave". ## Ultimo giro: backup_completo/pcloud_backup/wg_monitor/guacamole_watchdog + bug reale trovato Mauro conferma di procedere anche sui quattro script più delicati (backup/rete reali, non solo report): `backup_completo.sh` (Restic, cron 03:00), `pcloud_backup.sh` (mirror pCloud, cron 01:15), `wg_monitor.sh` (tunnel WireGuard, ogni 5 min), `guacamole_watchdog.sh` (watchdog Guacamole/x11vnc, ogni 5 min) — tutti Nave-esclusivi (nessun equivalente Ufficio), a differenza di `archivio-raw-conversazioni/` scartata nel giro precedente. Spostati in `gemello-nave/` insieme a `backup_completo_exclusions.txt`/`backup_home_exclusions*.txt` e ai relativi log (`scripts/logs/backup_completo.log`/`pcloud_mirror.log`/`wg_monitor.log` → `gemello-nave/logs/`). **Bug reale di produzione trovato durante la verifica, non correlato allo spostamento in sé**: `pcloud_backup.sh` usava `$SCRIPT_DIR` (per `HOME_EXCLUSION_FILE`) **prima** di definire `SCRIPT_DIR` stesso, poche righe più sotto. Con `set -euo pipefail` (nounset attivo), questo fa terminare lo script all'istante, prima ancora di scrivere la prima riga di log — e il cron lo invoca con `2>&1 >/dev/null`, quindi zero errore visibile ovunque. Il bug risale al commit `10134e7` (05/09/2026, "estende backup restic/pCloud a /home/mauro" — l'aggiunta del mirror home ha introdotto la variabile fuori ordine) — confermato dal log stesso (`pcloud_mirror.log` fermo al 05/09/2026, nessuna riga nuova nei 6 giorni successivi). **Il mirror offsite pCloud è stato silenziosamente rotto per 6 giorni**, scoperto solo mentre si verificava la sicurezza dello spostamento (path self-relativi, ordine delle variabili). Corretto spostando la definizione di `SCRIPT_DIR` subito dopo `SOURCE_PATH`, prima di qualunque uso. Verificato con `bash -n` + controllo esplicito dell'ordine delle assegnazioni (non eseguito un run reale live: sync pCloud reale, lasciato al prossimo trigger cron delle 01:15 — a differenza di `guacamole_watchdog.sh`/ `wg_monitor.sh`, sola lettura/idempotenti, eseguiti live per verifica, `exit 0` entrambi). Corretto anche il pattern di esclusione rclone di `pcloud_backup.sh`: escludeva `scripts/logs/**` per evitare che il mirror si inseguisse la coda da solo (bug diverso, già noto e fixato l'11/08/2026) — con i log ora in `gemello-nave/logs/`, aggiunta la nuova esclusione accanto alla vecchia (lasciata, innocua). Aggiornati: `collect_logs.py` (`LOG_SOURCES` per `backup_completo`/`pcloud_mirror`), crontab (4 righe), `config.md`/`RECOVERY.md` (fuori git, crontab risincronizzato una seconda volta lo stesso pomeriggio — notata una discrepanza sulla frequenza di `wg_monitor.sh`, ogni 5 min nel refresh più recente contro ogni ora nel refresh di poco prima, nessuna mia modifica di oggi ha toccato quella riga, causa non investigata, segnalata a Mauro nei due file), `conoscenza-nave.md`, `gemello-nave/CLAUDE.md`. `.gitignore` non ha richiesto modifiche: la regola generica `logs/` (riga 65, nessuno slash iniziale) copre già `gemello-nave/logs/` a qualunque profondità. ## Pulizia finale: `servizio-schedulazioni`/`maintenance_check.py`/`nave_registro.json` (11/09) Chiusura del thread aperto durante la scansione precedente ("non ancora ripulito"). Non uno spostamento — tre residui morti, eliminati: - **`servizio-schedulazioni`**: la cartella non esisteva già più in `scripts/` (verificato con `find`) — restava solo il nome in `ADRIAN.md` come "in produzione (di fatto defunto)". Riga aggiornata, tolto dall'elenco. - **`nave_registro.json`** (`scripts/`, gitignored, mai tracciato da git): registro di stato orfano del vecchio `servizio-schedulazioni` (voci fino al 03/09/2026, poi nulla) — nessun riferimento da nessuna parte nel codice, eliminato con `rm`. Rimossa anche la riga corrispondente in `.gitignore` (non serve più ignorare un file che non esiste). - **`maintenance_check.py`** (`scripts/`, tracciato): router mattutino per "Elon" (bot Telegram/vault, agente smantellato da mesi) — cron già "PAUSED 2026-07-15" (commentato, non solo disabilitato), nessun altro riferimento attivo. Eliminato con `git rm`, insieme alla riga di crontab morta (`# PAUSED ...`, non solo lasciata commentata) e agli accenni "attivo"/"paused ma presente" in `config.md`/`conoscenza-nave.md`, corretti per riflettere la rimozione reale. Verifica fatta prima di eliminare: `grep` per ogni nome su tutto il repo (`*.py`/`*.sh`/`*.md`), nessun risultato attivo — solo narrativa storica in `CHANGELOG.md`/canvas dei progetti Elon/manutenzione, lasciata intatta (storico, non stato corrente). ## Un quinto residuo trovato su domanda diretta di Mauro: `scripts/msg_venv/` Mauro chiede esplicitamente "tutte quelle cartelle in scripts non c'è qualcosa da spostare?" — riscansione completa di `scripts/` voce per voce (non solo i file più vistosi). Trovato un'altra migrazione a metà, stessa classe del bug `gmail-check`: `inbox-watch/PROTOCOLLO.md` (già dentro `gemello-nave/`) referenzia `scripts/msg_venv/` (venv con `extract-msg`, per allegati `.msg` di Outlook in `inbox-documenti/`) — la dipendenza era rimasta indietro quando la cartella si è spostata. **Diversa gravità dal caso `gmail-check`**: qui non era rotto (`avvia_slave.sh` lancia la slave con `cwd=ROOT`, quindi il path relativo `scripts/msg_venv` nel prompt risolveva comunque correttamente) — spostato lo stesso per coerenza/pulizia, non per un bug reale stavolta. Spostato in `gemello-nave/inbox-watch/msg_venv/` (dentro la cartella del servizio che lo usa, come il `.venv` di `gmail-check/`), tre occorrenze in `PROTOCOLLO.md` aggiornate, verificato con `import extract_msg` dal nuovo path. Rimosso anche `scripts/logs/slave_dialog_test.log`, unico file rimasto in quella cartella dopo lo spostamento dei quattro script di backup/rete — un log di test orfano, nessun riferimento nel codice. Cartella `scripts/logs/` risultava vuota, rimossa. **Scansione finale di `scripts/` (tutto il resto verificato, nessun altro candidato)**: `archivio-raw-conversazioni/` (condivisa Nave+Ufficio, esclusa deliberatamente), `avvia_slave.sh`/ `slave_sentinel_ensure.sh`/`slave_mailbox_*` (infrastruttura condivisa, usata anche da `gemello-ufficio`), `free_port.sh` (utility manuale generica, nessuno schedule), `reminders_sync.py` + `reminders_venv/` + `.reminders_state.json` (tool interattivo di Adrian, non un demone Nave), `schedulazioni_venv/` (usato da `check_job_health.py`, che vive in `PYTHON/gate-ufficio/nave/` — non spostato, resta neutro in `scripts/`), `slave-sentinels/` (esperti — categoria distinta, gerarchia esperti/gemelli non ancora decisa, vedi `project_standard_esperti.md`, fuori scope oggi). ## Sesto residuo: `scripts/secrets/restic_password` (11/09, domanda diretta di Mauro) Mauro nota la cartella `secrets/` e chiede se sia nel posto giusto — prima ipotesi sua: `archivio/_secrets/` (dove vivono già `credentials.md`/`config.md`/`RECOVERY.md`/`servizi.md`). **Chiarito perché non è lì che va**: `archivio/_secrets/` è la copia di riferimento leggibile (markdown, per consultazione umana/Adrian) — il valore della password restic è già lì, `credentials.md` sezione "RESTIC BACKUP". `scripts/secrets/restic_password` è invece il file **operativo** che il binario `restic` legge alla lettera via `RESTIC_PASSWORD_FILE` (solo la password, niente markdown) — stesso pattern già documentato in `credentials.md` per la password MQTT di `domotica-iot` (copia di riferimento in `_secrets/`, file operativo vicino a chi lo consuma). Le due cose coesistono per design, non sono una duplicazione da correggere. Verificato invece che è **Nave-esclusivo e usato solo da consumer ormai in `gemello-nave/`** (`backup_completo.sh`, `flotta-diagnostics/sentinel.py`) — stesso criterio di tutto il resto di oggi. Spostato in `gemello-nave/secrets/` con `mv` (non `git mv`: mai stato tracciato da git, correttamente ignorato). Permessi verificati preservati (`700`/`600`). Aggiornati i due consumer (`RESTIC_PASSWORD_FILE` in `backup_completo.sh`, env var nella riga crontab di `sentinel.py`), `.gitignore` (riga esplicita sul path vecchio, non un pattern generico — stesso tipo di gotcha già visto per `msg_venv`), `config.md`/`RECOVERY.md` (fuori git), `gemello-nave/CLAUDE.md`. Non eseguito alcun backup live per verificare (operazione su dati reali) — `test -r` sul file dal nuovo path conferma solo la leggibilità, il vero test è il prossimo cron delle 03:00. ## Settimo passaggio: apertura della domanda "esperti sotto i gemelli" (11/09) Mauro riapre esplicitamente il punto già segnato in `MEMORY.md` come rimandato ("nesting degli esperti vs `gemello-nave/` — chiedersi se anche gli esperti dovrebbero salire"), proponendo `expert-domotica` come primo candidato. Verificato il motivo: il suo dominio (stack Docker Zigbee/MQTT/Home Assistant/InfluxDB, `domotica-mosquitto`/`domotica-zigbee2mqtt`/ `domotica-homeassistant`/`domotica-influxdb`) è Nave-esclusivo quanto `flotta-diagnostics` — container che esistono fisicamente solo su Nave. C'è anche sovrapposizione concettuale già esistente: `gemello-nave/CLAUDE.md` consulta sia `conoscenza-nave.md` (infrastruttura generale) sia, implicitamente, questo esperto per lo stack smart-home — due fonti Nave parallele, non annidate, oggi. **Complicazione reale, diversa da tutti gli spostamenti precedenti**: `scripts/ slave_sentinel_ensure.sh` (il lanciatore generico di tutti e tre gli esperti) aveva `SENTINELS_DIR="scripts/slave-sentinels"` fissa, condivisa. Spostare `expert-domotica` da sola richiedeva generalizzare quello script, non solo aggiornare path assoluti come per i sei spostamenti precedenti. Fatto: introdotto `EXTRA_SENTINELS=("gemello-nave/expert-domotica/")`, il loop ora scansiona `"$SENTINELS_DIR"/*/ "${EXTRA_SENTINELS[@]}"` insieme — verificato con un test isolato della sola logica di discovery (non ha lanciato nessuna sessione reale, `expert- domotica` risultava già `exited`/non viva, nessun rischio di toccare un processo in corso): trova correttamente tutti e tre gli esperti, dal path giusto ciascuno. Eseguito: `git mv scripts/slave-sentinels/expert-domotica gemello-nave/expert-domotica` (+ `.session_id`, gitignored, spostato a mano), `.gitignore` (riga esplicita nuova per il path nuovo, la vecchia resta valida per gli altri due esperti), `gemello-nave/CLAUDE.md` (voce esplicita: categoria diversa dai servizi meccanici, è un esperto, non entra nel perimetro di giudizio), `ADRIAN.md`, `conoscenza-nave.md`, `MEMORY.md` (sezione canvas aggiornata). **Deciso di NON toccare `expert-data`/`expert-flussi`**: Mauro li vede come "probabili candidati" sotto `gemello-ufficio` invece (dominio Ufficio-adjacent — `gate-ufficio`/`OnAir`/`Frank`/`I:\`), ma ho fatto notare una differenza sostanziale rispetto a tutto il resto di oggi: girano fisicamente su Nave (sessioni `claude --bg` via `slave_sentinel_ensure.sh`, raggiunte via `SendMessage`) leggendo mirror/parquet già sincronizzati — `gemello-ufficio` è un meccanismo del tutto diverso (secondo account Windows, raggiungibile solo via mailbox, mai `SendMessage`). "Spostarli sotto gemello-ufficio" potrebbe voler dire solo riassegnazione logica/documentale (restano Nave-hosted, cambia solo il "genitore" concettuale) oppure una migrazione reale dell'esecuzione (diventerebbero mailbox-only come `gemello-ufficio` stesso) — due lavori molto diversi, esattamente il bivio "pull vs push" già non deciso in `project_standard_esperti.md`. Mauro: lasciamo in sospeso per ora, non deciso quale delle due letture intendeva. ## Spostamento fisico di `config-backups` (11/09, quarta richiesta della stessa giornata) Stessa domanda di Mauro, quarto giro: `config-backups/` (snapshot notturno config VPS Avamposto via SSH/scp, `backup_avamposto.py`, cron 02:45) — stesso principio, nessun evento verso `gemello-nave`. Spostata con `git mv config-backups gemello-nave/config-backups`. Path interni allo script già relativi a `Path(__file__).parent` (nessun fix necessario lì, a differenza di `flotta-diagnostics`). Un solo riferimento esterno da correggere: `gemello-nave/logbook/collect_logs.py` leggeva `config-backups/avamposto_snapshot.log` per il report giornaliero — path aggiornato a `gemello-nave/config-backups/avamposto_snapshot.log`. Crontab aggiornato (`cd` al nuovo path). **Non eseguito uno SSH live di prova**: lo script si connette a un VPS di produzione (Avamposto) via `ssh`/`scp` — a differenza di `sentinel.py` (sola lettura locale) girare `backup_avamposto.py` fuori dal suo orario tocca infrastruttura esterna reale; verificato invece con syntax-check (`py_compile`) e lettura del codice (i path sono tutti relativi a sé stesso), lasciando che sia il cron delle 02:45 di stanotte a validarlo sul serio. `archivio/_secrets/config.md`/`RECOVERY.md` aggiornati (fuori git) — consolidato anche un albero duplicato in `config.md` dove `config-backups/` era rimasta fuori dal blocco `gemello-nave/` appena introdotto nello stesso file.