# CBTRN02C — discovery note (playbook phase 1)

Made with `/sf-discover CBTRN02C` on the hosted SteelFrame estate (CardDemo), 2026-10-21. Every section names
the call it came from so it can be re-run; nothing here is inferred beyond what the estate returned.

## Purpose

`CARDDEMO.SOURCE(CBTRN02C)`, header: "Post the records from daily transaction file." A batch COBOL program
(619 lines, 27 paragraphs) that reads the daily transaction file sequentially, validates each transaction
against the card cross-reference and the account master, posts the valid ones (transaction master, account
balances, transaction-category balances) and writes the invalid ones to a rejects file with a reason trailer.
`RETURN-CODE 4` when anything was rejected. — `read_member {"dsn":"CARDDEMO.SOURCE","member":"CBTRN02C"}`

## Where it runs

`find_program {"name":"CBTRN02C"}`:

- `jclRunning`: job `POSTTRAN`, member `CARDDEMO.JCL(POSTTRAN)`, step `STEP15` (`EXEC PGM=CBTRN02C`, STEPLIB
  `AWS.M2.CARDDEMO.LOADLIB`). Also in `IBMUSER.CS520.JCL`.
- `scheduledBy`: CA-7 folder `CARDDEMO`, job `POSTTRAN`, triggers `WAITSTEP`.
- `loadModules`: `AWS.M2.CARDDEMO.LOADLIB(CBTRN02C)`.
- CSD: none (batch only). Mapsets: none. SQL: 0. CALLs: `CEE3ABD` (abend, literal, line 711).

## Files and records

From the deck (`read_member {"dsn":"CARDDEMO.JCL","member":"POSTTRAN"}`), the SELECTs (`find_program ... summary.files`),
`dataset_info` and `decode_records` with the copybooks `find_program` listed:

| DD | DSN | DSORG / key | open | copybook (record) | charset the engine states |
|---|---|---|---|---|---|
| DALYTRAN | `AWS.M2.CARDDEMO.DALYTRAN.PS` | PS FB 350 | INPUT | `CVTRA06Y` (DALYTRAN-RECORD) | ebcdic |
| TRANFILE | `AWS.M2.CARDDEMO.TRANSACT.VSAM.KSDS` | KSDS key 16@0, recl 350, AIX 26@304 | **OUTPUT** | `CVTRA05Y` (TRAN-RECORD) | program |
| XREFFILE | `AWS.M2.CARDDEMO.CARDXREF.VSAM.KSDS` | KSDS key 16@0, recl 50, AIX 11@25 | INPUT | `CVACT03Y` (CARD-XREF-RECORD) | program |
| DALYREJS | `AWS.M2.CARDDEMO.DALYREJS(+1)` | new GDG generation, F 430 | OUTPUT | none on the estate (FD: 350-byte record + 80-byte trailer) | ebcdic |
| ACCTFILE | `AWS.M2.CARDDEMO.ACCTDATA.VSAM.KSDS` | KSDS key 11@0, recl 300 | I-O | `CVACT01Y` (ACCOUNT-RECORD) | program |
| TCATBALF | `AWS.M2.CARDDEMO.TCATBALF.VSAM.KSDS` | KSDS key 17@0, recl 50 | I-O | `CVTRA01Y` (TRAN-CAT-BAL-RECORD) | program |

Record layouts (offsets from `decode_records ... layout`):

- `CVTRA06Y` DALYTRAN-RECORD (350): ID X(16)@0, TYPE-CD X(02)@16, CAT-CD 9(04)@18, SOURCE X(10)@22, DESC X(100)@32,
  AMT S9(09)V99@132 (11 bytes, zoned), MERCHANT-ID 9(09)@143, MERCHANT-NAME X(50)@152, MERCHANT-CITY X(50)@202,
  MERCHANT-ZIP X(10)@252, CARD-NUM X(16)@262, ORIG-TS X(26)@278, PROC-TS X(26)@304, FILLER X(20)@330.
- `CVTRA05Y` TRAN-RECORD (350): the same fields as TRAN-*; PROC-TS is stamped by this program.
- `CVACT03Y` CARD-XREF-RECORD (50): CARD-NUM X(16), CUST-ID 9(09), ACCT-ID 9(11), FILLER X(14).
- `CVACT01Y` ACCOUNT-RECORD (300): ACCT-ID 9(11), ACTIVE-STATUS X, CURR-BAL / CREDIT-LIMIT / CASH-CREDIT-LIMIT
  S9(10)V99, OPEN-DATE / EXPIRAION-DATE (sic) / REISSUE-DATE X(10), CURR-CYC-CREDIT / CURR-CYC-DEBIT S9(10)V99,
  ADDR-ZIP X(10), GROUP-ID X(10), FILLER X(178).
- `CVTRA01Y` TRAN-CAT-BAL-RECORD (50): key = ACCT-ID 9(11) + TYPE-CD X(02) + CD 9(04); TRAN-CAT-BAL S9(09)V99;
  FILLER X(22).

Sample rows (`decode_records ..., "count":"4", "numbers":"decimal"`): accounts 1–4 have expiry dates
2023-12-16 … 2025-05-20 and zero cycle credit/debit; TCATBALF holds types 01 and 03 for category 1 per account.
The live `DALYTRAN.PS` held 5 records from a course exercise on the day of discovery (its content is not part of
the program's contract — the characterization seeds its own).

## Business rules found (quoted from the source)

1. `1500-A-LOOKUP-XREF`: `READ XREF-FILE` by card number; `INVALID KEY` → reason 100 "INVALID CARD NUMBER FOUND".
2. `1500-B-LOOKUP-ACCT`: `READ ACCOUNT-FILE` by `XREF-ACCT-ID`; `INVALID KEY` → 101 "ACCOUNT RECORD NOT FOUND";
   otherwise `COMPUTE WS-TEMP-BAL = ACCT-CURR-CYC-CREDIT - ACCT-CURR-CYC-DEBIT + DALYTRAN-AMT`;
   `IF ACCT-CREDIT-LIMIT >= WS-TEMP-BAL` ok else 102 "OVERLIMIT TRANSACTION"; then, **unconditionally after
   that**, `IF ACCT-EXPIRAION-DATE >= DALYTRAN-ORIG-TS (1:10)` ok else 103 "TRANSACTION RECEIVED AFTER ACCT
   EXPIRATION" — a transaction that is both over limit and after expiry is rejected with 103 (the later MOVE
   wins). `WS-TEMP-BAL` is `S9(09)V99` while the account fields are `S9(10)V99` (a size edge the seed does not
   reach).
3. `2000-POST-TRANSACTION`: copies the daily fields to TRAN-RECORD, `TRAN-PROC-TS` = `Z-GET-DB2-FORMAT-TIMESTAMP`
   (`FUNCTION CURRENT-DATE` as `YYYY-MM-DD-HH.MM.SS.NN0000`), then `2700-UPDATE-TCATBAL`, `2800-UPDATE-ACCOUNT-REC`,
   `2900-WRITE-TRANSACTION-FILE`.
4. `2700-UPDATE-TCATBAL`: key = XREF-ACCT-ID + TYPE-CD + CAT-CD; not found → `DISPLAY 'TCATBAL record not found for
   key : ' FD-TRAN-CAT-KEY '.. Creating.'` and `2700-A-CREATE-TCATBAL-REC` (INITIALIZE, key, `ADD DALYTRAN-AMT TO
   TRAN-CAT-BAL`, WRITE); found → `2700-B-UPDATE-TCATBAL-REC` (`ADD`, REWRITE).
5. `2800-UPDATE-ACCOUNT-REC`: `ADD DALYTRAN-AMT TO ACCT-CURR-BAL`; `IF DALYTRAN-AMT >= 0 ADD ... TO
   ACCT-CURR-CYC-CREDIT ELSE ADD ... TO ACCT-CURR-CYC-DEBIT`; REWRITE (INVALID KEY → reason 109, after the fact).
6. `2500-WRITE-REJECT-REC`: the daily record as read (350) + `WS-VALIDATION-FAIL-REASON 9(04)` +
   `-DESC X(76)`.
7. End: `DISPLAY 'TRANSACTIONS PROCESSED :' 9(09)`, `'TRANSACTIONS REJECTED  :' 9(09)`; `IF WS-REJECT-COUNT > 0
   MOVE 4 TO RETURN-CODE`. Every file error → `9999-ABEND-PROGRAM` (`CEE3ABD` 999).
8. `TRANSACT-FILE` is opened **OUTPUT**: the transaction master after the run holds only this run's postings.

## Coupling (who else touches these datasets)

`analysis_graph {"kind":"job"}` / `find_program` on the CICS side: `ACCTDATA` and `TCATBALF` are read and
updated online (account view/update transactions), `CARDXREF` is read online, `TRANSACT` is read by the
transaction list/view transactions and by the statement and interest batch jobs. The coupling is through data,
not calls — the playbook's "implicit coupling".

## Prerequisites for a characterization run

- `AWS.M2.CARDDEMO.DALYREJS` must be a cataloged GDG base (`DISP=(NEW,CATLG,DELETE)` on `(+1)`). On a fresh copy
  of the estate it is not (`list_datasets {"pattern":"AWS.M2.CARDDEMO.DALYREJS**"}` → only `DALYREJS.PS`), and
  CardDemo's `DEFGDGB` does not define it → `prepare` step with IDCAMS `DEFINE GENERATIONDATAGROUP`.
- `TRANSACT` is opened OUTPUT: VSAM refuses that on a loaded cluster defined without REUSE (RC 8 reason 232 →
  FILE STATUS 37), which is the state of the hosted estate's `AWS.M2.CARDDEMO.TRANSACT.VSAM.KSDS` (re-defined
  2026-10-10, 115 records); CardDemo's cycle runs `TRANBKP` (backup, DELETE, DEFINE) first, and on the hosted
  estate the `*.VSAM.**` masters are UPDATE-only for ordinary users (no DELETE/DEFINE). → run the deck with
  `TRANFILE`, `DALYTRAN` and `DALYREJS(+1)` under your own HLQ, defined in `prepare`.
- The clock must be pinned (`TRAN-PROC-TS`).
- `DALYREJS` has no copybook on the estate → compare as bytes (EBCDIC at rest).

## Open questions for the SME

- Is 103 overriding 102 intended, or should over-limit be reported when both apply?
- `WS-TEMP-BAL S9(09)V99` vs account amounts `S9(10)V99`: what should happen above 999,999,999.99?
- `ACCT-ACTIVE-STATUS` is never checked by this program: is posting to an inactive account intended?
