Skip to content

feature: translate PORTTRAN.cbl to PortfolioTransactionProcessor (Child 1) - #257

Open
devin-ai-integration[bot] wants to merge 3 commits into
devin/1785227633-phase0-java-foundationfrom
devin/1785228027-child1-porttran
Open

devin-ai-integration[bot] wants to merge 3 commits into
devin/1785227633-phase0-java-foundationfrom
devin/1785228027-child1-porttran

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 28, 2026

Copy link
Copy Markdown

Summary

Stacked on PR #256 — this targets devin/1785227633-phase0-java-foundation, not main, because it consumes the Phase 0 models, subroutine contracts and test harness. Rebase onto main once Phase 0 lands.

Child 1 of the coordinated COBOL-to-Java translation: src/programs/portfolio/PORTTRAN.cbl becomes modernized/java/service/PortfolioTransactionProcessor.java, one method per paragraph, with the paragraph named in each method's Javadoc. mvn -f modernized/java/pom.xml test is green: 113 tests, 47 of them new.

The most important behaviour preserved is that the main flow does nothing but validate. Nothing in the COBOL performs 2200-UPDATE-POSITIONS, so the entire position-update and audit subtree is unreachable (G2). That is reproduced rather than repaired:

public void main() {
    initialize();
    if (STATUS_SUCCESS.equals(wsTranStatus)) {
        while (!endOfFile && wsErrorCount <= ERROR_LIMIT) {   // UNTIL END-OF-FILE OR WS-ERROR-COUNT > 100
            processTransactions();                            // READ + validateTransaction(), and nothing else
        }
    }
    terminate();
}

so main() over a stream of valid buys leaves the repository untouched and never calls AUDPROC — pinned by DeadCode.validBuyChangesNothing. The update paragraphs are translated anyway as public methods nothing calls, so the logic is captured and directly testable.

Other defects reproduced, not fixed: transfers stay unimplemented (G3), 9000-ERROR-ROUTINE leaves ERR-CODE blank and ERR-SEVERITY zero (G6), and 2300-UPDATE-AUDIT-TRAIL decides SUCC/FAIL from WS-PORT-STATUS, which on a path that did no I/O is whatever the last file operation left (G7).

Interfaces added (modernized/java/service/)

COBOL Java
FD TRANSACTION-FILE, sequential TransactionSourceopen(), read() returning null for AT END, close()
FD PORTFOLIO-FILE, indexed I-O PortfolioRepositoryopen(), findById() returning Optional.empty() for INVALID KEY, update(), close(), getFileStatus()

getFileStatus() exists because 2300 branches on WS-PORT-STATUS (G7). Repository, source, AuditProcessor and ErrorProcessor are constructor-injected; the class does no I/O, holds no static state, and every decimal is a BigDecimal at its picture's scale. Clock and the user id are also injectable, so FUNCTION CURRENT-DATE is deterministic in tests.

Rendering decisions worth checking

  • STRING ... DELIMITED BY SIZE INTO overlays the receiver from the left, does not pad, and has no ON OVERFLOW here, so an id of PORT99 yields Invalid Portfolio ID: PORT99 — the sending field's trailing spaces included.
  • 2130-CHECK-AMOUNTS applies the quantity check to every type but writes the price and amount checks AND TRN-TYPE NOT = 'TR', so a transfer validates with a zero price and zero amount. Left exactly as written.
  • AUD-MESSAGE strings two COMP-3 fields, which is not legal COBOL (G8). The packed senders are rendered as CobolDecimal.image renders them, giving Transaction: BU Amount: +000000001250000 Units: +000000001000000 for the seeded buy.

New discrepancies (TRANSLATION-NOTES.md section 4, each with a test)

  • G11 — the EVALUATE in 2200 has no WHEN OTHER, so an unrecognised type updates nothing and is still audited, with a blank action.
  • G12AUD-BEFORE-IMAGE is captured after the update despite the comment saying otherwise, and AUD-AFTER-IMAGE is never populated.
  • G13 — a failed OPEN I-O PORTFOLIO-FILE is logged and then ignored, since only WS-TRAN-STATUS gates the loop; 3000-TERMINATE closes both files regardless.
  • G14 — the cutoff is test-before, so the run stops after the 101st error with 101 records read and no non-zero return code; a failed open spends the same budget.

Phase 0 catalogued G9 (the record area is PORTFOLIO-RECORD in one paragraph and PORT-RECORD in another) and G10 (FUNCTION USER-ID is not an intrinsic) while this branch was open; the merge keeps its wording and folds this slice's pinning tests and rendering decision into those entries.

TRANSLATION-NOTES.md also flips the Child 1 row to landed and gains a PORTTRAN section with the full paragraph-to-method, file-to-interface and error-string mapping. No Phase 0 type was changed.

Link to Devin session: https://app.devin.ai/sessions/0b3ee627d5e242fdb56ca5519b7c147d
Requested by: @gaelkekatos-jpg


Devin Review

Status Commit
⚪ Not started

Run Devin Review

Open in Devin Review (Staging)

Child 1 of the COBOL-to-Java translation, stacked on the Phase 0 foundation.

Translates src/programs/portfolio/PORTTRAN.cbl paragraph by paragraph, preserving
its defects rather than repairing them: the main flow only validates because the
whole position-update subtree is unreachable (G2), transfers stay unimplemented
(G3), errors carry no code or severity (G6) and the audit status is decided by a
possibly stale portfolio file status (G7).

Adds PortfolioRepository and TransactionSource for the two files, 47 tests, and
six new discrepancy entries (G9-G14) in TRANSLATION-NOTES.md.

Co-Authored-By: Gael Kekatos <gael.kekatos@cognition.ai>
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Phase 0 catalogued the duplicate record-area names and FUNCTION USER-ID as its
own G9 and G10 while this branch was open. Keeps the Phase 0 wording for both and
folds Child 1's pinning tests into them; Child 1's remaining entries stay G11-G14.

Co-Authored-By: Gael Kekatos <gael.kekatos@cognition.ai>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

Open in Devin Review

`MOVE` pads to the 26 bytes of `AUD-TIMESTAMP`. Pinned by `ControlFlow.userIdIsSupplied` and
`AuditTrail.successfulUpdateIsAuditedSucc`.

### G11 - an unrecognised transaction type is audited as if something had happened

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Section 4 intro still says discrepancies are catalogued G1-G10 after G11-G14 were added

This PR appends four new discrepancy entries (G11-G14) to section 4 of modernized/java/TRANSLATION-NOTES.md (added at lines 285-329), but the summary sentence at modernized/java/TRANSLATION-NOTES.md:15 still reads "Discrepancies found so far are catalogued as G1-G10 in section 4." That line is now stale; it should read G1-G14. Minor documentation inconsistency only (the line itself is unchanged context, not part of an edited hunk).

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 3257f93: the header line now reads G1-G14.

Comment on lines +320 to +336
public void checkAmounts() {
if (CobolDecimal.isNotPositive(transactionRecord.getTrnQuantity())) {
errorMessage.setErrText(ERR_QUANTITY_NOT_POSITIVE);
return;
}

boolean transfer = "TR".equals(transactionRecord.getTrnType());

if (CobolDecimal.isNotPositive(transactionRecord.getTrnPrice()) && !transfer) {
errorMessage.setErrText(ERR_PRICE_NOT_POSITIVE);
return;
}

if (CobolDecimal.isNotPositive(transactionRecord.getTrnAmount()) && !transfer) {
errorMessage.setErrText(ERR_AMOUNT_NOT_POSITIVE);
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: checkAmounts matches the COBOL ELSE-IF (EXIT PARAGRAPH) short-circuit ordering

I verified checkAmounts() against 2130-CHECK-AMOUNTS in src/programs/portfolio/PORTTRAN.cbl. The COBOL uses EXIT PARAGRAPH after the quantity and price checks and applies AND TRN-TYPE NOT = 'TR' only to the price and amount checks. The Java return after quantity and after price reproduces the first-failure-wins ordering exactly, and the !transfer guard on price/amount (lines 328-334) matches. A non-transfer with quantity>0, price<=0 and amount<=0 reports the price error in both, so no ordering divergence exists. Not a bug — faithful reproduction.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matches my reading of 2130-CHECK-AMOUNTS — the quantity check applies to every type and only price/amount carry AND TRN-TYPE NOT = 'TR', with first failure winning. Deliberate, no change.

Comment on lines +602 to +619
private String currentDate() {
ZonedDateTime now = ZonedDateTime.now(clock);
int offsetMinutes = now.getOffset().getTotalSeconds() / 60;
char sign = offsetMinutes < 0 ? '-' : '+';
int absoluteMinutes = Math.abs(offsetMinutes);
return String.format(
"%04d%02d%02d%02d%02d%02d%02d%s%02d%02d",
now.getYear(),
now.getMonthValue(),
now.getDayOfMonth(),
now.getHour(),
now.getMinute(),
now.getSecond(),
now.getNano() / 10_000_000,
sign,
absoluteMinutes / 60,
absoluteMinutes % 60);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: currentDate renders the 21-char CURRENT-DATE format and MOVE-pads to 26

currentDate() builds YYYYMMDDhhmmssnn±hhmm (21 chars): now.getNano() / 10_000_000 yields hundredths of a second, and the GMT offset is rendered as signed hhmm. This is 21 characters which the audit setter pads to the 26-byte AUD-TIMESTAMP, matching the test constant 2024032015304512+0000. The %s format specifier receiving a char sign autoboxes to Character and renders correctly. Faithful to MOVE FUNCTION CURRENT-DATE TO AUD-TIMESTAMP.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and that is the intent — FUNCTION CURRENT-DATE returns 21 characters and MOVE pads them into the 26 bytes of AUD-TIMESTAMP. No change.

Co-Authored-By: Gael Kekatos <gael.kekatos@cognition.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants