@blamejs/core 0.8.83 → 0.8.87

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/a2a.js CHANGED
@@ -389,9 +389,19 @@ function verifyCard(envelope, publicKeyPem, opts) {
389
389
  };
390
390
  }
391
391
 
392
+ var tasks = require("./a2a-tasks");
393
+
392
394
  module.exports = {
393
395
  signCard: signCard,
394
396
  verifyCard: verifyCard,
395
397
  canonicalize: canonicalize,
396
398
  createCard: createCard,
399
+ tasks: {
400
+ send: tasks.send,
401
+ get: tasks.get,
402
+ cancel: tasks.cancel,
403
+ ALLOWED_METHODS: tasks.ALLOWED_METHODS,
404
+ },
405
+ middleware: tasks.middleware,
406
+ A2aTasksError: tasks.A2aTasksError,
397
407
  };
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,210 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.auth.fal
4
+ * @nav Identity & Access
5
+ * @title NIST 800-63-4 FAL Classifier
6
+ * @order 120
7
+ *
8
+ * @intro
9
+ * NIST SP 800-63-4 Federation Assurance Levels — FAL1 / FAL2 /
10
+ * FAL3. While AAL describes the rigor of authentication (what the
11
+ * user did to prove they are who they say they are), FAL describes
12
+ * the rigor of the FEDERATION assertion that carried that
13
+ * authentication from the IdP to the RP.
14
+ *
15
+ * FAL bands per NIST 800-63C-4:
16
+ *
17
+ * FAL1: Bearer assertion delivered through the front channel
18
+ * (typical OIDC ID token over the browser redirect).
19
+ * Signed by the IdP; verified by the RP. No audience
20
+ * binding beyond the standard `aud` claim.
21
+ *
22
+ * FAL2: Bearer assertion delivered through the back channel
23
+ * OR front-channel assertion that is encrypted to the RP.
24
+ * Replay-protection nonce required. Typical OIDC
25
+ * Authorization Code Flow with mTLS or DPoP-bound token.
26
+ *
27
+ * FAL3: Holder-of-Key assertion. RP verifies the subject
28
+ * cryptographically holds a key bound to the assertion
29
+ * (mTLS client-cert pinned to the subject, DPoP-bound +
30
+ * audience-restricted, OR SAML HoK SubjectConfirmation).
31
+ * Defeats stolen-bearer-token replay.
32
+ *
33
+ * Operators classify the FAL of an incoming federation assertion
34
+ * via `fromAssertion(opts)` — pass the assertion's properties
35
+ * (channel, encrypted, hokBinding, etc.) and get back the band.
36
+ * Compose with `b.middleware.requireFal({ minimum: "FAL2" })` for
37
+ * the gate.
38
+ *
39
+ * @card
40
+ * NIST 800-63-4 Federation Assurance Level classifier — describes the rigor of the federation assertion (FAL1 bearer / FAL2 encrypted-or-back-channel / FAL3 Holder-of-Key) carried from IdP to RP.
41
+ */
42
+
43
+ var validateOpts = require("../validate-opts");
44
+ var { AuthError } = require("../framework-error");
45
+
46
+ var FAL1 = "FAL1";
47
+ var FAL2 = "FAL2";
48
+ var FAL3 = "FAL3";
49
+
50
+ var BANDS = Object.freeze([FAL1, FAL2, FAL3]);
51
+
52
+ function _bandRank(band) {
53
+ if (band === FAL1) return 1;
54
+ if (band === FAL2) return 2;
55
+ if (band === FAL3) return 3;
56
+ return 0;
57
+ }
58
+
59
+ /**
60
+ * @primitive b.auth.fal.isValidBand
61
+ * @signature b.auth.fal.isValidBand(band)
62
+ * @since 0.8.87
63
+ * @status stable
64
+ *
65
+ * Predicate returning `true` when `band` is one of the documented
66
+ * FAL band strings (`"FAL1"` / `"FAL2"` / `"FAL3"`).
67
+ *
68
+ * @example
69
+ * b.auth.fal.isValidBand("FAL2"); // → true
70
+ * b.auth.fal.isValidBand("FALX"); // → false
71
+ */
72
+ function isValidBand(band) {
73
+ return _bandRank(band) > 0;
74
+ }
75
+
76
+ /**
77
+ * @primitive b.auth.fal.meets
78
+ * @signature b.auth.fal.meets(actualBand, requiredBand)
79
+ * @since 0.8.87
80
+ * @status stable
81
+ *
82
+ * Predicate returning `true` when `actualBand` satisfies the
83
+ * `requiredBand` floor (FAL3 ≥ FAL2 ≥ FAL1). Invalid band strings
84
+ * on either argument return `false`.
85
+ *
86
+ * @example
87
+ * b.auth.fal.meets("FAL3", "FAL2"); // → true
88
+ * b.auth.fal.meets("FAL1", "FAL2"); // → false
89
+ */
90
+ function meets(actualBand, requiredBand) {
91
+ return _bandRank(actualBand) >= _bandRank(requiredBand);
92
+ }
93
+
94
+ /**
95
+ * @primitive b.auth.fal.fromAssertion
96
+ * @signature b.auth.fal.fromAssertion(opts)
97
+ * @since 0.8.87
98
+ * @status stable
99
+ *
100
+ * Classify an incoming federation assertion's FAL band per NIST
101
+ * 800-63C-4. Returns one of `"FAL1"` / `"FAL2"` / `"FAL3"`. Throws
102
+ * `auth/bad-fal-opts` on missing required fields.
103
+ *
104
+ * - HoK binding (mTLS client-cert pinned, DPoP-bound, SAML HoK) → FAL3
105
+ * - Back-channel delivery OR encrypted-to-RP front-channel +
106
+ * replay-protection nonce → FAL2
107
+ * - Anything else → FAL1
108
+ *
109
+ * The classifier is conservative: missing replay-protection on a
110
+ * back-channel assertion downgrades to FAL1 because §5.2 requires
111
+ * nonce / jti binding before back-channel can claim FAL2.
112
+ *
113
+ * @opts
114
+ * channel: "front" | "back", // REQUIRED
115
+ * encrypted: boolean, // assertion encrypted to RP
116
+ * replayProtected: boolean, // nonce / jti / iat binding present
117
+ * hokBinding: "mtls" | "dpop" | "saml-hok" | null,
118
+ * // proof-of-possession binding present
119
+ * bearerOnly: boolean, // alias for hokBinding === null
120
+ *
121
+ * @example
122
+ * var fal = b.auth.fal.fromAssertion({
123
+ * channel: "back",
124
+ * encrypted: false,
125
+ * replayProtected: true,
126
+ * hokBinding: null,
127
+ * });
128
+ * // → "FAL2"
129
+ *
130
+ * var fal3 = b.auth.fal.fromAssertion({
131
+ * channel: "back",
132
+ * hokBinding: "mtls",
133
+ * replayProtected: true,
134
+ * });
135
+ * // → "FAL3"
136
+ */
137
+ function fromAssertion(opts) {
138
+ if (!opts || typeof opts !== "object") {
139
+ throw new AuthError("auth/bad-fal-opts",
140
+ "fal.fromAssertion: opts required (channel + replayProtected at minimum)");
141
+ }
142
+ if (opts.channel !== "front" && opts.channel !== "back") {
143
+ throw new AuthError("auth/bad-fal-opts",
144
+ "fal.fromAssertion: channel must be 'front' or 'back'");
145
+ }
146
+ var hokBinding = opts.hokBinding;
147
+ if (hokBinding !== undefined && hokBinding !== null) {
148
+ if (hokBinding !== "mtls" && hokBinding !== "dpop" && hokBinding !== "saml-hok") {
149
+ throw new AuthError("auth/bad-fal-opts",
150
+ "fal.fromAssertion: hokBinding must be 'mtls' | 'dpop' | 'saml-hok' | null");
151
+ }
152
+ }
153
+
154
+ // FAL3 — Holder-of-Key with replay protection.
155
+ if (hokBinding && opts.replayProtected === true) {
156
+ return FAL3;
157
+ }
158
+
159
+ // FAL2 — back-channel OR encrypted front-channel, with replay protection.
160
+ var replaySafe = opts.replayProtected === true;
161
+ if (replaySafe && (opts.channel === "back" || opts.encrypted === true)) {
162
+ return FAL2;
163
+ }
164
+
165
+ // Everything else — FAL1 (bearer front-channel).
166
+ return FAL1;
167
+ }
168
+
169
+ /**
170
+ * @primitive b.auth.fal.requireFal
171
+ * @signature b.auth.fal.requireFal(minimumBand)
172
+ * @since 0.8.87
173
+ * @status stable
174
+ * @related b.auth.fal.fromAssertion
175
+ *
176
+ * Build a guard that throws `auth/fal-insufficient` when the
177
+ * supplied band is below the minimum. The middleware form
178
+ * (`b.middleware.requireFal`) wraps this guard at the request layer.
179
+ *
180
+ * @example
181
+ * var fal3Only = b.auth.fal.requireFal("FAL3");
182
+ * fal3Only(req.session.federationFal);
183
+ * // throws auth/fal-insufficient if not FAL3
184
+ */
185
+ function requireFal(minimumBand) {
186
+ validateOpts.requireNonEmptyString(
187
+ minimumBand, "fal.requireFal.minimumBand", AuthError, "auth/bad-fal-band");
188
+ if (!isValidBand(minimumBand)) {
189
+ throw new AuthError("auth/bad-fal-band",
190
+ "fal.requireFal: minimumBand must be one of " + BANDS.join(", "));
191
+ }
192
+ return function falGuard(actualBand) {
193
+ if (!isValidBand(actualBand) || !meets(actualBand, minimumBand)) {
194
+ throw new AuthError("auth/fal-insufficient",
195
+ "fal.requireFal: actual band '" + actualBand + "' does not meet minimum '" + minimumBand + "'");
196
+ }
197
+ return actualBand;
198
+ };
199
+ }
200
+
201
+ module.exports = {
202
+ FAL1: FAL1,
203
+ FAL2: FAL2,
204
+ FAL3: FAL3,
205
+ BANDS: BANDS,
206
+ isValidBand: isValidBand,
207
+ meets: meets,
208
+ fromAssertion: fromAssertion,
209
+ requireFal: requireFal,
210
+ };
@@ -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
  };