Skip to content

Fix #3992: no-JCL batch controller names an undefined <File>DTO - #3994

Merged
squid-protocol merged 1 commit into
mainfrom
fix/3992-controller-undefined-dto
Sep 29, 2026
Merged

squid-protocol merged 1 commit into
mainfrom
fix/3992-controller-undefined-dto

Conversation

@squid-protocol

Copy link
Copy Markdown
Owner

Fixes #3992

Root cause

JCL isn't actually involved. base_intent.files_requested comes from analyze_cobol_intent's SELECT/ASSIGN anchor (cobol_jcl_forge._FILE_ASSIGN_ANCHOR), not from JCL. That anchor only read a word operand. So:

  • ASSIGN "TEST-FILE" / ASSIGN "./TEST-FILE" (the form used by GnuCOBOL, opensourcecobol4j and Micro Focus) matched nothing. files_requested came back empty, the program was classified transactional, and the controller bound @RequestBody TestFileDTO. No generator ever writes that class, so compilation failed with cannot find symbol.
  • ASSIGN TO "./TEST-FILE" backtracked past the optional TO and read the keyword TO itself as the DD name. That compiled, but produced a wrong @RequestParam("toFile") and a //TO DD in the zero-trust JCL.

The engine's own reader (mainframe_boundary._SELECT_ASSIGN) already accepts literals and SELECT OPTIONAL, so lineage.inputs had TEST-FILE while files_requested was empty.

Approach: option (a) plus a general guard

  1. cobol_jcl_forge: the SELECT anchor now reads a quoted literal (bounded to 1024 chars), SELECT OPTIONAL, and never takes TO as the operand. This matches the engine's reader. A non-CICS program doing file I/O is batch, so it gets the MultipartFile upload signature, the same way CardDemo batch programs do.
  2. api_contract_forge, as a guard for any SELECT form the intent reader still misses:
    • A non-CICS program with lineage inputs is batch even when files_requested is empty. Its input files become the upload params.
    • The transactional branch no longer names a <File>DTO. No generator writes a per-file DTO (a CICS program's COMMAREA contract comes from its skeleton via forges.cics.controller). The inputs are listed in a // Input files: ... comment for the porter. Two @RequestBody on one method was never valid Spring anyway.
    • A DD name from a literal ("01.DAT", KUNDE§NR) is made a legal Java identifier (java_legal_chars + file prefix if it can't start one). Legal names are unchanged.

Why not only option (b)? Keeping the program transactional and dropping the parameter would compile, but it would describe a file-processing batch program as a request/response endpoint and leave the JCL reading TO. Option (a) is the semantically right classification. The guard means the controller can't name an ungenerated class again, even if the intent reader misses a SELECT.

Output impact

Estate: opensourcecobol4j jp-compat (mvn -o compile, JDK 17)

before (origin/main) after
compile FAILED: 10 controllers cannot find symbol: class TestFileDTO OK
controllers 36 36
@RequestBody TestFileDTO 10 0
@RequestParam("toFile") (TO misread) 15 0
upload params toFile×15, sys01DaDkS*×4 testFileFile×28, inputTxtFile×2, nowhereFile×2, sys01DaDkS*×4

The zero-trust JCL for the literal-ASSIGN programs now declares //TESTFILE DD with the right DISP instead of //TO DD ... NO EXPLICIT OPEN INTENT. The analyze.py findings are unchanged.

Tests

  • New tests/cobol_mainframe/test_no_jcl_batch_controller.py:
    • The intent reader handles literal, TO literal, OPTIONAL and word forms, and never reads the TO keyword as a name.
    • Controller unit tests: no-SELECT non-CICS is batch, a CICS program names no DTO, literal names become legal identifiers.
    • End to end on a no-JCL estate: scan, refract, generate. It asserts every controller names only classes the run generated, and that mvn -o compile succeeds (skipped without Maven + JDK 17 or an offline-resolvable repo).
    • 13 of the 17 fail on main.
  • tests/cobol_mainframe + tests/tools_recorders: 1338 passed, 32 skipped.
  • ruff_audit --ci, mypy_audit --ci, dead_key_audit --ci: no new findings. tool_regex_redos.py --ci: 0 offenders. ruff format is clean on the touched files.

Not addressed

cobol_dag_architect._SELECT (the forge lineage used without --scan) has the same ASSIGN TO "x" → TO backtrack. Its code_view blanks literal contents, so it can't read the literal name. The controller guard keeps that path compiling.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EC8FWfn3aPmsUPnRgKNVup

A PC-dialect program ASSIGNs its files to literals (`ASSIGN "TEST-FILE"`,
`ASSIGN TO "./input.txt"`). The intent reader's SELECT anchor read only a word
operand, so files_requested came back empty, the program was classified
transactional, and its controller bound `@RequestBody TestFileDTO` -- a class
no generator writes -- so the Spring project did not compile. `ASSIGN TO "x"`
also backtracked to read the TO keyword as the DD name (`toFile`).

- cobol_jcl_forge: the SELECT anchor reads quoted literals and SELECT OPTIONAL,
  as the engine's own reader (mainframe_boundary._SELECT_ASSIGN) does, and
  never takes TO as the operand.
- api_contract_forge: a non-CICS program with input files is batch even when
  no SELECT was read (its lineage inputs become the uploads); the
  transactional branch no longer names a per-file DTO class (it lists the
  files in a comment); a DD name from a literal is made a legal identifier.
- Tests: intent forms, controller output, and an end-to-end no-JCL estate
  that asserts controllers name only generated classes and that mvn compiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EC8FWfn3aPmsUPnRgKNVup
@github-actions

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

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.

Generated REST controller references an undefined <File>DTO for a batch program without JCL: jp-compat still does not compile after #3957

1 participant