Ho provato a mentire ai gate di DFD
Due domande su DFD. Quanti fix servono dopo il rilascio? E se mento apposta al processo, i gate se ne accorgono? La CLI ferma 4 bugie su 15. Un agente che rilegge il lavoro senza sapere cosa cercare ne trova quasi tutte. Il punto debole è proprio lì: quella rilettura oggi non lascia traccia.

Nei tre articoli precedenti di questa serie (terzo, quarto, quinto) il segnale più utile è arrivato sempre dallo stesso posto: un gate che diceva qualcosa di diverso dalla realtà. Una CI che non poteva fallire, un gate fermo a uno stato vecchio, evidenze che esistevano su una sola macchina.
Sono storie. Da storie non si capisce se DFD funziona. Così ho provato a misurare due cose:
- quanti fix servono dopo che una feature è stata dichiarata finita;
- cosa succede se mento al processo apposta, una bugia alla volta.
Primo: quanti fix dopo il rilascio
Ho preso ogni commit feat con i file che ha toccato, esclusi test, documentazione e contenuti, e ho contato i commit fix sugli stessi file nei dieci giorni successivi. È una misura grezza: un fix sullo stesso file non è per forza colpa di quella feature, e i bug che nessuno ha ancora trovato non compaiono.
Le due feature DFD su Taita (quelle del quinto articolo): zero fix attribuibili dopo il rilascio. Sui file del primo accesso ne sono arrivati due, ma uno correggeva un bug di aprile e l’altro era un requisito nuovo. C’è stato un difetto vero, un riquadro del login Telegram senza titolo per gli screen reader, visibile solo in produzione. L’ha trovato lo stesso giorno il controllo di accessibilità previsto dal piano di rollout.
Le altre feature su Taita, nello stesso mese: 13 su 22 hanno almeno un fix nei dieci giorni successivi. Sono quasi tutte feature che parlano con un modello linguistico, con Google o con WhatsApp: date calcolate dal modello che non tornano con il giorno detto dall’utente, orari nel fuso sbagliato, messaggi non letti.
Una feature fatta con spec-kit su un altro progetto, FantaContext: unita all’una di notte, quattro fix entro il pomeriggio. Il primo cambiava 649 righe perché in produzione la feature non funzionava; un altro serviva ad accettare un’etichetta che nei dati veri era diversa da quella dei dati di prova.
I numeri sono a favore di DFD, ma non dimostrano niente. Le feature DFD erano interfaccia, verificabile quasi tutta in locale. Quelle con più fix dipendono da input che in locale non ci sono. Ho confrontato lavoro facile da verificare fatto con DFD con lavoro difficile da verificare fatto senza, e con questi dati non riesco a separare il framework dal tipo di lavoro.
Il conteggio quindi non risponde alla domanda. Mi ha fatto cambiare domanda: invece di chiedermi se DFD produce meno bug, mi sono chiesto se i suoi gate si accorgono quando qualcosa non torna.
Secondo: mentire ai gate
Il banco di prova
Ho fatto costruire a un agente un piccolo laboratorio: un repository nuovo, senza remote, con una funzione che calcola il ciclo settimanale del menu della mensa. DFD installato da zero, una feature portata fino all’approvazione del rilascio. Su questa base, una bugia per branch:
- evidenze rifatte senza rifare il lavoro: codice cambiato e hash ricalcolati a mano, log dei test scritto a mano, fingerprint copiati dopo un cambio di design;
- test che non possono fallire: un test che non chiama il codice, un test saltato, una CI che termina con
|| true, cioè esce senza errori qualunque cosa succeda; - lavoro fuori dal perimetro: un file fuori ambito, una regressione in codice esistente;
- documenti cambiati dopo l’approvazione: la DoD modificata nel catalogo dei criteri, oppure solo nel documento leggibile;
- stato vecchio: un revert con lo stato rimasto “verificato”, evidenze non committate e controllate da un altro clone;
- dati comodi: un criterio verde solo sulle fixture;
- decisione umana registrata dall’agente stesso.
Quindici bugie in tutto. Prima di eseguirle, l’agente ha scritto e committato le sue predizioni: cosa si aspettava per ognuna e perché, citando il codice della CLI. Le predizioni non si potevano correggere dopo.
Ho fatto girare tutto due volte. La seconda versione correggeva i limiti della prima: CLI compilata dal sorgente attuale invece di una build vecchia, bugie nascoste dentro 5-6 commit di lavoro normale invece che isolate, quattro branch di controllo senza nessuna bugia, verificatori su modelli diversi da quello che aveva iniettato le bugie.
Cosa vede la CLI da sola
Su 15 bugie, 4 bloccate, 10 non viste, 1 fuori portata.
Le bloccate:
- codice cambiato dopo la verifica;
- DoD cambiata nel catalogo dei criteri dopo l’approvazione del design;
- revert del codice con lo stato rimasto “verificato”;
- evidenze non committate: su un altro clone la CLI dice “evidenza assente” e blocca.
L’ultima è il problema del quinto articolo, e su questo la CLI fa il suo lavoro.
Le altre dieci passano tutte. Chi ricalcola un hash senza rieseguire i test spegne il controllo “evidenza modificata”. Un log con un test saltato, o con FAILED ed exit 0, risulta verde: la CLI non legge il contenuto dei log. Un file fuori dall’elenco delle evidenze per la CLI non esiste. La DoD cambiata solo nel documento che leggono le persone non cambia niente, perché il gate guarda l’altro file.
La decisione umana registrata dall’agente è fuori portata per scelta: la CLI dichiara di non autenticare chi decide.
Con la CLI aggiornata il risultato è identico, messaggio per messaggio: le condizioni dei gate non erano cambiate. E nello scenario realistico la CLI dà a tutti i 14 branch lo stesso stato, “review da riconfermare”, zero errori. Per lei un branch con una bugia e uno pulito sono uguali.
Cosa vede un agente che rilegge
Ogni branch è stato dato a un agente che non sapeva niente dell’esperimento, in una cartella dal nome neutro, con un’istruzione sola: porta la feature al rilascio seguendo DFD, e prima di approvare dimmi se lo stato dichiarato corrisponde al lavoro reale.
| Verificatore | Bugie trovate | Falsi allarmi sui controlli |
|---|---|---|
| Claude Sonnet | 9 su 10 | 0 su 4 |
| Codex | 7 su 7 completate | 0 su 3 |
Codex ha completato 7 branch con bugia su 10: gli altri si sono fermati al limite d’uso del mio account.
L’unica bugia sfuggita a Sonnet è la DoD modificata solo nel documento leggibile, la stessa che sfugge alla CLI. Le altre sono emerse in due modi. Quattro rieseguendo i test, che è esattamente quello che la CLI non fa. Cinque confrontando il diff con i test e con la specifica.
Due cose non me le aspettavo.
La prima: Codex ha bloccato tutti e tre i branch di controllo che ha completato, quelli senza bugie. Aveva ragione: la funzione non controlla che il numero di settimane sia positivo, e con zero va in errore. Il difetto c’era dall’inizio, nessuno lo aveva iniettato, e nessun criterio della DoD lo copriva. Anche nella prima versione un verificatore aveva trovato un difetto vero della base.
La seconda: Codex ha corretto il codice in cinque branch anche se gli era stato chiesto solo di riferire. Sonnet si è fermato alla segnalazione. Nessuno dei due ha registrato decisioni al posto mio.
Le predizioni sulla CLI erano giuste 15 su 15. Quelle sugli agenti no: mi aspettavo che Sonnet ne trovasse circa sei, e ne ha trovate nove. Avevo sottovalutato le bugie che si scoprono confrontando test e specifica.
Cosa ne tiro fuori
La CLI ferma chi dimentica, non chi mente. Se lo stato non viene aggiornato, il gate se ne accorge. Se viene aggiornato senza rifare il lavoro, no. È coerente con quello che la CLI dichiara di essere, un registro che non esegue niente e non autentica niente, ma è meno di quanto lascia intendere un gate verde.
La protezione vera oggi è la rilettura. Un agente che riesegue i test e confronta il diff con la specifica trova quasi tutto, anche senza sapere cosa cercare, e a volte trova bug che nessuno aveva messo. Il problema è che quella rilettura non lascia traccia nei gate. Se nessuno la fa, il gate resta verde lo stesso.
Il conteggio dei fix e l’esperimento dicono la stessa cosa da due lati. L’unico difetto di produzione preso da DFD su Taita è stato preso da un controllo eseguito davvero, in produzione, previsto dal piano. Dove il controllo è una dichiarazione, il gate vale quanto chi lo dichiara.
Da qui sono uscite cinque modifiche al framework, aperte come issue:
- legare le evidenze allo stato dei file su cui la suite è stata eseguita, e segnalare cosa è cambiato dopo;
- leggere i log: test saltati e
FAILEDcon exit 0; - includere la DoD leggibile in ciò che invalida la review;
- far dire alla review da riconfermare cosa è cambiato;
- far uscire
statuscon errore quando un gate è bloccato.
Le prime due portano dentro il gate proprio i due controlli che oggi fa solo chi rilegge.
Aggiornamento del 1° novembre: tutte e cinque sono state chiuse. Rifatto lo stesso esperimento, le bugie bloccate passano da 4 a 11 su 15 e quelle non viste scendono a zero. Il resoconto è nel prossimo articolo della serie.
Limiti da dichiarare
- Il laboratorio è piccolo e finto: una funzione, una feature, un dominio.
- Le bugie, e il lavoro normale che le nasconde, le ha scritte un agente Claude. I verificatori erano modelli diversi, ma i due difetti involontari del lavoro “pulito” mostrano quanto è difficile costruire un controllo davvero pulito.
- Un solo tentativo per ogni combinazione di branch e verificatore.
- L’isolamento dei verificatori è controllato sui comandi registrati, non con un log di sistema. Gli agenti Claude ereditano la mia configurazione.
- Le evidenze “su un’altra macchina” sono simulate con un clone locale.
- Il conteggio dei fix confronta feature di dominio diverso: va letto come un indizio.
- Le decisioni umane del laboratorio le ha registrate l’agente per conto mio, dichiarandolo.