⚡ demo⇄ rewrite🛤 journey📊 assessment📈 stats🖥 green screen🔬 console🚧 blockers⬢ served from poc-smk · Linux x86_64 · cloud VM (poc-jkdm-ai)
The rewrite · side by side

One program, three languages, zero drift

Every code block below is extracted from the real files. Scroll — each rule writes itself across the pipeline: mainframe, then Linux, then PHP.

COBOL · z/OSJCL · VSAM · assembler
replatform: GnuCOBOL, asm→COBOL, JCL→shell
COBOL · Linuxsame source, new runtime
rewrite: AI port, rule by rule
PHP 8integer-sen arithmetic
⚖ oracle verdict: 50 records · 5,350 bytes · byte-for-byte MATCH

How it runs

Same program, three homes: a JCL job step on z/OS, a compiled binary on Linux, a script anywhere PHP runs.

COBOL · z/OS jcl/READACCT.jcl
//STEP05 EXEC PGM=CBACT01C
//STEPLIB  DD DISP=SHR,
//         DSN=AWS.M2.CARDDEMO.LOADLIB
//ACCTFILE DD DISP=SHR,
//         DSN=AWS.M2.CARDDEMO.ACCTDATA.VSAM.KSDS
//OUTFILE  DD DSN=AWS.M2.CARDDEMO.ACCTDATA.PSCOMP,
//            DISP=(NEW,CATLG,DELETE),
Submitted as a batch job; datasets are catalogued VSAM.
COBOL · Linux GnuCOBOL, no mainframe
cobc -x -std=ibm -I cpy cbl/CBACT01C.cbl -o cbact01c
ACCTFILE=ACCTFILE OUTFILE=outfile.dat ./cbact01c
Identical source compiled natively; DD names become env vars.
PHP 8 liveapp/cbact01c.php · main loop
$in = fopen($acctData, 'r');
if (!$in) { fwrite(STDERR, "cannot open $acctData\n"); exit(1); }
$out = fopen($outPath, 'wb');
if (!$out) { fwrite(STDERR, "cannot open $outPath\n"); exit(1); }

$count = 0;
while (($line = fgets($in)) !== false) {
    $r = rtrim($line, "\n");
    if (strlen($r) < 295) { continue; }
Full rewrite. Invoked as: ACCT_DATA=acctdata.txt php cbact01c.php

Reading the money fields

COBOL declares the record shape; the runtime decodes zoned decimals for free. PHP has to decode the sign-overpunch by hand - in integer sen, never floats.

COBOL · z/OS cpy/CVACT01Y.cpy
       01  ACCOUNT-RECORD.
           05  ACCT-ID                           PIC 9(11).
           05  ACCT-ACTIVE-STATUS                PIC X(01).
           05  ACCT-CURR-BAL                     PIC S9(10)V99.
           05  ACCT-CREDIT-LIMIT                 PIC S9(10)V99.
           05  ACCT-CASH-CREDIT-LIMIT            PIC S9(10)V99.
COBOL · Linux cpy/CVACT01Y.cpy - UNCHANGED
       01  ACCOUNT-RECORD.
           05  ACCT-ID                           PIC 9(11).
           05  ACCT-ACTIVE-STATUS                PIC X(01).
           05  ACCT-CURR-BAL                     PIC S9(10)V99.
           05  ACCT-CREDIT-LIMIT                 PIC S9(10)V99.
           05  ACCT-CASH-CREDIT-LIMIT            PIC S9(10)V99.
Same copybook, byte for byte. The replatform does not touch it.
PHP 8 zonedToSen()
function zonedToSen(string $s): int {
    $pos = ['{' => 0, 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5,
            'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9];
    $neg = ['}' => 0, 'J' => 1, 'K' => 2, 'L' => 3, 'M' => 4, 'N' => 5,
            'O' => 6, 'P' => 7, 'Q' => 8, 'R' => 9];
    $last = substr($s, -1);
    $sign = 1;
    if (isset($pos[$last]))      { $digits = substr($s, 0, -1) . $pos[$last]; }
    elseif (isset($neg[$last]))  { $digits = substr($s, 0, -1) . $neg[$last]; $sign = -1; }
    else                         { $digits = $s; }
    return $sign * (int)$digits;

The date module - the piece that could not move

CBACT01C calls COBDATFT, which is z/OS assembler. The Linux hop replaces it with a portable COBOL drop-in (same copybook interface); the PHP hop is three lines.

ASM · z/OS asm/COBDATFT.asm
VALIDIN2 EQU *
*        CLC   COINPDT+4,C'-'
*        BNE   GOTOERR
         CLI   COOUTYPE,C'1'
         BE    GOTOERR
         MVC   COOUTDT(4),COINPDT
         MVC   COOUTDT+4(2),COINPDT+5
         MVC   COOUTDT+6(2),COINPDT+8
         B     EXITL
IBM assembler - meaningless off the mainframe.
COBOL · Linux liveapp/COBDATFT.cbl - REPLACEMENT
             WHEN CODATECN-TYPE = '2' AND CODATECN-OUTTYPE = '2'
      *        YYYY-MM-DD -> YYYYMMDD (asm VALIDIN2)
               IF CODATECN-INP-DATE(5:1) NOT = '-' OR
                  CODATECN-INP-DATE(8:1) NOT = '-'
                   MOVE 'INVALID INPUT DATE FORMAT' TO
                        CODATECN-ERROR-MSG
               ELSE
                   MOVE SPACES TO CODATECN-0UT-DATE
                   MOVE CODATECN-INP-DATE(1:4)
                     TO CODATECN-0UT-DATE(1:4)
                   MOVE CODATECN-INP-DATE(6:2)
                     TO CODATECN-0UT-DATE(5:2)
                   MOVE CODATECN-INP-DATE(9:2)
                     TO CODATECN-0UT-DATE(7:2)
AI-written drop-in, same CODATECN interface. CBACT01C never notices.
PHP 8 cbact01c.php
    // COBDATFT equivalent: YYYY-MM-DD -> YYYYMMDD (10-char field, space padded)
    $reissueOut = str_pad(
        substr($reissue, 0, 4) . substr($reissue, 5, 2) . substr($reissue, 8, 2),
        10, ' ');

The hidden business rule

Buried in paragraph 1300: a zero cycle-debit becomes 2525.00. Every conversion must carry this quirk - the oracle catches it if dropped.

COBOL · z/OS cbl/CBACT01C.cbl · 1300-POPUL-ACCT-RECORD
           IF  ACCT-CURR-CYC-DEBIT EQUAL TO ZERO
               MOVE 2525.00         TO   OUT-ACCT-CURR-CYC-DEBIT
           END-IF.
COBOL · Linux cbl/CBACT01C.cbl - UNCHANGED
           IF  ACCT-CURR-CYC-DEBIT EQUAL TO ZERO
               MOVE 2525.00         TO   OUT-ACCT-CURR-CYC-DEBIT
           END-IF.
Same source. Replatforming preserves business rules by definition.
PHP 8 cbact01c.php
 *   - CURR-CYC-DEBIT == 0    -> 2525.00            (business quirk in 1300-)
 *   - money kept as integer sen end-to-end; zoned + COMP-3 encoded by hand.

Writing the output - including packed decimal

One output field is COMP-3 (packed BCD). COBOL just declares it; the PHP port must build the nibbles by hand. The oracle proved both produce identical bytes - 50 records, 5,350 bytes, byte-for-byte.

COBOL · z/OS cbl/CBACT01C.cbl · FD OUT-FILE
          05  OUT-ACCT-CURR-CYC-CREDIT   PIC S9(10)V99.
          05  OUT-ACCT-CURR-CYC-DEBIT    PIC S9(10)V99
                                         USAGE IS COMP-3.
          05  OUT-ACCT-GROUP-ID          PIC X(10).
COBOL · Linux cbl/CBACT01C.cbl - UNCHANGED
          05  OUT-ACCT-CURR-CYC-CREDIT   PIC S9(10)V99.
          05  OUT-ACCT-CURR-CYC-DEBIT    PIC S9(10)V99
                                         USAGE IS COMP-3.
          05  OUT-ACCT-GROUP-ID          PIC X(10).
GnuCOBOL implements COMP-3 identically to the mainframe.
PHP 8 senToComp3()
function senToComp3(int $sen): string {
    $sign = $sen < 0 ? 'd' : 'c';
    $digits = str_pad((string)abs($sen), 12, '0', STR_PAD_LEFT);
    return pack('H*', '0' . $digits . $sign);   // pad nibble + 12 digits + sign