Found by the #2669 pass over every non-green cell in the keyword-rosetta bias report (2026-09-05, engine 0e521e8). jcl/cleanup is n/a — jcl.py has no cleanup rule — and the absence is ledgered on #2610's decision that DISP=(...,DELETE) would double-count io's DISP=. #2742 (closing #2733) has since taken the opposite call for the same operand, and its argument applies to DELETE verbatim.
What #2742 decided, and why it carries over
#2742 added sync_locks on DISP=OLD|MOD with an accepted, test-pinned overlap with io, on two grounds: the rule counts a narrow, semantically distinct subset of DISP= (48 of 525, ~9%), and the engine already tolerates overlaps where the meanings differ (COND=((4,LT),EVEN) counts safety and safety_bypasses; haskell finally counts cleanup and safety). Its PR body distinguishes #2610's rejection as being about a positional "that rides along on essentially every DISP=".
Measured on language-crucible (186 files, 77bc85e), DELETE is the same kind of narrow subset:
DISP= form |
occurrences |
all DISP= |
533 |
DELETE in the normal-termination position — DISP=(MOD,DELETE,DELETE), DISP=(OLD,DELETE) |
36 |
DELETE only in the abnormal-termination position — DISP=(NEW,CATLG,DELETE) |
12 |
| files carrying either |
25 of 186 |
The normal-position form is not incidental: //S EXEC PGM=IEFBR14 + DD DSN=X,DISP=(MOD,DELETE,DELETE) is the z/OS idiom for deleting a dataset (a no-op program run purely for its allocation side effect — 46 DELETE tokens sit in the corpus's IEFBR14 members). It is the teardown step batch jobs run before an allocate and after a run, i.e. exactly what cleanup measures through rm -f/trap … EXIT in shell, apt-get clean in dockerfile and docker compose down in yaml (#2647). The abnormal-position form is the closest thing JCL has to a finally block.
Candidate shape
# #2610 declined this for the io overlap; #2742 reversed that posture for the
# same operand's OLD/MOD subset. DELETE as a *disposition* (never the SHR/NEW/
# OLD/MOD status positional) is JCL's dataset-teardown idiom: IEFBR14 +
# DISP=(MOD,DELETE,DELETE) is how a batch job deletes a dataset.
"cleanup": re.compile(r"\bDISP=\((?:[^()\n]*,)?[ \t]*DELETE\b", re.I),
Maintainer call inside the shape: count only the normal-termination position (36 hits; drop the (?:…,)? optional group to [^,()\n]*,[ \t]*DELETE) or both positions (48). Not proposed: UNCATLG (0 crucible hits) and IDCAMS DELETE commands, which live in SYSIN in-stream data — the same payload the state_mutation rule was deliberately anchored away from.
Overlap, measured
screen_plant.py: //DD3 DD DSN=CORPUS.TMP,DISP=(MOD,DELETE,DELETE) fires io 1 (the DSN; DISP=( does not match io's \bDISP=\b because =( has no word boundary) and sync_locks 1 (MOD). Same accepted-overlap posture as #2742, and a test pinning it the way test_jcl_sync_locks_overlaps_io_by_design does.
Corpus pairing
keyword-rosetta's c.jcl probe_cleanup step carries no DD today. One line, //DD3 DD DSN=CORPUS.TMP,DISP=(OLD,DELETE), plants cleanup 1 and the DISP=OLD sync_locks plant #2742's PR body already lists as owed — one DD pays both debts. Cost: io +1 corpus-wide (3 → 4 against a median of 3, amber unless main's DD1 gives up its DISP=SHR). Ledger entry jcl-2610-rebaseline-residual-morphology's cleanup clause flips with the decision either way, as jcl-sync-locks-disp-enq-question did.
Part of #2669 (rosetta[jcl] was #2581). Siblings from the same pass: jcl api, jcl globals, jcl high_risk_execution.
Filed together on 2026-09-05: jcl api #2748 · jcl cleanup #2749 · jcl globals #2750 · jcl high_risk_execution #2751 · css io #2752 · yaml args #2753. Corpus-side pairings: squid-protocol/keyword-rosetta#59.
Found by the #2669 pass over every non-green cell in the keyword-rosetta bias report (2026-09-05, engine 0e521e8).
jcl/cleanupis n/a —jcl.pyhas nocleanuprule — and the absence is ledgered on #2610's decision thatDISP=(...,DELETE)would double-countio'sDISP=. #2742 (closing #2733) has since taken the opposite call for the same operand, and its argument applies toDELETEverbatim.What #2742 decided, and why it carries over
#2742 added
sync_locksonDISP=OLD|MODwith an accepted, test-pinned overlap withio, on two grounds: the rule counts a narrow, semantically distinct subset ofDISP=(48 of 525, ~9%), and the engine already tolerates overlaps where the meanings differ (COND=((4,LT),EVEN)countssafetyandsafety_bypasses; haskellfinallycountscleanupandsafety). Its PR body distinguishes #2610's rejection as being about a positional "that rides along on essentially everyDISP=".Measured on language-crucible (186 files,
77bc85e),DELETEis the same kind of narrow subset:DISP=formDISP=DELETEin the normal-termination position —DISP=(MOD,DELETE,DELETE),DISP=(OLD,DELETE)DELETEonly in the abnormal-termination position —DISP=(NEW,CATLG,DELETE)The normal-position form is not incidental:
//S EXEC PGM=IEFBR14+DD DSN=X,DISP=(MOD,DELETE,DELETE)is the z/OS idiom for deleting a dataset (a no-op program run purely for its allocation side effect — 46DELETEtokens sit in the corpus'sIEFBR14members). It is the teardown step batch jobs run before an allocate and after a run, i.e. exactly whatcleanupmeasures throughrm -f/trap … EXITin shell,apt-get cleanin dockerfile anddocker compose downin yaml (#2647). The abnormal-position form is the closest thing JCL has to afinallyblock.Candidate shape
Maintainer call inside the shape: count only the normal-termination position (36 hits; drop the
(?:…,)?optional group to[^,()\n]*,[ \t]*DELETE) or both positions (48). Not proposed:UNCATLG(0 crucible hits) and IDCAMSDELETEcommands, which live inSYSINin-stream data — the same payload thestate_mutationrule was deliberately anchored away from.Overlap, measured
screen_plant.py://DD3 DD DSN=CORPUS.TMP,DISP=(MOD,DELETE,DELETE)firesio1 (theDSN;DISP=(does not match io's\bDISP=\bbecause=(has no word boundary) andsync_locks1 (MOD). Same accepted-overlap posture as #2742, and a test pinning it the waytest_jcl_sync_locks_overlaps_io_by_designdoes.Corpus pairing
keyword-rosetta's
c.jclprobe_cleanupstep carries no DD today. One line,//DD3 DD DSN=CORPUS.TMP,DISP=(OLD,DELETE), plantscleanup1 and theDISP=OLDsync_locksplant #2742's PR body already lists as owed — one DD pays both debts. Cost:io+1 corpus-wide (3 → 4 against a median of 3, amber unless main'sDD1gives up itsDISP=SHR). Ledger entryjcl-2610-rebaseline-residual-morphology's cleanup clause flips with the decision either way, asjcl-sync-locks-disp-enq-questiondid.Part of #2669 (rosetta[jcl] was #2581). Siblings from the same pass: jcl
api, jclglobals, jclhigh_risk_execution.Filed together on 2026-09-05: jcl
api#2748 · jclcleanup#2749 · jclglobals#2750 · jclhigh_risk_execution#2751 · cssio#2752 · yamlargs#2753. Corpus-side pairings: squid-protocol/keyword-rosetta#59.