Found by the #2669 pass over every non-green cell in the keyword-rosetta bias report (2026-09-05, engine 0e521e8). jcl/raw_arch_api reads 0 against a corpus median of 3 and is ledgered as api-no-rule-defined ("a JCL job exposes nothing to a caller"). That sentence is true of a JOB and false of a PROC.
The morphology is real
jcl.py L96 wires "api": None. docs/api_rule_contract.md (#2730) defines api as a declaration that makes a named function or type visible outside the file it is declared in, and documents a fallback family for languages with no per-function visibility keyword (dockerfile EXPOSE, makefile .PHONY, cobol ENTRY, matlab's column-0 function). JCL has the construct the fallback family exists for, and it is not approximate: a cataloged procedure is a member whose only purpose is to be invoked from other jobs by name.
//IGYWCLG PROC LNGPRFX='IGY630',LIBPRFX='CEE',SRC=COBOL <- declares the callable surface
//CBL0001 EXEC IGYWCLG <- a call site in another member
On the language-crucible corpus (186 .jcl/.prc/.bms files, crucible 77bc85e):
| shape |
count |
//name PROC declarations |
13 statements in 13 files (6 of them on line 1 and six more within the first seven lines, after the comment banner — the member is the procedure) |
EXEC <procname> / EXEC PROC= call sites |
185 of the corpus's 376 EXEC statements |
EXEC PGM= (a program, not a procedure) |
190 |
Half of every step in the real corpus invokes a surface that some PROC statement declared, and the engine records that surface as 0 everywhere. api feeds _calc_api_exposure, so jcl/risk_api_exposure is today entirely the orphan conversion (5.289, flagged in the bias report as a registry/engine mismatch: every registry input absent, value nonzero).
Candidate shape
# A cataloged or in-stream procedure is JCL's callable surface: `//name PROC`
# declares what `EXEC name` / `EXEC PROC=name` in other members invoke. Name
# optional like the other statement rules (a cataloged PROC statement may be
# unnamed); `PEND` closes an in-stream proc and is not a second declaration.
"api": re.compile(r"^[ \t]*//([A-Za-z0-9_#$@]*)[ \t]+PROC\b", re.M | re.I),
Contract check: corollary 1 (a reference is not a declaration) — EXEC name stays out, it is the call. Corollary 3 (public by default → the declaration is the marker) — a PROC exists to be called, so the statement itself is the marker, the same reading perl's sub and matlab's function got in #2743.
Overlap, measured
tools/screen_plant.py against the live compiled rules: a bare //ROSPROC PROC line fires nothing scored (structural_boundaries only). A PROC statement that carries parameter defaults (//BATCH PROC MEMBER=, 6 of the 13 crucible statements) also matches the args rule's PROC[ \t]+(\S.*) alternative — that is a declaration with its parameter list, the same api+args pair every def f(x) produces, so no new overlap shape.
Corpus pairing (AGENTS.md rule 8 / GATING "Filling a None rule")
keyword-rosetta's jcl shell invokes ROSPROC 13 times and never declares it. A paired plant wraps the existing //STEP2 EXEC PGM=IEFBR14 in //ROSPROC PROC … // PEND in main.jcl: api 1 in main (a file-level surface, the api-non-function-surfaces shape the report already ledgers for dockerfile/yaml/css/yacc), 0 in a/b/c, no other planted count moves. Ledger entry api-no-rule-defined narrows to markdown.
Part of #2669 (rosetta[jcl] was #2581). Siblings filed from the same pass: jcl cleanup, 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/raw_arch_apireads 0 against a corpus median of 3 and is ledgered asapi-no-rule-defined("a JCL job exposes nothing to a caller"). That sentence is true of a JOB and false of a PROC.The morphology is real
jcl.pyL96 wires"api": None.docs/api_rule_contract.md(#2730) definesapias a declaration that makes a named function or type visible outside the file it is declared in, and documents a fallback family for languages with no per-function visibility keyword (dockerfileEXPOSE,makefile.PHONY,cobolENTRY,matlab's column-0function). JCL has the construct the fallback family exists for, and it is not approximate: a cataloged procedure is a member whose only purpose is to be invoked from other jobs by name.On the language-crucible corpus (186
.jcl/.prc/.bmsfiles, crucible77bc85e)://name PROCdeclarationsEXEC <procname>/EXEC PROC=call sitesEXECstatementsEXEC PGM=(a program, not a procedure)Half of every step in the real corpus invokes a surface that some PROC statement declared, and the engine records that surface as 0 everywhere.
apifeeds_calc_api_exposure, sojcl/risk_api_exposureis today entirely the orphan conversion (5.289, flagged in the bias report as a registry/engine mismatch: every registry input absent, value nonzero).Candidate shape
Contract check: corollary 1 (a reference is not a declaration) —
EXEC namestays out, it is the call. Corollary 3 (public by default → the declaration is the marker) — a PROC exists to be called, so the statement itself is the marker, the same readingperl'ssubandmatlab'sfunctiongot in #2743.Overlap, measured
tools/screen_plant.pyagainst the live compiled rules: a bare//ROSPROC PROCline fires nothing scored (structural_boundariesonly). A PROC statement that carries parameter defaults (//BATCH PROC MEMBER=, 6 of the 13 crucible statements) also matches theargsrule'sPROC[ \t]+(\S.*)alternative — that is a declaration with its parameter list, the same api+args pair everydef f(x)produces, so no new overlap shape.Corpus pairing (AGENTS.md rule 8 / GATING "Filling a None rule")
keyword-rosetta's jcl shell invokes
ROSPROC13 times and never declares it. A paired plant wraps the existing//STEP2 EXEC PGM=IEFBR14in//ROSPROC PROC…// PENDinmain.jcl:api1 in main (a file-level surface, theapi-non-function-surfacesshape the report already ledgers for dockerfile/yaml/css/yacc), 0 in a/b/c, no other planted count moves. Ledger entryapi-no-rule-definednarrows to markdown.Part of #2669 (rosetta[jcl] was #2581). Siblings filed from the same pass: jcl
cleanup, 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.