@blamejs/exceptd-skills 0.18.25 → 0.18.26

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.
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * scripts/check-framework-gap-coverage.js
4
+ *
5
+ * AGENTS.md Hard Rule #5 (global-first) enforcement gate. Every curated CVE
6
+ * in data/cve-catalog.json must declare a framework_control_gaps statement
7
+ * for all five jurisdiction buckets:
8
+ *
9
+ * NIST — NIST-800-53-* / NIST-800-63*
10
+ * EU — NIS2-* / DORA-* / EU-AI-* / GDPR-*
11
+ * UK — UK-CAF-*
12
+ * AU — AU-Essential-8-* / AU-ISM-*
13
+ * ISO — ISO-27001-2022-*
14
+ *
15
+ * A multi-jurisdiction operator reading the offline catalog's framework-gap
16
+ * output must get coverage for every required jurisdiction on every CVE, not
17
+ * a US-centric subset. Before this gate, two-thirds of the corpus mapped only
18
+ * a subset; a codex review surfaced it, the corpus was completed, and this
19
+ * gate keeps it from regressing when new CVEs are curated.
20
+ *
21
+ * Draft entries (_auto_imported === true) are exempt — they carry raw NVD
22
+ * data that has not been through curation yet. The curation bar (elsewhere)
23
+ * requires drafts to be promoted before shipping, at which point this gate
24
+ * applies.
25
+ *
26
+ * Exit code: 0 when every curated entry is complete, 1 when any entry omits a
27
+ * required bucket (the list is printed). Uses process.exitCode (not
28
+ * process.exit) so buffered stdout drains before the process ends.
29
+ */
30
+
31
+ 'use strict';
32
+
33
+ const fs = require('node:fs');
34
+ const path = require('node:path');
35
+
36
+ const ROOT = path.resolve(__dirname, '..');
37
+ // Catalog path is overridable (argv[2]) so the gate's own test can point it at
38
+ // a fixture; defaults to the shipped catalog for the real predeploy run.
39
+ const CATALOG = process.argv[2] || path.join(ROOT, 'data', 'cve-catalog.json');
40
+
41
+ const REQUIRED = ['NIST', 'EU', 'UK', 'AU', 'ISO'];
42
+
43
+ function bucketOf(key) {
44
+ if (/^NIST/i.test(key)) return 'NIST';
45
+ // EU bucket is the security-regulation family AGENTS.md Hard Rule #5 names:
46
+ // NIS2, DORA, the EU AI Act, and the EU Cyber Resilience Act. GDPR / EU-GDPR
47
+ // (data-protection) is NOT sufficient on its own — an entry mapped only to
48
+ // GDPR still owes NIS2/DORA/EU-AI coverage, so GDPR does not satisfy this
49
+ // bucket. Note EU-AI / EU-CRA match here; EU-GDPR deliberately does not.
50
+ if (/^(NIS2|DORA|EU-AI|EU-CRA)/i.test(key)) return 'EU';
51
+ if (/^UK-CAF/i.test(key)) return 'UK';
52
+ if (/^(AU-Essential-?8|AU-ISM|Essential-?8)/i.test(key)) return 'AU';
53
+ if (/^ISO-?27001/i.test(key)) return 'ISO';
54
+ return 'OTHER';
55
+ }
56
+
57
+ function main() {
58
+ let catalog;
59
+ try {
60
+ catalog = JSON.parse(fs.readFileSync(CATALOG, 'utf8'));
61
+ } catch (e) {
62
+ process.stdout.write(`[check-framework-gap-coverage] cannot read ${CATALOG}: ${e.message}\n`);
63
+ process.exitCode = 1;
64
+ return;
65
+ }
66
+
67
+ const partial = [];
68
+ let checked = 0;
69
+ for (const id of Object.keys(catalog)) {
70
+ if (id === '_meta') continue;
71
+ const entry = catalog[id];
72
+ if (!entry || entry._auto_imported === true) continue;
73
+ checked++;
74
+ const gaps = entry.framework_control_gaps || {};
75
+ const have = new Set(Object.keys(gaps).map(bucketOf));
76
+ const missing = REQUIRED.filter((b) => !have.has(b));
77
+ if (missing.length) partial.push({ id, missing });
78
+ }
79
+
80
+ if (partial.length === 0) {
81
+ process.stdout.write(
82
+ `[check-framework-gap-coverage] ok — all ${checked} curated entries declare framework-gap coverage for every required jurisdiction (${REQUIRED.join(', ')}).\n`
83
+ );
84
+ return;
85
+ }
86
+
87
+ process.stdout.write(
88
+ `[check-framework-gap-coverage] FAIL: ${partial.length} of ${checked} curated entries omit at least one required jurisdiction bucket (Hard Rule #5, global-first):\n`
89
+ );
90
+ for (const p of partial.slice(0, 40)) {
91
+ process.stdout.write(` ${p.id}: missing ${p.missing.join(', ')}\n`);
92
+ }
93
+ if (partial.length > 40) {
94
+ process.stdout.write(` ... and ${partial.length - 40} more\n`);
95
+ }
96
+ process.stdout.write(
97
+ 'Add a CVE-specific framework_control_gaps statement for each missing bucket ' +
98
+ '(NIST/EU/UK/AU/ISO). Keys must resolve to a real control in data/framework-control-gaps.json.\n'
99
+ );
100
+ process.exitCode = 1;
101
+ }
102
+
103
+ main();
@@ -211,6 +211,17 @@ const GATES = [
211
211
  args: [path.join(ROOT, "scripts", "check-catalog-gap-budget.js")],
212
212
  ciJobName: "Data integrity (catalog + manifest snapshot)",
213
213
  },
214
+ {
215
+ // Global-first framework-gap coverage gate (AGENTS.md Hard Rule #5).
216
+ // Every curated CVE must declare a framework_control_gaps statement for
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.
220
+ name: "Framework-gap jurisdiction coverage (Hard Rule #5)",
221
+ command: process.execPath,
222
+ args: [path.join(ROOT, "scripts", "check-framework-gap-coverage.js")],
223
+ ciJobName: "Data integrity (catalog + manifest snapshot)",
224
+ },
214
225
  {
215
226
  // Version-tag drift gate. Compares the tracked tree against a
216
227
  // baseline snapshot of pre-existing `// vX.Y.Z` comments and