Costruire un knowledge graph delle dipendenze COBOL mi ha mostrato che la modernizzazione fallisce nel retrieval, non nella traduzione, e perché una finestra di contesto più grande non chiude mai il divario.
COBOLLegacy SystemsSoftware Modernization

Un singolo file COBOL ha detto all'AI tutto tranne l'unico fatto che contava, così ho costruito prima la mappa.

Ashutosh SinghalAshutosh Singhal30 giugno 202615 min

La riga di COBOL che ha dato inizio a tutto questo era lunga tre parole, e ognuna di esse mi mentiva.

COMPUTE WS-RESULT = WS-WIRE-AMOUNT - TRN-LIMIT. Stavo costruendo un patrimonio demo per il sottosistema di bonifici di una banca di fascia media, e questa era la riga fatale all'interno di un programma chiamato WIRETXN. Sembra un'aritmetica che uno studente del primo anno potrebbe portare. Sottrai un limite da un importo, scrivi il risultato. Se consegnassi quel singolo file a qualsiasi modello moderno e chiedessi Java, ti darebbe Java pulito, compilabile e che passa gli unit test in circa quattro secondi. Digiterebbe TRN-LIMIT come un long. E al primo bonifico in produzione, scriverebbe byte corrotti in un database di produzione.

Lo so perché TRN-LIMIT non è un long. È un campo packed-decimal COMP-3, definito tre file più in là, la cui interpretazione live è scelta da un flag impostato in un programma completamente diverso, sequenziato da un job batch che gira alle due del mattino. Nulla di tutto ciò è visibile in WIRETXN. Il file che contiene il pericoloso COMPUTE non contiene nessuno dei fatti che lo rendono pericoloso.

Quel divario è l'intera ragione per cui ho costruito CodeGraph, e questo saggio parla di ciò che ho sbagliato lungo la strada. All'inizio ero sicuro che il problema fosse la qualità della traduzione. Mi sbagliavo. Il problema è che il modello non riesce a vedere ciò di cui ha bisogno, e ho passato un po' a dimostrare a me stesso che nessuna quantità di "dagli più contesto" lo risolve.

Il COMPUTE che sembrava sicuro e non lo era

Ho mappato a mano il cambiamento del bonifico per primo, prima di fidarmi di qualsiasi strumento per farlo, e c'erano esattamente nove fatti che una migrazione corretta doveva conoscere.

Tre di essi vivono dentro WIRETXN e sono genuinamente visibili a un lettore di singolo file. WIRETXN usa TRN-LIMIT in quel COMPUTE alla riga 33. Importa un copybook chiamato CBACCT, solo per nome, alla riga 13. Esegue un UPDATE sulla tabella DB2 ACCOUNTS alla riga 37. Uno strumento a finestra di testo vede tutti e tre. Se quelli fossero gli unici fatti, il porting ingenuo andrebbe bene.

Gli altri sei sono quelli che fanno male. TRN-LIMIT è dichiarato PIC S9(9)V99 COMP-3 in CBACCT.cpy alla riga 11, il che significa packed decimal, il che significa BigDecimal in Java e assolutamente non long. Subito sotto, TRN-LIMIT-ALPHA REDEFINES TRN-LIMIT, sovrapponendo gli stessi sei byte sul campo come testo grezzo. Un terzo campo, LIMIT-TYPE-FLAG, decide a runtime quale di quelle due interpretazioni è quella live. Quel flag è scritto da un programma chiamato LIMITSET, e di nuovo da un job batch notturno chiamato BATCHUPD. E il job JCL NIGHTLY gira alle 02:00 come predecessore del job dei bonifici, che è l'unico posto nell'intero patrimonio in cui l'ordinamento tra "imposta il flag" e "esegui il trasferimento" è addirittura registrato.

Pannello di impatto CodeGraph per TRN-LIMIT che mostra il recupero a grafo di 9 fatti su 9 contro i 3 su 9 del singolo file ingenuo, con ogni fatto recuperato che porta la provenienza di file e riga da WIRETXN.cbl e CBACCT.cpy.
La chiusura di TRN-LIMIT sul fixture sintetico spedito. Il recupero a grafo recupera 9 fatti ground-truth su 9, la finestra ingenua a singolo file ne vede 3 su 9, e ogni fatto porta il proprio file e riga. Da F4 a F9, i sei che rompono il porting, sono quelli marcati nascosti nel singolo file.

Sei fatti. Ognuno di essi vero, ognuno di essi portante, e ognuno di essi strutturalmente invisibile dal file che effettivamente esegue il calcolo. Quando li ho allineati così, ciò che mi ha turbato non era che il porting ingenuo fosse sbagliato. Era che il porting ingenuo non aveva modo di sapere di essere sbagliato. Ha letto l'unico file che gli era stato dato, e quell'unico file taceva sui sei fatti che contavano.

Il file che contiene la riga pericolosa non contiene nessuno dei fatti che la rendono pericolosa. Non è un bug di traduzione. È un fallimento di retrieval vestito con i panni di un bug di traduzione.

Perché ho smesso di cercare di rendere più grande la finestra di contesto

Il mio primo istinto era lo stesso istinto che ha tutti in questo momento, e voglio essere onesto sul fatto che l'ho inseguito per un po': basta dare di più al modello.

Il ragionamento sembrava a prova di bomba. Se il fallimento è che il modello ha visto un solo file, allora dagli anche il copybook. Digli i programmi che toccano il flag. Digli il JCL. Le finestre di contesto ora sono enormi e crescono ogni trimestre, quindi di sicuro la risposta è smettere di essere avari e versare tutto il vicinato di codice nel prompt. Mi aspettavo davvero che funzionasse, e per un esempio giocattolo in un certo senso funziona, perché quando sai già quali sei file incollare, hai già risolto a mano il problema vero.

Quella era la crepa. Per dare al modello il contesto giusto, dovevo prima sapere quale contesto fosse quello giusto. E sapere che TRN-LIMIT, il cui tipo è deciso da un flag scritto in BATCHUPD e ordinato da un job JCL delle 02:00 non è qualcosa che estrai leggendo WIRETXN più a fondo. È qualcosa che ottieni solo avendo già tracciato il grafo delle dipendenze. La finestra di contesto non ti dice cosa mettere nella finestra di contesto. Stavo cercando di rispondere alla domanda con la risposta.

Poi i numeri hanno fissato il punto in modo definitivo. I patrimoni che queste banche gestiscono davvero non sono sei file. Sono da uno a dieci milioni di righe di COBOL, a volte di più, e 220 miliardi di righe sono ancora in produzione attiva nell'industria (meta-analisi di settore, 2025). Un vero cambiamento su un bonifico potrebbe avere una chiusura transitiva di quaranta file o quattrocento. Quello non entra mai in una finestra di contesto, né oggi né nella versione del modello che arriverà tra tre anni, perché il patrimonio cresce più in fretta della finestra e la finestra non è mai stata il vincolo. Il vincolo è sapere quali quaranta file sui dieci milioni sono quelli che questo cambiamento tocca, e dimostrare di averli trovati tutti e quaranta e non trentotto.

Una finestra di contesto più grande è una risposta migliore a una domanda che ho smesso di porre. La domanda non è "il modello può contenere più codice," è "quale codice, e come dimostri che è tutto."

Quella riformulazione è l'intera ragione per cui CodeGraph non è un traduttore. Di proposito non incollo COBOL e restituisco Java. La mappa è il prodotto, e la traduzione è un caso d'uso a valle che qualsiasi strumento può fare una volta che la mappa esiste. Ciò che costruisco è lo strato di comprensione sotto. Sul fixture spedito il patrimonio si analizza in un knowledge graph tipizzato di 47 nodi e 70 archi, e l'"impatto di un cambiamento" è un attraversamento del grafo, la chiusura transitiva di tutto ciò che il cambiamento tocca, con ogni arco che porta il file:line da cui proviene. È deliberatamente un lavoro di grafo plain-Python noioso, nessun modello nell'hot path, perché ciò di cui ho bisogno non è che sia furbo. Ho bisogno che sia completo e riproducibile. Stesso fixture in ingresso, stessa chiusura in uscita, ogni singola volta.

Continuo a ripetermelo come regola. Gli agent consigliano, il codice decide. Lo strato linguistico opzionale nella demo, la parte che risponderà alle domande sulla chiusura in inglese semplice, è disattivato di default e protetto da una chiave. Il valore non dipende da esso. Il valore è il retrieval e la prova, e nessuna delle due è una capacità del modello.

Cosa cancella davvero la vista ingenua?

Ho costruito un toggle nella demo apposta per poter guardare i sei fatti sparire, perché non credevo del tutto al fallimento finché non l'ho visto accadere.

Spunta "Naive AI context view" e il grafo collassa fino al singolo file sorgente più una finestra di righe intorno al cambiamento, che è esattamente ciò che uno strumento a finestra di testo dà a un modello. Il pannello che leggeva 9 su 9 scende a 3 su 9. I tre fatti in-file restano accesi. Gli altri sei diventano grigi e tacciono: il tipo COMP-3, l'overlay REDEFINES, il flag di controllo, i suoi due writer cross-module, e il predecessore JCL delle 02:00. Un banner rosso spiega la conseguenza con le parole dell'app stessa: consegnati solo i tre fatti visibili, un modello emette long TRN_LIMIT e corrompe il database.

Vista Naive AI context di CodeGraph attivata, che spegne di grigio sei dei nove fatti con un banner rosso che afferma che solo 3 fatti su 9 vivono dentro WIRETXN.cbl e il resto è invisibile a uno strumento a finestra di testo.
La vista ingenua a singolo file, che è una simulazione di ciò che uno strumento a finestra di testo vede davvero, non un connettore live. Sei fatti si spengono di grigio. Il banner nomina ogni cosa sparita: il tipo COMP-3, l'overlay REDEFINES, il flag di controllo, i suoi due writer, e il predecessore delle 02:00.

Voglio stare attento qui, perché è esattamente il punto in cui un founder è tentato di esagerare. Il risultato 9-contro-3 è misurato sul fixture sintetico di bonifico spedito, un patrimonio che ho scritto a mano per questa demo, proprio perché l'insieme vero delle dipendenze sia noto e il numero di recall sia una misura etichettata reale invece di una sensazione. Non è una garanzia sul tuo COBOL. La vista ingenua è una simulazione, non una pipeline z/OS live. Il grafo è in-memory con SQLite sotto, non una piattaforma grafo di produzione. Ho costruito una banca sintetica perché non potevo eticamente mostrartene una reale, e perché una ground truth nota è l'unico modo onesto di dire "il grafo ha preso tutti e nove e il singolo file ne ha presi tre."

Ma la forma del fallimento non è sintetica, ed è quella la parte che conta. Il campo COMP-3 il cui tipo è deciso altrove, il flag impostato da un job batch, l'ordinamento che esiste solo nel JCL, queste sono la trama ordinaria di un patrimonio bancario quarantennale, non casi limite esotici. Quando circa il 70-80 percento dei progetti di modernizzazione mainframe non raggiunge i propri obiettivi (meta-analisi di settore, 2025), non penso più che sia perché il passo di traduzione è cattivo. Il passo di traduzione va bene. Gli viene data un'immagine con i sei fatti più importanti ritagliati via.

La prova, o non conta

La funzionalità di cui sono più orgoglioso è quella che ammette ciò che non può fare, e non l'ho apprezzata finché una conversazione sulla compliance non me l'ha riformulata.

Un ingegnere vuole una migrazione corretta. Un regolatore vuole qualcosa di diverso e più difficile: evidenza. Sotto DORA, una banca deve un inventario degli asset ICT. Sotto SOC-2, deve le ricevute di change-control. Nessuna delle due è soddisfatta da un modello che dice "fidati, ho trovato le dipendenze." Serve una prova di completezza, una dichiarazione di quanto del codebase lo strumento ha potuto davvero risolvere e, cosa più importante, un flag onesto su ciò che non ha potuto. Così ho costruito un gate di completezza. Ogni PERFORM, CALL, COPY, e riferimento DB2 nel fixture deve risolversi in un nodo reale del grafo oppure essere marcato "needs review." Nulla può sparire in silenzio.

Sul fixture, quel gate risolve 33 riferimenti su 34, cioè una copertura del 97,1 percento. Quello che non riesce a risolvere è un programma chiamato DISPATCH, che fa un CALL WS-PROGNAME dinamico, un target calcolato a runtime che nessun parser statico può seguire perché la destinazione non è nota finché il programma non gira. E il comportamento corretto lì non è indovinare. È alzare un flag che dice "un umano deve guardare questo," e lasciarlo nel report.

Scheda audit di CodeGraph che mostra il 97,1 percento dei riferimenti risolti con 1 segnalato per revisione, l'elemento segnalato essendo il CALL dinamico WS-PROGNAME di DISPATCH a DISPATCH.cbl riga 15, elencato come flagged e non silenziosamente eliminato.
Il gate di completezza sul fixture. 33 riferimenti su 34 si risolvono, 97,1 percento, e l'unico irrisolto, il CALL dinamico di DISPATCH a un target calcolato a runtime, è segnalato per revisione invece di essere eliminato. L'onestà su quello che non può seguire è il punto, non una nota a piè di pagina.

Quel DISPATCH call segnalato è la mia cosa preferita nell'intero build, e lo dico sul serio. Uno strumento che risolve il 97 percento e ti dice esattamente quale 3 percento non ha potuto vale più di uno strumento che afferma 100 e nasconde il buco, perché il buco nascosto è dove vive il bonifico corrotto. Il gate di completezza produce un "Codebase Topology and Completeness Report" esportabile, un JSON e un HTML stampabile con il riepilogo di nodi e archi, le chiusure per modulo con provenienza file:line, il risultato di recall, e gli elementi segnalati con un timestamp. Quell'artefatto è il punto. È la cosa che puoi consegnare a un regolatore, rieseguire il trimestre dopo, e ottenere la risposta identica perché è deterministico.

Preferirei spedire un numero che ammette il proprio buco piuttosto che uno più tondo che lo nasconde. Il CALL dinamico segnalato non è una debolezza della demo. È la demo.

Questa è anche la parte che non invecchia. Un modello perfetto, uno che non allucina mai una sola riga di Java, non può comunque dimostrare a un regolatore quali dipendenze ha recuperato. Non può comunque seguire staticamente un CALL calcolato a runtime, perché è una proprietà del codice e non del lettore. Provenienza e completezza sono proprietà del sistema che costruisci intorno al modello, non capacità che sblocchi scalandolo.

L'ordine in cui tocchi le cose

L'ultima cosa che il grafo mi ha dato è qualcosa che non avevo nemmeno deciso di costruire: un ordine sicuro in cui fare il lavoro.

Una volta che hai la topologia completa delle dipendenze, puoi valutare ogni programma per quanto è intrecciato. Uso una formula semplice, coupling pesato contro le trappole COMP-3, criticità JCL e call irrisolti, che classifica i quattordici programmi del fixture in un ordine di estrazione strangler-fig. Il programma a rischio più basso si estrae per primo, il god-program per ultimo. Sul fixture, AUDITLOG esce al rank 1 con un risk score di zero, perché non ha coupling e nulla dipende dal fatto che sia corretto. È il posto sicuro da cui partire. Il programma WIRETXN di cui ci siamo preoccupati siede al rank 11, portando la sua unica trappola COMP-3 e la sua criticità JCL. DISPATCH, con il suo call dinamico irrisolto, siede al rank 12. E ACCTMGR, il god-program su cui tutto poggia, si estrae per ultimo al rank 14 con un risk score di 15.

Scheda di estrazione CodeGraph che mostra i quattordici programmi del fixture classificati in ordine strangler-fig, AUDITLOG al rank 1 con risk score 0 e ACCTMGR al rank 14 con risk score 15, colonne per coupling, trappole COMP-3 e criticità JCL.
L'ordine di estrazione strangler-fig sul fixture. AUDITLOG si estrae per primo a rischio 0, il god-program ACCTMGR si estrae per ultimo a rischio 15, e WIRETXN e DISPATCH siedono in alto nella lista per la loro trappola COMP-3 e il call dinamico irrisolto. La sequenza è una proprietà del grafo, non un giudizio.

Non mi aspettavo di tenere alla sequenza quanto ci tengo ora. Ma è la stessa lezione per la terza volta. Dove puoi iniziare in sicurezza è un fatto sulla topologia, non un'opinione su cui discuti in una riunione di pianificazione. Un team che guarda un milione di righe non è davvero in disaccordo su come tradurre un paragrafo. Discute, all'infinito e a caro prezzo, su dove cominciare e cosa si rompe se tocca prima la cosa sbagliata. Quella è una domanda di grafo, e il grafo la risponde allo stesso modo a ogni esecuzione.

L'ordine di estrazione, il gate di completezza, la chiusura d'impatto, sono tutti lo stesso oggetto visto da tre angoli. Recupera la fetta vera, dimostra che è l'intera fetta, e classifica le fette per rischio. Nessuno di quei tre è un problema di traduzione, e nessuno è risolto da un modello più intelligente.

La domanda a cui continuo a tornare

Ho iniziato a fare una sola domanda a ogni pitch di modernizzazione AI che vedo, compreso il mio, ed è diventata in silenzio l'unica di cui mi fido.

Non "sa scrivere buon Java," perché la risposta è quasi sempre sì e quasi mai conta. La domanda più difficile è quella che la riga TRN-LIMIT mi ha insegnato: può dimostrare, adesso, quali dipendenze ha recuperato, e quella prova sopravviverebbe a un regolatore che volesse farla fallire. Se lo strumento non può mostrarmi la chiusura con provenienza file:line e non può dirmi onestamente ciò che non ha potuto risolvere, allora non importa quanto fluente sembri l'output. Sta indovinando con buona grammatica, e ho visto esattamente quell'indovinare digitare long su un campo packed-decimal e mettere mano al database.

L'industria ha passato un decennio a migliorare il passo di traduzione mentre il 70-80 percento dei progetti continuava a mancare gli obiettivi (meta-analisi di settore, 2025), e penso che sia perché il passo di traduzione non è mai stato dove viveva il rischio. Il rischio vive nella topologia, nei sei fatti invisibili, nel flag impostato alle due del mattino. Se vuoi guardare un grafo recuperare quei sei fatti e poi segnalare quello che onestamente non può, la demo è qui: veriprajna.com/it/demos/modernizzazione-cobol-con-un-grafo-di-conoscenza.

E se preferisci guardarlo piuttosto che leggermi descriverlo, ecco l'intera cosa che gira da capo a fondo.

Non credo più che il prossimo rilascio del modello sia ciò che sblocca queste migrazioni. Una finestra più grande tiene più codice; non sa quale codice, e non può dimostrare di averlo trovato tutto. Era vero quando ho digitato la prima riga del parser, e penso che sarà ancora vero molto dopo che il modello che ho usato per costruire questo sarà stato ritirato. La mappa è sempre stata la parte difficile. Abbiamo solo continuato a guardare la traduzione perché era la parte che sapevamo come valutare.

Ricerca correlata

Pubblicato anche su

Costruisci la tua IA con fiducia.

Collabora con un team che vanta una profonda esperienza nella creazione della prossima generazione di IA aziendale. Lascia che ti aiutiamo a progettare, sviluppare e implementare una strategia di IA di cui ti puoi fidare.

Veriprajna società di consulenza Deep Tech è specializzata nella creazione di sistemi di IA safety-critical per i settori sanitario, finanziario e regolamentato. Le nostre architetture sono validate rispetto a protocolli consolidati con una documentazione di conformità completa.