
Eine einzige COBOL-Datei erzählte der KI alles — außer dem einen Fakt, der zählte. Also baute ich zuerst die Karte.
Die COBOL-Zeile, mit der das alles begann, war drei Wörter lang — und jedes einzelne hat mich belogen.
COMPUTE WS-RESULT = WS-WIRE-AMOUNT - TRN-LIMIT. Ich baute ein Demo-Estate für das Überweisungs-Subsystem einer Mid-Tier-Bank, und das war die fatale Zeile in einem Programm namens WIRETXN. Sie sieht aus wie Arithmetik, die ein Erstsemester portieren könnte. Ein Limit von einem Betrag abziehen, das Ergebnis schreiben. Würde man diese einzelne Datei irgendeinem modernen Modell geben und nach Java fragen, bekäme man in etwa vier Sekunden sauberes, kompilierendes, Unit-Test-bestehendes Java. Es würde TRN-LIMIT als long tippen. Und bei der ersten echten Überweisung würde es korrumpierte Bytes in eine Produktionsdatenbank schreiben.
Ich weiß das, weil TRN-LIMIT kein long ist. Es ist ein gepacktes Dezimal-COMP-3-Feld, drei Dateien entfernt definiert, dessen Live-Interpretation von einem Flag gewählt wird, das in einem völlig anderen Programm gesetzt und von einem Batch-Job sequenziert wird, der um zwei Uhr morgens läuft. Nichts davon ist in WIRETXN sichtbar. Die Datei, die den gefährlichen COMPUTE enthält, enthält keine der Fakten, die ihn gefährlich machen.
Genau diese Lücke ist der ganze Grund, warum ich CodeGraph gebaut habe, und dieser Essay handelt davon, was ich auf dem Weg dorthin falsch gemacht habe. Am Anfang war ich sicher, das Problem sei Übersetzungsqualität. Ich lag falsch. Das Problem ist, dass das Modell nicht sehen kann, was es sehen müsste — und ich habe eine Weile damit verbracht, mir selbst zu beweisen, dass keine Menge an „gib ihm mehr Kontext“ das behebt.
Der COMPUTE, der sicher aussah und es nicht war
Ich habe die Überweisungs-Änderung zuerst von Hand kartiert, bevor ich irgendeinem Werkzeug vertraute, und es gab genau neun Fakten, die eine korrekte Migration kennen musste.
Drei davon leben in WIRETXN und sind einem Single-File-Leser wirklich sichtbar. WIRETXN verwendet TRN-LIMIT in diesem COMPUTE in Zeile 33. Es importiert ein Copybook namens CBACCT, nur dem Namen nach, in Zeile 13. Es feuert ein UPDATE auf der DB2-Tabelle ACCOUNTS in Zeile 37. Ein Textfenster-Werkzeug sieht alle drei. Wären das die einzigen Fakten, wäre der naive Port in Ordnung.
Die anderen sechs sind die, die wehtun. TRN-LIMIT ist als PIC S9(9)V99 COMP-3 in CBACCT.cpy in Zeile 11 deklariert, was gepacktes Dezimal bedeutet, was in Java BigDecimal heißt und absolut kein long. Direkt darunter liegt TRN-LIMIT-ALPHA REDEFINES TRN-LIMIT, das dieselben sechs Bytes als Rohtext über das Feld legt. Ein drittes Feld, LIMIT-TYPE-FLAG, entscheidet zur Laufzeit, welche dieser beiden Interpretationen die live gültige ist. Dieses Flag wird von einem Programm namens LIMITSET geschrieben, und erneut von einem nächtlichen Batch-Job namens BATCHUPD. Und der JCL-Job NIGHTLY läuft um 02:00 als Vorgänger des Überweisungs-Jobs — der einzige Ort im gesamten Estate, an dem die Reihenfolge zwischen „Flag setzen“ und „Überweisung ausführen“ überhaupt festgehalten ist.

Sechs Fakten. Jeder einzelne wahr, jeder einzelne tragend, und jeder einzelne strukturell unsichtbar aus der Datei, die die Berechnung tatsächlich ausführt. Als ich sie so aufreihte, war das, was mich beunruhigte, nicht, dass der naive Port falsch war. Es war, dass der naive Port keine Möglichkeit hatte zu wissen, dass er falsch war. Er las die eine Datei, die man ihm gab, und diese eine Datei schwieg zu den sechs Fakten, die zählten.
Die Datei, die die gefährliche Zeile enthält, enthält keine der Fakten, die sie gefährlich machen. Das ist kein Übersetzungsbug. Es ist ein Abrufversagen im Gewand eines Übersetzungsbugs.
Warum ich aufgehört habe, das Kontextfenster größer zu machen
Mein erster Instinkt war derselbe Instinkt, den gerade jeder hat, und ich will ehrlich sein, dass ich ihm eine Weile hinterhergelaufen bin: gib dem Modell einfach mehr.
Die Argumentation fühlte sich wasserdicht an. Wenn das Versagen darin besteht, dass das Modell nur eine Datei gesehen hat, dann füttere ihm auch das Copybook. Füttere ihm die Programme, die das Flag berühren. Füttere ihm das JCL. Kontextfenster sind inzwischen enorm und werden jedes Quartal größer, also muss die Antwort doch sein, aufzuhören geizig zu sein und die ganze Nachbarschaft des Codes in den Prompt zu gießen. Ich habe ernsthaft erwartet, dass das funktioniert — und bei einem Spielbeispiel tut es das irgendwie, weil man, wenn man schon weiß, welche sechs Dateien man einfügen muss, das eigentliche Problem bereits von Hand gelöst hat.
Das war der Riss darin. Um dem Modell den richtigen Kontext zu füttern, musste ich zuerst wissen, welcher Kontext der richtige war. Und zu wissen, dass der Typ von TRN-LIMIT von einem Flag entschieden wird, das in BATCHUPD geschrieben und von einem 02:00-JCL-Job geordnet wird, extrahiert man nicht, indem man WIRETXN härter liest. Das bekommt man nur, indem man den Abhängigkeitsgraphen bereits nachverfolgt hat. Das Kontextfenster sagt einem nicht, was man in das Kontextfenster legen soll. Ich hatte versucht, die Frage mit der Antwort zu beantworten.
Dann machten die Zahlen den Punkt endgültig. Die Estates, die diese Banken tatsächlich betreiben, sind keine sechs Dateien. Es sind ein bis zehn Millionen Zeilen COBOL, manchmal mehr, und 220 Milliarden Zeilen davon sind branchenweit noch in aktiver Produktion (Branchen-Metaanalyse, 2025). Eine echte Überweisungs-Änderung kann eine transitive Closure von vierzig Dateien oder vierhundert haben. Das passt nie in ein Kontextfenster — nicht heute und nicht in der Modellversion, die in drei Jahren erscheint —, weil das Estate schneller wächst als das Fenster und das Fenster ohnehin nie die Einschränkung war. Die Einschränkung ist zu wissen, welche vierzig Dateien von den zehn Millionen diese Änderung berührt, und zu beweisen, dass man alle vierzig gefunden hat und nicht achtunddreißig.
Ein größeres Kontextfenster ist eine bessere Antwort auf eine Frage, die ich nicht mehr stelle. Die Frage ist nicht „kann das Modell mehr Code halten“, sondern „welchen Code, und wie beweist man, dass es der gesamte ist.“
Diese Umformulierung ist der gesamte Grund, warum CodeGraph kein Übersetzer ist. Ich füge absichtlich kein COBOL ein und reiche Java zurück. Die Karte ist das Produkt, und Übersetzung ist ein nachgelagerter Anwendungsfall, den jedes Werkzeug erledigen kann, sobald die Karte existiert. Was ich baue, ist die Verständnisschicht darunter. Auf der ausgelieferten Fixture parst das Estate in einen typisierten Knowledge Graph mit 47 Knoten und 70 Kanten, und der „Impact einer Änderung“ ist eine Graph-Traversierung — die transitive Closure von allem, was die Änderung berührt —, wobei jede Kante die file:line trägt, aus der sie stammt. Es ist absichtlich langweilige Plain-Python-Graph-Arbeit, kein Modell im Hot Path, weil das, was ich brauche, nicht clever sein soll. Ich brauche, dass es vollständig und reproduzierbar ist. Dieselbe Fixture rein, dieselbe Closure raus, jedes einzelne Mal.
Ich sage es mir immer wieder als Regel. Agenten beraten, Code entscheidet. Die optionale Sprachschicht in der Demo — der Teil, der Fragen zur Closure in einfachem Englisch beantwortet — ist standardmäßig aus und hinter einem Key gesperrt. Der Wert hängt nicht davon ab. Der Wert ist der Abruf und der Beweis, und keines von beiden ist eine Modellfähigkeit.
Was löscht die naive Sicht tatsächlich?
Ich habe eigens einen Toggle in die Demo gebaut, damit ich zusehen konnte, wie die sechs Fakten verschwinden, weil ich dem Versagen nicht ganz glaubte, bis ich es geschehen sah.
Aktivieren Sie „Naive AI context view“, und der Graph kollabiert auf die einzelne Quelldatei plus ein Fenster von Zeilen um die Änderung — genau das, was ein Textfenster-Werkzeug einem Modell füttert. Das Panel, das 9 von 9 las, fällt auf 3 von 9. Die drei In-File-Fakten bleiben beleuchtet. Die anderen sechs vergrauen und verstummen: der COMP-3-Typ, das REDEFINES-Overlay, das steuernde Flag, seine beiden modulübergreifenden Schreiber und der 02:00-JCL-Vorgänger. Ein rotes Banner benennt die Konsequenz in den eigenen Worten der App: mit nur den drei sichtbaren Fakten gibt ein Modell long TRN_LIMIT aus und korrumpiert die Datenbank.

Ich will hier vorsichtig sein, denn genau hier ist die Stelle, an der ein Gründer zum Überschreiben verführt wird. Das 9-gegen-3-Ergebnis wird gemessen an der ausgelieferten synthetischen Überweisungs-Fixture, einem Estate, das ich für diese Demo von Hand geschrieben habe, genau damit die wahre Abhängigkeitsmenge bekannt ist und die Recall-Zahl eine echte gelabelte Messung ist statt einer Stimmung. Es ist keine Garantie für Ihr COBOL. Die naive Sicht ist eine Simulation, keine Live-z/OS-Pipeline. Der Graph liegt im Speicher mit SQLite darunter, keine Produktions-Graph-Plattform. Ich habe eine synthetische Bank gebaut, weil ich Ihnen ethisch keine echte zeigen konnte — und weil eine bekannte Ground Truth der einzige ehrliche Weg ist zu sagen: „der Graph hat alle neun, die Einzeldatei hat drei.“
Aber die Form des Versagens ist nicht synthetisch, und das ist der Teil, der zählt. Das COMP-3-Feld, dessen Typ woanders entschieden wird, das Flag, das ein Batch-Job setzt, die Reihenfolge, die nur im JCL existiert — das ist die gewöhnliche Textur eines vierzig Jahre alten Banking-Estates, keine exotischen Randfälle. Wenn grob 70 bis 80 Prozent der Mainframe-Modernisierungsprojekte ihre Ziele verfehlen (Branchen-Metaanalyse, 2025), denke ich nicht mehr, dass es an einem schlechten Übersetzungsschritt liegt. Der Übersetzungsschritt ist in Ordnung. Er bekommt ein Bild, aus dem die sechs wichtigsten Fakten herausgeschnitten sind.
Beweis — oder es zählt nicht
Das Feature, auf das ich am stolzesten bin, ist das, das zugibt, was es nicht kann — und ich habe das erst zu schätzen gelernt, als ein Compliance-Gespräch es für mich neu rahmte.
Ein Ingenieur will eine korrekte Migration. Ein Regulator will etwas anderes und Schwierigeres: Evidenz. Unter DORA schuldet eine Bank ein ICT-Asset-Inventar. Unter SOC-2 schuldet sie Change-Control-Belege. Keines davon ist erfüllt, wenn ein Modell sagt „vertrau mir, ich habe die Abhängigkeiten gefunden.“ Sie brauchen einen Vollständigkeitsbeweis — eine Aussage, wie viel der Codebasis das Werkzeug tatsächlich auflösen konnte, und wichtiger noch: ein ehrliches Flag für das, was es nicht konnte. Also habe ich ein Completeness Gate gebaut. Jeder PERFORM, CALL, COPY, und DB2-Verweis in der Fixture muss zu einem echten Knoten im Graph auflösen oder als „needs review“ markiert werden. Nichts darf still verschwinden.
Auf der Fixture löst dieses Gate 33 von 34 Referenzen auf — 97,1 Prozent Abdeckung. Die eine, die es nicht auflösen kann, ist ein Programm namens DISPATCH, das einen dynamischen CALL WS-PROGNAME ausführt — ein zur Laufzeit berechnetes Ziel, dem kein statischer Parser folgen kann, weil das Ziel erst bekannt ist, wenn das Programm läuft. Und das richtige Verhalten dort ist nicht zu raten. Es ist, ein Flag zu setzen, das sagt „hier muss ein Mensch hinschauen“, und es im Report zu belassen.

Dieser geflaggte DISPATCH-Call ist mein Lieblingsding am ganzen Build, und das meine ich ernst. Ein Werkzeug, das 97 Prozent auflöst und Ihnen genau sagt, welche 3 Prozent es nicht konnte, ist mehr wert als eines, das 100 behauptet und die Lücke versteckt, weil in der versteckten Lücke die korrumpierte Überweisung lebt. Das Completeness Gate erzeugt einen exportierbaren „Codebase Topology and Completeness Report“ — ein JSON und ein druckbares HTML mit Knoten- und Kantenzusammenfassung, den per-Modul-Closures mit file:line-Provenienz, dem Recall-Ergebnis und den geflaggten Items mit Zeitstempel. Dieses Artefakt ist der Punkt. Es ist das, was man einem Regulator übergeben, im nächsten Quartal erneut laufen lassen und die identische Antwort bekommen kann, weil es deterministisch ist.
Ich würde lieber eine Zahl ausliefern, die ihr eigenes Loch zugibt, als eine rundere, die es versteckt. Der geflaggte dynamische CALL ist keine Schwäche der Demo. Er ist die Demo.
Das ist auch der Teil, der nicht veraltet. Ein perfektes Modell — eines, das nie eine einzige Zeile Java halluziniert — kann einem Regulator trotzdem nicht beweisen, welche Abhängigkeiten es abgerufen hat. Es kann einem zur Laufzeit berechneten CALL trotzdem nicht statisch folgen, weil das eine Eigenschaft des Codes ist und nicht des Lesers. Provenienz und Vollständigkeit sind Eigenschaften des Systems, das man um das Modell herum baut — keine Fähigkeiten, die man freischaltet, indem man es skaliert.
Die Reihenfolge, in der man Dinge anfasst
Das Letzte, was mir der Graph gab, war etwas, das ich gar nicht vorhatte zu bauen: eine sichere Reihenfolge, in der man die Arbeit erledigt.
Sobald man die volle Abhängigkeitstopologie hat, kann man jedes Programm danach bewerten, wie verflochten es ist. Ich nutze eine schlichte Formel — Kopplung gewichtet gegen COMP-3-Fallen, JCL-Kritikalität und unaufgelöste Calls —, und sie rangiert die vierzehn Programme der Fixture in eine Strangler-Fig-Extraktionsreihenfolge. Das risikoärmste Programm wird zuerst extrahiert, das God-Program zuletzt. Auf der Fixture kommt AUDITLOG auf Rang 1 mit einem Risiko-Score von null, weil es keine Kopplung hat und nichts davon abhängt, dass es richtig ist. Es ist der sichere Einstieg. Das WIRETXN-Programm, um das wir uns Sorgen gemacht haben, sitzt auf Rang 11 und trägt seine eine COMP-3-Falle und seine JCL-Kritikalität. DISPATCH mit seinem unaufgelösten dynamischen Call sitzt auf Rang 12. Und ACCTMGR, das God-Program, auf das alles sich stützt, wird ganz zuletzt auf Rang 14 mit einem Risiko-Score von 15 extrahiert.

Ich hatte nicht erwartet, dass mir Sequenzierung so wichtig würde wie jetzt. Aber es ist dieselbe Lektion zum dritten Mal. Wo man sicher anfangen kann, ist ein Fakt über die Topologie — keine Meinung, über die man in einem Planungsmeeting streitet. Ein Team, das auf eine Million Zeilen starrt, streitet sich eigentlich nicht darüber, wie man einen Absatz übersetzt. Es streitet — endlos und teuer — darüber, wo man beginnt und was bricht, wenn man zuerst das Falsche anfasst. Das ist eine Graph-Frage, und der Graph beantwortet sie bei jedem Lauf gleich.
Die Extraktionsreihenfolge, das Completeness Gate, die Impact-Closure — sie sind alle dasselbe Objekt aus drei Blickwinkeln. Den wahren Slice abrufen, beweisen, dass es der ganze Slice ist, und die Slices nach Risiko rangieren. Keines dieser drei ist ein Übersetzungsproblem, und keines wird durch ein schlaueres Modell gelöst.
Die Frage, zu der ich immer zurückkomme
Ich habe angefangen, jedem AI-Modernisierungs-Pitch, den ich sehe — einschließlich meiner eigenen —, eine Frage zu stellen, und sie ist stillschweigend die einzige geworden, der ich vertraue.
Nicht „kann es gutes Java schreiben“, denn die Antwort ist fast immer ja und sie spielt fast nie eine Rolle. Die schwierigere Frage ist die, die mir die TRN-LIMIT-Zeile beigebracht hat: kann es jetzt sofort beweisen, welche Abhängigkeiten es abgerufen hat, und würde dieser Beweis einem Regulator standhalten, der wollte, dass er scheitert. Wenn das Werkzeug mir die Closure nicht mit file:line-Provenienz zeigen und mir nicht ehrlich sagen kann, was es nicht auflösen konnte, dann spielt es keine Rolle, wie flüssig die Ausgabe aussieht. Es rät mit guter Grammatik — und ich habe genau diese Vermutung long über ein gepacktes Dezimalfeld tippen und nach der Datenbank greifen sehen.
Die Branche hat ein Jahrzehnt damit verbracht, den Übersetzungsschritt zu verbessern, während 70 bis 80 Prozent der Projekte ihre Ziele weiter verfehlten (Branchen-Metaanalyse, 2025), und ich denke, das liegt daran, dass der Übersetzungsschritt nie der Ort war, an dem das Risiko lebte. Das Risiko lebt in der Topologie, in den sechs unsichtbaren Fakten, in dem Flag, das um zwei Uhr morgens gesetzt wird. Wenn Sie zusehen wollen, wie ein Graph diese sechs Fakten wiederherstellt und dann die eine flaggt, die er ehrlich nicht kann — die Demo ist hier: veriprajna.com/de/demos/cobol-modernisierung-mit-einem-wissensgraphen.
Und wenn Sie lieber zuschauen möchten, als meinen Text darüber zu lesen, hier läuft das Ganze End-to-End.
Ich glaube nicht mehr, dass das nächste Modell-Release diese Migrationen freischaltet. Ein größeres Fenster hält mehr Code; es weiß nicht, welchen Code, und es kann nicht beweisen, dass es den gesamten gefunden hat. Das war wahr, als ich die erste Zeile des Parsers tippte, und ich denke, es wird noch lange wahr sein, nachdem das Modell, mit dem ich das gebaut habe, außer Dienst ist. Die Karte war immer der schwierige Teil. Wir haben nur weiter auf die Übersetzung gestarrt, weil das der Teil war, den wir zu bewerten wussten.


