@blamejs/exceptd-skills 0.13.22 → 0.13.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.24 — 2026-05-20
4
+
5
+ `attest verify` and `attest diff` are now usable at the terminal without piping through `jq`.
6
+
7
+ ### Features
8
+
9
+ - **Human renderer for `attest verify`.** One-screen answer to "did anyone tamper with my evidence since I ran it?" — verdict icon (`[ok]` / `[!! TAMPERED]` / `[i REPLAY_TAMPER]`), per-file row with reason, then a next-step block: `attest diff` + `attest show` on a clean run, `attest show --pretty` + `attest list --playbook` on a tamper. `--json` / `--pretty` reach the structured envelope unchanged.
10
+ - **Human renderer for `attest diff`.** Status line (`[ok] status=unchanged` / `[i DRIFTED] status=drifted`), prior + replay evidence_hash + capture timestamps, replay classification + RWEP, sidecar-verify class, replay record path. When DRIFTED, points at `attest show` + a fresh `run --evidence <new>` capture.
11
+
12
+ ## 0.13.23 — 2026-05-19
13
+
14
+ Continuation of the v0.13.22 UX pass: stage-by-stage next-step guidance so an operator (or an AI walking the workflow cold) never has to ask "what do I do now?"
15
+
16
+ ### Features
17
+
18
+ - **`ci` human renderer emits a "Next steps" block per verdict.** BLOCKED → one `exceptd lint <playbook> -` command per blocked playbook plus the `--evidence <file>` re-run. NO_EVIDENCE → lint the first playbook + `ci --evidence-dir <dir>`. FAIL/detected → `run <playbook> --format markdown` / `--format csaf-2.0`. CLOCK_STARTED → `--format csaf-2.0` for the advisory draft. Pre-0.13.23 a blocked or no-evidence run printed only the reason — operators saw *why* they were stuck without the concrete command to unblock.
19
+ - **`run` verdict line surfaces evidence_completeness.** Every successful run now shows `evidence: complete (13/13 indicators evaluated)` (or `partial` / `missing`) under the classification line. Distinguishes "ran every indicator and found nothing" from "couldn't evaluate, no evidence supplied" — pre-0.13.23 both states printed identically. When evidence is partial or missing, a `→ next: exceptd lint <playbook> -` pointer is appended.
20
+ - **`run` attestation persistence is now visible.** Successful runs print `Attestation written: <full path>` followed by `exceptd attest verify <session-id>` and `exceptd attest diff <session-id>` so the operator knows where the JSON lives and how to verify or diff it. The persisted file path is also hoisted to the result envelope as `attestation_path`. Pre-0.13.23 the attestation went to `~/.exceptd/attestations/<repo>@<branch>/<session-id>/attestation.json` with zero indication in any output.
21
+ - **`run` remediation prose matches the verdict.** Pre-0.13.23 every classification printed `Recommended remediation: <id>` — misleading on `not_detected` and `inconclusive` runs, where there is nothing to remediate. Now non-detect runs print `Remediation path (informational — verdict=<x>, no action required now): <id>`; detected runs are unchanged.
22
+
23
+ ### Bugs
24
+
25
+ - **Stale playbook count in error messages.** `exceptd run <unknown-id>` (and the lint-not-found path) said "Run \`exceptd brief --all\` to list the 13 playbooks." There are 23 playbooks shipped. Now uses the live `listPlaybooks().length`.
26
+ - **`lint` did not warn on the artifact-only-no-signal_overrides path.** An operator following lint's per-artifact guidance for a nested submission populated every required artifact, ran the playbook, and got every indicator = inconclusive with no explanation. The detect phase needs `signal_overrides` (or a `verdict.classification` override) to mark indicators as hit / miss — artifact presence alone is not enough. The flat-shape path already surfaced this as `detect_will_be_inconclusive`; the nested-shape path was silent. Now lint emits `no_signal_overrides_supplied` with the exact JSON shape to add.
27
+
3
28
  ## 0.13.22 — 2026-05-19
4
29
 
5
30
  `ci` is now usable at the terminal without piping through `jq`.
package/bin/exceptd.js CHANGED
@@ -1624,7 +1624,7 @@ function buildSkillToPlaybookHint(runner, wanted) {
1624
1624
  if (matches.length > 0) {
1625
1625
  return `That is a SKILL (read-only knowledge unit), not a PLAYBOOK (executable). Skill "${wanted}" is loaded by playbook${matches.length === 1 ? "" : "s"}: ${matches.join(", ")}. ` +
1626
1626
  `To execute: \`exceptd run ${matches[0]}\`. To read the skill: \`exceptd skill ${wanted}\`. ` +
1627
- `Tip: \`exceptd brief --all\` lists all 13 playbooks; \`exceptd watch\` lists skills.`;
1627
+ `Tip: \`exceptd brief --all\` lists all ${ids.length} playbooks; \`exceptd watch\` lists skills.`;
1628
1628
  }
1629
1629
  // No matching skill either — provide nearest-playbook suggestions.
1630
1630
  // v0.12.9 (P3 #9 from production smoke): substring fallback first (cheap),
@@ -1638,7 +1638,7 @@ function buildSkillToPlaybookHint(runner, wanted) {
1638
1638
  if (near.length > 0) {
1639
1639
  return `Did you mean: ${near.join(", ")}? Run \`exceptd brief --all\` for the full list.`;
1640
1640
  }
1641
- return `Run \`exceptd brief --all\` to list the 13 playbooks.`;
1641
+ return `Run \`exceptd brief --all\` to list the ${ids.length} playbooks.`;
1642
1642
  } catch { return null; }
1643
1643
  }
1644
1644
 
@@ -2288,6 +2288,27 @@ function cmdLint(runner, args, runOpts, pretty) {
2288
2288
  hint: `Flat submission with ${observationsCount} observation(s) but no indicator+result fields and no verdict.classification. detect() will return 'inconclusive'. Each observation needs { "indicator": "<id>", "result": "hit"|"miss"|"inconclusive" } to drive an indicator outcome. Run \`exceptd run ${playbookId} --signal-list\` for the indicator IDs.`,
2289
2289
  });
2290
2290
  }
2291
+ } else {
2292
+ // v0.13.23 — nested submission with artifacts but no signal_overrides
2293
+ // also lands every indicator on inconclusive. The flat-shape branch
2294
+ // already surfaced this; the nested-shape silence was the workflow gap
2295
+ // operators hit: they fill in lint's per-artifact guidance, run, and
2296
+ // get an opaque "every indicator inconclusive" result. Surface the
2297
+ // signal_overrides shape explicitly so the operator/AI knows the
2298
+ // next step is to set `signal_overrides[<indicator-id>] = "hit" |
2299
+ // "miss" | "inconclusive"` per indicator they investigated.
2300
+ const verdictClass = submission.verdict?.classification;
2301
+ const verdictWillDrive = verdictClass === "clean" || verdictClass === "not_detected" || verdictClass === "detected" || verdictClass === "inconclusive";
2302
+ const normalizedHasOverrides = Object.keys(normalized.signal_overrides || {}).length > 0;
2303
+ const submissionHasArtifacts = Object.keys(submission.artifacts || {}).length > 0;
2304
+ if (submissionHasArtifacts && !verdictWillDrive && !normalizedHasOverrides) {
2305
+ const someIndicatorIds = [...knownIndicators].slice(0, 3).join('", "');
2306
+ issues.push({
2307
+ severity: "info",
2308
+ kind: "no_signal_overrides_supplied",
2309
+ hint: `Nested submission has artifacts but no signal_overrides — every indicator will return 'inconclusive' (verdict will be 'inconclusive', not 'detected' / 'not_detected'). To drive a concrete verdict, populate \`signal_overrides\` with the indicators you investigated: { "${someIndicatorIds}": "hit"|"miss" }. ${knownIndicators.size} indicator(s) known — see \`exceptd brief ${playbookId}\` for the full list. Alternatively, supply \`verdict.classification = "clean"|"not_detected"|"detected"\` to bypass indicator evaluation.`,
2310
+ });
2311
+ }
2291
2312
  }
2292
2313
 
2293
2314
  const ok = issues.every(i => i.severity !== "error");
@@ -2928,6 +2949,12 @@ function cmdRun(runner, args, runOpts, pretty) {
2928
2949
  result.prior_session_id = persistResult.prior_session_id;
2929
2950
  result.overwrote_at = persistResult.overwrote_at;
2930
2951
  }
2952
+ // v0.13.23 — surface the attestation file path so the human renderer
2953
+ // can echo it and the next-step guidance (attest verify <sid>) lands
2954
+ // on an artifact the operator can actually find.
2955
+ if (persistResult.attestation_path) {
2956
+ result.attestation_path = persistResult.attestation_path;
2957
+ }
2931
2958
  }
2932
2959
 
2933
2960
  if (result && result.ok === false) {
@@ -3151,6 +3178,21 @@ function cmdRun(runner, args, runOpts, pretty) {
3151
3178
  const top = rwep?.threshold?.escalate ?? "n/a";
3152
3179
  const verdictIcon = cls === "detected" ? "[!! DETECTED]" : cls === "inconclusive" ? "[i INCONCLUSIVE]" : "[ok]";
3153
3180
  lines.push(`\n${verdictIcon} classification=${cls} RWEP ${adj}/${top}${adj !== base ? ` (Δ${adj - base} from operator evidence)` : " (catalog baseline)"} blast_radius=${obj.phases?.analyze?.blast_radius_score ?? "n/a"}/5`);
3181
+ // v0.13.23 — surface evidence_completeness on the verdict line so
3182
+ // operators distinguish "ran every indicator and found nothing"
3183
+ // (evidence=complete) from "couldn't evaluate, no evidence supplied"
3184
+ // (evidence=missing). Pre-v0.13.23 the two states printed identically
3185
+ // — a not_detected run with zero evidence looked the same as one
3186
+ // with a fully-populated submission.
3187
+ if (obj.evidence_completeness && obj.indicators_known != null) {
3188
+ const ev = obj.evidence_completeness;
3189
+ const ke = obj.indicators_evaluated ?? 0;
3190
+ const kn = obj.indicators_known;
3191
+ lines.push(` evidence: ${ev} (${ke}/${kn} indicators evaluated)`);
3192
+ if (ev === "missing" || ev === "partial") {
3193
+ lines.push(` → next: exceptd lint ${obj.playbook_id} - # paste {} on stdin, see exact JSON paths to populate`);
3194
+ }
3195
+ }
3154
3196
  // F11: surface --diff-from-latest verdict in the human renderer so
3155
3197
  // operators see whether the run drifted from the previous attestation
3156
3198
  // without adding --json. One summary line follows the classification.
@@ -3189,9 +3231,20 @@ function cmdRun(runner, args, runOpts, pretty) {
3189
3231
  lines.push(`\nIndicators that fired (${hits.length}):`);
3190
3232
  for (const i of hits.slice(0, 8)) lines.push(` ${i.id} (${i.confidence}${i.deterministic ? "/deterministic" : ""})`);
3191
3233
  }
3234
+ // v0.13.23 — selected_remediation is informational on non-detect
3235
+ // runs (validate() always picks the highest-priority remediation
3236
+ // path as a "what would you do IF you found something" anchor).
3237
+ // Rendering it as "Recommended remediation:" on a not_detected /
3238
+ // inconclusive verdict misleads operators into thinking action is
3239
+ // required. Tag it conditionally so the operator-facing prose
3240
+ // matches the verdict.
3192
3241
  const rem = obj.phases?.validate?.selected_remediation;
3193
3242
  if (rem) {
3194
- lines.push(`\nRecommended remediation: ${rem.id} (priority ${rem.priority})`);
3243
+ if (cls === "detected") {
3244
+ lines.push(`\nRecommended remediation: ${rem.id} (priority ${rem.priority})`);
3245
+ } else {
3246
+ lines.push(`\nRemediation path (informational — verdict=${cls}, no action required now): ${rem.id} (priority ${rem.priority})`);
3247
+ }
3195
3248
  lines.push(` ${rem.description?.slice(0, 200) || ""}`);
3196
3249
  }
3197
3250
  const notif = (obj.phases?.close?.notification_actions || []).filter(n => n.clock_started_at);
@@ -3201,6 +3254,18 @@ function cmdRun(runner, args, runOpts, pretty) {
3201
3254
  }
3202
3255
  const feeds = obj.phases?.close?.feeds_into || [];
3203
3256
  if (feeds.length) lines.push(`\nNext playbooks suggested: ${feeds.join(", ")}`);
3257
+
3258
+ // v0.13.23 — tell the operator WHERE the attestation went and HOW
3259
+ // to verify/diff it. Pre-v0.13.23 a successful run silently wrote
3260
+ // ~/.exceptd/attestations/<repo>@<branch>/<sid>/attestation.json
3261
+ // with zero indication to the operator. The next-time-they-asked-
3262
+ // about-this-run lookup ("attest verify <sid>") then failed with
3263
+ // "no session dir" because they were in a different cwd.
3264
+ if (obj.attestation_path) {
3265
+ lines.push(`\nAttestation written: ${obj.attestation_path}`);
3266
+ lines.push(` exceptd attest verify ${obj.session_id} # tamper check`);
3267
+ lines.push(` exceptd attest diff ${obj.session_id} # vs. most-recent prior for this playbook`);
3268
+ }
3204
3269
  const issues = obj.preflight_issues || [];
3205
3270
  if (issues.length) {
3206
3271
  lines.push(`\nPreflight warnings (${issues.length}):`);
@@ -3757,7 +3822,12 @@ function persistAttestation(args) {
3757
3822
 
3758
3823
  try {
3759
3824
  writeAttestation(null, null, "wx");
3760
- return { ok: true, prior_session_id: null, overwrote_at: null };
3825
+ // v0.13.23 surface the absolute path so the caller can echo it
3826
+ // in the human renderer. Pre-v0.13.23 the attestation was silently
3827
+ // written to ~/.exceptd/attestations/<repo>@<branch>/<session-id>/
3828
+ // and the operator had no way to find it short of grepping the
3829
+ // filesystem.
3830
+ return { ok: true, prior_session_id: null, overwrote_at: null, attestation_path: filePath };
3761
3831
  } catch (eExcl) {
3762
3832
  if (eExcl.code !== "EEXIST") throw eExcl;
3763
3833
  // Slot already taken — read prior to chain audit trail, then decide.
@@ -3874,6 +3944,7 @@ function persistAttestation(args) {
3874
3944
  ok: true,
3875
3945
  prior_session_id: lockedPrior ? sessionId : null,
3876
3946
  overwrote_at: lockedPrior ? lockedPrior.captured_at : null,
3947
+ attestation_path: filePath,
3877
3948
  };
3878
3949
  } finally {
3879
3950
  try { fs.unlinkSync(lockPath); } catch {}
@@ -4525,7 +4596,32 @@ function cmdReattest(runner, args, runOpts, pretty) {
4525
4596
  // reason) so an auditor reading the CLI response can locate the
4526
4597
  // on-disk artifact without re-deriving the filename.
4527
4598
  replay_persisted: replayPersisted,
4528
- }, pretty);
4599
+ }, pretty, (obj) => {
4600
+ // v0.13.24 — human renderer for `attest diff` (reattest path).
4601
+ // Pre-v0.13.24 the only output was JSON, so an operator asking "did
4602
+ // anything change since the last run?" had to parse the envelope to
4603
+ // get the one-line answer they wanted.
4604
+ const lines = [];
4605
+ lines.push(`attest diff: ${obj.session_id} (${obj.playbook_id})`);
4606
+ const icon = obj.status === "unchanged" ? "[ok]" : "[i DRIFTED]";
4607
+ lines.push(`\n${icon} status=${obj.status}`);
4608
+ lines.push(` prior: ${obj.prior_evidence_hash} (${obj.prior_captured_at})`);
4609
+ lines.push(` replay: ${obj.replay_evidence_hash} (${obj.replayed_at})`);
4610
+ if (obj.replay_classification) {
4611
+ lines.push(` replay classification: ${obj.replay_classification} RWEP=${obj.replay_rwep_adjusted ?? 0}`);
4612
+ }
4613
+ if (obj.sidecar_verify_class) {
4614
+ lines.push(` sidecar verify: ${obj.sidecar_verify_class}`);
4615
+ }
4616
+ if (obj.replay_persisted && obj.replay_persisted.ok && obj.replay_persisted.path) {
4617
+ lines.push(` replay record: ${obj.replay_persisted.path}`);
4618
+ }
4619
+ if (obj.status === "drifted") {
4620
+ lines.push(`\n → next: exceptd attest show ${obj.session_id} # inspect the prior submission`);
4621
+ lines.push(` exceptd run ${obj.playbook_id} --evidence <new> # capture a fresh attestation against the new state`);
4622
+ }
4623
+ return lines.join("\n");
4624
+ });
4529
4625
  }
4530
4626
 
4531
4627
  /**
@@ -4843,7 +4939,63 @@ function cmdAttest(runner, args, runOpts, pretty) {
4843
4939
  body.replay_tamper = true;
4844
4940
  body.warnings = ["one or more replay records failed Ed25519 verification — audit-trail corruption suspected, regenerate via reattest"];
4845
4941
  }
4846
- emit(body, pretty);
4942
+ // v0.13.24 — human renderer for `attest verify`. Pre-v0.13.24 the
4943
+ // only output was a JSON envelope, even at the terminal. The whole
4944
+ // point of `attest verify` is "did anyone tamper with my evidence
4945
+ // since I ran it?" — that question deserves a one-line answer.
4946
+ emit(body, pretty, (obj) => {
4947
+ const lines = [];
4948
+ lines.push(`attest verify: ${obj.session_id}`);
4949
+ const att = obj.results || [];
4950
+ const rep = obj.replay_results || [];
4951
+ if (att.length === 0 && rep.length === 0) {
4952
+ lines.push(` [!! NO_DATA] no attestation files found for session.`);
4953
+ lines.push(`\n → next: exceptd attest list # browse persisted sessions`);
4954
+ return lines.join("\n");
4955
+ }
4956
+ // "Clean enough to proceed" = no tamper signal. Explicitly-unsigned
4957
+ // attestations are NOT a tamper (the operator's environment lacks
4958
+ // .keys/private.pem; this is the default on CI runners and on hosts
4959
+ // doing posture-only walks). Distinguish from real verification
4960
+ // failures (tamper_class present) so the next-step block still fires.
4961
+ const noTamper = att.every(r => !r.tamper_class) && rep.every(r => !r.tamper_class);
4962
+ const allVerified = att.every(r => r.verified) && rep.every(r => r.verified);
4963
+ const icon = obj.ok === false ? "[!! TAMPERED]" : (obj.replay_tamper ? "[i REPLAY_TAMPER]" : (allVerified ? "[ok]" : "[i UNSIGNED]"));
4964
+ lines.push(`\n${icon} ${att.filter(r => r.verified).length}/${att.length} attestation(s) verified, ${rep.filter(r => r.verified).length}/${rep.length} replay record(s) verified`);
4965
+ // Status icon precedence: verified → [ok]. tamper_class set →
4966
+ // [!! <CLASS>] (real tamper signal). signed=false AND no
4967
+ // tamper_class → [i UNSIGNED] (not a failure — the attestation
4968
+ // was legitimately written without a key, e.g. on a CI runner
4969
+ // without .keys/private.pem). signed=true but verified=false
4970
+ // without a tamper_class indicates a verify error (missing pub
4971
+ // key, read error) — [i VERIFY-ERROR].
4972
+ const statusFor = (r) => {
4973
+ if (r.verified) return "[ok]";
4974
+ if (r.tamper_class) return `[!! ${r.tamper_class.toUpperCase()}]`;
4975
+ if (r.signed === false) return "[i UNSIGNED]";
4976
+ return "[i VERIFY-ERROR]";
4977
+ };
4978
+ for (const r of att) {
4979
+ lines.push(` ${statusFor(r)} ${r.file} — ${r.reason || "(no reason)"}`);
4980
+ }
4981
+ for (const r of rep) {
4982
+ lines.push(` ${statusFor(r)} ${r.file} (replay) — ${r.reason || "(no reason)"}`);
4983
+ }
4984
+ if (obj.ok === false) {
4985
+ lines.push(`\n → next: exceptd attest show ${obj.session_id} --pretty # inspect the disputed file directly`);
4986
+ lines.push(` exceptd attest list --playbook <id> # find a non-tampered prior session for the same playbook`);
4987
+ } else if (obj.replay_tamper) {
4988
+ lines.push(`\n → next: exceptd attest diff ${obj.session_id} --force-replay # regenerate the replay record`);
4989
+ } else if (allVerified || noTamper) {
4990
+ // allVerified → signed + verified; noTamper → unsigned but not
4991
+ // tampered (legitimate CI / posture-only state). Both states are
4992
+ // safe to keep working with, so point at the same next-step
4993
+ // commands.
4994
+ lines.push(`\n → next: exceptd attest diff ${obj.session_id} # compare against prior session for this playbook`);
4995
+ lines.push(` exceptd attest show ${obj.session_id} --pretty # inspect the persisted attestation`);
4996
+ }
4997
+ return lines.join("\n");
4998
+ });
4847
4999
  return;
4848
5000
  }
4849
5001
 
@@ -6993,6 +7145,63 @@ function cmdCi(runner, args, runOpts, pretty) {
6993
7145
  for (const r of s.fail_reasons) lines.push(` - ${r}`);
6994
7146
  }
6995
7147
 
7148
+ // v0.13.23 — Next-step guidance. An operator (or an AI walking the
7149
+ // workflow cold) reading the ci output should never have to ask
7150
+ // "what do I do now?" The verdict dictates the next move:
7151
+ // BLOCKED → operator must supply evidence asserting the
7152
+ // halted preconditions; `exceptd lint <pb> -`
7153
+ // emits the exact JSON paths to fill in.
7154
+ // NO_EVIDENCE → no --evidence was supplied and every playbook
7155
+ // returned inconclusive; same lint -> run loop.
7156
+ // FAIL/detected → look at the matched_cves + recommended
7157
+ // remediation in the per-playbook results.
7158
+ // CLOCK_STARTED → notification clock running; see deadline above.
7159
+ // PASS → nothing to do.
7160
+ const blockedRows = (obj.results || []).filter(r => r && r.ok === false);
7161
+ const lintCmd = (id) => ` exceptd lint ${id} - # paste {} on stdin, get exact JSON paths`;
7162
+ if (s.verdict === "BLOCKED" && blockedRows.length) {
7163
+ lines.push(`\nNext steps (unblock the ${blockedRows.length} halted playbook(s)):`);
7164
+ for (const row of blockedRows.slice(0, 4)) {
7165
+ lines.push(lintCmd(row.playbook_id || "?"));
7166
+ }
7167
+ lines.push(` exceptd run <playbook> --evidence <file> # re-run after filling in evidence`);
7168
+ } else if (s.verdict === "NO_EVIDENCE") {
7169
+ const firstId = (obj.results[0] && obj.results[0].playbook_id) || (obj.playbooks_run[0]) || "<playbook>";
7170
+ lines.push(`\nNext steps (every playbook ran inconclusive — no evidence supplied):`);
7171
+ lines.push(lintCmd(firstId));
7172
+ lines.push(` exceptd ci --scope <type> --evidence-dir <dir> # gate again with real submissions`);
7173
+ } else if (s.verdict === "FAIL") {
7174
+ // codex P2 (PR #63): FAIL can fire in two distinct shapes —
7175
+ // (a) at least one playbook classification=detected → s.detected > 0
7176
+ // (b) inconclusive playbook(s) whose rwep_delta (operator
7177
+ // evidence) crossed the cap → s.detected stays at 0
7178
+ // Pre-fix this branch was keyed on `s.detected > 0`, so the
7179
+ // shape-(b) FAIL exited with no "Next steps" footer at all,
7180
+ // contradicting the v0.13.23 stage-by-stage promise.
7181
+ if (s.detected > 0) {
7182
+ lines.push(`\nNext steps (review the ${s.detected} detected finding(s)):`);
7183
+ lines.push(` exceptd run <playbook> --format markdown # operator-readable digest`);
7184
+ lines.push(` exceptd run <playbook> --format csaf-2.0 # advisory bundle for downstream`);
7185
+ } else {
7186
+ // Operator evidence pushed RWEP across --max-rwep cap on an
7187
+ // otherwise-inconclusive run. The fix is to review which
7188
+ // signals moved the score and decide whether they warrant
7189
+ // escalation or whether the cap is set too low for the
7190
+ // current evidence quality.
7191
+ const inconclusivePb = (obj.results || [])
7192
+ .filter(r => r && r.ok !== false && r.phases?.detect?.classification === "inconclusive")
7193
+ .map(r => r.playbook_id)
7194
+ .filter(Boolean);
7195
+ const exampleId = inconclusivePb[0] || (obj.playbooks_run && obj.playbooks_run[0]) || "<playbook>";
7196
+ lines.push(`\nNext steps (RWEP-delta cap exceeded — no playbook hit "detected", but operator evidence raised at least one score past --max-rwep):`);
7197
+ lines.push(` exceptd run ${exampleId} --pretty # inspect phases.analyze.rwep.base + adjusted to see which signal moved the score`);
7198
+ lines.push(` exceptd ci ... --max-rwep <higher> # raise the cap if the evidence-driven escalation is acceptable for your gate`);
7199
+ }
7200
+ } else if (s.verdict === "CLOCK_STARTED") {
7201
+ lines.push(`\nNext steps (jurisdiction clock running — notification deadlines above):`);
7202
+ lines.push(` exceptd run <playbook> --format csaf-2.0 # draft the operator-of-record advisory`);
7203
+ }
7204
+
6996
7205
  lines.push(`\nFull structured result: --json (or --pretty for indented JSON).`);
6997
7206
  return lines.join("\n");
6998
7207
  });
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schema_version": "1.1.0",
3
- "generated_at": "2026-05-19T22:22:02.165Z",
3
+ "generated_at": "2026-05-20T00:33:15.546Z",
4
4
  "generator": "scripts/build-indexes.js",
5
5
  "source_count": 54,
6
6
  "source_hashes": {
7
- "manifest.json": "068eb6c464d8999f41906bd3ea080b6b579d6dc15c3c1b05c1caf94adb72cd27",
7
+ "manifest.json": "4740f0bfc023012b913852e96c3085f4d843c0d13cc7f24e11a469cbbdf3f578",
8
8
  "data/atlas-ttps.json": "d296c1d3e71807c9279b731f047e57796e85137f186586743a8cdad214b408f9",
9
9
  "data/attack-techniques.json": "49b6010b317edd219def135171ea8f3b1bbf1e00e9c5a08bf7237215ff54e2c3",
10
10
  "data/cve-catalog.json": "a09c83af3f9679a7ea73935726a1ff9de2cab94b4ab6321fc017fc147747d7c3",
package/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exceptd-security",
3
- "version": "0.13.22",
3
+ "version": "0.13.24",
4
4
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation",
5
5
  "homepage": "https://exceptd.com",
6
6
  "license": "Apache-2.0",
@@ -53,7 +53,7 @@
53
53
  ],
54
54
  "last_threat_review": "2026-05-01",
55
55
  "signature": "lXhZgoIrrVloO3XaTvo/43AxZn4mwErstd7DR0O/oVhD3AOGODM4HqrageYEou9WKOdMEGP5mJNTjJsXdP5NDA==",
56
- "signed_at": "2026-05-19T22:21:07.815Z",
56
+ "signed_at": "2026-05-20T00:33:14.269Z",
57
57
  "cwe_refs": [
58
58
  "CWE-125",
59
59
  "CWE-362",
@@ -117,7 +117,7 @@
117
117
  ],
118
118
  "last_threat_review": "2026-05-01",
119
119
  "signature": "vSVqu4wBm+d68ujZmM6Rto/HzViCkE0gPUcv/MYE/bjFiqamf/s0On4kTOo1KIveV9cOwYNxiItaGEWlVkRFDg==",
120
- "signed_at": "2026-05-19T22:21:07.817Z",
120
+ "signed_at": "2026-05-20T00:33:14.270Z",
121
121
  "cwe_refs": [
122
122
  "CWE-1039",
123
123
  "CWE-1426",
@@ -180,7 +180,7 @@
180
180
  ],
181
181
  "last_threat_review": "2026-05-01",
182
182
  "signature": "RIgXKvolQjgJdnlrDnVOd90IOY1B7VHHZD/YJQRzouL+wUeOLclPrdK/EgEuFyiu7lR4bi+Pl6aGB9G9tOxYCQ==",
183
- "signed_at": "2026-05-19T22:21:07.817Z",
183
+ "signed_at": "2026-05-20T00:33:14.271Z",
184
184
  "cwe_refs": [
185
185
  "CWE-22",
186
186
  "CWE-345",
@@ -226,7 +226,7 @@
226
226
  "framework_gaps": [],
227
227
  "last_threat_review": "2026-05-01",
228
228
  "signature": "RYOxeq/o3uTwTWq4H7RcdH2Aclg9UyCERfUH9Frwkzncsowg7LgxpaEDc3swTCv73HMEGbU8wVbXguZ4JxHUCQ==",
229
- "signed_at": "2026-05-19T22:21:07.818Z"
229
+ "signed_at": "2026-05-20T00:33:14.271Z"
230
230
  },
231
231
  {
232
232
  "name": "compliance-theater",
@@ -257,7 +257,7 @@
257
257
  ],
258
258
  "last_threat_review": "2026-05-01",
259
259
  "signature": "DneJCPKCPcoe6nQ82XptqSqNfSRdt1orKaO+o7K36YCciDrzwJb+1BuBLusPDtpcdDaGY0y0e+AqiTYJklhBAQ==",
260
- "signed_at": "2026-05-19T22:21:07.818Z"
260
+ "signed_at": "2026-05-20T00:33:14.272Z"
261
261
  },
262
262
  {
263
263
  "name": "exploit-scoring",
@@ -286,7 +286,7 @@
286
286
  ],
287
287
  "last_threat_review": "2026-05-01",
288
288
  "signature": "NA1hoQycvQhSUoG5rwlXX0mOVmGxoXRVezkELGEA2nZOdGis4gXkHT3O6Sfw7zxE4JuMrsCb65TEeOWk9WEPDg==",
289
- "signed_at": "2026-05-19T22:21:07.819Z"
289
+ "signed_at": "2026-05-20T00:33:14.272Z"
290
290
  },
291
291
  {
292
292
  "name": "rag-pipeline-security",
@@ -323,7 +323,7 @@
323
323
  ],
324
324
  "last_threat_review": "2026-05-01",
325
325
  "signature": "XgrzcA2brPhXrSTxrcLnJec0OpgGYJBoSTUlJ10UdePHffxqb9LTVGnfbmEk1ykQifXREZexui2bG7X/+eFfCQ==",
326
- "signed_at": "2026-05-19T22:21:07.819Z",
326
+ "signed_at": "2026-05-20T00:33:14.273Z",
327
327
  "cwe_refs": [
328
328
  "CWE-1395",
329
329
  "CWE-1426"
@@ -380,7 +380,7 @@
380
380
  ],
381
381
  "last_threat_review": "2026-05-01",
382
382
  "signature": "9+hZlZOqZdeACUmamQk66L5levZhhwnFXuYRhdT6Mce99eQaKT7wNfWq12hXQztkRcVRKaFH+a01zwJQwsRQCA==",
383
- "signed_at": "2026-05-19T22:21:07.819Z",
383
+ "signed_at": "2026-05-20T00:33:14.273Z",
384
384
  "d3fend_refs": [
385
385
  "D3-CA",
386
386
  "D3-CSPP",
@@ -415,7 +415,7 @@
415
415
  "framework_gaps": [],
416
416
  "last_threat_review": "2026-05-01",
417
417
  "signature": "ciqhVloMWWXEigPZvvwoV2c54tEqsDqsoc+sS/mNTFFJk2H+tz2+XUrgfEPRuYw0FeyNB6/+27pL2NpKHzUqAg==",
418
- "signed_at": "2026-05-19T22:21:07.819Z",
418
+ "signed_at": "2026-05-20T00:33:14.273Z",
419
419
  "cwe_refs": [
420
420
  "CWE-1188"
421
421
  ]
@@ -443,7 +443,7 @@
443
443
  "framework_gaps": [],
444
444
  "last_threat_review": "2026-05-01",
445
445
  "signature": "xiHAhhdufm9hCKU8PLiPE0MX65ej2F4OZwtlWLGLCiie9/km+Kiqbt192LcMvr94v83C98pb9wIaqFsFWft6AQ==",
446
- "signed_at": "2026-05-19T22:21:07.820Z"
446
+ "signed_at": "2026-05-20T00:33:14.274Z"
447
447
  },
448
448
  {
449
449
  "name": "global-grc",
@@ -475,7 +475,7 @@
475
475
  "framework_gaps": [],
476
476
  "last_threat_review": "2026-05-01",
477
477
  "signature": "oYsSk35N2Uzq7MRofACykylcVwkgPhI4luWZ14vmQT+gUKLyZiKVOUJbe1+7lGl6BYPRN0sUDQ0f7S5Eu5w2Ag==",
478
- "signed_at": "2026-05-19T22:21:07.820Z"
478
+ "signed_at": "2026-05-20T00:33:14.274Z"
479
479
  },
480
480
  {
481
481
  "name": "zeroday-gap-learn",
@@ -502,7 +502,7 @@
502
502
  "framework_gaps": [],
503
503
  "last_threat_review": "2026-05-01",
504
504
  "signature": "igRqYyU1unRFH40BsPyAR62SPrk8QZv8dPGb8S9O9EvLCNOZAzm3t+HdT/NKqzWHwrpomOzkkkyLfYI/0qTUDA==",
505
- "signed_at": "2026-05-19T22:21:07.820Z"
505
+ "signed_at": "2026-05-20T00:33:14.274Z"
506
506
  },
507
507
  {
508
508
  "name": "pqc-first",
@@ -554,7 +554,7 @@
554
554
  ],
555
555
  "last_threat_review": "2026-05-01",
556
556
  "signature": "vhc3wuQEro/86s1ro2b/KakUXg8QVnySYTBqA7ebzv9oeR2HYO5bvGEJp3oOHWtL37JDqcCAHYadSN/qxIyCCA==",
557
- "signed_at": "2026-05-19T22:21:07.821Z",
557
+ "signed_at": "2026-05-20T00:33:14.275Z",
558
558
  "cwe_refs": [
559
559
  "CWE-327"
560
560
  ],
@@ -601,7 +601,7 @@
601
601
  ],
602
602
  "last_threat_review": "2026-05-01",
603
603
  "signature": "MS35nWm8djfJGn4OOoT0JKJ2aO+Dkbb6wOOWJYvNZlRKT3UGA59o2gxg1JOnD20hb/RwxtkmCujhl2tuYSR+AQ==",
604
- "signed_at": "2026-05-19T22:21:07.821Z"
604
+ "signed_at": "2026-05-20T00:33:14.275Z"
605
605
  },
606
606
  {
607
607
  "name": "security-maturity-tiers",
@@ -638,7 +638,7 @@
638
638
  ],
639
639
  "last_threat_review": "2026-05-01",
640
640
  "signature": "8Px1s2lDj10/Q6erwEQlXgUHM1+OTruUR8qAHPX7Oo3k/l69N6P9sm0PsafS9wDFtj9l5C/OiLiFgzMlMt6vBw==",
641
- "signed_at": "2026-05-19T22:21:07.821Z",
641
+ "signed_at": "2026-05-20T00:33:14.275Z",
642
642
  "cwe_refs": [
643
643
  "CWE-1188"
644
644
  ]
@@ -673,7 +673,7 @@
673
673
  "framework_gaps": [],
674
674
  "last_threat_review": "2026-05-11",
675
675
  "signature": "WAu5fRirzSOcnnZsTx2d/JJZwa/LPpXCi+31qATTGLmoNuhyy81k3ooPe9kCM3E0CLMtvTePg9DagYqBninZDQ==",
676
- "signed_at": "2026-05-19T22:21:07.822Z"
676
+ "signed_at": "2026-05-20T00:33:14.276Z"
677
677
  },
678
678
  {
679
679
  "name": "attack-surface-pentest",
@@ -744,7 +744,7 @@
744
744
  "PTES revision incorporating AI-surface enumeration"
745
745
  ],
746
746
  "signature": "7eEwCXFd9pDKUw7yCUbRJSjfzozE44dwwwemCQUPm8JBPztLltibD9bL/RszSbYyCrYJmVb5Drncz2cGe62gCw==",
747
- "signed_at": "2026-05-19T22:21:07.822Z"
747
+ "signed_at": "2026-05-20T00:33:14.276Z"
748
748
  },
749
749
  {
750
750
  "name": "fuzz-testing-strategy",
@@ -804,7 +804,7 @@
804
804
  "OSS-Fuzz-Gen / AI-assisted harness generation becoming the default expectation for OSS maintainers"
805
805
  ],
806
806
  "signature": "Z7ypCUnXx8JpLtgxxB6RHNi39w74AmrGY1N4ofAGCXhkuM2EaFVm1AU0dvl9UQ1bVLfHKEDGqMO/TwlIY7RABg==",
807
- "signed_at": "2026-05-19T22:21:07.822Z"
807
+ "signed_at": "2026-05-20T00:33:14.276Z"
808
808
  },
809
809
  {
810
810
  "name": "dlp-gap-analysis",
@@ -879,7 +879,7 @@
879
879
  "Quebec Law 25, India DPDPA, KSA PDPL enforcement actions naming AI-tool prompt data as in-scope personal information"
880
880
  ],
881
881
  "signature": "fgxG344JGYBWWWwFXZ1IzGipWKP7EyBhrsvsbsb0CCGXfv/MvNHVNI6G0zQddCsWX1JeQbhZT3Vk8v1uJKDTDA==",
882
- "signed_at": "2026-05-19T22:21:07.822Z"
882
+ "signed_at": "2026-05-20T00:33:14.277Z"
883
883
  },
884
884
  {
885
885
  "name": "supply-chain-integrity",
@@ -956,7 +956,7 @@
956
956
  "OpenSSF model-signing — emerging Sigstore-based signing standard for ML model weights; track for production adoption"
957
957
  ],
958
958
  "signature": "pcLrM98A3vUSZRjwNAk0aZ9umvOwB41XCLLsCOy/IebB2F/06oIrGUKkMHtHwm4pTVPShMMcKdZQQ3jz30FnCg==",
959
- "signed_at": "2026-05-19T22:21:07.823Z"
959
+ "signed_at": "2026-05-20T00:33:14.277Z"
960
960
  },
961
961
  {
962
962
  "name": "defensive-countermeasure-mapping",
@@ -1013,7 +1013,7 @@
1013
1013
  ],
1014
1014
  "last_threat_review": "2026-05-11",
1015
1015
  "signature": "gqF8eU3VBrZhO2WnlcqKa7wm1d2mmWtvpbmx0kNCgHojNV+qEt+Ij84RO6bZvaUqhfYPWizWL79Fa4DL0curAQ==",
1016
- "signed_at": "2026-05-19T22:21:07.823Z"
1016
+ "signed_at": "2026-05-20T00:33:14.277Z"
1017
1017
  },
1018
1018
  {
1019
1019
  "name": "identity-assurance",
@@ -1080,7 +1080,7 @@
1080
1080
  "d3fend_refs": [],
1081
1081
  "last_threat_review": "2026-05-11",
1082
1082
  "signature": "Wv5hGMeHjlaQK1zwicVCA7AvdKgJBgvcjdpGM9Ywahh9tagAKhbkOjybowDQZzu7OZ3bDkbh6pBYc1Sdwr6NAA==",
1083
- "signed_at": "2026-05-19T22:21:07.823Z"
1083
+ "signed_at": "2026-05-20T00:33:14.278Z"
1084
1084
  },
1085
1085
  {
1086
1086
  "name": "ot-ics-security",
@@ -1136,7 +1136,7 @@
1136
1136
  "d3fend_refs": [],
1137
1137
  "last_threat_review": "2026-05-11",
1138
1138
  "signature": "8t5qKHd3yWi57dvG36YQkLN/X9bQWqtEiYjay4IfSmqhJpM/xXPaQVKNGz3wscrO8OLKUZ0OaX7Mj5kzpgBKBQ==",
1139
- "signed_at": "2026-05-19T22:21:07.824Z"
1139
+ "signed_at": "2026-05-20T00:33:14.278Z"
1140
1140
  },
1141
1141
  {
1142
1142
  "name": "coordinated-vuln-disclosure",
@@ -1188,7 +1188,7 @@
1188
1188
  "NYDFS 23 NYCRR 500 amendments potentially adding explicit CVD program requirements"
1189
1189
  ],
1190
1190
  "signature": "GDGt4UPqBa04PjlpSmpyihGzd3OgfBN7jaAK5tfwp+LRSs3ygKOdbeivUCCHNagTY1hE6hG2Ou40ADfBFuXeAg==",
1191
- "signed_at": "2026-05-19T22:21:07.824Z"
1191
+ "signed_at": "2026-05-20T00:33:14.278Z"
1192
1192
  },
1193
1193
  {
1194
1194
  "name": "threat-modeling-methodology",
@@ -1238,7 +1238,7 @@
1238
1238
  "PASTA v2 updates incorporating AI/ML application threats"
1239
1239
  ],
1240
1240
  "signature": "rFBpOQEJUPpl+v88Lw/WqVJRhTl80vy0VbPAbzQj3Q0suJRRrJg368I9uKu5LXIBKFDvKxnGIcIzbGg9NUtaCA==",
1241
- "signed_at": "2026-05-19T22:21:07.824Z"
1241
+ "signed_at": "2026-05-20T00:33:14.279Z"
1242
1242
  },
1243
1243
  {
1244
1244
  "name": "webapp-security",
@@ -1312,7 +1312,7 @@
1312
1312
  "d3fend_refs": [],
1313
1313
  "last_threat_review": "2026-05-11",
1314
1314
  "signature": "ux85YI4t2mVHOyt744Yin1HHy+z11JIFygjKfFfQOBBl5QVV3A267jeIy7utix85irMcpZm/T3yx/ooqiK2tBA==",
1315
- "signed_at": "2026-05-19T22:21:07.825Z"
1315
+ "signed_at": "2026-05-20T00:33:14.279Z"
1316
1316
  },
1317
1317
  {
1318
1318
  "name": "ai-risk-management",
@@ -1362,7 +1362,7 @@
1362
1362
  "d3fend_refs": [],
1363
1363
  "last_threat_review": "2026-05-11",
1364
1364
  "signature": "IIXnkZ5ZNqFwOto5KfytADTLLZLoyXNZACD1ORZ40P1HUAQxe6u2uyXFzzsfuob4Uy06jNkRGr2FFgCphUH1Cw==",
1365
- "signed_at": "2026-05-19T22:21:07.825Z"
1365
+ "signed_at": "2026-05-20T00:33:14.279Z"
1366
1366
  },
1367
1367
  {
1368
1368
  "name": "sector-healthcare",
@@ -1422,7 +1422,7 @@
1422
1422
  "d3fend_refs": [],
1423
1423
  "last_threat_review": "2026-05-11",
1424
1424
  "signature": "AhF9KF8ZBlDteciV+F8IBSmFVYCvQOn44GmD4rZjgLoPxfIv/QE1/vSkK32zyqDKtHWkLSXExbkkPkxA/V6dDw==",
1425
- "signed_at": "2026-05-19T22:21:07.825Z"
1425
+ "signed_at": "2026-05-20T00:33:14.280Z"
1426
1426
  },
1427
1427
  {
1428
1428
  "name": "sector-financial",
@@ -1503,7 +1503,7 @@
1503
1503
  "TIBER-EU framework v2.0 alignment with DORA TLPT RTS (JC 2024/40); cross-recognition with CBEST and iCAST"
1504
1504
  ],
1505
1505
  "signature": "HQgZvb4ReziEz5rNFr8i/O8/rJEZR+iHRROT7m/D2QUqhrcNISPkYXENsUZlG8xapzy/Ik92ehkseyj4hdmhCQ==",
1506
- "signed_at": "2026-05-19T22:21:07.826Z"
1506
+ "signed_at": "2026-05-20T00:33:14.280Z"
1507
1507
  },
1508
1508
  {
1509
1509
  "name": "sector-federal-government",
@@ -1572,7 +1572,7 @@
1572
1572
  "Australia PSPF 2024 revision and ISM quarterly updates — track for Essential Eight Maturity Level requirements for federal entities"
1573
1573
  ],
1574
1574
  "signature": "linxmsXZiOYtcs71sSWgGCrvb8xQfmxmtTY5PRvZJ0/8FgJulo0tQtejzexYG775s7XhjAmGsDP238BQTQ8ADA==",
1575
- "signed_at": "2026-05-19T22:21:07.826Z"
1575
+ "signed_at": "2026-05-20T00:33:14.281Z"
1576
1576
  },
1577
1577
  {
1578
1578
  "name": "sector-energy",
@@ -1637,7 +1637,7 @@
1637
1637
  "ICS-CERT advisory feed (https://www.cisa.gov/news-events/cybersecurity-advisories/ics-advisories) for vendor CVEs in Siemens, Rockwell, Schneider Electric, ABB, GE Vernova, Hitachi Energy, AVEVA / OSIsoft PI"
1638
1638
  ],
1639
1639
  "signature": "JjBfc0ovta560Clk0x3QGRM5osFJDwcvpy3rT7QEGdCIL827jzE8QCow1C8deXq+4JhY2sA/d7/8IsxikdlkCg==",
1640
- "signed_at": "2026-05-19T22:21:07.827Z"
1640
+ "signed_at": "2026-05-20T00:33:14.281Z"
1641
1641
  },
1642
1642
  {
1643
1643
  "name": "sector-telecom",
@@ -1723,7 +1723,7 @@
1723
1723
  "O-RAN SFG / WG11 security specifications"
1724
1724
  ],
1725
1725
  "signature": "JWVxKFoKrbX4d+Tko1d4OBdwyg25MfFFKn4CT6E/CzH+YwnU3T6Y76uBQIKg3+gIGTvPduqyvQwQQ5FxKDuPBw==",
1726
- "signed_at": "2026-05-19T22:21:07.827Z"
1726
+ "signed_at": "2026-05-20T00:33:14.281Z"
1727
1727
  },
1728
1728
  {
1729
1729
  "name": "api-security",
@@ -1792,7 +1792,7 @@
1792
1792
  "d3fend_refs": [],
1793
1793
  "last_threat_review": "2026-05-11",
1794
1794
  "signature": "BmCRCestWqr55+fCynEhtAl5NWLT+xLTkpwS0Icp3SaoZOw/ce3Y6TtqjHRSKn4CBJq7YDiLRWxmhO3MStvOAA==",
1795
- "signed_at": "2026-05-19T22:21:07.827Z"
1795
+ "signed_at": "2026-05-20T00:33:14.282Z"
1796
1796
  },
1797
1797
  {
1798
1798
  "name": "cloud-security",
@@ -1873,7 +1873,7 @@
1873
1873
  "CISA KEV additions for cloud-control-plane CVEs (IMDSv1 abuses, federation token mishandling, cross-tenant boundary failures); CISA Cybersecurity Advisories for cross-cloud advisories"
1874
1874
  ],
1875
1875
  "signature": "/DV3pmZwrRySrk1OCbyI+0BQESacjupJfUX3eC2NGtXuYOBro0vndIP+z27heFxumnjU3a9sfla7/U9X+pqnDw==",
1876
- "signed_at": "2026-05-19T22:21:07.827Z"
1876
+ "signed_at": "2026-05-20T00:33:14.282Z"
1877
1877
  },
1878
1878
  {
1879
1879
  "name": "container-runtime-security",
@@ -1935,7 +1935,7 @@
1935
1935
  "d3fend_refs": [],
1936
1936
  "last_threat_review": "2026-05-11",
1937
1937
  "signature": "E2UGSf9ATyYgzBr8uM/0ubOUmDqo1jVA7f9mVxv6LHfWGCNuQNXDyuNou9VAmUCeeXEeUYIi3AFjXkJqpOkxDA==",
1938
- "signed_at": "2026-05-19T22:21:07.828Z"
1938
+ "signed_at": "2026-05-20T00:33:14.282Z"
1939
1939
  },
1940
1940
  {
1941
1941
  "name": "mlops-security",
@@ -2006,7 +2006,7 @@
2006
2006
  "MITRE ATLAS v5.6.0 (released February 2026) shipped the AML.T0010 sub-technique expansion this forecast tracked plus new techniques (\"Publish Poisoned AI Agent Tool\", \"Escape to Host\"); inventory now 16 tactics, 84 techniques, 56 sub-techniques. Forward watch: ATLAS v5.5 / v6.0 — track next-cadence updates to agentic-AI TTPs and MLOps-pipeline-specific techniques"
2007
2007
  ],
2008
2008
  "signature": "BGNE6ZQWBA1LmsUFe8tU0L67iGDSrFqiuqaZD2f1KqfcyqqzQfMs9PWNHFzxxaJmXeKlm87eU8lgELF0bX+RBA==",
2009
- "signed_at": "2026-05-19T22:21:07.828Z"
2009
+ "signed_at": "2026-05-20T00:33:14.283Z"
2010
2010
  },
2011
2011
  {
2012
2012
  "name": "incident-response-playbook",
@@ -2068,7 +2068,7 @@
2068
2068
  "NYDFS 23 NYCRR 500.17 amendments tightening ransom-payment 24h disclosure operationalization"
2069
2069
  ],
2070
2070
  "signature": "FkZQerh3VHVJAwIcCktDyMRh5KE2+Em/i0ek8zEz7JG/PXtQx8ujHWTh3VjZbOLhPNtdB2qxgXOIAYIofaVOAQ==",
2071
- "signed_at": "2026-05-19T22:21:07.829Z"
2071
+ "signed_at": "2026-05-20T00:33:14.283Z"
2072
2072
  },
2073
2073
  {
2074
2074
  "name": "ransomware-response",
@@ -2148,7 +2148,7 @@
2148
2148
  ],
2149
2149
  "last_threat_review": "2026-05-15",
2150
2150
  "signature": "n3UToNuN3A1HgLvcuqmIx8vrZY71+r/79waK92jG+rSX4uYOzkmxMUpROrE5K9bDwMezNBHdjWv8Uul6zugyDQ==",
2151
- "signed_at": "2026-05-19T22:21:07.829Z"
2151
+ "signed_at": "2026-05-20T00:33:14.283Z"
2152
2152
  },
2153
2153
  {
2154
2154
  "name": "email-security-anti-phishing",
@@ -2201,7 +2201,7 @@
2201
2201
  "d3fend_refs": [],
2202
2202
  "last_threat_review": "2026-05-11",
2203
2203
  "signature": "rK+WnuS+9tqEABmwc0jO/PEmxcLjG1/tmUb897HsClQeKzf+TQOlwBE+OsbtuKxpjYNwur62Xxs3TxObkwm8Cw==",
2204
- "signed_at": "2026-05-19T22:21:07.829Z"
2204
+ "signed_at": "2026-05-20T00:33:14.284Z"
2205
2205
  },
2206
2206
  {
2207
2207
  "name": "age-gates-child-safety",
@@ -2269,7 +2269,7 @@
2269
2269
  "US state adult-site age-verification laws — 19+ states by mid-2026 (TX HB 18 upheld by SCOTUS June 2025 in Free Speech Coalition v. Paxton); track ongoing challenges in remaining states"
2270
2270
  ],
2271
2271
  "signature": "+OO0RhQ303RJV7kaH38IuZpLeQbapep6Ds4Re/WEZu0FHBwKSlwvF7jbtP7KQ57xldJYn/xZm2jaszyOacMfDg==",
2272
- "signed_at": "2026-05-19T22:21:07.830Z"
2272
+ "signed_at": "2026-05-20T00:33:14.284Z"
2273
2273
  },
2274
2274
  {
2275
2275
  "name": "cloud-iam-incident",
@@ -2349,7 +2349,7 @@
2349
2349
  ],
2350
2350
  "last_threat_review": "2026-05-15",
2351
2351
  "signature": "e/kij7GtKaytROyIj7V5RH+FC9WtmVFzrmG2kIlNDNn29ep/CRNlIQKwXLpzo/81AIf634pmdr1qy/+vwIuUDA==",
2352
- "signed_at": "2026-05-19T22:21:07.830Z"
2352
+ "signed_at": "2026-05-20T00:33:14.284Z"
2353
2353
  },
2354
2354
  {
2355
2355
  "name": "idp-incident-response",
@@ -2430,11 +2430,11 @@
2430
2430
  ],
2431
2431
  "last_threat_review": "2026-05-15",
2432
2432
  "signature": "ew9Kglc9fAZzbn0ZIfGP7WSK/j4eV2VhSvpy+s5bEfNEVYIMa2kZjnGBapgUsyGDLes9H9K2ovjQyX17+GKiBw==",
2433
- "signed_at": "2026-05-19T22:21:07.830Z"
2433
+ "signed_at": "2026-05-20T00:33:14.285Z"
2434
2434
  }
2435
2435
  ],
2436
2436
  "manifest_signature": {
2437
2437
  "algorithm": "Ed25519",
2438
- "signature_base64": "6RWl1bUtDhlyizAkvu6GBBmMFW7EvtD68X552wCliURN1GsvyvercoB2JDdXJFwR2rNZdlFuhxpWa7UR+4vGAg=="
2438
+ "signature_base64": "UR1pyH7Jbsptyh55rc9Z7G2d1f3V6Yz9Z3ssxrBGjj/HsvsCLrbDwGJ6g5oEt65YLm/Ef6P9zzq6sTH19NymCw=="
2439
2439
  }
2440
2440
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/exceptd-skills",
3
- "version": "0.13.22",
3
+ "version": "0.13.24",
4
4
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 10 catalogs (312 CVEs / 171 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 7476 RFCs), 34 jurisdictions, 10-class catalog gap detector + budget gate, real XML parser + canonical-form diff + content-pattern regression detection, Ed25519-signed.",
5
5
  "keywords": [
6
6
  "ai-security",
package/sbom.cdx.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.6",
4
- "serialNumber": "urn:uuid:7f62a713-9c4b-42e1-bfdf-a445644dcc1a",
4
+ "serialNumber": "urn:uuid:cfdcbf8e-e88f-44e0-b871-5e7ca0e19f9c",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2093-09-21T18:48:51.000Z",
7
+ "timestamp": "2136-07-05T20:31:10.000Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "blamejs",
11
11
  "name": "scripts/refresh-sbom.js",
12
- "version": "0.13.22"
12
+ "version": "0.13.24"
13
13
  }
14
14
  ],
15
15
  "component": {
16
- "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.13.22",
16
+ "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.13.24",
17
17
  "type": "application",
18
18
  "name": "@blamejs/exceptd-skills",
19
- "version": "0.13.22",
19
+ "version": "0.13.24",
20
20
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 10 catalogs (312 CVEs / 171 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 7476 RFCs), 34 jurisdictions, 10-class catalog gap detector + budget gate, real XML parser + canonical-form diff + content-pattern regression detection, Ed25519-signed.",
21
21
  "licenses": [
22
22
  {
@@ -25,17 +25,17 @@
25
25
  }
26
26
  }
27
27
  ],
28
- "purl": "pkg:npm/%40blamejs/exceptd-skills@0.13.22",
28
+ "purl": "pkg:npm/%40blamejs/exceptd-skills@0.13.24",
29
29
  "hashes": [
30
30
  {
31
31
  "alg": "SHA-256",
32
- "content": "dfcb0e018fbcbd75bfe5b68c68f21a46b30eb98e9663a6ca8a2b3318a447abbd"
32
+ "content": "eb0888b7437c0c6d63ac7afdf5bdba1885f2fed74eceb86ed7eff97a3bce22e6"
33
33
  }
34
34
  ],
35
35
  "externalReferences": [
36
36
  {
37
37
  "type": "distribution",
38
- "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.13.22"
38
+ "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.13.24"
39
39
  },
40
40
  {
41
41
  "type": "vcs",
@@ -116,11 +116,11 @@
116
116
  "hashes": [
117
117
  {
118
118
  "alg": "SHA-256",
119
- "content": "f8f20d7d8a9c4a826dfe21240752016c9ddaa6e02c16b0c37fa04461dbb78c2b"
119
+ "content": "430120b0433642c04374e2da25fc41df0258ceba4280398fa9d7ed22a18b018e"
120
120
  },
121
121
  {
122
122
  "alg": "SHA3-512",
123
- "content": "fd4d4db63a2eae9d1cac3e745d53e3ec0af66bf7847cdba8922a9044d9c7658d348e0f82334e4642e06409c766493dd69ae16e78593e3e1fe2882a1025d1a33a"
123
+ "content": "45b40d2ccac8e4accd19291c159d7ca4919684b18ceb53264cb808f351c3510073c9228c1f6cb131ebe66ce7358f0fe6adc5bc177e5d81661c87f45190a3b2bf"
124
124
  }
125
125
  ]
126
126
  },
@@ -281,11 +281,11 @@
281
281
  "hashes": [
282
282
  {
283
283
  "alg": "SHA-256",
284
- "content": "41c11c8960c05923ed4650f2a451029bd41f4149f24baf9f8d77dc5a5974bd12"
284
+ "content": "3e7347a2e6cd07fa465648be7a35a1c696c2f4b5d52f8f9e6977e33d48c17cb9"
285
285
  },
286
286
  {
287
287
  "alg": "SHA3-512",
288
- "content": "27d6e17884c3a8e18a42a424086ebaaf48f1cfb9f8da0b83f91b16af7fb36cc3e1de9f05b39176310ecdbd8b84eb6e627134f64a4c37df9d32fededd72c14d80"
288
+ "content": "292aa5fba8a763c2cc7f5cd6bcb5f36c8648f542f1d04d61553786dcdc7196f74a8e8e9c000363958fe5ca7316576a7b80779896ddc978d882078569cef77bb2"
289
289
  }
290
290
  ]
291
291
  },
@@ -1451,11 +1451,11 @@
1451
1451
  "hashes": [
1452
1452
  {
1453
1453
  "alg": "SHA-256",
1454
- "content": "068eb6c464d8999f41906bd3ea080b6b579d6dc15c3c1b05c1caf94adb72cd27"
1454
+ "content": "4740f0bfc023012b913852e96c3085f4d843c0d13cc7f24e11a469cbbdf3f578"
1455
1455
  },
1456
1456
  {
1457
1457
  "alg": "SHA3-512",
1458
- "content": "258f9d6343d5b9a2d983ed483e8038d2442d7830fe1d5a8b84541bd8fbae9a2dbd23a682c7e34cc769fea48ca22bd7cfc49ed21ba8f3dab7bac384f77ea24490"
1458
+ "content": "2e9bb5c05229e90c14ea6e5105e84209e4b9146dd10e6cb9702d73b6cda90553d87a63cc0cc63a1e2e5cf452e10bb3f6e6eefb235e2c6240a922017d5b847421"
1459
1459
  }
1460
1460
  ]
1461
1461
  },