@blamejs/exceptd-skills 0.19.33 → 0.19.34
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/CHANGELOG.md +10 -0
- package/bin/exceptd.js +896 -2824
- package/data/_indexes/_meta.json +2 -2
- package/lib/auto-discovery.js +56 -286
- package/lib/canonical-eq.js +7 -40
- package/lib/citation-resolve.js +22 -70
- package/lib/collectors/ai-api.js +20 -54
- package/lib/collectors/cicd-pipeline-compromise.js +40 -108
- package/lib/collectors/citation-hygiene.js +72 -210
- package/lib/collectors/containers.js +41 -130
- package/lib/collectors/cred-stores.js +31 -115
- package/lib/collectors/crypto-codebase.js +55 -138
- package/lib/collectors/crypto.js +24 -54
- package/lib/collectors/hardening.js +20 -78
- package/lib/collectors/kernel.js +16 -46
- package/lib/collectors/library-author.js +57 -206
- package/lib/collectors/mcp.js +24 -70
- package/lib/collectors/runtime.js +24 -86
- package/lib/collectors/sbom.js +34 -106
- package/lib/collectors/scan-excludes.js +31 -138
- package/lib/collectors/secrets.js +62 -178
- package/lib/cross-ref-api.js +39 -123
- package/lib/currency-severity.js +8 -27
- package/lib/cve-batch.js +13 -21
- package/lib/cve-cli.js +13 -20
- package/lib/cve-curation.js +72 -239
- package/lib/cve-regression-watcher.js +29 -152
- package/lib/cvss.js +13 -54
- package/lib/doctor-bucketing.js +3 -19
- package/lib/exit-codes.js +10 -42
- package/lib/flag-suggest.js +7 -25
- package/lib/framework-gap.js +35 -114
- package/lib/gap-detectors.js +37 -159
- package/lib/id-validation.js +9 -30
- package/lib/job-queue.js +13 -36
- package/lib/lint-skills.js +64 -232
- package/lib/playbook-runner.js +693 -2095
- package/lib/prefetch.js +100 -376
- package/lib/refresh-external.js +199 -627
- package/lib/refresh-network.js +75 -307
- package/lib/rfc-cli.js +23 -68
- package/lib/scoring.js +77 -145
- package/lib/sign.js +43 -229
- package/lib/source-advisories.js +43 -194
- package/lib/source-ghsa.js +37 -120
- package/lib/source-osv.js +94 -266
- package/lib/ttp-mapper.js +14 -24
- package/lib/upstream-check-cli.js +10 -28
- package/lib/upstream-check.js +19 -44
- package/lib/validate-catalog-meta.js +17 -61
- package/lib/validate-cve-catalog.js +43 -119
- package/lib/validate-indexes.js +25 -76
- package/lib/validate-package.js +16 -62
- package/lib/validate-playbooks.js +69 -275
- package/lib/validate-vendor.js +16 -49
- package/lib/verify.js +56 -286
- package/lib/version-pins.js +5 -34
- package/lib/worker-pool.js +11 -30
- package/lib/xml-tokenizer.js +47 -152
- package/manifest.json +53 -53
- package/orchestrator/dispatcher.js +17 -68
- package/orchestrator/event-bus.js +11 -74
- package/orchestrator/index.js +138 -412
- package/orchestrator/pipeline.js +28 -85
- package/orchestrator/scanner.js +34 -138
- package/orchestrator/scheduler.js +20 -84
- package/package.json +1 -1
- package/sbom.cdx.json +241 -241
- package/scripts/audit-catalog-gaps.js +9 -62
- package/scripts/audit-cross-skill.js +5 -31
- package/scripts/audit-perf.js +6 -16
- package/scripts/backfill-theater-test.js +7 -64
- package/scripts/bootstrap.js +12 -44
- package/scripts/build-indexes.js +40 -154
- package/scripts/builders/activity-feed.js +4 -14
- package/scripts/builders/catalog-summaries.js +3 -10
- package/scripts/builders/currency.js +7 -20
- package/scripts/builders/cwe-chains.js +7 -30
- package/scripts/builders/did-ladders.js +6 -13
- package/scripts/builders/frequency.js +5 -19
- package/scripts/builders/jurisdiction-clocks.js +6 -25
- package/scripts/builders/recipes.js +6 -14
- package/scripts/builders/section-offsets.js +13 -51
- package/scripts/builders/stale-content.js +7 -28
- package/scripts/builders/summary-cards.js +8 -29
- package/scripts/builders/theater-fingerprints.js +12 -27
- package/scripts/builders/token-budget.js +4 -31
- package/scripts/check-agents-md-collectors.js +11 -54
- package/scripts/check-catalog-gap-budget.js +15 -32
- package/scripts/check-changelog-extract.js +18 -48
- package/scripts/check-codebase-patterns-currency.js +6 -22
- package/scripts/check-codebase-patterns.js +50 -143
- package/scripts/check-epss-consistency.js +9 -64
- package/scripts/check-framework-gap-coverage.js +13 -31
- package/scripts/check-manifest-snapshot.js +13 -73
- package/scripts/check-sbom-currency.js +44 -142
- package/scripts/check-test-count.js +15 -52
- package/scripts/check-test-coverage.js +66 -197
- package/scripts/check-test-subjects.js +21 -62
- package/scripts/check-ttp-references.js +14 -38
- package/scripts/check-ttp-upstream.js +8 -40
- package/scripts/check-version-bump.js +9 -61
- package/scripts/check-version-tags.js +20 -121
- package/scripts/predeploy.js +38 -184
- package/scripts/refresh-manifest-snapshot.js +16 -38
- package/scripts/refresh-mitre-atlas.js +3 -8
- package/scripts/refresh-mitre-attack.js +1 -8
- package/scripts/refresh-mitre-d3fend.js +3 -9
- package/scripts/refresh-mitre-ics-attack.js +3 -8
- package/scripts/refresh-reverse-refs.js +27 -94
- package/scripts/refresh-rfc-index.js +2 -10
- package/scripts/refresh-sbom.js +31 -161
- package/scripts/refresh-upstream-catalogs.js +40 -137
- package/scripts/release.js +69 -232
- package/scripts/run-e2e-scenarios.js +24 -71
- package/scripts/sync-manifest-metadata.js +10 -34
- package/scripts/sync-package-description.js +8 -17
- package/scripts/validate-vendor-online.js +13 -44
- package/scripts/verify-shipped-tarball.js +35 -140
package/scripts/predeploy.js
CHANGED
|
@@ -1,33 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the `.github/workflows/ci.yml` workflow runs, in order. Each gate is
|
|
7
|
-
* isolated — a failure does not short-circuit the rest, so a single run
|
|
8
|
-
* surfaces all problems instead of just the first one (matches the CI
|
|
9
|
-
* shape where each job runs independently).
|
|
10
|
-
*
|
|
11
|
-
* Run before pushing to main or opening a PR:
|
|
12
|
-
* npm run predeploy
|
|
13
|
-
*
|
|
14
|
-
* Exit code:
|
|
15
|
-
* 0 — all gates passed
|
|
16
|
-
* 1 — one or more gates failed (per-gate output already printed)
|
|
17
|
-
* 2 — runner-level error (missing script, fork failure, etc.)
|
|
18
|
-
*
|
|
19
|
-
* Single-source-of-truth: the GATES list below mirrors the job sequence
|
|
20
|
-
* in .github/workflows/ci.yml. Test coverage in tests/predeploy.test.js
|
|
21
|
-
* asserts the two stay in sync.
|
|
22
|
-
*
|
|
23
|
-
* when the manifest-snapshot gate fails, the fix is NOT to
|
|
24
|
-
* run `npm run refresh-snapshot` blindly. The refresh script now refuses
|
|
25
|
-
* unless the operator passes `--commit-only` or sets
|
|
26
|
-
* EXCEPTD_SNAPSHOT_AUDIT_ACK=1. This is intentional: a failing snapshot
|
|
27
|
-
* gate means a breaking change was detected, and an accidental refresh
|
|
28
|
-
* would silently rewrite the baseline. Read the breaking-change list
|
|
29
|
-
* first, then run `node scripts/refresh-manifest-snapshot.js --commit-only`
|
|
30
|
-
* if the change is intentional.
|
|
3
|
+
* Local mirror of the CI pre-deployment gate sequence. Gates are isolated, so one
|
|
4
|
+
* failure does not short-circuit the rest and a single run surfaces every problem.
|
|
5
|
+
* Exit 0 all passed, 1 one or more failed, 2 runner-level error.
|
|
31
6
|
*/
|
|
32
7
|
|
|
33
8
|
const { execFileSync } = require("child_process");
|
|
@@ -36,10 +11,7 @@ const fs = require("fs");
|
|
|
36
11
|
|
|
37
12
|
const ROOT = path.join(__dirname, "..");
|
|
38
13
|
|
|
39
|
-
// Ordered
|
|
40
|
-
// ciJobName matches the `name:` field of the corresponding job in
|
|
41
|
-
// .github/workflows/ci.yml (or scorecard.yml). Used by the workflow-sync
|
|
42
|
-
// test to assert the two never drift.
|
|
14
|
+
// Ordered CI gates; `ciJobName` matches the job `name:` in .github/workflows/ci.yml.
|
|
43
15
|
const GATES = [
|
|
44
16
|
{
|
|
45
17
|
name: "Verify skill signatures (Ed25519)",
|
|
@@ -51,39 +23,19 @@ const GATES = [
|
|
|
51
23
|
{
|
|
52
24
|
name: "Run tests (node:test)",
|
|
53
25
|
command: process.execPath,
|
|
54
|
-
// Glob
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
// sits inside a path containing parentheses (e.g. Dropbox).
|
|
58
|
-
//
|
|
59
|
-
// --test-concurrency=1 forces sequential file execution. Several
|
|
60
|
-
// test files (build-incremental, indexes-v070, refresh-*) touch
|
|
61
|
-
// shared filesystem state under data/_indexes/ + refresh-report.json
|
|
62
|
-
// + skill bodies; running in parallel produces flaky races. Sequential
|
|
63
|
-
// is ~1.5s slower locally but eliminates the false negative we hit
|
|
64
|
-
// on the Linux CI runner in the v0.9.0 release attempt.
|
|
26
|
+
// Glob, not a directory arg: on Windows a bare directory resolves through the
|
|
27
|
+
// module loader and fails under a path containing parentheses. --test-concurrency=1
|
|
28
|
+
// is required — build-incremental, indexes-v070 and refresh-* share state.
|
|
65
29
|
args: ["--test", "--test-concurrency=1", "tests/*.test.js"],
|
|
66
30
|
ciJobName: "Tests",
|
|
67
31
|
},
|
|
68
32
|
{
|
|
69
33
|
name: "Validate CVE catalog schema + zero-day learning coverage",
|
|
70
34
|
command: process.execPath,
|
|
71
|
-
// --strict promotes
|
|
72
|
-
// resolution, strict CVSS-vector prefix, KEV-date-required, Hard-Rule-#14
|
|
73
|
-
// IoCs) to hard failures so they block a release rather than scrolling
|
|
74
|
-
// past. Auto-imported drafts stay exempt.
|
|
35
|
+
// --strict promotes deferred warnings to hard failures; drafts stay exempt.
|
|
75
36
|
args: [path.join(ROOT, "lib", "validate-cve-catalog.js"), "--strict"],
|
|
76
37
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
77
38
|
},
|
|
78
|
-
// the "validate-cves --offline --no-fail" and
|
|
79
|
-
// "validate-rfcs --offline --no-fail" gates were enumeration-only sanity
|
|
80
|
-
// checks: `--no-fail` forced them to always exit 0, so they never blocked
|
|
81
|
-
// a release on a real catalog problem. The deep catalog validation is
|
|
82
|
-
// already performed by the gate above (`lib/validate-cve-catalog.js`),
|
|
83
|
-
// including cross-catalog reference resolution after this same audit.
|
|
84
|
-
// Keeping the no-op gates as predeploy steps inflated the gate count for
|
|
85
|
-
// no marginal value and risked false confidence ("X gates passed"). They
|
|
86
|
-
// are removed in v0.12.14; document the removal in CHANGELOG.
|
|
87
39
|
{
|
|
88
40
|
name: "Manifest snapshot gate (breaking-change detector)",
|
|
89
41
|
command: process.execPath,
|
|
@@ -97,13 +49,7 @@ const GATES = [
|
|
|
97
49
|
ciJobName: "Lint skill files",
|
|
98
50
|
},
|
|
99
51
|
{
|
|
100
|
-
//
|
|
101
|
-
// an exit code of 0 means "ok", 1 means "items present
|
|
102
|
-
// (informational)", 2+ means a runtime error in the gate itself.
|
|
103
|
-
// The runner now distinguishes the two: 0/1 stay informational, 2+
|
|
104
|
-
// surface as a real failure. Pre-fix, any non-zero exit was rolled up
|
|
105
|
-
// as informational, which hid crashes (a 137 OOM looked the same as
|
|
106
|
-
// "found 12 items to review").
|
|
52
|
+
// Exit 0 ok, 1 "items present"; 2+ is a gate runtime error and fails the run.
|
|
107
53
|
name: "Forward-watch aggregator (informational)",
|
|
108
54
|
command: process.execPath,
|
|
109
55
|
args: [
|
|
@@ -145,11 +91,7 @@ const GATES = [
|
|
|
145
91
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
146
92
|
},
|
|
147
93
|
{
|
|
148
|
-
//
|
|
149
|
-
// EXTRACTED tree. Catches the class of bug where verify-on-source-tree
|
|
150
|
-
// passes (38/38) but verify-on-shipped-tarball fails (0/38) because
|
|
151
|
-
// something between sign and pack swapped keys/public.pem. Every release
|
|
152
|
-
// v0.11.x through v0.12.2 shipped this regression invisibly.
|
|
94
|
+
// Verifies the EXTRACTED tarball: a step between sign and pack can swap keys/public.pem.
|
|
153
95
|
name: "Verify shipped tarball (sign + pack + extract + verify round-trip)",
|
|
154
96
|
command: process.execPath,
|
|
155
97
|
args: [path.join(ROOT, "scripts", "verify-shipped-tarball.js")],
|
|
@@ -157,170 +99,100 @@ const GATES = [
|
|
|
157
99
|
requiresKeys: true,
|
|
158
100
|
},
|
|
159
101
|
{
|
|
160
|
-
// AGENTS.md
|
|
161
|
-
// CLI verb, CLI flag, lib/orchestrator/scripts export, playbook
|
|
162
|
-
// indicator, or CVE iocs field must land with a covering test
|
|
163
|
-
// reference in the same PR. The analyzer parses git diff against
|
|
164
|
-
// origin/main, classifies each change shape, and fails if a covered
|
|
165
|
-
// surface lacks a test literal anywhere under tests/. Blocking — a
|
|
166
|
-
// covered surface change without a covering test fails the gate.
|
|
102
|
+
// AGENTS.md Hard Rule #15: a CLI/export/indicator/iocs diff lands with a covering test.
|
|
167
103
|
name: "Diff coverage (feature changes require test coverage)",
|
|
168
104
|
command: process.execPath,
|
|
169
105
|
args: [path.join(ROOT, "scripts", "check-test-coverage.js")],
|
|
170
106
|
ciJobName: "Diff coverage",
|
|
171
107
|
},
|
|
172
108
|
{
|
|
173
|
-
// Validate every playbook in data/playbooks/ against the JSON schema
|
|
174
|
-
// + cross-playbook + cross-catalog references. v0.12.12 first wired
|
|
175
|
-
// this as informational so the patch-class release could land without
|
|
176
|
-
// retroactively breaking schema-drift cases; v0.13.0 flips it to
|
|
177
|
-
// required because the 20-playbook canonical set (including the 4
|
|
178
|
-
// v0.13.0 additions) all validate cleanly.
|
|
179
109
|
name: "Validate playbooks (schema + cross-refs)",
|
|
180
110
|
command: process.execPath,
|
|
181
111
|
args: [path.join(ROOT, "lib", "validate-playbooks.js"), "--strict"],
|
|
182
112
|
ciJobName: "Validate playbooks",
|
|
183
113
|
},
|
|
184
114
|
{
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
// baseline in tests/.test-count-baseline.json. Catches the class
|
|
188
|
-
// of regression where a test file gets accidentally deleted, a
|
|
189
|
-
// skip-all lands without review, or a misnamed file slips through
|
|
190
|
-
// the glob. The baseline is operator-refreshed on releases that
|
|
191
|
-
// intentionally add many new tests; --update-baseline rewrites it.
|
|
115
|
+
// Refuses silent test-set shrinkage: a deleted file, a skip-all or a misnamed
|
|
116
|
+
// file cannot pass. --update-baseline rewrites the pinned baseline.
|
|
192
117
|
name: "Test-count baseline (no silent shrinkage)",
|
|
193
118
|
command: process.execPath,
|
|
194
119
|
args: [path.join(ROOT, "scripts", "check-test-count.js")],
|
|
195
|
-
// Folds under the existing Data integrity CI job rather than a
|
|
196
|
-
// dedicated job — the check is fast (~70ms) static analysis and
|
|
197
|
-
// shares the integrity-tier framing with manifest-snapshot etc.
|
|
198
120
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
199
121
|
},
|
|
200
122
|
{
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
// temporal-staleness, logical-consistency, cross-ref-completeness,
|
|
204
|
-
// schema-evolution, operator-action-sla, unused-orphan) against
|
|
205
|
-
// the shipped catalog and fails if any class regresses beyond its
|
|
206
|
-
// documented budget. Mirrors the budget enforced by
|
|
207
|
-
// tests/shipped-catalog-integrity.test.js so the regression
|
|
208
|
-
// surfaces in BOTH the gate-summary table AND the test output.
|
|
123
|
+
// Fails if a detection class regresses past its budget. The same budget is in
|
|
124
|
+
// tests/shipped-catalog-integrity.test.js; the two must not drift.
|
|
209
125
|
name: "Catalog-gap budget (v0.13.21 extended detection classes)",
|
|
210
126
|
command: process.execPath,
|
|
211
127
|
args: [path.join(ROOT, "scripts", "check-catalog-gap-budget.js")],
|
|
212
128
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
213
129
|
},
|
|
214
130
|
{
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
// all five jurisdiction buckets (NIST, EU, UK, AU, ISO). Drafts are
|
|
218
|
-
// exempt. Prevents a US-centric subset from shipping in the offline
|
|
219
|
-
// catalog's framework-gap output for multi-jurisdiction operators.
|
|
131
|
+
// AGENTS.md Hard Rule #5: every curated CVE declares framework_control_gaps for
|
|
132
|
+
// all five jurisdiction buckets (NIST, EU, UK, AU, ISO). Drafts exempt.
|
|
220
133
|
name: "Framework-gap jurisdiction coverage (Hard Rule #5)",
|
|
221
134
|
command: process.execPath,
|
|
222
135
|
args: [path.join(ROOT, "scripts", "check-framework-gap-coverage.js")],
|
|
223
136
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
224
137
|
},
|
|
225
138
|
{
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
// retires and renumbers between releases, and a reference living outside
|
|
229
|
-
// the source catalogs is never dereferenced at runtime — so a stale id
|
|
230
|
-
// keeps rendering in operator output, pointing at a page that no longer
|
|
231
|
-
// resolves and leaving any control mapped to it orphaned (Hard Rule #4).
|
|
232
|
-
// Resolves against the pin rather than the network, so it can block.
|
|
139
|
+
// MITRE retires and renumbers technique ids, and a reference outside the two
|
|
140
|
+
// pinned catalogs is never dereferenced at runtime, orphaning its control.
|
|
233
141
|
name: "TTP reference integrity (no orphaned technique ids)",
|
|
234
142
|
command: process.execPath,
|
|
235
143
|
args: [path.join(ROOT, "scripts", "check-ttp-references.js")],
|
|
236
144
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
237
145
|
},
|
|
238
146
|
{
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
// rank — so sorting a publication's entries by score must sort them by
|
|
242
|
-
// percentile too. Refreshing one field without the other leaves an entry
|
|
243
|
-
// that passes every range and type check while ranking a CVE on a score
|
|
244
|
-
// that no longer supports it, which is precisely the input operators
|
|
245
|
-
// prioritise by. The ordering check catches that offline.
|
|
147
|
+
// epss_percentile is the score's rank within one daily publication, so a
|
|
148
|
+
// publication's entries must sort the same way by both.
|
|
246
149
|
name: "EPSS score/percentile consistency",
|
|
247
150
|
command: process.execPath,
|
|
248
151
|
args: [path.join(ROOT, "scripts", "check-epss-consistency.js")],
|
|
249
152
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
250
153
|
},
|
|
251
154
|
{
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
// `*-vX_Y_Z.test.js` filenames. Fails on NEW additions outside
|
|
255
|
-
// the authoritative version surfaces (package.json /
|
|
256
|
-
// manifest.json / CHANGELOG headings / git tags). The full rule
|
|
257
|
-
// is documented at the top of check-version-tags.js; refresh the
|
|
258
|
-
// baseline after an organic cleanup via
|
|
259
|
-
// `node scripts/check-version-tags.js --update-baseline`.
|
|
155
|
+
// Fails on NEW `// vX.Y.Z` comments and `*-vX_Y_Z.test.js` filenames: versions
|
|
156
|
+
// live in package.json, manifest.json, CHANGELOG headings and git tags.
|
|
260
157
|
name: "Version-tag drift (no new phase residue)",
|
|
261
158
|
command: process.execPath,
|
|
262
159
|
args: [path.join(ROOT, "scripts", "check-version-tags.js")],
|
|
263
160
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
264
161
|
},
|
|
265
162
|
{
|
|
266
|
-
// AGENTS.md collector
|
|
267
|
-
// where lib/collectors/ gets a new module but AGENTS.md's
|
|
268
|
-
// "<N> reference collectors ship today (...)" paragraph isn't
|
|
269
|
-
// bumped (or vice versa). The paragraph is the canonical source
|
|
270
|
-
// for AI-agent consumers; drift produces stale enumeration.
|
|
163
|
+
// Keeps lib/collectors/ and AGENTS.md's collector-enumeration paragraph in step.
|
|
271
164
|
name: "AGENTS.md collector enumeration drift",
|
|
272
165
|
command: process.execPath,
|
|
273
166
|
args: [path.join(ROOT, "scripts", "check-agents-md-collectors.js")],
|
|
274
167
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
275
168
|
},
|
|
276
169
|
{
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
// stdout then calls process.exit() (truncates the buffered write when
|
|
280
|
-
// piped — the stdout-flush-truncation class), and a stale/typo'd `// allow:` marker.
|
|
281
|
-
// dynamic-RegExp construction is surfaced warn-only this release. The
|
|
282
|
-
// exception mechanism + the "owned elsewhere" boundary are documented in
|
|
283
|
-
// the script header.
|
|
170
|
+
// Blocks a library-callable function that writes to stdout then calls
|
|
171
|
+
// process.exit(), and an orphaned allow marker. Dynamic RegExp is warn-only.
|
|
284
172
|
name: "Codebase-pattern gates (stdout-flush, dynamic RegExp, bidi codepoints, orphan markers)",
|
|
285
173
|
command: process.execPath,
|
|
286
174
|
args: [path.join(ROOT, "scripts", "check-codebase-patterns.js")],
|
|
287
175
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
288
176
|
},
|
|
289
177
|
{
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
// CVE id / playbook / data primitive / repo artifact), and every such
|
|
293
|
-
// subject must have a test. Blocks the naming drift that lets a test be
|
|
294
|
-
// filed under a version/finding label (where downstream readers can't find
|
|
295
|
-
// it) and surfaces any module/playbook that ships without a test. Derived
|
|
296
|
-
// dynamically from the source tree, so the list is never hand-maintained.
|
|
178
|
+
// Bidirectional: every tests/<x>.test.js names a real subject, and every subject
|
|
179
|
+
// has a test. Derived from the source tree, never hand-maintained.
|
|
297
180
|
name: "Test-subject coverage (every test maps to a subject; every subject has a test)",
|
|
298
181
|
command: process.execPath,
|
|
299
182
|
args: [path.join(ROOT, "scripts", "check-test-subjects.js")],
|
|
300
183
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
301
184
|
},
|
|
302
185
|
{
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
// Release body, and lints it for operator-facing quality (no internal
|
|
306
|
-
// phase/pass/slice narrative, no agent-dispatch / conversation residue,
|
|
307
|
-
// no tautological green claims). A malformed or internal-narrative section
|
|
308
|
-
// fails here rather than shipping as the public release body / falling
|
|
309
|
-
// back to the generic "Release of v<version>." line.
|
|
186
|
+
// Runs the same `## <version>` CHANGELOG extraction the release workflow
|
|
187
|
+
// publishes, then lints it, so a bad section fails here rather than publicly.
|
|
310
188
|
name: "Release-notes extract + operator-facing lint (CHANGELOG section)",
|
|
311
189
|
command: process.execPath,
|
|
312
190
|
args: [path.join(ROOT, "scripts", "check-changelog-extract.js")],
|
|
313
191
|
ciJobName: "Data integrity (catalog + manifest snapshot)",
|
|
314
192
|
},
|
|
315
193
|
{
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
// (tests/.version-bump-ack.json naming the exact target version).
|
|
319
|
-
// Compares the top two `## X.Y.Z` CHANGELOG headings — hermetic, so it
|
|
320
|
-
// enforces identically locally and in the release.yml validate job. A
|
|
321
|
-
// hand-bumped minor without the ack fails here rather than shipping a
|
|
322
|
-
// wrong version number (the class of error behind two mis-versioned
|
|
323
|
-
// releases). Full contract at the top of check-version-bump.js.
|
|
194
|
+
// Patch is the ONLY default bump; a minor or major needs a committed
|
|
195
|
+
// tests/.version-bump-ack.json naming the exact target version.
|
|
324
196
|
name: "Version-bump cadence (patch-only default)",
|
|
325
197
|
command: process.execPath,
|
|
326
198
|
args: [path.join(ROOT, "scripts", "check-version-bump.js")],
|
|
@@ -341,9 +213,7 @@ function runGate(gate) {
|
|
|
341
213
|
}
|
|
342
214
|
}
|
|
343
215
|
const t0 = Date.now();
|
|
344
|
-
//
|
|
345
|
-
// can count `WARN ` lines for the summary table. We still want the live
|
|
346
|
-
// output, so each chunk is forwarded as it arrives.
|
|
216
|
+
// Piped stdio, forwarded on: the summary needs a WARN count from the output.
|
|
347
217
|
const { spawnSync } = require("child_process");
|
|
348
218
|
const r = spawnSync(gate.command, gate.args, {
|
|
349
219
|
cwd: ROOT,
|
|
@@ -353,9 +223,7 @@ function runGate(gate) {
|
|
|
353
223
|
const durationMs = Date.now() - t0;
|
|
354
224
|
if (r.stdout) process.stdout.write(r.stdout);
|
|
355
225
|
if (r.stderr) process.stderr.write(r.stderr);
|
|
356
|
-
//
|
|
357
|
-
// can surface them. Lint / validate output uses "WARN " at line start;
|
|
358
|
-
// count both the table form and an inline "[warn]" form.
|
|
226
|
+
// Both forms count: "WARN" at line start, and an inline "[warn]".
|
|
359
227
|
const combined = (r.stdout || "") + (r.stderr || "");
|
|
360
228
|
const warnCount = (
|
|
361
229
|
combined.match(/^WARN\b/gm) || []
|
|
@@ -365,23 +233,13 @@ function runGate(gate) {
|
|
|
365
233
|
if (r.status === 0) {
|
|
366
234
|
return { status: "passed", durationMs, warnCount };
|
|
367
235
|
}
|
|
368
|
-
//
|
|
369
|
-
// "soft signal" (exit codes 0..N) from "crash" (> N). Default behaviour
|
|
370
|
-
// for an informational gate without that field stays the same.
|
|
236
|
+
// informationalMaxExitCode separates a soft signal (0..N) from a crash; absent = any.
|
|
371
237
|
if (gate.informational) {
|
|
372
238
|
const ceil = typeof gate.informationalMaxExitCode === "number"
|
|
373
239
|
? gate.informationalMaxExitCode
|
|
374
240
|
: Infinity;
|
|
375
|
-
// A
|
|
376
|
-
//
|
|
377
|
-
// informational soft-signal. So is a signal kill (status:null with r.signal
|
|
378
|
-
// set — e.g. a 137 OOM kill) and a status that exceeds the soft-signal
|
|
379
|
-
// ceiling. Without surfacing the spawn-error case, an informational gate
|
|
380
|
-
// that never even ran fell through to "informational" and the release
|
|
381
|
-
// proceeded as if the gate had merely produced advisory output. The
|
|
382
|
-
// status===null && !signal case (no error object, but the process never
|
|
383
|
-
// produced an exit code) is treated the same way — a gate that did not
|
|
384
|
-
// exit cleanly cannot be classified as a soft signal.
|
|
241
|
+
// A gate that never ran cleanly is a crash, not a soft signal: spawn failure,
|
|
242
|
+
// a signal kill (137 OOM), and a status above the ceiling all fail here.
|
|
385
243
|
const spawnFailed = !!r.error || (r.status === null && !r.signal);
|
|
386
244
|
if (r.error || r.signal || spawnFailed || (r.status !== null && r.status > ceil)) {
|
|
387
245
|
return {
|
|
@@ -442,7 +300,6 @@ function main() {
|
|
|
442
300
|
}
|
|
443
301
|
}
|
|
444
302
|
|
|
445
|
-
// Summary table.
|
|
446
303
|
process.stdout.write("\n=== Pre-deploy summary ===\n");
|
|
447
304
|
const widest = results.reduce(
|
|
448
305
|
(n, r) => Math.max(n, r.gate.name.length),
|
|
@@ -459,10 +316,7 @@ function main() {
|
|
|
459
316
|
: "✗";
|
|
460
317
|
const timing = fmtMs(outcome.durationMs);
|
|
461
318
|
const timingSuffix = timing ? ` (${timing})` : "";
|
|
462
|
-
//
|
|
463
|
-
// distinguishable from one that passed cleanly. Pre-fix, warnings
|
|
464
|
-
// printed by individual gates (validate-cve-catalog, lint-skills,
|
|
465
|
-
// validate-playbooks) scrolled past invisible in the summary.
|
|
319
|
+
// A gate that "passed (3 warnings)" must be distinguishable from a clean pass.
|
|
466
320
|
const warnSuffix =
|
|
467
321
|
outcome.warnCount && outcome.warnCount > 0
|
|
468
322
|
? ` (${outcome.warnCount} warning${outcome.warnCount === 1 ? "" : "s"})`
|
|
@@ -1,32 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Captures the public skill surface from manifest.json into
|
|
4
|
+
* manifest-snapshot.json. Run it AFTER an intentional surface change and commit
|
|
5
|
+
* the new snapshot alongside that change — never to "fix" a failing
|
|
6
|
+
* check-manifest-snapshot.js gate, whose breaking-change list is the thing to
|
|
7
|
+
* read first. A breaking change is a surface narrowing every downstream
|
|
8
|
+
* consumer needs to know about.
|
|
4
9
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Do NOT run this to "fix" a failing check-manifest-snapshot.js gate
|
|
11
|
-
* blindly — read the breaking-change list first. A breaking change is
|
|
12
|
-
* a surface narrowing every downstream consumer needs to know about.
|
|
13
|
-
*
|
|
14
|
-
* commitOnly mode. Pass `--commit-only` (or set the env
|
|
15
|
-
* EXCEPTD_SNAPSHOT_AUDIT_ACK=1) to acknowledge that the operator
|
|
16
|
-
* deliberately wants to overwrite the committed snapshot. When neither
|
|
17
|
-
* flag nor env is set AND the snapshot would actually change, the
|
|
18
|
-
* script refuses and emits a structured diff hint. This stops an
|
|
19
|
-
* accidental `npm run refresh-snapshot` (run as muscle-memory while
|
|
20
|
-
* triaging a failing gate) from masking a real breaking change.
|
|
21
|
-
*
|
|
22
|
-
* Usage:
|
|
23
|
-
* node scripts/refresh-manifest-snapshot.js # dry-shows the diff
|
|
24
|
-
* EXCEPTD_SNAPSHOT_AUDIT_ACK=1 \
|
|
25
|
-
* node scripts/refresh-manifest-snapshot.js # writes the new snapshot
|
|
26
|
-
* node scripts/refresh-manifest-snapshot.js --commit-only # same thing, on argv
|
|
27
|
-
*
|
|
28
|
-
* The flag is documented in scripts/predeploy.js so contributors see it
|
|
29
|
-
* the moment the snapshot gate fails.
|
|
10
|
+
* Overwriting an existing snapshot takes `--commit-only` or
|
|
11
|
+
* EXCEPTD_SNAPSHOT_AUDIT_ACK=1. Without either, a run whose capture differs
|
|
12
|
+
* refuses and prints a diff hint, so muscle memory cannot mask a real breaking
|
|
13
|
+
* change. scripts/predeploy.js names the flag where the gate fails.
|
|
30
14
|
*/
|
|
31
15
|
|
|
32
16
|
const fs = require("fs");
|
|
@@ -64,18 +48,14 @@ const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, "utf8"));
|
|
|
64
48
|
const snapshot = captureSurface(manifest);
|
|
65
49
|
const newJson = JSON.stringify(snapshot, null, 2) + "\n";
|
|
66
50
|
|
|
67
|
-
// F5 — refuse to overwrite an existing snapshot unless the operator
|
|
68
|
-
// has explicitly acknowledged the rewrite (env or --commit-only flag).
|
|
69
51
|
const argv = process.argv.slice(2);
|
|
70
52
|
const commitOnly =
|
|
71
53
|
argv.includes("--commit-only") ||
|
|
72
54
|
process.env.EXCEPTD_SNAPSHOT_AUDIT_ACK === "1";
|
|
73
55
|
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
// guard decides on may not be the file it then reads. ENOENT IS the "no prior
|
|
78
|
-
// snapshot, write a fresh one" signal.
|
|
56
|
+
// Branch on the read RESULT, never on an existsSync-then-read probe: that is a
|
|
57
|
+
// check-then-use window (CodeQL js/file-system-race) where the file the guard
|
|
58
|
+
// decided on need not be the one read. ENOENT IS the "no prior snapshot" signal.
|
|
79
59
|
let current = null;
|
|
80
60
|
try {
|
|
81
61
|
current = fs.readFileSync(SNAPSHOT_PATH, "utf8");
|
|
@@ -83,8 +63,7 @@ try {
|
|
|
83
63
|
if (e.code !== "ENOENT") throw e;
|
|
84
64
|
}
|
|
85
65
|
if (current !== null && !commitOnly) {
|
|
86
|
-
//
|
|
87
|
-
// changes every run and shouldn't trigger the guard.
|
|
66
|
+
// _generated_at changes every run, so it must not trigger the guard.
|
|
88
67
|
const stripGenerated = (s) => s.replace(
|
|
89
68
|
/"_generated_at":\s*"[^"]+",?\s*\n?/, ""
|
|
90
69
|
);
|
|
@@ -108,9 +87,8 @@ fs.writeFileSync(SNAPSHOT_PATH, newJson, "utf8");
|
|
|
108
87
|
console.log(`[refresh-manifest-snapshot] wrote ${snapshot.skill_count} skills to manifest-snapshot.json`);
|
|
109
88
|
console.log("[refresh-manifest-snapshot] commit this file alongside the surface change.");
|
|
110
89
|
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
// after refresh).
|
|
90
|
+
// A tracked SHA-256 lets check-manifest-snapshot.js catch a hand edit made
|
|
91
|
+
// after the refresh.
|
|
114
92
|
const crypto = require("crypto");
|
|
115
93
|
const snapshotSha = crypto.createHash("sha256").update(newJson).digest("hex");
|
|
116
94
|
const snapshotShaPath = path.join(ROOT, "manifest-snapshot.sha256");
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* scripts/refresh-upstream-catalogs.js#refreshAtlas.
|
|
8
|
-
*
|
|
9
|
-
* node scripts/refresh-mitre-atlas.js [--dry-run]
|
|
10
|
-
*
|
|
11
|
-
* Wired as `npm run refresh-mitre-atlas`.
|
|
4
|
+
* Thin per-type wrapper for the MITRE ATLAS refresher; the logic lives in
|
|
5
|
+
* scripts/refresh-upstream-catalogs.js#refreshAtlas. Wired as
|
|
6
|
+
* `npm run refresh-mitre-atlas`, and takes --dry-run.
|
|
12
7
|
*/
|
|
13
8
|
const { refreshAtlas } = require("./refresh-upstream-catalogs.js");
|
|
14
9
|
const dry = process.argv.includes("--dry-run");
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Thin per-type wrapper for the MITRE ATT&CK refresher. Logic lives in
|
|
4
|
+
* Refreshes only the MITRE ATT&CK catalog. Logic lives in
|
|
7
5
|
* scripts/refresh-upstream-catalogs.js#refreshAttack.
|
|
8
|
-
*
|
|
9
|
-
* node scripts/refresh-mitre-attack.js [--dry-run]
|
|
10
|
-
* CAP=200 node scripts/refresh-mitre-attack.js
|
|
11
|
-
*
|
|
12
|
-
* Wired as `npm run refresh-mitre-attack`.
|
|
13
6
|
*/
|
|
14
7
|
const { refreshAttack } = require("./refresh-upstream-catalogs.js");
|
|
15
8
|
const dry = process.argv.includes("--dry-run");
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* scripts/refresh-upstream-catalogs.js#refreshD3fend.
|
|
8
|
-
*
|
|
9
|
-
* node scripts/refresh-mitre-d3fend.js [--dry-run]
|
|
10
|
-
* CAP=120 node scripts/refresh-mitre-d3fend.js
|
|
11
|
-
*
|
|
12
|
-
* Wired as `npm run refresh-mitre-d3fend`.
|
|
4
|
+
* Thin wrapper for the MITRE D3FEND refresher; the logic lives in
|
|
5
|
+
* scripts/refresh-upstream-catalogs.js#refreshD3fend. Wired as
|
|
6
|
+
* `npm run refresh-mitre-d3fend`.
|
|
13
7
|
*/
|
|
14
8
|
const { refreshD3fend } = require("./refresh-upstream-catalogs.js");
|
|
15
9
|
const dry = process.argv.includes("--dry-run");
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* lives in scripts/refresh-upstream-catalogs.js#refreshIcsAttack.
|
|
8
|
-
*
|
|
9
|
-
* node scripts/refresh-mitre-ics-attack.js [--dry-run]
|
|
10
|
-
*
|
|
11
|
-
* Wired as `npm run refresh-mitre-ics-attack`.
|
|
4
|
+
* `npm run refresh-mitre-ics-attack [-- --dry-run]` — per-type wrapper for the
|
|
5
|
+
* MITRE ICS-attack STIX refresher; the logic lives in
|
|
6
|
+
* scripts/refresh-upstream-catalogs.js#refreshIcsAttack.
|
|
12
7
|
*/
|
|
13
8
|
const { refreshIcsAttack } = require("./refresh-upstream-catalogs.js");
|
|
14
9
|
const dry = process.argv.includes("--dry-run");
|