@blamejs/core 0.8.83 → 0.8.86

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/lib/audit.js CHANGED
@@ -294,6 +294,7 @@ var FRAMEWORK_NAMESPACES = [
294
294
  "mailbimi", // b.mail.bimi (mail.bimi.vmc.fetched / verified — RFC 9091 VMC chain validation)
295
295
  "localdb", // b.localDb.thin (localdb.thin.opened / recovered / closed — desktop-daemon SQLite wrapper)
296
296
  "dataact", // b.dataAct (EU Data Act 2023/2854 — product_declared / user_access / share_with_third_party / share_refused / switch_request)
297
+ "idempotency", // b.middleware.idempotencyKey (idempotency.missing_key / bad_key / replay / key_reuse_mismatch / cache_store / store_read_failed / store_write_failed / skip_5xx / body_too_large — draft-ietf-httpapi-idempotency-key)
297
298
  ];
298
299
  var registeredNamespaces = new Set(FRAMEWORK_NAMESPACES);
299
300
 
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.cacheStatus
4
+ * @nav HTTP
5
+ * @title RFC 9211 Cache-Status
6
+ * @order 310
7
+ *
8
+ * @intro
9
+ * RFC 9211 Cache-Status response header builder + parser. The
10
+ * `Cache-Status` header documents which intermediate cache (CDN,
11
+ * reverse proxy, application cache) handled a request — operators
12
+ * diagnosing why a request was slow / stale / not-cached read the
13
+ * header and see the entire cache-decision chain instead of
14
+ * guessing from elapsed-time metrics.
15
+ *
16
+ * Each cache in the response path appends a comma-separated entry:
17
+ *
18
+ * Cache-Status: ExampleCache; hit; fwd=stale; ttl=600
19
+ *
20
+ * Where:
21
+ * - The first token is the cache identifier (sf-string)
22
+ * - Parameters follow as `key` or `key=value` pairs
23
+ * - Standard parameters per RFC 9211 §2: `hit`, `fwd`, `fwd-status`,
24
+ * `ttl`, `stored`, `collapsed`, `key`, `detail`
25
+ *
26
+ * `b.cacheStatus.append(prevHeader, entry)` builds a single
27
+ * well-formed entry and appends to whatever previous caches in the
28
+ * chain wrote. `b.cacheStatus.parse(headerValue)` returns the
29
+ * parsed chain as an array of `{ cache, params }` records.
30
+ *
31
+ * @card
32
+ * RFC 9211 Cache-Status header — documents which intermediate caches handled a request with structured `hit` / `fwd` / `ttl` parameters so operators diagnose cache-decision chains.
33
+ */
34
+
35
+ var validateOpts = require("./validate-opts");
36
+ var { defineClass } = require("./framework-error");
37
+
38
+ var CacheStatusError = defineClass("CacheStatusError", { alwaysPermanent: true });
39
+
40
+ // RFC 9211 §2 — cache identifier is a Structured-Fields Item: sf-token
41
+ // (RFC 8941 §3.3.4) OR sf-string. We accept sf-token shape bare; an
42
+ // operator wanting an identifier with sf-delimiter chars (comma /
43
+ // semicolon / quote / backslash / whitespace) can emit it quoted via
44
+ // the operator-side sf-string form themselves, but this builder
45
+ // refuses raw delimiters since they would split into multiple list
46
+ // members or break the parameter grammar downstream. Token grammar
47
+ // per RFC 8941: starts with ALPHA or "*", continues with tchar / ":"
48
+ // / "/". tchar excludes `, ; " \ space and all controls.
49
+ var CACHE_NAME_RE = /^[A-Za-z*][!#$%&'*+\-.^_`|~0-9A-Za-z:/]*$/; // allow:duplicate-regex — sf-token shape per RFC 8941 §3.3.4
50
+ var CACHE_NAME_MAX = 128; // allow:raw-byte-literal — cache-name length cap, not bytes
51
+ var FWD_VALUES = Object.freeze(["bypass", "method", "uri-miss", "vary-miss", "miss", "request", "stale", "partial"]);
52
+ var BOOLEAN_PARAMS = Object.freeze(["hit", "stored", "collapsed"]);
53
+ // Reserved parameter names per RFC 9211 §2 — the framework knows their
54
+ // semantics (hit/stored/collapsed are flags, fwd is enum, ttl is number,
55
+ // fwd-status is HTTP status, key + detail are sf-strings). Operators
56
+ // passing other keys get passed-through verbatim as token=value.
57
+ var KNOWN_PARAMS = Object.freeze(["hit", "fwd", "fwd-status", "ttl", "stored", "collapsed", "key", "detail"]);
58
+
59
+ function _sfStringQuote(s) {
60
+ // RFC 8941 sf-string — quoted-string with escaping for " and \.
61
+ // Operator-supplied detail/key strings get the full quote-escape.
62
+ return "\"" + String(s).replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"";
63
+ }
64
+
65
+ /**
66
+ * @primitive b.cacheStatus.append
67
+ * @signature b.cacheStatus.append(prevHeader, entry)
68
+ * @since 0.8.86
69
+ * @status stable
70
+ * @related b.cacheStatus.parse, b.cacheStatus.entry
71
+ *
72
+ * Append a Cache-Status entry to an existing chain header. `prevHeader`
73
+ * is the inbound Cache-Status string (empty / undefined / null means
74
+ * "this is the first entry"). `entry` is an object describing the
75
+ * current cache's decision. Returns the combined header string.
76
+ *
77
+ * @opts
78
+ * cache: string, // required — cache identifier (e.g. "ExampleCDN")
79
+ * hit: boolean, // true if served from cache
80
+ * fwd: string, // one of: bypass | method | uri-miss | vary-miss
81
+ * // | miss | request | stale | partial
82
+ * fwdStatus: number, // HTTP status the upstream returned (when fwd)
83
+ * ttl: number, // remaining freshness lifetime in seconds
84
+ * stored: boolean, // true if the response was newly stored
85
+ * collapsed: boolean, // true if request-collapsing merged this with another
86
+ * key: string, // operator-defined cache-key shape
87
+ * detail: string, // free-form diagnostic note
88
+ *
89
+ * @example
90
+ * res.setHeader("Cache-Status",
91
+ * b.cacheStatus.append(req.headers["cache-status"], {
92
+ * cache: "blamejs",
93
+ * hit: false,
94
+ * fwd: "miss",
95
+ * stored: true,
96
+ * ttl: 3600,
97
+ * }));
98
+ * // → "ExampleCDN; hit; ttl=300, blamejs; fwd=miss; stored; ttl=3600"
99
+ */
100
+ function append(prevHeader, entry) {
101
+ var formatted = entryString(entry);
102
+ if (typeof prevHeader === "string" && prevHeader.length > 0) {
103
+ return prevHeader + ", " + formatted;
104
+ }
105
+ return formatted;
106
+ }
107
+
108
+ /**
109
+ * @primitive b.cacheStatus.entry
110
+ * @signature b.cacheStatus.entry(entry)
111
+ * @since 0.8.86
112
+ * @status stable
113
+ * @related b.cacheStatus.append, b.cacheStatus.parse
114
+ *
115
+ * Format a single Cache-Status entry without combining with a prior
116
+ * chain. Useful when the operator wants to write the header without
117
+ * regard to upstream entries (e.g. an origin-only deployment).
118
+ *
119
+ * @example
120
+ * res.setHeader("Cache-Status", b.cacheStatus.entry({
121
+ * cache: "blamejs", hit: true, ttl: 600,
122
+ * }));
123
+ * // → "blamejs; hit; ttl=600"
124
+ */
125
+ function entryString(entry) {
126
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
127
+ throw new CacheStatusError("cache-status/bad-entry",
128
+ "entry must be a non-null object", true);
129
+ }
130
+ validateOpts.requireNonEmptyString(
131
+ entry.cache, "entry.cache", CacheStatusError, "cache-status/bad-cache-name");
132
+ if (entry.cache.length > CACHE_NAME_MAX || !CACHE_NAME_RE.test(entry.cache)) {
133
+ throw new CacheStatusError("cache-status/bad-cache-name",
134
+ "entry.cache '" + entry.cache + "' must be a structured-fields token " +
135
+ "(RFC 8941 §3.3.4: starts with ALPHA or '*', uses tchar / ':' / '/' only — " +
136
+ "no comma / semicolon / quote / backslash / whitespace) and <= " +
137
+ CACHE_NAME_MAX + " chars. Quote-and-escape an operator-supplied label " +
138
+ "via b.cacheStatus.entry({ ..., key: '<label>' }) instead.");
139
+ }
140
+ var parts = [entry.cache];
141
+
142
+ // Booleans — emit as bare-token when truthy.
143
+ for (var i = 0; i < BOOLEAN_PARAMS.length; i += 1) {
144
+ if (entry[BOOLEAN_PARAMS[i]] === true) parts.push(BOOLEAN_PARAMS[i]);
145
+ }
146
+
147
+ if (entry.fwd !== undefined && entry.fwd !== null) {
148
+ if (typeof entry.fwd !== "string" || FWD_VALUES.indexOf(entry.fwd) === -1) {
149
+ throw new CacheStatusError("cache-status/bad-fwd",
150
+ "entry.fwd must be one of " + FWD_VALUES.join(", "));
151
+ }
152
+ parts.push("fwd=" + entry.fwd);
153
+ }
154
+ if (entry.fwdStatus !== undefined && entry.fwdStatus !== null) {
155
+ if (typeof entry.fwdStatus !== "number" || !Number.isInteger(entry.fwdStatus) ||
156
+ entry.fwdStatus < 100 || entry.fwdStatus > 599) { // allow:raw-byte-literal — HTTP status range
157
+ throw new CacheStatusError("cache-status/bad-fwd-status",
158
+ "entry.fwdStatus must be an integer 100..599");
159
+ }
160
+ parts.push("fwd-status=" + entry.fwdStatus);
161
+ }
162
+ if (entry.ttl !== undefined && entry.ttl !== null) {
163
+ // RFC 9211 §2.2 — ttl is a signed Integer. Negative values are
164
+ // explicitly valid: a `hit` paired with `ttl=-30` reports the
165
+ // response was served stale by 30 seconds (typically with
166
+ // `fwd=stale`). Refusing negatives would block the very scenario
167
+ // `fwd=stale` exists to surface.
168
+ if (typeof entry.ttl !== "number" || !Number.isInteger(entry.ttl)) {
169
+ throw new CacheStatusError("cache-status/bad-ttl",
170
+ "entry.ttl must be an integer (negative permitted for stale-cache hits per RFC 9211 §2.2)");
171
+ }
172
+ parts.push("ttl=" + entry.ttl);
173
+ }
174
+ if (entry.key !== undefined && entry.key !== null) {
175
+ if (typeof entry.key !== "string") {
176
+ throw new CacheStatusError("cache-status/bad-key",
177
+ "entry.key must be a string when provided");
178
+ }
179
+ parts.push("key=" + _sfStringQuote(entry.key));
180
+ }
181
+ if (entry.detail !== undefined && entry.detail !== null) {
182
+ if (typeof entry.detail !== "string") {
183
+ throw new CacheStatusError("cache-status/bad-detail",
184
+ "entry.detail must be a string when provided");
185
+ }
186
+ parts.push("detail=" + _sfStringQuote(entry.detail));
187
+ }
188
+ return parts.join("; ");
189
+ }
190
+
191
+ /**
192
+ * @primitive b.cacheStatus.parse
193
+ * @signature b.cacheStatus.parse(headerValue)
194
+ * @since 0.8.86
195
+ * @status stable
196
+ * @related b.cacheStatus.append, b.cacheStatus.entry
197
+ *
198
+ * Parse a Cache-Status header into an array of `{ cache, params }`
199
+ * records, one per cache in the chain. The params object carries the
200
+ * RFC 9211 §2 standard parameters as proper types (`hit`/`stored`/
201
+ * `collapsed` as booleans, `ttl`/`fwdStatus` as numbers, `fwd` as the
202
+ * raw enum string, `key`/`detail` as unquoted strings). Unknown
203
+ * params survive as raw string values so operators inspecting custom
204
+ * cache implementations can read them.
205
+ *
206
+ * Empty / non-string / malformed inputs return `[]` — defensive
207
+ * request-shape reader returns sane defaults rather than throwing.
208
+ *
209
+ * @example
210
+ * var chain = b.cacheStatus.parse(
211
+ * 'ExampleCDN; hit; ttl=300, blamejs; fwd=miss; stored; ttl=3600');
212
+ * // chain[0] = { cache: "ExampleCDN", params: { hit: true, ttl: 300 } }
213
+ * // chain[1] = { cache: "blamejs", params: { fwd: "miss", stored: true, ttl: 3600 } }
214
+ */
215
+ function parse(headerValue) {
216
+ if (typeof headerValue !== "string" || headerValue.length === 0) return [];
217
+ var out = [];
218
+ // Split entries on commas NOT inside quoted strings.
219
+ var entries = _splitTopLevel(headerValue, ",");
220
+ for (var i = 0; i < entries.length; i += 1) {
221
+ var raw = entries[i].trim();
222
+ if (raw.length === 0) continue;
223
+ var fields = _splitTopLevel(raw, ";").map(function (s) { return s.trim(); });
224
+ var cache = fields.shift();
225
+ if (!cache) continue;
226
+ var params = {};
227
+ for (var j = 0; j < fields.length; j += 1) {
228
+ var f = fields[j];
229
+ if (f.length === 0) continue;
230
+ var eq = f.indexOf("=");
231
+ if (eq === -1) {
232
+ // Bare token — boolean
233
+ params[f] = true;
234
+ continue;
235
+ }
236
+ var name = f.slice(0, eq).trim();
237
+ var val = f.slice(eq + 1).trim();
238
+ params[_normalizeParamName(name)] = _parseParamValue(name, val);
239
+ }
240
+ out.push({ cache: cache, params: params });
241
+ }
242
+ return out;
243
+ }
244
+
245
+ function _normalizeParamName(n) {
246
+ // RFC 9211 §2 uses fwd-status as the canonical name; surface as
247
+ // `fwdStatus` in the parsed object for JS-natural access.
248
+ if (n === "fwd-status") return "fwdStatus";
249
+ return n;
250
+ }
251
+
252
+ function _parseParamValue(name, raw) {
253
+ if (raw.length >= 2 && raw.charAt(0) === "\"" && raw.charAt(raw.length - 1) === "\"") {
254
+ // sf-string — unquote + unescape.
255
+ return raw.slice(1, -1).replace(/\\(.)/g, "$1");
256
+ }
257
+ if (name === "ttl" || name === "fwd-status" || name === "fwdStatus") {
258
+ var n = Number(raw);
259
+ return Number.isFinite(n) ? n : raw;
260
+ }
261
+ return raw;
262
+ }
263
+
264
+ function _splitTopLevel(s, sep) {
265
+ var out = [];
266
+ var buf = "";
267
+ var inQuotes = false;
268
+ var escaped = false;
269
+ for (var i = 0; i < s.length; i += 1) {
270
+ var c = s.charAt(i);
271
+ if (escaped) { buf += c; escaped = false; continue; }
272
+ if (c === "\\" && inQuotes) { buf += c; escaped = true; continue; }
273
+ if (c === "\"") { inQuotes = !inQuotes; buf += c; continue; }
274
+ if (c === sep && !inQuotes) { out.push(buf); buf = ""; continue; }
275
+ buf += c;
276
+ }
277
+ if (buf.length > 0) out.push(buf);
278
+ return out;
279
+ }
280
+
281
+ module.exports = {
282
+ append: append,
283
+ entry: entryString,
284
+ parse: parse,
285
+ FWD_VALUES: FWD_VALUES,
286
+ KNOWN_PARAMS: KNOWN_PARAMS,
287
+ CacheStatusError: CacheStatusError,
288
+ };
package/lib/compliance.js CHANGED
@@ -201,6 +201,17 @@ var KNOWN_POSTURES = Object.freeze([
201
201
  "eu-cer", // EU Critical Entities Resilience Directive (2022/2557; transposition 2024-10-17)
202
202
  "eu-cyber-sol", // EU Cyber Solidarity Act (Regulation 2025/38; effective 2025-02-04)
203
203
  "eidas-2", // eIDAS 2 / EUDI Wallet (Regulation 2024/1183; rollout 2026-2027)
204
+ // ---- v0.8.86 expansion — sectoral + cybersecurity directives ----
205
+ "cmmc-2.0", // US DoD Cybersecurity Maturity Model Certification 2.0 (effective 2025-Q1)
206
+ "cjis-v6", // FBI Criminal Justice Information Services Security Policy v6.0 (Dec 2024)
207
+ "iso-27001-2022", // ISO/IEC 27001:2022 — Information Security Management System
208
+ "iso-27002-2022", // ISO/IEC 27002:2022 — Code of practice for information security controls
209
+ "iso-27017", // ISO/IEC 27017 — Cloud-services security controls
210
+ "iso-27018", // ISO/IEC 27018 — PII protection in public-cloud processors
211
+ "iso-27701", // ISO/IEC 27701 — Privacy Information Management System
212
+ "nist-800-66-r2", // NIST SP 800-66 Rev 2 — HIPAA Security Rule implementation guidance // allow:raw-byte-literal — NIST publication number, not bytes
213
+ "ehds", // EU European Health Data Space (Regulation 2025/327; phased 2027-2029)
214
+ "circia", // US Cyber Incident Reporting for Critical Infrastructure Act (final rule pending)
204
215
  ]);
205
216
 
206
217
  var STATE = { posture: null, setAt: null };
@@ -665,6 +676,17 @@ var REGIME_MAP = Object.freeze({
665
676
  "eu-cer": { name: "EU Critical Entities Resilience Directive", citation: "Directive (EU) 2022/2557 (transposition 2024-10-17)", jurisdiction: "EU", domain: "cybersecurity" },
666
677
  "eu-cyber-sol": { name: "EU Cyber Solidarity Act", citation: "Regulation (EU) 2025/38 (effective 2025-02-04)", jurisdiction: "EU", domain: "cybersecurity" },
667
678
  "eidas-2": { name: "eIDAS 2 / EUDI Wallet", citation: "Regulation (EU) 2024/1183 (rollout 2026-2027)", jurisdiction: "EU", domain: "identity" },
679
+ // ---- v0.8.86 — sectoral + cybersecurity directives ----
680
+ "cmmc-2.0": { name: "Cybersecurity Maturity Model Certification 2.0", citation: "32 CFR Part 170 (DFARS rule effective 2025-Q1)", jurisdiction: "US", domain: "cybersecurity" },
681
+ "cjis-v6": { name: "FBI CJIS Security Policy v6.0", citation: "CJIS Security Policy v6.0 (effective 2024-12)", jurisdiction: "US", domain: "law-enforcement" },
682
+ "iso-27001-2022": { name: "ISO/IEC 27001:2022 Information Security Management System", citation: "ISO/IEC 27001:2022", jurisdiction: "international", domain: "cybersecurity" },
683
+ "iso-27002-2022": { name: "ISO/IEC 27002:2022 Information Security Controls", citation: "ISO/IEC 27002:2022", jurisdiction: "international", domain: "cybersecurity" },
684
+ "iso-27017": { name: "ISO/IEC 27017 Cloud Services Security Controls", citation: "ISO/IEC 27017:2015", jurisdiction: "international", domain: "cybersecurity" },
685
+ "iso-27018": { name: "ISO/IEC 27018 PII Protection in Public Cloud", citation: "ISO/IEC 27018:2019", jurisdiction: "international", domain: "privacy" },
686
+ "iso-27701": { name: "ISO/IEC 27701 Privacy Information Management System", citation: "ISO/IEC 27701:2019", jurisdiction: "international", domain: "privacy" },
687
+ "nist-800-66-r2": { name: "NIST SP 800-66 Rev 2 — HIPAA Security Rule Guidance", citation: "NIST SP 800-66 Rev 2 (Feb 2024)", jurisdiction: "US", domain: "health" },
688
+ "ehds": { name: "European Health Data Space", citation: "Regulation (EU) 2025/327 (phased 2027-2029)", jurisdiction: "EU", domain: "health" },
689
+ "circia": { name: "Cyber Incident Reporting for Critical Infrastructure Act", citation: "6 U.S.C. §681 et seq. (final rule pending)", jurisdiction: "US", domain: "cybersecurity" },
668
690
  });
669
691
 
670
692
  /**
@@ -928,6 +950,20 @@ var POSTURE_DEFAULTS = Object.freeze({
928
950
  "eu-cer": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
929
951
  "eu-cyber-sol": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
930
952
  "eidas-2": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
953
+ // v0.8.86 — sectoral + cybersecurity directives. DoD CMMC + FBI
954
+ // CJIS + healthcare regimes share an encrypted-at-rest + signed-
955
+ // audit-chain floor; ISO 27001/27002 + ISO 27017/27018/27701 are
956
+ // operator-adopted governance standards with the same baseline.
957
+ "cmmc-2.0": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
958
+ "cjis-v6": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
959
+ "iso-27001-2022": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
960
+ "iso-27002-2022": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
961
+ "iso-27017": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
962
+ "iso-27018": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
963
+ "iso-27701": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
964
+ "nist-800-66-r2": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
965
+ "ehds": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
966
+ "circia": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
931
967
  });
932
968
 
933
969
  /**
@@ -602,6 +602,23 @@ var PublicSuffixError = defineClass("PublicSuffixError", { alwaysPermane
602
602
  // alwaysPermanent — every case is operator-shape or message-shape
603
603
  // errors that retry will not recover.
604
604
  var MailMdnError = defineClass("MailMdnError", { alwaysPermanent: true });
605
+ // ProblemDetailsError — b.problemDetails (lib/problem-details.js). RFC
606
+ // 9457 Problem Details for HTTP APIs builder + validator violations:
607
+ // bad opts at create/respond/validate, type/title/status/detail/
608
+ // instance shape mismatches, reserved-field collision in extensions,
609
+ // prototype-pollution-shaped extension keys, bad response object at
610
+ // respond(), bad inbound document shape. alwaysPermanent — every case
611
+ // is operator-shape or wire-shape errors that retry will not recover.
612
+ var ProblemDetailsError = defineClass("ProblemDetailsError", { alwaysPermanent: true });
613
+ // IdempotencyError — b.middleware.idempotencyKey (lib/middleware/
614
+ // idempotency-key.js). draft-ietf-httpapi-idempotency-key middleware
615
+ // violations: bad opts at create (missing store, bad ttl, bad methods
616
+ // list), bad idempotency key shape (non-string, too long, control
617
+ // chars), store-backend transport errors that exhausted retries.
618
+ // alwaysPermanent — every operator-facing failure is config-shape;
619
+ // transient store-backend failures route through audit signals so
620
+ // they don't escape as exceptions to the middleware caller.
621
+ var IdempotencyError = defineClass("IdempotencyError", { alwaysPermanent: true });
605
622
 
606
623
  module.exports = {
607
624
  FrameworkError: FrameworkError,
@@ -696,4 +713,6 @@ module.exports = {
696
713
  FidoMds3Error: FidoMds3Error,
697
714
  PublicSuffixError: PublicSuffixError,
698
715
  MailMdnError: MailMdnError,
716
+ ProblemDetailsError: ProblemDetailsError,
717
+ IdempotencyError: IdempotencyError,
699
718
  };