Dal PHP legacy alla parità verificabile: un caso DFD di migrazione
Un gestionale PHP in uso, una conversione Laravel che deve raggiungere la parità prima del passaggio, e DFD (DoD-First Driven) guidato da un agente diverso: Codex. La tesi della serie regge, ma il segnale più grosso torna dallo stesso posto del terzo articolo: un gate che diceva 'fatto' senza esserlo.

Nel primo articolo di questa serie ho sostenuto che con gli agenti il costo dello sviluppo non sparisce: migra a monte (specificare) e a valle (verificare). Nel terzo ho scoperto, provandoci, che il segnale più importante non veniva dai test ma da un gate che mentiva: una CI che non poteva fallire.
Questo è un quarto banco di prova, su un terreno diverso: non una feature nuova, ma una migrazione. Un gestionale legacy in PHP, ancora in uso, e una conversione Laravel che deve raggiungere la parità funzionale prima del passaggio. E un dettaglio che cambia il peso del resto: il lavoro tecnico non l’ha guidato Claude Code come negli esperimenti precedenti, ma Codex, con DFD — DoD-First Driven, un framework che sto sviluppando — e la sua CLI.
Attenzione: anche questo è un esperimento, non una dimostrazione. Un solo progetto, materiale reale e imperfetto, e alcune verifiche che non ho potuto rilanciare per limiti della sessione. Quello che segue è un caso onesto, con i suoi buchi dichiarati.
Perché una migrazione è il caso perfetto per la tesi
Una feature nuova ti lascia margine: se la spec è vaga, il comportamento “giusto” è in parte una scelta. Una migrazione no. Il comportamento giusto esiste già, è quello del sistema in uso, e il lavoro non è inventare: è dimostrare equivalenza. È il caso in cui il costo si sposta tutto su specificare cosa deve restare uguale e verificare che lo sia — esattamente la tesi del primo articolo, ma senza vie di fuga.
Il problema è che le regole del vecchio gestionale erano quasi tutte implicite: vivevano nel codice PHP e nelle abitudini di chi lo usa, non in un documento. DFD è servito a questo: trasformarle in decisioni esplicite e verificabili.
| Regola implicita nel legacy | Diventata, con DFD |
|---|---|
| Ruoli e permessi | Decisione esplicita, con casi di test |
| Tariffe annuali | Spec + verifica sugli importi storici |
| Cena separata dalla quota | Regola a sé, collaudabile |
| Numeri assegnati alla consegna | Decisione su quando e come si assegna |
| Anzianità | Criterio esplicito e testato |
| Conservazione cartacea delle firme | Vincolo di processo, dichiarato |
Il perno di tutto è stata la matrice di parità: una tabella che, voce per voce, dice se il nuovo sistema è equivalente, diverso o ancora da collaudare rispetto al vecchio. È la versione da migrazione della DoD del primo articolo: non “cosa deve fare la feature”, ma “cosa deve restare identico, e come lo provo”.
Cosa ha funzionato
Qui la tesi regge, e si vede.
- La parità applicativa è stata coperta da prove automatiche. Non “sembra uguale”: un test che fallisce se smette di esserlo.
- Database nuovo separato dal legacy. Nessuna scrittura sul sistema in uso: la rete di sicurezza più banale e più spesso saltata.
- L’importazione ha preservato gli importi storici e ha evitato di fondere automaticamente record ambigui — la tentazione peggiore di ogni import, quella che “pulisce” i dati facendoli sparire.
- Password dei nuovi account con hash bcrypt. Auth e migrazioni dati sono le zone che nel primo articolo mettevo sotto revisione obbligatoria: qui sono state trattate come tali.
- Niente CI: il progetto era in modalità solo sviluppo, una fase che DFD prevede esplicitamente — CI ancora prematura, ma verifiche locali tracciate come evidenza.
Questo ultimo punto, però, è anche la crepa.
Dove il materiale reale ha spinto indietro
Gli ostacoli più istruttivi non sono venuti dall’agente: sono venuti dai dati veri. È lo stesso fenomeno che nel primo articolo chiamavo “i requisiti emergono usando il prodotto”, qui in versione archeologica.
- Il dump non offriva un listino annuale affidabile: le tariffe storiche andavano ricostruite.
- Conteneva numeri duplicati tra iscritti.
- Aveva uno stato “online” che il vecchio codice nemmeno interpretava.
- Mancava un dataset di fixture iniziale: niente base nota su cui far girare le prove.
E sul processo, tre attriti concreti: gate DFD non sempre allineati alle attività reali (ci torno sotto), l’accesso SSH a Git non funzionava da quella sessione, e il runtime locale non permetteva di rilanciare i test.
La risposta giusta a dati così non è forzarli a essere puliti. È conservare e tracciare:
| Problema nei dati | Correttivo |
|---|---|
| Nessun listino annuale affidabile | Tariffe storiche inattive ricostruite dagli importi effettivi delle iscrizioni |
| Numeri duplicati | Numeri conflittuali conservati come provenienza, senza riassegnarli come attivi |
| Stato “online” non interpretato | Stato mantenuto, non reinterpretato |
| Utenti amministrativi legacy | Esclusi dall’import |
È la stessa lezione del primo articolo vista da un altro lato: lì il rischio era il codice che nessuno ha chiesto, qui è la reinterpretazione che nessuno ha chiesto. In una migrazione, inventare un significato è un bug che si scopre mesi dopo.
Per il trasferimento su GitHub ho spostato il delta con un branch e una PR dedicati, controllando i file prima del merge ed escludendo dump e artefatti locali — nessun travaso cieco.
Il segnale, di nuovo: un gate che diceva “fatto”
E qui succede la cosa che non mi aspettavo, perché l’avevo già vista nel terzo articolo con un altro strumento e un altro agente.
Gli aggiornamenti dello stato DFD non erano sempre allineati al lavoro reale. Risultato: gate obsoleti. Celle della matrice che dicevano “collaudato” mentre il runtime non permetteva nemmeno di rilanciare i test; voci “pronte” basate su una PR unita e non su un collaudo osservato.
È lo stesso identico fallimento della CI che non poteva fallire, spostato di un livello:
| Terzo articolo | Questo caso |
|---|---|
CI verde per via di cmd | tee file |
Gate DFD verde per via di uno stato non aggiornato |
| Il sigillo non provava ciò che dichiarava | La matrice non provava la parità che dichiarava |
| Il segnale vero era nella pipeline, non nei test | Il segnale vero era nello scarto tra gate e stato reale |
La lezione non riguarda DFD più di quanto riguardasse GitHub Actions. Anzi: DFD gli strumenti per evitarlo ce li ha — distingue la modalità solo sviluppo dalla preparazione al rilascio, e una PR unita nella prima non autorizza a dire “pronto”. Il buco non era nel framework, ma nella disciplina di tenerne aggiornato lo stato. Il che riporta al punto vero: di cosa è evidenza un gate verde. Una PR unita dimostra che del codice è stato integrato. Non dimostra, da sola, un collaudo operativo, un ripristino riuscito o un rilascio pronto. Se il gate pretende di dirlo, deve essere agganciato a un fatto osservato — un test rilanciato, un ripristino eseguito — non allo stato che qualcuno ha dimenticato di aggiornare.
Il collegamento con la validazione in produzione è diretto: lo shift-right funziona perché i segnali di produzione sono fatti, non dichiarazioni. Un gate scollegato dallo stato reale è il contrario esatto di un canary.
Cosa mi porto a casa
DFD aiuta quando registra decisioni, evidenze e incertezze insieme al codice. È la forma concreta dell’“artefatto vivo” del primo articolo: la matrice di parità ha reso possibile seguire cosa fosse equivalente, diverso o ancora da collaudare, e le incertezze sono rimaste visibili invece di essere risolte di nascosto. Per una migrazione, dove il comportamento giusto esiste già, è esattamente lo strumento che sposta il costo dove deve stare.
Ma uno strumento di gate vale quanto la fedeltà dei suoi gate allo stato reale. E qui la tesi della serie prende la sua piega più scomoda: non basta avere i gate giusti, devono dire la verità nel momento in cui li guardi. Un verde che non corrisponde al lavoro fatto non è neutro — è peggio di nessun gate, perché autorizza a fidarsi.
Due esperimenti, due strumenti, due agenti diversi, stessa scoperta. Comincio a pensare che non sia un caso: quando il costo si sposta sulla verifica, il punto più fragile diventa il meccanismo che dichiara “verificato”.
Poscritto. Nel caso successivo, di nuovo con Claude Code e su due feature di Taita portate in produzione, il gate diceva la verità, ma solo sulla macchina dove avevo fatto le prove: i log delle evidenze non finivano nel repository. Il caso è qui: Da un feedback esterno al rilascio.
Limiti da dichiarare
- Un solo progetto reale, con dati imperfetti; niente repliche.
- Verifiche non rilanciabili nella sessione (runtime locale e accesso SSH): parte delle evidenze è quella raccolta, non riprodotta a comando.
- La ricostruzione delle tariffe storiche dagli importi effettivi è una stima, non un dato di origine.
- L’esperimento è stato guidato con Codex e DFD: non ho un confronto a parità di condizioni con un altro agente o un altro framework sullo stesso progetto.