Un graphe de dépendances COBOL montre que la modernisation échoue à la récupération, pas à la traduction : une plus grande fenêtre de contexte ne referme jamais l'écart.
COBOLLegacy SystemsSoftware Modernization

Un seul fichier COBOL a tout dit à l'IA sauf le seul fait qui comptait, alors j'ai d'abord construit la carte.

Ashutosh SinghalAshutosh Singhal30 juin 202615 min

La ligne de COBOL qui a déclenché tout cela ne faisait que trois mots, et chacun d'eux m'a menti.

COMPUTE WS-RESULT = WS-WIRE-AMOUNT - TRN-LIMIT. Je construisais un parc de démonstration pour le sous-système de virements d'une banque de taille moyenne, et c'était la ligne fatidique à l'intérieur d'un programme appelé WIRETXN. Cela ressemble à une arithmétique qu'un étudiant de première année pourrait porter. Soustraire une limite d'un montant, écrire le résultat. Si vous confiez ce seul fichier à n'importe quel modèle moderne et demandez du Java, il vous donnerait un Java propre, qui compile et passe les tests unitaires en environ quatre secondes. Il taperait TRN-LIMIT comme un long. Et au premier virement en production, il écrirait des octets corrompus dans une base de données de production.

Je le sais parce que TRN-LIMIT n'est pas un long. C'est un champ décimal compacté COMP-3, défini trois fichiers plus loin, dont l'interprétation en production est choisie par un indicateur positionné dans un programme complètement différent, séquencé par un traitement batch qui s'exécute à deux heures du matin. Rien de tout cela n'est visible dans WIRETXN. Le fichier qui contient le dangereux COMPUTE ne contient aucun des faits qui le rendent dangereux.

Cet écart est toute la raison pour laquelle j'ai construit CodeGraph, et cet essai porte sur ce que j'ai mal compris en chemin. J'étais d'abord convaincu que le problème était la qualité de la traduction. J'avais tort. Le problème, c'est que le modèle ne peut pas voir ce qu'il a besoin de voir, et j'ai passé un moment à me prouver qu'aucune quantité de « donnez-lui plus de contexte » ne corrige cela.

Le COMPUTE qui semblait sûr et ne l'était pas

J'ai d'abord cartographié le changement de virement à la main, avant de faire confiance à un quelconque outil pour le faire, et il y avait exactement neuf faits qu'une migration correcte devait connaître.

Trois d'entre eux vivent à l'intérieur de WIRETXN et sont vraiment visibles pour un lecteur d'un seul fichier. WIRETXN utilise TRN-LIMIT dans ce COMPUTE à la ligne 33. Il importe un copybook appelé CBACCT, par le nom seulement, à la ligne 13. Il déclenche un UPDATE sur la table DB2 ACCOUNTS à la ligne 37. Un outil à fenêtre de texte voit les trois. Si c'étaient les seuls faits, le portage naïf serait correct.

Les six autres sont ceux qui font mal. TRN-LIMIT est déclaré PIC S9(9)V99 COMP-3 dans CBACCT.cpy à la ligne 11, ce qui signifie décimal compacté, ce qui signifie BigDecimal en Java et absolument pas long. Juste en dessous, TRN-LIMIT-ALPHA REDEFINES TRN-LIMIT, qui superpose les mêmes six octets sur le champ en texte brut. Un troisième champ, LIMIT-TYPE-FLAG, décide à l'exécution laquelle de ces deux interprétations est celle en vigueur. Cet indicateur est écrit par un programme appelé LIMITSET, et à nouveau par un job batch nocturne appelé BATCHUPD. Et le job JCL NIGHTLY s'exécute à 02:00 comme prédécesseur du job de virement, qui est le seul endroit dans tout le parc où l'ordre entre « positionner l'indicateur » et « exécuter le transfert » est même enregistré.

Panneau d'impact CodeGraph pour TRN-LIMIT montrant une récupération par graphe de 9 faits sur 9 contre 3 sur 9 pour la fenêtre naïve mono-fichier, chaque fait récupéré portant sa provenance fichier et ligne depuis WIRETXN.cbl et CBACCT.cpy.
La clôture TRN-LIMIT sur le jeu de données synthétique livré. La récupération par graphe retrouve 9 faits sur 9 de vérité terrain, la fenêtre naïve mono-fichier en voit 3 sur 9, et chaque fait porte son propre fichier et sa ligne. F4 à F9, les six qui cassent le portage, sont ceux marqués comme cachés dans un seul fichier.

Six faits. Chacun d'eux vrai, chacun d'eux indispensable, et chacun d'eux structurellement invisible depuis le fichier qui fait réellement le calcul. Quand je les ai alignés ainsi, ce qui m'a troublé n'était pas que le portage naïf soit faux. C'était que le portage naïf n'avait aucun moyen de savoir qu'il était faux. Il a lu le seul fichier qu'on lui a donné, et ce seul fichier était silencieux sur les six faits qui comptaient.

Le fichier qui contient la ligne dangereuse ne contient aucun des faits qui la rendent dangereuse. Ce n'est pas un bug de traduction. C'est un échec de récupération vêtu des habits d'un bug de traduction.

Pourquoi j'ai arrêté d'essayer d'agrandir la fenêtre de contexte

Mon premier instinct était le même que tout le monde a en ce moment, et je veux être honnête de l'avoir poursuivi un moment : juste donner plus au modèle.

Le raisonnement paraissait hermétique. Si l'échec vient de ce que le modèle n'a vu qu'un seul fichier, alors nourrissez-le aussi du copybook. Nourrissez-le des programmes qui touchent l'indicateur. Nourrissez-le du JCL. Les fenêtres de contexte sont énormes maintenant et s'agrandissent chaque trimestre, alors sûrement la réponse est d'arrêter d'être avare et de verser tout le voisinage de code dans le prompt. Je m'attendais vraiment à ce que cela fonctionne, et pour un exemple jouet cela marche à peu près, parce que lorsque vous savez déjà quels six fichiers coller, vous avez déjà résolu le vrai problème à la main.

C'était la faille. Pour nourrir le modèle du bon contexte, je devais d'abord savoir quel contexte était le bon contexte. Et savoir que TRN-LIMIT a son type décidé par un indicateur écrit dans BATCHUPD et ordonné par un job JCL à 02:00 n'est pas quelque chose que l'on extrait en lisant WIRETXN plus attentivement. C'est quelque chose que l'on n'obtient qu'en ayant déjà tracé le graphe de dépendances. La fenêtre de contexte ne vous dit pas quoi mettre dans la fenêtre de contexte. J'avais essayé de répondre à la question avec la réponse.

Puis les chiffres ont rendu le point définitif. Les parcs que ces banques font réellement tourner ne sont pas six fichiers. Ce sont un à dix millions de lignes de COBOL, parfois plus, et 220 milliards de lignes sont encore en production active dans l'industrie (méta-analyse sectorielle, 2025). Un vrai changement de virement peut avoir une clôture transitive de quarante fichiers ou de quatre cents. Cela ne tient jamais dans une fenêtre de contexte, ni aujourd'hui ni dans la version du modèle qui sortira dans trois ans, parce que le parc grandit plus vite que la fenêtre et que la fenêtre n'a de toute façon jamais été la contrainte. La contrainte, c'est de savoir quels quarante fichiers parmi les dix millions sont ceux que ce changement touche, et de prouver que vous avez trouvé les quarante plutôt que trente-huit.

Une plus grande fenêtre de contexte est une meilleure réponse à une question que j'ai arrêté de poser. La question n'est pas « le modèle peut-il contenir plus de code », c'est « quel code, et comment prouver que c'est tout ».

Ce recadrage est toute la raison pour laquelle CodeGraph n'est pas un traducteur. Je ne colle délibérément pas du COBOL pour rendre du Java. La carte est le produit, et la traduction est un cas d'usage en aval que n'importe quel outil peut faire une fois que la carte existe. Ce que je construis, c'est la couche de compréhension en dessous. Sur le jeu livré, le parc est analysé en un graphe de connaissances typé de 47 nœuds et 70 arêtes, et « l'impact d'un changement » est une traversée de graphe, la clôture transitive de tout ce que le changement touche, chaque arête portant le file:line dont elle est issue. C'est délibérément du travail de graphe en Python simple et ennuyeux, aucun modèle dans le chemin critique, parce que ce dont j'ai besoin n'est pas d'être malin. J'ai besoin que ce soit complet et reproductible. Même jeu en entrée, même clôture en sortie, à chaque fois.

Je me le répète comme une règle. Les agents conseillent, le code décide. La couche langage optionnelle dans la démo, celle qui répondra aux questions sur la clôture en anglais clair, est désactivée par défaut et protégée derrière une clé. La valeur n'en dépend pas. La valeur, c'est la récupération et la preuve, et aucune des deux n'est une capacité de modèle.

Que supprime réellement la vue naïve ?

J'ai intégré un basculeur dans la démo précisément pour pouvoir regarder les six faits disparaître, parce que je ne croyais pas pleinement l'échec tant que je ne l'avais pas vu se produire.

Cochez « Naive AI context view » et le graphe s'effondre jusqu'au seul fichier source plus une fenêtre de lignes autour du changement, exactement ce qu'un outil à fenêtre de texte nourrit à un modèle. Le panneau qui lisait 9 sur 9 tombe à 3 sur 9. Les trois faits intra-fichier restent allumés. Les six autres grisent et se taisent : le type COMP-3, le REDEFINES de recouvrement, l'indicateur de contrôle, ses deux programmes d'écriture inter-modules, et le prédécesseur JCL à 02:00. Une bannière rouge énonce la conséquence dans les propres mots de l'application : nourri des seuls trois faits visibles, un modèle émet long TRN_LIMIT et corrompt la base de données.

Vue de contexte IA naïve CodeGraph activée, assombrissant six des neuf faits en gris avec une bannière rouge indiquant que seuls 3 faits sur 9 vivent à l'intérieur de WIRETXN.cbl et que le reste est invisible pour un outil à fenêtre de texte.
La vue naïve mono-fichier, qui est une simulation de ce qu'un outil à fenêtre de texte voit réellement, pas un connecteur live. Six faits s'assombrissent en gris. La bannière nomme chaque chose disparue : le type COMP-3, le recouvrement REDEFINES, l'indicateur de contrôle, ses deux programmes d'écriture, et le prédécesseur à 02:00.

Je veux être prudent ici, car c'est exactement l'endroit où un fondateur est tenté de trop vendre. Le résultat 9 contre 3 est mesuré sur le jeu de données synthétique de virement livré, un parc que j'ai rédigé à la main pour cette démo, précisément pour que l'ensemble réel de dépendances soit connu et que le chiffre de rappel soit une mesure étiquetée réelle plutôt qu'une impression. Ce n'est pas une garantie sur votre COBOL. La vue naïve est une simulation, pas un pipeline z/OS live. Le graphe est en mémoire avec SQLite en dessous, pas une plateforme de graphe de production. J'ai construit une banque synthétique parce que je ne pouvais pas éthiquement vous en montrer une réelle, et parce qu'une vérité terrain connue est le seul moyen honnête de dire « le graphe a obtenu les neuf et le fichier unique en a obtenu trois ».

Mais la forme de l'échec n'est pas synthétique, et c'est la partie qui compte. Le champ COMP-3 dont le type est décidé ailleurs, l'indicateur positionné par un job batch, l'ordonnancement qui n'existe que dans le JCL, c'est la texture ordinaire d'un parc bancaire vieux de quarante ans, pas des cas limites exotiques. Quand environ 70 à 80 pour cent des projets de modernisation mainframe n'atteignent pas leurs objectifs (méta-analyse sectorielle, 2025), je ne pense plus que ce soit parce que l'étape de traduction est mauvaise. L'étape de traduction va bien. On lui fournit une image dont les six faits les plus importants ont été rognés.

La preuve, ou cela ne compte pas

La fonctionnalité dont je suis le plus fier est celle qui admet ce qu'elle ne peut pas faire, et je n'ai pas apprécié cela tant qu'une conversation de conformité ne me l'a pas recadré.

Un ingénieur veut une migration correcte. Un régulateur veut quelque chose de différent et plus dur : des preuves. Sous DORA, une banque doit tenir un inventaire d'actifs TIC. Sous SOC-2, elle doit produire des reçus de contrôle des changements. Aucun des deux n'est satisfait par un modèle qui dit « faites-moi confiance, j'ai trouvé les dépendances ». Ils ont besoin d'une preuve de complétude, un énoncé de la part de la base de code que l'outil a réellement pu résoudre et, plus important, un drapeau honnête sur ce qu'il n'a pas pu. Alors j'ai construit une porte de complétude. Chaque PERFORM, CALL, COPY, et chaque référence DB2 dans le jeu doit se résoudre vers un nœud réel du graphe ou être marquée « à revoir ». Rien n'est autorisé à disparaître silencieusement.

Sur le jeu, cette porte résout 33 références sur 34, soit 97,1 pour cent de couverture. Celle qu'elle ne peut pas résoudre est un programme appelé DISPATCH, qui effectue un CALL WS-PROGNAME dynamique, une cible calculée à l'exécution qu'aucun parseur statique ne peut suivre parce que la destination n'est pas connue tant que le programme ne tourne pas. Et le bon comportement là n'est pas de deviner. C'est de lever un drapeau qui dit « un humain doit regarder celui-ci », et de le laisser dans le rapport.

Onglet d'audit CodeGraph montrant 97,1 pour cent des références résolues avec 1 signalée pour revue, l'élément signalé étant le CALL dynamique WS-PROGNAME de DISPATCH à la ligne 15 de DISPATCH.cbl, listé comme signalé et non silencieusement abandonné.
La porte de complétude sur le jeu. 33 références sur 34 se résolvent, 97,1 pour cent, et la seule non résolue, le CALL dynamique de DISPATCH vers une cible calculée à l'exécution, est signalée pour revue plutôt qu'abandonnée. L'honnêteté sur celle qu'elle ne peut pas suivre est le point, pas une note de bas de page.

Cet appel DISPATCH signalé est ma chose préférée dans toute la construction, et je le pense. Un outil qui résout 97 pour cent et vous dit exactement quels 3 pour cent il n'a pas pu vaut plus qu'un outil qui revendique 100 et cache l'écart, parce que l'écart caché est là où vit le virement corrompu. La porte de complétude produit un « Codebase Topology and Completeness Report » exportable, un JSON et un HTML imprimable avec le résumé des nœuds et arêtes, les clôtures par module avec file:line provenance, le résultat de rappel, et les éléments signalés avec un horodatage. Cet artefact est le point. C'est la chose que vous pouvez tendre à un régulateur, relancer le trimestre suivant, et obtenir la réponse identique parce qu'elle est déterministe.

Je préfère livrer un chiffre qui admet son propre trou qu'un chiffre plus rond qui le cache. Le CALL dynamique signalé n'est pas une faiblesse de la démo. C'est la démo.

C'est aussi la partie qui ne vieillit pas. Un modèle parfait, qui n'hallucine jamais une seule ligne de Java, ne peut toujours pas prouver à un régulateur quelles dépendances il a récupérées. Il ne peut toujours pas suivre un CALL calculé à l'exécution de façon statique, parce que c'est une propriété du code et non du lecteur. La provenance et la complétude sont des propriétés du système que vous construisez autour du modèle, pas des capacités que vous débloquez en le mettant à l'échelle.

L'ordre dans lequel vous touchez les choses

La dernière chose que le graphe m'a donnée était quelque chose que je n'avais même pas cherché à construire : un ordre sûr pour faire le travail.

Une fois que vous avez la topologie de dépendances complète, vous pouvez noter chaque programme selon son enchevêtrement. J'utilise une formule simple, le couplage pondéré contre les pièges COMP-3, la criticité JCL, et les appels non résolus, et elle classe les quatorze programmes du jeu dans un ordre d'extraction en figuier étrangleur. Le programme à plus faible risque s'extrait en premier, le programme dieu en dernier. Sur le jeu, AUDITLOG sort au rang 1 avec un score de risque de zéro, parce qu'il n'a pas de couplage et que rien ne dépend de son exactitude. C'est l'endroit sûr pour commencer. Le programme WIRETXN dont nous nous sommes inquiétés siège au rang 11, portant son unique piège COMP-3 et sa criticité JCL. DISPATCH, avec son appel dynamique non résolu, siège au rang 12. Et ACCTMGR, le programme dieu sur lequel tout s'appuie, s'extrait en tout dernier au rang 14 avec un score de risque de 15.

Onglet d'extraction CodeGraph montrant les quatorze programmes du jeu classés dans l'ordre figuier étrangleur, AUDITLOG au rang 1 avec un score de risque 0 et ACCTMGR au rang 14 avec un score de risque 15, colonnes pour le couplage, les pièges COMP-3 et la criticité JCL.
L'ordre d'extraction en figuier étrangleur sur le jeu. AUDITLOG s'extrait en premier au risque 0, le programme dieu ACCTMGR s'extrait en dernier au risque 15, et WIRETXN et DISPATCH siègent haut dans la liste pour leur piège COMP-3 et leur appel dynamique non résolu. La séquence est une propriété du graphe, pas un jugement.

Je ne m'attendais pas à me soucier autant du séquençage que maintenant. Mais c'est la même leçon une troisième fois. Où l'on peut commencer en sécurité est un fait sur la topologie, pas une opinion que l'on débat dans une réunion de planification. Une équipe face à un million de lignes n'est pas vraiment en désaccord sur comment traduire un paragraphe. Elle est en désaccord, sans fin et à grands frais, sur où commencer et ce qui casse si elle touche la mauvaise chose en premier. C'est une question de graphe, et le graphe y répond de la même façon à chaque exécution.

L'ordre d'extraction, la porte de complétude, la clôture d'impact, ce sont tous le même objet vu sous trois angles. Récupérer la vraie tranche, prouver que c'est toute la tranche, et classer les tranches par risque. Aucune de ces trois n'est un problème de traduction, et aucune n'est résolue par un modèle plus intelligent.

La question à laquelle je reviens sans cesse

J'ai commencé à poser une question à chaque pitch de modernisation par l'IA que je vois, y compris le mien, et elle est devenue tranquillement la seule en laquelle j'ai confiance.

Pas « peut-il écrire du bon Java », parce que la réponse est presque toujours oui et cela n'importe presque jamais. La question plus dure est celle que la ligne TRN-LIMIT m'a enseignée : peut-il prouver, maintenant, quelles dépendances il a récupérées, et cette preuve survivrait-elle à un régulateur qui voudrait la faire échouer. Si l'outil ne peut pas me montrer la clôture avec provenance file:line et ne peut pas me dire honnêtement ce qu'il n'a pas pu résoudre, alors peu importe à quel point la sortie paraît fluide. C'est de la conjecture avec une bonne grammaire, et j'ai vu exactement cette conjecture taper long sur un champ décimal compacté et aller écrire dans la base de données.

L'industrie a passé une décennie à améliorer l'étape de traduction tandis que 70 à 80 pour cent des projets continuaient à manquer leurs objectifs (méta-analyse sectorielle, 2025), et je pense que c'est parce que l'étape de traduction n'a jamais été là où vivait le risque. Le risque vit dans la topologie, dans les six faits invisibles, dans l'indicateur positionné à deux heures du matin. Si vous voulez regarder un graphe récupérer ces six faits puis signaler celui qu'il ne peut honnêtement pas, la démo est ici : veriprajna.com/fr/demos/modernisation-cobol-avec-un-graphe-de-connaissances.

Et si vous préférez le regarder plutôt que de me lire le décrire, voici le tout qui tourne de bout en bout.

Je ne crois plus que la prochaine version du modèle soit ce qui débloque ces migrations. Une plus grande fenêtre contient plus de code ; elle ne sait pas quel code, et elle ne peut pas prouver qu'elle a tout trouvé. C'était vrai quand j'ai tapé la première ligne du parseur, et je pense que ce sera encore vrai longtemps après que le modèle dont je me suis servi pour construire ceci aura été retiré. La carte a toujours été la partie difficile. Nous n'avons cessé de regarder la traduction parce que c'était la partie que nous savions noter.

Recherche associée

Également publié sur

Développez votre IA en toute confiance.

Collaborez avec une équipe forte d'une solide expérience dans la conception de la prochaine génération d'IA d'entreprise. Nous vous aidons à concevoir, développer et déployer une stratégie d'IA digne de confiance.

Veriprajna société de conseil en Deep Tech est spécialisée dans la conception de systèmes d'IA critiques pour la sûreté destinés aux secteurs de la santé, de la finance et de la réglementation. Nos architectures sont validées au regard de protocoles établis et accompagnées d'une documentation de conformité complète.