@portll/cobolwork 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -26
- package/STABILITY.md +76 -0
- package/bin/cobolwork.mjs +56 -14
- package/lib/baseline.mjs +9 -0
- package/lib/bms.mjs +21 -7
- package/lib/build.mjs +133 -53
- package/lib/capabilities.mjs +13 -11
- package/lib/cics-commands.mjs +501 -9
- package/lib/compliance.mjs +11 -0
- package/lib/consequence.mjs +17 -0
- package/lib/control-reuse.mjs +113 -0
- package/lib/control-workers.mjs +161 -0
- package/lib/control.mjs +77 -4
- package/lib/dataflow.mjs +402 -152
- package/lib/db2/cursor.mjs +15 -0
- package/lib/db2/read.mjs +170 -0
- package/lib/db2/rules.mjs +77 -0
- package/lib/db2/stmt/alter.mjs +473 -0
- package/lib/db2/stmt/grant.mjs +125 -0
- package/lib/db2/stmt/index.mjs +141 -0
- package/lib/db2/stmt/misc.mjs +325 -0
- package/lib/db2/stmt/routine.mjs +564 -0
- package/lib/db2/stmt/storage.mjs +146 -0
- package/lib/db2/stmt/table.mjs +540 -0
- package/lib/db2/stmt/view.mjs +146 -0
- package/lib/diff.mjs +17 -5
- package/lib/equivalence.mjs +44 -5
- package/lib/evidence/cli.mjs +26 -7
- package/lib/evidence/record.mjs +16 -6
- package/lib/evidence/seal.mjs +17 -0
- package/lib/evidence/store.mjs +44 -15
- package/lib/evidence/timestamp.mjs +83 -0
- package/lib/evidence/verify.mjs +120 -42
- package/lib/exec-reading.mjs +51 -0
- package/lib/execution.mjs +3 -2
- package/lib/explain.mjs +2 -0
- package/lib/exploitability.mjs +11 -2
- package/lib/hlasm/asm/data.mjs +128 -0
- package/lib/hlasm/asm/listing.mjs +43 -0
- package/lib/hlasm/asm/output.mjs +45 -0
- package/lib/hlasm/asm/sections.mjs +161 -0
- package/lib/hlasm/asm/symbols.mjs +78 -0
- package/lib/hlasm/exec.mjs +27 -0
- package/lib/hlasm/expr.mjs +167 -0
- package/lib/hlasm/instr.mjs +73 -0
- package/lib/hlasm/locate.mjs +333 -0
- package/lib/hlasm/macro/authorization.mjs +146 -0
- package/lib/hlasm/macro/datasets.mjs +107 -0
- package/lib/hlasm/macro/io.mjs +162 -0
- package/lib/hlasm/macro/le.mjs +53 -0
- package/lib/hlasm/macro/linkage.mjs +143 -0
- package/lib/hlasm/macro/operator.mjs +77 -0
- package/lib/hlasm/macro/program.mjs +184 -0
- package/lib/hlasm/macro/recovery.mjs +85 -0
- package/lib/hlasm/macro/storage.mjs +167 -0
- package/lib/hlasm/macro/structured.mjs +131 -0
- package/lib/hlasm/model.mjs +97 -0
- package/lib/hlasm/mvs38.mjs +47 -0
- package/lib/hlasm/operands.mjs +59 -0
- package/lib/hlasm/optable.mjs +61 -0
- package/lib/hlasm/read.mjs +130 -0
- package/lib/hlasm.mjs +44 -7
- package/lib/ims/dli.mjs +37 -0
- package/lib/ims/macro/dbd.mjs +299 -0
- package/lib/ims/macro/psb.mjs +286 -0
- package/lib/ims/model.mjs +149 -0
- package/lib/ims/operands.mjs +23 -0
- package/lib/ims/read.mjs +37 -0
- package/lib/ims/rules.mjs +135 -0
- package/lib/inventory.mjs +10 -7
- package/lib/ironwork-ids.mjs +24 -0
- package/lib/ironwork.mjs +20 -13
- package/lib/kernel/pds-archive.mjs +256 -0
- package/lib/kernel/registry.mjs +32 -23
- package/lib/kernel/shared-pass.mjs +163 -0
- package/lib/kernel/source-tree.mjs +104 -36
- package/lib/kernel/version-key.mjs +17 -0
- package/lib/layout.mjs +26 -31
- package/lib/options.mjs +26 -4
- package/lib/parser.mjs +136 -17
- package/lib/pli/cursor.mjs +15 -0
- package/lib/pli/expr.mjs +101 -0
- package/lib/pli/include.mjs +82 -0
- package/lib/pli/layout.mjs +125 -0
- package/lib/pli/lex.mjs +198 -0
- package/lib/pli/program.mjs +280 -0
- package/lib/pli/rules/based.mjs +95 -0
- package/lib/pli/rules/conditions.mjs +68 -0
- package/lib/pli/rules/entry.mjs +130 -0
- package/lib/pli/rules/index.mjs +24 -0
- package/lib/pli/rules/preprocessor.mjs +55 -0
- package/lib/pli/statements.mjs +130 -0
- package/lib/pli/stmt/alloc.mjs +45 -0
- package/lib/pli/stmt/assignment.mjs +56 -0
- package/lib/pli/stmt/call.mjs +104 -0
- package/lib/pli/stmt/conditions.mjs +94 -0
- package/lib/pli/stmt/control.mjs +219 -0
- package/lib/pli/stmt/declare.mjs +149 -0
- package/lib/pli/stmt/exec.mjs +55 -0
- package/lib/pli/stmt/io.mjs +239 -0
- package/lib/pli/stmt/misc.mjs +4 -0
- package/lib/pli/stmt/preprocessor.mjs +242 -0
- package/lib/pli/stmt/procedure.mjs +258 -0
- package/lib/pli/stmt/stream.mjs +283 -0
- package/lib/pli/storage.mjs +129 -0
- package/lib/policy.mjs +6 -0
- package/lib/precompile-check.mjs +124 -0
- package/lib/precompile-cics.mjs +8 -4
- package/lib/reach.mjs +11 -2
- package/lib/revision.json +1 -1
- package/lib/sarif.mjs +44 -3
- package/lib/scan.mjs +28 -12
- package/lib/sets/abend.mjs +82 -8
- package/lib/sets/build.mjs +43 -37
- package/lib/sets/cics.mjs +20 -38
- package/lib/sets/compile.mjs +46 -18
- package/lib/sets/copybook.mjs +5 -3
- package/lib/sets/crypto.mjs +5 -3
- package/lib/sets/ddl.mjs +36 -0
- package/lib/sets/flow.mjs +32 -8
- package/lib/sets/hidden.mjs +5 -3
- package/lib/sets/hlasm.mjs +124 -8
- package/lib/sets/ims.mjs +139 -0
- package/lib/sets/log.mjs +11 -9
- package/lib/sets/opaque.mjs +27 -7
- package/lib/sets/pli.mjs +40 -0
- package/lib/sets/priv.mjs +5 -3
- package/lib/sets/recon.mjs +5 -3
- package/lib/sets/secrets.mjs +112 -0
- package/lib/sets/semantics.mjs +8 -3
- package/lib/sets/web.mjs +39 -23
- package/lib/site.mjs +10 -0
- package/lib/sources.mjs +80 -20
- package/lib/statement-cursor.mjs +67 -0
- package/lib/verify.mjs +3 -2
- package/lib/version.mjs +6 -0
- package/package.json +3 -2
- package/rules/compliance-cobit2019.json +520 -5
- package/rules/compliance-dora.json +509 -5
- package/rules/compliance-ffiec.json +505 -1
- package/rules/compliance-nist80053.json +557 -1
- package/rules/gitleaks-mainframe.toml +46 -15
- package/rules/hlasm-instructions.json +2396 -0
- package/rules/hlasm-optables.json +8024 -0
- package/schema/cobolwork-baseline.schema.json +36 -0
- package/schema/cobolwork-build-provenance.schema.json +187 -0
- package/schema/cobolwork-build.schema.json +382 -0
- package/schema/cobolwork-capabilities.schema.json +239 -0
- package/schema/cobolwork-diff.schema.json +217 -0
- package/schema/cobolwork-evidence.schema.json +161 -0
- package/schema/cobolwork-execution.schema.json +53 -0
- package/schema/cobolwork-explain.schema.json +360 -0
- package/schema/cobolwork-finding.schema.json +465 -0
- package/schema/cobolwork-flow.schema.json +465 -0
- package/schema/cobolwork-gate.schema.json +211 -0
- package/schema/cobolwork-inventory.schema.json +206 -0
- package/schema/cobolwork-parse.schema.json +105 -0
- package/schema/cobolwork-reach.schema.json +74 -0
- package/schema/cobolwork-report.schema.json +559 -0
- package/schema/cobolwork-witness.schema.json +107 -0
- package/schema/cobolwork.baseline.schema.json +101 -0
- package/schema/cobolwork.policy.schema.json +7 -0
- package/schema/cobolwork.site.schema.json +116 -0
package/lib/build.mjs
CHANGED
|
@@ -6,25 +6,29 @@ import { checkEquivalence } from './equivalence.mjs';
|
|
|
6
6
|
import { optionChanges } from './option-diff.mjs';
|
|
7
7
|
import { createHash } from 'node:crypto';
|
|
8
8
|
import { spawnSync } from 'node:child_process';
|
|
9
|
-
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
10
|
-
import {
|
|
9
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
12
|
+
import { parseAllowedSigners } from './evidence/sshsig.mjs';
|
|
11
13
|
import { scanAll } from './scan.mjs';
|
|
12
14
|
import { withRefs, configurationChanges, WORKING_TREE } from './diff.mjs';
|
|
13
15
|
import { pairLists, findExecutable } from './gate.mjs';
|
|
14
16
|
import { pairingKeys } from './kernel/identity.mjs';
|
|
15
|
-
import { directoryTree } from './kernel/source-tree.mjs';
|
|
17
|
+
import { directoryTree, pdsExportTree, resolvesInside } from './kernel/source-tree.mjs';
|
|
16
18
|
import { printable } from './kernel/printable.mjs';
|
|
17
19
|
import { ALL_RULES } from './kernel/registry.mjs';
|
|
18
20
|
import { loadBaseline, applyBaseline, SUPPRESSING } from './baseline.mjs';
|
|
19
21
|
import { loadSite, SITE_FILE } from './site.mjs';
|
|
20
|
-
import { isProgram, isJcl, isSource,
|
|
21
|
-
import {
|
|
22
|
+
import { isProgram, isJcl, isSource, relPath } from './sources.mjs';
|
|
23
|
+
import { parseJcl } from './jcl.mjs';
|
|
22
24
|
import { POLICY_FILE, treePolicy, floorPolicy, combinePolicies, canonical, policyHash } from './policy.mjs';
|
|
23
25
|
import { classesOf, tierOf, TIERS, TIER_RANK } from './consequence.mjs';
|
|
24
|
-
import { optionCards, enterpriseChecks, compilerChecks, scriptedChecks, compilerInvocations, compilerTasks, compileStepOptions, compilerOf } from './options.mjs';
|
|
26
|
+
import { optionCards, enterpriseChecks, compilerChecks, scriptedChecks, compilerInvocations, compilerTasks, compileStepOptions, compilerOf, olderThan } from './options.mjs';
|
|
27
|
+
import { isBuildFile, pinsIn } from './sets/build.mjs';
|
|
25
28
|
import { FLOW_MODEL, TOOL_VERSION } from './version.mjs';
|
|
26
29
|
import { checkWithIronwork, ironworkVerdict, ironworkReasons, ironworkVersion, listed } from './ironwork.mjs';
|
|
27
30
|
import { buildFileIndex } from './parser.mjs';
|
|
31
|
+
import { precompileCheck } from './precompile-check.mjs';
|
|
28
32
|
|
|
29
33
|
export const BUILD_SCHEMA_VERSION = 1;
|
|
30
34
|
export const BUILD_EXIT = { pass: 0, fail: 1, undecided: 3, compilerFailed: 4 };
|
|
@@ -62,8 +66,11 @@ function introducedIn(before, after, baseRoot, headRoot) {
|
|
|
62
66
|
// Why a finding blocks, or null. `renewed` is a finding whose waiver expired or ran past maxDays: in
|
|
63
67
|
// ratchet mode it is judged as new, because the promise that carried it has lapsed.
|
|
64
68
|
export function blockingReason(f, { policy, ratchet, introduced, renewed }) {
|
|
69
|
+
if (f.abend?.inputFrom === 'interface' && policy.interface !== 'tier') return null;
|
|
65
70
|
const setting = policy.rules[f.rule];
|
|
66
71
|
if (setting === 'warn') return null;
|
|
72
|
+
// A rule whose firing rate the corpus has not recorded blocks only where the policy names it.
|
|
73
|
+
if (ALL_RULES[f.rule]?.measured === false) return setting === 'block' ? 'rule' : null;
|
|
67
74
|
const tier = tierOf(f);
|
|
68
75
|
if (ratchet && TIER_RANK[tier] >= TIER_RANK[policy.always]) return 'tier';
|
|
69
76
|
if (ratchet && !introduced.has(f) && !renewed.has(f.fingerprint)) return null;
|
|
@@ -82,12 +89,12 @@ const MAX_BARE_SCRIPT_BYTES = 64 << 10;
|
|
|
82
89
|
const kindOf = (rel) => (/(^|\/)(gnu)?makefile$|\.mk$/i.test(rel) ? 'make' : /(^|\/)dockerfile$/i.test(rel) ? 'docker'
|
|
83
90
|
: /\.(bat|cmd)$/i.test(rel) ? 'batch' : /\.ps1$/i.test(rel) ? 'powershell' : /\.ya?ml$/i.test(rel) ? 'yaml' : 'sh');
|
|
84
91
|
|
|
85
|
-
function scriptInvocations(file, rel) {
|
|
86
|
-
if (EDITOR_TASKS.test(rel)) return compilerTasks(
|
|
87
|
-
const size = statSync(file).size;
|
|
88
|
-
if (BUILD_SCRIPT.test(rel) && size <= MAX_SCRIPT_BYTES) return compilerInvocations(
|
|
92
|
+
function scriptInvocations(tree, file, rel) {
|
|
93
|
+
if (EDITOR_TASKS.test(rel)) return compilerTasks(tree.text(file).text);
|
|
94
|
+
const size = tree.kind === 'directory' ? statSync(file).size : tree.bytes(file).length;
|
|
95
|
+
if (BUILD_SCRIPT.test(rel) && size <= MAX_SCRIPT_BYTES) return compilerInvocations(tree.text(file).text, { kind: kindOf(rel) });
|
|
89
96
|
if (!extname(file) && size <= MAX_BARE_SCRIPT_BYTES) {
|
|
90
|
-
const bytes =
|
|
97
|
+
const bytes = tree.bytes(file);
|
|
91
98
|
return bytes.includes(0) ? [] : compilerInvocations(bytes.toString('latin1'), { kind: 'sh' });
|
|
92
99
|
}
|
|
93
100
|
return [];
|
|
@@ -95,14 +102,14 @@ function scriptInvocations(file, rel) {
|
|
|
95
102
|
|
|
96
103
|
// How the repository says it compiles its programs: the cobc and gcobol commands its build scripts
|
|
97
104
|
// run, and the Enterprise COBOL compile steps its JCL holds.
|
|
98
|
-
function declaredCompiles(
|
|
105
|
+
function declaredCompiles(tree, files) {
|
|
99
106
|
const invocations = [];
|
|
100
107
|
const parsedJcl = [];
|
|
101
108
|
for (const f of files) {
|
|
102
|
-
const rel =
|
|
109
|
+
const rel = tree.rel(f);
|
|
103
110
|
try {
|
|
104
|
-
if (isJcl(f)) parsedJcl.push({ ...
|
|
105
|
-
else if (!isProgram(f)) for (const inv of scriptInvocations(f, rel)) invocations.push({ ...inv, file: rel });
|
|
111
|
+
if (isJcl(f)) parsedJcl.push({ ...parseJcl(tree.text(f).text, f), file: rel });
|
|
112
|
+
else if (!isProgram(f)) for (const inv of scriptInvocations(tree, f, rel)) invocations.push({ ...inv, file: rel });
|
|
106
113
|
} catch { /* unreadable: the scan counts it, and it declares nothing here */ }
|
|
107
114
|
}
|
|
108
115
|
const steps = new Map();
|
|
@@ -115,27 +122,41 @@ function declaredCompiles(root, files) {
|
|
|
115
122
|
|
|
116
123
|
const memberName = (file) => basename(file).replace(/\.[^.]*$/, '').toUpperCase();
|
|
117
124
|
|
|
125
|
+
// The GnuCOBOL version the repository's build files pin, the oldest where they pin several.
|
|
126
|
+
function pinnedGnucobol(tree, files) {
|
|
127
|
+
let oldest = null;
|
|
128
|
+
for (const file of files.filter(isBuildFile)) {
|
|
129
|
+
let text;
|
|
130
|
+
try { text = tree.text(file).text; } catch { continue; }
|
|
131
|
+
for (const p of pinsIn(text)) {
|
|
132
|
+
if (p.product === 'gnucobol' && (!oldest || olderThan(p.version, oldest.version))) oldest = { version: p.version, where: tree.rel(file) };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return oldest;
|
|
136
|
+
}
|
|
137
|
+
|
|
118
138
|
// The options every program in the tree is compiled with, held to the policy. A program's options
|
|
119
139
|
// come from the estate's declared defaults, the JCL step that compiles it and its own option cards;
|
|
120
140
|
// where it has none of those, from the cobc and gcobol commands the repository's build scripts run.
|
|
121
141
|
// `ok` is false where a required check is not generated and null where no one can tell; `forbidden`
|
|
122
142
|
// counts options the policy's forbid list names. `generated` maps each program and build script to
|
|
123
143
|
// the checks it generates, for telling a change that removes one.
|
|
124
|
-
function optionsCheck({ root, allow, site, policy, compiler }) {
|
|
144
|
+
function optionsCheck({ root, tree: given = null, allow, site, policy, compiler }) {
|
|
125
145
|
const required = policy.checks;
|
|
126
146
|
const kind = compiler && compilerOf(compiler[0]);
|
|
127
147
|
if (compiler && !kind) {
|
|
128
148
|
return { ok: null, forbidden: 0, reasons: [`the gate reads the options of cobc, of gcobol and of Enterprise COBOL option cards; for ${printable(compiler[0], 60)} it cannot tell what reaches the compiler`], add: [], generated: new Map() };
|
|
129
149
|
}
|
|
150
|
+
const tree = given || directoryTree(root);
|
|
151
|
+
const files = [...new Set(tree.list())].filter((p) => !allow || allow.has(p)).sort();
|
|
152
|
+
const pinned = pinnedGnucobol(tree, files);
|
|
130
153
|
if (compiler) {
|
|
131
|
-
const r = compilerChecks(kind, compiler.slice(1), { required, forbid: policy.forbid[kind] });
|
|
154
|
+
const r = compilerChecks(kind, compiler.slice(1), { required, forbid: policy.forbid[kind], pinned });
|
|
132
155
|
const missing = r.checks.filter((c) => c.ok === false).map((c) => c.why);
|
|
133
156
|
return { ok: missing.length || r.forbidden.length ? false : true, forbidden: r.forbidden.length, reasons: [...r.forbidden, ...missing], add: r.forbidden.length ? [] : r.add, compiler: kind, generated: new Map() };
|
|
134
157
|
}
|
|
135
|
-
const tree = directoryTree(root);
|
|
136
|
-
const files = [...new Set(tree.list())].filter((p) => !allow || allow.has(p)).sort();
|
|
137
158
|
const programs = files.filter(isProgram);
|
|
138
|
-
const declared = declaredCompiles(
|
|
159
|
+
const declared = declaredCompiles(tree, files);
|
|
139
160
|
let ok = true;
|
|
140
161
|
let forbidden = 0;
|
|
141
162
|
const reasons = [];
|
|
@@ -150,7 +171,7 @@ function optionsCheck({ root, allow, site, policy, compiler }) {
|
|
|
150
171
|
};
|
|
151
172
|
|
|
152
173
|
for (const inv of declared.invocations) {
|
|
153
|
-
const r = scriptedChecks(inv.compiler, inv.args, { required, forbid: policy.forbid[inv.compiler] });
|
|
174
|
+
const r = scriptedChecks(inv.compiler, inv.args, { required, forbid: policy.forbid[inv.compiler], pinned });
|
|
154
175
|
record(inv.file, r.checks);
|
|
155
176
|
forbidden += r.forbidden.length;
|
|
156
177
|
for (const why of [...r.checks.filter((c) => c.ok === false).map((c) => c.why), ...r.forbidden]) fail(`${inv.file}:${inv.line}: ${why}`);
|
|
@@ -158,7 +179,7 @@ function optionsCheck({ root, allow, site, policy, compiler }) {
|
|
|
158
179
|
let unknown = 0;
|
|
159
180
|
for (const file of programs) {
|
|
160
181
|
let text;
|
|
161
|
-
try { text =
|
|
182
|
+
try { text = tree.text(file).text; } catch { open(); reasons.push(`${printable(tree.rel(file), 120)} could not be read, so its options are not known`); continue; }
|
|
162
183
|
const cards = optionCards(text);
|
|
163
184
|
const steps = declared.steps.get(memberName(file)) || [];
|
|
164
185
|
const siteOptions = site.compilerOptions || [];
|
|
@@ -167,10 +188,10 @@ function optionsCheck({ root, allow, site, policy, compiler }) {
|
|
|
167
188
|
continue;
|
|
168
189
|
}
|
|
169
190
|
const r = enterpriseChecks({ required, site: siteOptions, steps, cards, forbid: policy.forbid.enterprise });
|
|
170
|
-
record(
|
|
191
|
+
record(tree.rel(file), r.checks);
|
|
171
192
|
forbidden += r.forbidden.length;
|
|
172
193
|
const failed = r.checks.filter((c) => c.ok === false).map((c) => c.why);
|
|
173
|
-
for (const why of [...failed, ...r.forbidden]) fail(`${
|
|
194
|
+
for (const why of [...failed, ...r.forbidden]) fail(`${tree.rel(file)}: ${why}`);
|
|
174
195
|
if (!failed.length && !r.forbidden.length && r.checks.some((c) => c.ok === null)) { unknown++; open(); }
|
|
175
196
|
}
|
|
176
197
|
if (unknown) reasons.push(`${unknown} program(s) have no option this gate can read for ${required.join(', ')}: no compile step or build script names them; declare the estate's defaults as compilerOptions in cobolwork.site.json`);
|
|
@@ -193,20 +214,41 @@ function optionsRemoved(before, after) {
|
|
|
193
214
|
return removed;
|
|
194
215
|
}
|
|
195
216
|
|
|
196
|
-
// The files a scan of `root
|
|
197
|
-
function sourceHashes(root, allow) {
|
|
198
|
-
const tree = directoryTree(root);
|
|
217
|
+
// The files a scan of `root`, or of a PDS export's tree, reads, hashed, for the provenance record.
|
|
218
|
+
function sourceHashes(root, allow, given = null) {
|
|
219
|
+
const tree = given || directoryTree(root);
|
|
199
220
|
return [...new Set(tree.list())].filter((p) => isSource(p) && (!allow || allow.has(p))).sort()
|
|
200
|
-
.map((p) => ({ path:
|
|
221
|
+
.map((p) => ({ path: tree.rel(p), sha256: sha256(tree.bytes(p)) }))
|
|
201
222
|
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
202
223
|
}
|
|
203
224
|
|
|
225
|
+
// A PDS export's members written out as DATA.SET/MEMBER for ironwork, which reads files: the
|
|
226
|
+
// programs to check, and each copy library's directory. `fn` gets the layout's root and those.
|
|
227
|
+
function withLayout(tree, allow, fn) {
|
|
228
|
+
const root = mkdtempSync(join(tmpdir(), 'cobolwork-pds-layout-'));
|
|
229
|
+
try {
|
|
230
|
+
const programs = [];
|
|
231
|
+
for (const p of tree.list()) {
|
|
232
|
+
const file = join(root, ...tree.rel(p).split('/'));
|
|
233
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
234
|
+
writeFileSync(file, tree.text(p).text, 'latin1');
|
|
235
|
+
if (isProgram(p) && (!allow || allow.has(p))) programs.push(file);
|
|
236
|
+
}
|
|
237
|
+
return fn(root, programs.sort(), tree.index.copyDirs.map((d) => join(root, ...tree.rel(d).split('/'))));
|
|
238
|
+
} finally { rmSync(root, { recursive: true, force: true }); }
|
|
239
|
+
}
|
|
240
|
+
|
|
204
241
|
// ironwork checks the head tree, which in ratchet mode exists only while judge runs, so it runs
|
|
205
242
|
// here rather than after the verdict the way a caller's compiler does.
|
|
206
|
-
function ironworkCompile(path, { root, allow, copylibs, env }) {
|
|
207
|
-
|
|
243
|
+
function ironworkCompile(path, { root, tree = null, allow, copylibs, env }) {
|
|
244
|
+
if (tree) return withLayout(tree, allow, (layout, programs, copyDirs) => ironworkChecked(path, { root: layout, programs, copyDirs, copylibs, env }));
|
|
245
|
+
return ironworkChecked(path, { root, allow, copylibs, env });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function ironworkChecked(path, { root, allow = null, programs = null, copyDirs = null, copylibs, env }) {
|
|
249
|
+
const result = checkWithIronwork(path, root, { allow, programs, copyDirs, copylibs, env });
|
|
208
250
|
const check = ironworkVerdict(result);
|
|
209
|
-
const libraries = [...buildFileIndex(root).copyDirs.map((d) => relPath(root, d) || '.'), ...copylibs];
|
|
251
|
+
const libraries = [...(copyDirs || buildFileIndex(root).copyDirs).map((d) => relPath(root, d) || '.'), ...copylibs];
|
|
210
252
|
return {
|
|
211
253
|
check,
|
|
212
254
|
reasons: ironworkReasons(result),
|
|
@@ -219,16 +261,27 @@ function ironworkCompile(path, { root, allow, copylibs, env }) {
|
|
|
219
261
|
};
|
|
220
262
|
}
|
|
221
263
|
|
|
222
|
-
|
|
264
|
+
// The members of a PDS export whose files `allow` holds: an archive's members by the archive's file.
|
|
265
|
+
function membersAllowed(tree, allow) {
|
|
266
|
+
return new Set(tree.list().filter((p) => allow.has(resolve(tree.dir, tree.origin(p).replace(/\([^)]*\)$/, '')))));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function judge({ baseDir, headDir, allow: given, policyRoot, floor, noBaseline, compiler, compilerPath, ironworkPath, env, now, advisoryFeeds, copylibs, equivalenceFiles = [], allowed = null, refs = null, pdsExport = false }) {
|
|
223
270
|
const repoPolicy = treePolicy(policyRoot);
|
|
224
271
|
if (repoPolicy && repoPolicy.problems.length) throw refusal(`the repository policy does not validate: ${repoPolicy.problems.join('; ')}`);
|
|
225
272
|
const { policy, setBy, ignored } = combinePolicies({ floor: floor ? floor.raw : null, repository: repoPolicy ? repoPolicy.raw : null });
|
|
226
273
|
const ratchet = baseDir !== null;
|
|
274
|
+
// A PDS export is judged as its members, each side read as scan --pds-export reads it; the policy,
|
|
275
|
+
// site file and baseline stay files beside the members.
|
|
276
|
+
const trees = pdsExport ? { base: ratchet ? pdsExportTree(baseDir, { systemDirs: copylibs }) : null, head: pdsExportTree(headDir, { systemDirs: copylibs }) } : null;
|
|
277
|
+
const allow = trees && given ? membersAllowed(trees.head, given) : given;
|
|
227
278
|
// In ratchet mode the change is judged by its base's site file, not by one it wrote itself.
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
const
|
|
279
|
+
const siteFile = ratchet ? join(baseDir, SITE_FILE) : trees && existsSync(join(headDir, SITE_FILE)) ? join(headDir, SITE_FILE) : null;
|
|
280
|
+
const scanOpts = { ...(siteFile ? { site: siteFile } : {}), ...(advisoryFeeds ? { advisoryFeeds } : {}), systemDirs: copylibs };
|
|
281
|
+
const scanOf = (dir, tree, extra = {}) => (tree ? scanAll(tree.root, { ...scanOpts, ...extra, tree, feedRoot: dir }) : scanAll(dir, { ...scanOpts, ...extra }));
|
|
282
|
+
const before = ratchet ? scanOf(baseDir, trees?.base) : null;
|
|
283
|
+
const after = scanOf(headDir, trees?.head, { allow });
|
|
284
|
+
const introduced = ratchet ? introducedIn(before, after, trees ? trees.base.root : baseDir, trees ? trees.head.root : headDir) : new Set();
|
|
232
285
|
|
|
233
286
|
const bounded = boundBaseline(noBaseline ? null : loadBaseline(policyRoot), policy.waivers.maxDays);
|
|
234
287
|
applyBaseline(after, bounded.baseline, { now });
|
|
@@ -257,20 +310,22 @@ function judge({ baseDir, headDir, allow, policyRoot, floor, noBaseline, compile
|
|
|
257
310
|
reasons.push(`coverage is incomplete${sets.length ? ` in ${sets.join(', ')}` : ''}${policy.coverage === 'block' ? ', and the policy says block, so what was not read cannot be told from what is clean' : '; the policy says warn'}`);
|
|
258
311
|
}
|
|
259
312
|
const site = loadSite(policyRoot);
|
|
260
|
-
const options = optionsCheck({ root: headDir, allow, site, policy, compiler });
|
|
313
|
+
const options = optionsCheck({ root: headDir, tree: trees?.head, allow, site, policy, compiler });
|
|
261
314
|
reasons.push(...options.reasons);
|
|
262
315
|
// A missing run-time check is a weaker build, not a defect: warn passes it relaxed. What the
|
|
263
316
|
// policy forbids by name, and a check the change itself removed, still fail.
|
|
264
|
-
const removed = ratchet && !compiler ? optionsRemoved(optionsCheck({ root: baseDir, allow: null, site, policy, compiler }), options) : [];
|
|
317
|
+
const removed = ratchet && !compiler ? optionsRemoved(optionsCheck({ root: baseDir, tree: trees?.base, allow: null, site, policy, compiler }), options) : [];
|
|
265
318
|
reasons.push(...removed.slice(0, MAX_PROGRAM_REASONS));
|
|
266
|
-
const optionsChanged = ratchet ? programOptionChanges(baseDir, headDir, allow) : [];
|
|
319
|
+
const optionsChanged = ratchet ? programOptionChanges(baseDir, headDir, allow, trees) : [];
|
|
267
320
|
for (const c of optionsChanged.slice(0, MAX_PROGRAM_REASONS)) reasons.push(`${c.path}:${c.line} changes ${c.option} from ${c.base} to ${c.head}, which changes what the program's statements do without changing a statement`);
|
|
268
|
-
const
|
|
321
|
+
const wantsEquivalence = ratchet && (equivalenceFiles.length || policy.requireEquivalence !== 'never');
|
|
322
|
+
const equivalence = wantsEquivalence && !trees
|
|
269
323
|
? checkEquivalence({ baseDir, headDir, allow, files: equivalenceFiles, allowed, mode: policy.requireEquivalence, ...(refs || {}), systemDirs: copylibs, env })
|
|
270
|
-
: null;
|
|
324
|
+
: wantsEquivalence ? { problems: [], required: null, programs: [], mode: policy.requireEquivalence, notRun: 'equivalence is not run over a PDS export' } : null;
|
|
271
325
|
if (equivalence) {
|
|
272
326
|
reasons.push(...equivalence.problems);
|
|
273
|
-
if (equivalence.
|
|
327
|
+
if (equivalence.notRun) reasons.push(`${equivalence.notRun}, and the policy or --equivalence asks for it`);
|
|
328
|
+
else if (equivalence.required === null) reasons.push('the policy requires equivalence for machine-authored changes, and whether this one is could not be read from the commits');
|
|
274
329
|
if (equivalence.required) for (const p of equivalence.programs.filter((x) => !x.ok).slice(0, MAX_PROGRAM_REASONS)) reasons.push(`${p.path}: ${p.because}, and the policy requires equivalence${equivalence.mode === 'machineAuthored' ? ' for a machine-authored change' : ''}`);
|
|
275
330
|
}
|
|
276
331
|
const relaxOptions = policy.options === 'warn' && options.ok !== true && !options.forbidden && !removed.length;
|
|
@@ -288,7 +343,7 @@ function judge({ baseDir, headDir, allow, policyRoot, floor, noBaseline, compile
|
|
|
288
343
|
const verdict = decided.includes(false) ? 'fail' : decided.includes(null) ? 'undecided' : 'pass';
|
|
289
344
|
let compiled = null;
|
|
290
345
|
if (ironworkPath && verdict === 'pass') {
|
|
291
|
-
const iw = ironworkCompile(ironworkPath, { root: headDir, allow, copylibs, env });
|
|
346
|
+
const iw = ironworkCompile(ironworkPath, { root: headDir, tree: trees?.head, allow, copylibs, env });
|
|
292
347
|
compiled = iw.compiled;
|
|
293
348
|
checks.compile = iw.check;
|
|
294
349
|
reasons.push(...iw.reasons);
|
|
@@ -333,23 +388,26 @@ function judge({ baseDir, headDir, allow, policyRoot, floor, noBaseline, compile
|
|
|
333
388
|
byTier,
|
|
334
389
|
coverageIncomplete: incomplete,
|
|
335
390
|
...(after.summary.baseline && after.summary.baseline.problems ? { baselineProblems: after.summary.baseline.problems } : {}),
|
|
391
|
+
...(trees ? { pdsExport: { ...(trees.base ? { base: trees.base.export } : {}), head: trees.head.export } } : {}),
|
|
336
392
|
},
|
|
337
393
|
};
|
|
338
|
-
const hashes = sourceHashes(headDir, allow);
|
|
394
|
+
const hashes = sourceHashes(headDir, allow, trees?.head);
|
|
339
395
|
return { doc, report: after, policy, setBy, hashes, add: options.add, compilerPath };
|
|
340
396
|
}
|
|
341
397
|
|
|
342
398
|
// The watched options in force changed by the change, per program present on both sides.
|
|
343
|
-
function programOptionChanges(baseDir, headDir, allow) {
|
|
399
|
+
function programOptionChanges(baseDir, headDir, allow, trees = null) {
|
|
344
400
|
const out = [];
|
|
345
|
-
const
|
|
401
|
+
const headTree = trees ? trees.head : directoryTree(headDir);
|
|
402
|
+
const baseTree = trees ? trees.base : directoryTree(baseDir);
|
|
403
|
+
const files = headTree.list().filter((p) => isProgram(p) && (!allow || allow.has(p))).sort();
|
|
346
404
|
for (const head of files) {
|
|
347
|
-
const path =
|
|
348
|
-
const base =
|
|
349
|
-
if (!existsSync(base)) continue;
|
|
405
|
+
const path = headTree.rel(head);
|
|
406
|
+
const base = resolve(baseTree.root, ...path.split('/'));
|
|
407
|
+
if (!(trees ? baseTree.contains(base) : existsSync(base))) continue;
|
|
350
408
|
let before;
|
|
351
409
|
let after;
|
|
352
|
-
try { before =
|
|
410
|
+
try { before = baseTree.text(base).text; after = headTree.text(head).text; } catch { continue; }
|
|
353
411
|
for (const c of optionChanges(before, after)) out.push({ path, ...c });
|
|
354
412
|
}
|
|
355
413
|
return out;
|
|
@@ -364,10 +422,19 @@ function policyChanged(baseDir, headDir) {
|
|
|
364
422
|
|
|
365
423
|
// Runs the gate over `repo`. `compiler` is the argument vector after `--`, or null. Returns
|
|
366
424
|
// { doc, report, provenance, exit }; throws with code EBUILD when it could not run.
|
|
367
|
-
export function build(repo, { base = null, head = null, policy = null, noBaseline = false, compiler = null, ironwork = null, advisoryFeeds = null, copylibs = [], equivalence = [], allowed = null, now = new Date().toISOString(), env = process.env } = {}) {
|
|
425
|
+
export function build(repo, { base = null, head = null, policy = null, noBaseline = false, compiler = null, ironwork = null, advisoryFeeds = null, copylibs = [], equivalence = [], allowed: given = null, allowedSigners = null, now = new Date().toISOString(), env = process.env, pdsExport = false, precompile = false } = {}) {
|
|
368
426
|
const floor = policy ? floorPolicy(policy, repo) : null;
|
|
369
427
|
if (floor && floor.problems.length) throw refusal(floor.problems.join('; '));
|
|
428
|
+
let allowed = given;
|
|
429
|
+
if (allowedSigners) {
|
|
430
|
+
const path = resolve(allowedSigners);
|
|
431
|
+
if (resolvesInside(repo, path)) throw refusal(`--allowed-signers ${path} is inside the repository; signers the change can edit vouch for nothing`);
|
|
432
|
+
allowed = parseAllowedSigners(readFileSync(path, 'utf8'));
|
|
433
|
+
}
|
|
370
434
|
if (compiler && ironwork) throw refusal('one compiler per build: -- names one and --ironwork another');
|
|
435
|
+
if (precompile && !compiler) throw refusal('--precompile translates the programs a compiler command names, so it needs -- and the compiler');
|
|
436
|
+
if (precompile && !compilerOf(compiler[0])) throw refusal(`--precompile checks a translation with cobc or gcobol -fsyntax-only; for ${printable(compiler[0], 60)} it cannot`);
|
|
437
|
+
if (pdsExport && compiler) throw refusal('a PDS export is checked with --ironwork; the compiler after -- reads the files of a repository, not the members of an export');
|
|
371
438
|
let compilerPath = null;
|
|
372
439
|
if (compiler) {
|
|
373
440
|
if (!compiler.length) throw refusal('nothing follows --, so there is no compiler to run');
|
|
@@ -381,7 +448,7 @@ export function build(repo, { base = null, head = null, policy = null, noBaselin
|
|
|
381
448
|
if (!found.path) throw refusal(`--ironwork: ${found.why}`);
|
|
382
449
|
ironworkPath = found.path;
|
|
383
450
|
}
|
|
384
|
-
const run = (baseDir, headDir, allow, policyRoot) => judge({ baseDir, headDir, allow, policyRoot, floor, noBaseline, compiler, compilerPath, ironworkPath, env, now, advisoryFeeds, copylibs, equivalenceFiles: equivalence, allowed, refs: base ? { repo, base, head: head || 'HEAD' } : null });
|
|
451
|
+
const run = (baseDir, headDir, allow, policyRoot) => judge({ baseDir, headDir, allow, policyRoot, floor, noBaseline, compiler, compilerPath, ironworkPath, env, now, advisoryFeeds, copylibs, equivalenceFiles: equivalence, allowed, refs: base ? { repo, base, head: head || 'HEAD' } : null, pdsExport });
|
|
385
452
|
const judged = base
|
|
386
453
|
? withRefs(repo, base, head, {}, (baseDir, headDir, scoped) => {
|
|
387
454
|
const r = run(baseDir, headDir, scoped.allow || null, baseDir);
|
|
@@ -399,7 +466,20 @@ export function build(repo, { base = null, head = null, policy = null, noBaselin
|
|
|
399
466
|
if (doc.checks.compile === false) exit = BUILD_EXIT.compilerFailed;
|
|
400
467
|
else if (doc.checks.compile === null && !doc.relaxed.includes('compile')) exit = BUILD_EXIT.undecided;
|
|
401
468
|
}
|
|
402
|
-
|
|
469
|
+
// A program the compiler refuses once its EXEC SQL and CICS are translated stops the build before
|
|
470
|
+
// the caller's compiler runs, as a fail stops it.
|
|
471
|
+
if (precompile && doc.verdict === 'pass') {
|
|
472
|
+
const checked = precompileCheck({ compilerPath, argv: compiler.slice(1), repo, copylibs, env });
|
|
473
|
+
doc.precompiled = checked;
|
|
474
|
+
const refused = checked.programs.filter((p) => !p.ok);
|
|
475
|
+
doc.checks.precompile = refused.length === 0;
|
|
476
|
+
if (refused.length) {
|
|
477
|
+
exit = BUILD_EXIT.compilerFailed;
|
|
478
|
+
doc.reasons.push(`${refused.length} of ${checked.programs.length} program(s) holding EXEC SQL or CICS do not compile once precompiled`);
|
|
479
|
+
for (const p of refused.slice(0, MAX_PROGRAM_REASONS)) doc.reasons.push(`${p.path}: ${(p.errors || [])[0] || 'the compiler exited non-zero'}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (compiler && doc.verdict === 'pass' && doc.checks.precompile !== false) {
|
|
403
483
|
const argv = [...compiler.slice(1), ...judged.add];
|
|
404
484
|
// The compiler's own output goes to standard error: standard output carries the document.
|
|
405
485
|
const r = spawnSync(compilerPath, argv, { stdio: ['ignore', 2, 2], env, windowsHide: true });
|
package/lib/capabilities.mjs
CHANGED
|
@@ -6,29 +6,30 @@ import { RULE_SETS, ALL_RULES } from './kernel/registry.mjs';
|
|
|
6
6
|
import { EVIDENCE } from './kernel/findings.mjs';
|
|
7
7
|
import { FINGERPRINT_VERSION } from './kernel/identity.mjs';
|
|
8
8
|
import { PROGRAM_EXT, COPY_EXT, JCL_EXT, BMS_EXT } from './sources.mjs';
|
|
9
|
-
import { FLOW_MODEL, SCHEMA_VERSION, TOOL_VERSION } from './version.mjs';
|
|
9
|
+
import { FLOW_MODEL, SCHEMA_VERSION, TOOL_VERSION, EXPLAIN_SCHEMA_VERSION, PARSE_SCHEMA_VERSION, BASELINE_RESULT_SCHEMA_VERSION, EVIDENCE_RESULT_SCHEMA_VERSION } from './version.mjs';
|
|
10
10
|
import { BUILD_SCHEMA_VERSION, BUILD_EXIT } from './build.mjs';
|
|
11
11
|
import { VERDICT_EXIT } from './gate.mjs';
|
|
12
12
|
import { CHECKS, COMPILERS, DEFAULT_POLICY } from './policy.mjs';
|
|
13
13
|
import { CLASSES, TIERS } from './consequence.mjs';
|
|
14
14
|
import { toolRevision } from './revision.mjs';
|
|
15
|
+
import { IRONWORK_FORMATS, IRONWORK_MINIMUM, NOT_RUN_ABENDS, RUN_ENDINGS } from './ironwork-ids.mjs';
|
|
15
16
|
|
|
16
17
|
export const CAPABILITIES_SCHEMA_VERSION = 1;
|
|
17
18
|
|
|
18
19
|
// Each command, what it takes and what it writes.
|
|
19
20
|
export const COMMANDS = {
|
|
20
|
-
scan: { args: ['<path>'], options: ['--format', '--out', '--repos', '--only', '--quiet', '--full-trace', '--all-routes', '--baseline', '--no-baseline', '--advisories', '--copylib', '--pds-export', '--evidence'], documents: ['cobolwork', 'sarif'] },
|
|
21
|
+
scan: { args: ['<path>'], options: ['--format', '--out', '--repos', '--only', '--quiet', '--full-trace', '--all-routes', '--baseline', '--no-baseline', '--advisories', '--copylib', '--pds-export', '--mvs38-forms', '--no-mvs38-forms', '--hlasm-optable', '--evidence'], documents: ['cobolwork', 'sarif'] },
|
|
21
22
|
flow: { args: ['<path>'], options: ['--out', '--repos', '--quiet', '--full-trace', '--all-routes', '--baseline', '--no-baseline', '--copylib', '--evidence'], documents: ['cobolwork-flow'] },
|
|
22
23
|
inventory: { args: ['<path>'], options: ['--out', '--quiet', '--copylib', '--evidence'], documents: ['cobolwork-inventory'] },
|
|
23
24
|
parse: { args: ['<file>'], options: ['--out'], documents: ['cobolwork-parse'] },
|
|
24
25
|
sbom: { args: ['<path>'], options: ['--name', '--out', '--quiet', '--copylib', '--evidence'], documents: ['cyclonedx'] },
|
|
25
|
-
diff: { args: ['<repo>'], options: ['--base', '--head', '--format', '--out', '--only', '--quiet', '--full-trace', '--copylib', '--evidence'], documents: ['cobolwork-diff', 'sarif'] },
|
|
26
|
+
diff: { args: ['<repo>'], options: ['--base', '--head', '--format', '--out', '--only', '--quiet', '--full-trace', '--copylib', '--pds-export', '--evidence'], documents: ['cobolwork-diff', 'sarif'] },
|
|
26
27
|
baseline: { args: ['<path>'], options: ['--reason', '--who', '--expires', '--action', '--rule', '--out', '--repos', '--only', '--advisories', '--copylib', '--evidence'], documents: ['cobolwork-baseline'] },
|
|
27
28
|
tui: { args: ['[path]'], options: ['--report', '--keys', '--baseline', '--no-baseline', '--advisories', '--copylib'], documents: [] },
|
|
28
29
|
explain: { args: ['<path>', '<fingerprint>'], options: ['--report', '--out', '--baseline', '--no-baseline', '--advisories', '--copylib'], documents: ['cobolwork-explain'] },
|
|
29
30
|
gate: { args: ['<repo>'], options: ['--base', '--head', '--target', '--cobc', '--exit-code', '--target-only', '--out', '--evidence'], documents: ['cobolwork-gate'] },
|
|
30
|
-
build: { args: ['<repo>', '[-- <compiler> <arg>...]'], options: ['--base', '--head', '--policy', '--provenance', '--provenance-format', '--artifact', '--equivalence', '--allowed-signers', '--ironwork', '--advisories', '--copylib', '--no-baseline', '--format', '--out', '--evidence'], documents: ['cobolwork-build', 'cobolwork-build-provenance', 'sarif'] },
|
|
31
|
-
evidence: { args: ['verify|seal|anchor|sign', '[statement]'], options: ['--evidence', '--ssh-key', '--signer', '--expect-key', '--allowed-signers', '--anchor-git', '--ref', '--push', '--max-unsealed', '--tsq', '--out', '--quiet'], documents: ['cobolwork-evidence'] },
|
|
31
|
+
build: { args: ['<repo>', '[-- <compiler> <arg>...]'], options: ['--base', '--head', '--policy', '--provenance', '--provenance-format', '--artifact', '--equivalence', '--allowed-signers', '--ironwork', '--precompile', '--advisories', '--copylib', '--pds-export', '--no-baseline', '--format', '--out', '--evidence'], documents: ['cobolwork-build', 'cobolwork-build-provenance', 'sarif'] },
|
|
32
|
+
evidence: { args: ['verify|seal|anchor|sign', '[statement]'], options: ['--evidence', '--ssh-key', '--signer', '--expect-key', '--allowed-signers', '--anchor-git', '--ref', '--anchor-pin', '--push', '--max-unsealed', '--tsq', '--tsr', '--tsa-ca', '--cosign-bundle', '--cosign-key', '--certificate-identity', '--certificate-oidc-issuer', '--trusted-root', '--insecure-ignore-tlog', '--out', '--quiet'], documents: ['cobolwork-evidence'] },
|
|
32
33
|
capabilities: { args: [], options: ['--json'], documents: ['cobolwork-capabilities'] },
|
|
33
34
|
};
|
|
34
35
|
|
|
@@ -43,7 +44,6 @@ export function capabilities() {
|
|
|
43
44
|
toolRevision: toolRevision(),
|
|
44
45
|
commands: COMMANDS,
|
|
45
46
|
globalOptions: GLOBAL_OPTIONS,
|
|
46
|
-
// A document whose schemaVersion is null carries none yet; a reader should key on `tool`.
|
|
47
47
|
documents: {
|
|
48
48
|
cobolwork: SCHEMA_VERSION,
|
|
49
49
|
'cobolwork-flow': SCHEMA_VERSION,
|
|
@@ -53,10 +53,10 @@ export function capabilities() {
|
|
|
53
53
|
'cobolwork-build': BUILD_SCHEMA_VERSION,
|
|
54
54
|
'cobolwork-build-provenance': BUILD_SCHEMA_VERSION,
|
|
55
55
|
'cobolwork-capabilities': CAPABILITIES_SCHEMA_VERSION,
|
|
56
|
-
'cobolwork-explain':
|
|
57
|
-
'cobolwork-baseline':
|
|
58
|
-
'cobolwork-parse':
|
|
59
|
-
'cobolwork-evidence':
|
|
56
|
+
'cobolwork-explain': EXPLAIN_SCHEMA_VERSION,
|
|
57
|
+
'cobolwork-baseline': BASELINE_RESULT_SCHEMA_VERSION,
|
|
58
|
+
'cobolwork-parse': PARSE_SCHEMA_VERSION,
|
|
59
|
+
'cobolwork-evidence': EVIDENCE_RESULT_SCHEMA_VERSION,
|
|
60
60
|
cyclonedx: '1.6',
|
|
61
61
|
sarif: '2.1.0',
|
|
62
62
|
},
|
|
@@ -68,7 +68,7 @@ export function capabilities() {
|
|
|
68
68
|
jcl: JCL_EXT,
|
|
69
69
|
bms: BMS_EXT,
|
|
70
70
|
extensionless: 'sniffed by content: a file with no extension is classified by what it holds',
|
|
71
|
-
pdsExport: 'scan --pds-export: every member classified by content, whatever extension the download gave it; text, --binary and --record downloads',
|
|
71
|
+
pdsExport: 'scan --pds-export: every member classified by content, whatever extension the download gave it; text, --binary and --record downloads; XMIT files and IEBCOPY unloads of a PDS or a sequential data set',
|
|
72
72
|
zowe: ['zowe.config.json', 'zowe.config.user.json', '.mcp.json', '.vscode/mcp.json', '.cursor/mcp.json', '.claude/settings.json', '.claude/settings.local.json', '.gemini/settings.json', 'claude_desktop_config.json', '.vscode/settings.json', 'systems.json under a mock directory'],
|
|
73
73
|
},
|
|
74
74
|
evidenceKinds: Object.keys(EVIDENCE),
|
|
@@ -81,5 +81,7 @@ export function capabilities() {
|
|
|
81
81
|
exit: { ...BUILD_EXIT, couldNotRun: 2 },
|
|
82
82
|
},
|
|
83
83
|
gate: { exit: { ...VERDICT_EXIT, couldNotRun: 2 } },
|
|
84
|
+
// The ironwork releases cobolwork reads: from `minimum` on, each format from the release it names.
|
|
85
|
+
ironwork: { minimum: IRONWORK_MINIMUM, formats: IRONWORK_FORMATS, runEndings: RUN_ENDINGS, notRunAbends: NOT_RUN_ABENDS },
|
|
84
86
|
};
|
|
85
87
|
}
|