@blamejs/core 0.8.43 → 0.8.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/README.md +10 -10
  3. package/index.js +52 -0
  4. package/lib/a2a.js +159 -34
  5. package/lib/acme.js +762 -0
  6. package/lib/ai-pref.js +166 -43
  7. package/lib/api-key.js +108 -47
  8. package/lib/api-snapshot.js +157 -40
  9. package/lib/app-shutdown.js +113 -77
  10. package/lib/archive.js +337 -40
  11. package/lib/arg-parser.js +697 -0
  12. package/lib/asyncapi.js +99 -55
  13. package/lib/atomic-file.js +465 -104
  14. package/lib/audit-chain.js +123 -34
  15. package/lib/audit-daily-review.js +389 -0
  16. package/lib/audit-sign.js +302 -56
  17. package/lib/audit-tools.js +412 -63
  18. package/lib/audit.js +656 -35
  19. package/lib/auth/jwt-external.js +17 -0
  20. package/lib/auth/oauth.js +7 -0
  21. package/lib/auth-bot-challenge.js +505 -0
  22. package/lib/auth-header.js +92 -25
  23. package/lib/backup/bundle.js +26 -0
  24. package/lib/backup/index.js +512 -89
  25. package/lib/backup/manifest.js +168 -7
  26. package/lib/break-glass.js +415 -39
  27. package/lib/budr.js +103 -30
  28. package/lib/bundler.js +86 -66
  29. package/lib/cache.js +192 -72
  30. package/lib/chain-writer.js +65 -40
  31. package/lib/circuit-breaker.js +56 -33
  32. package/lib/cli-helpers.js +106 -75
  33. package/lib/cli.js +6 -30
  34. package/lib/cloud-events.js +99 -32
  35. package/lib/cluster-storage.js +162 -37
  36. package/lib/cluster.js +340 -49
  37. package/lib/codepoint-class.js +66 -0
  38. package/lib/compliance.js +424 -24
  39. package/lib/config-drift.js +111 -46
  40. package/lib/config.js +94 -40
  41. package/lib/consent.js +165 -18
  42. package/lib/constants.js +1 -0
  43. package/lib/content-credentials.js +153 -48
  44. package/lib/cookies.js +154 -62
  45. package/lib/credential-hash.js +133 -61
  46. package/lib/crypto-field.js +702 -18
  47. package/lib/crypto-hpke.js +256 -0
  48. package/lib/crypto.js +744 -22
  49. package/lib/csv.js +178 -35
  50. package/lib/daemon.js +456 -0
  51. package/lib/dark-patterns.js +186 -55
  52. package/lib/db-query.js +79 -2
  53. package/lib/db.js +1431 -60
  54. package/lib/ddl-change-control.js +523 -0
  55. package/lib/deprecate.js +195 -40
  56. package/lib/dev.js +82 -39
  57. package/lib/dora.js +67 -48
  58. package/lib/dr-runbook.js +368 -0
  59. package/lib/dsr.js +142 -11
  60. package/lib/dual-control.js +91 -56
  61. package/lib/events.js +120 -41
  62. package/lib/external-db-migrate.js +192 -2
  63. package/lib/external-db.js +795 -50
  64. package/lib/fapi2.js +122 -1
  65. package/lib/fda-21cfr11.js +395 -0
  66. package/lib/fdx.js +132 -2
  67. package/lib/file-type.js +87 -0
  68. package/lib/file-upload.js +93 -0
  69. package/lib/flag.js +82 -20
  70. package/lib/forms.js +132 -29
  71. package/lib/framework-error.js +169 -0
  72. package/lib/framework-schema.js +163 -35
  73. package/lib/gate-contract.js +849 -175
  74. package/lib/graphql-federation.js +68 -7
  75. package/lib/guard-all.js +172 -55
  76. package/lib/guard-archive.js +286 -124
  77. package/lib/guard-auth.js +194 -21
  78. package/lib/guard-cidr.js +190 -28
  79. package/lib/guard-csv.js +397 -51
  80. package/lib/guard-domain.js +213 -91
  81. package/lib/guard-email.js +236 -29
  82. package/lib/guard-filename.js +307 -75
  83. package/lib/guard-graphql.js +263 -30
  84. package/lib/guard-html.js +310 -116
  85. package/lib/guard-image.js +243 -30
  86. package/lib/guard-json.js +260 -54
  87. package/lib/guard-jsonpath.js +235 -23
  88. package/lib/guard-jwt.js +284 -30
  89. package/lib/guard-markdown.js +204 -22
  90. package/lib/guard-mime.js +190 -26
  91. package/lib/guard-oauth.js +277 -28
  92. package/lib/guard-pdf.js +251 -27
  93. package/lib/guard-regex.js +226 -18
  94. package/lib/guard-shell.js +229 -26
  95. package/lib/guard-svg.js +177 -10
  96. package/lib/guard-template.js +232 -21
  97. package/lib/guard-time.js +195 -29
  98. package/lib/guard-uuid.js +189 -30
  99. package/lib/guard-xml.js +259 -36
  100. package/lib/guard-yaml.js +241 -44
  101. package/lib/honeytoken.js +63 -27
  102. package/lib/html-balance.js +83 -0
  103. package/lib/http-client.js +486 -59
  104. package/lib/http-message-signature.js +582 -0
  105. package/lib/i18n.js +102 -49
  106. package/lib/iab-mspa.js +112 -32
  107. package/lib/iab-tcf.js +107 -2
  108. package/lib/inbox.js +90 -52
  109. package/lib/keychain.js +865 -0
  110. package/lib/legal-hold.js +374 -0
  111. package/lib/local-db-thin.js +320 -0
  112. package/lib/log-stream.js +281 -51
  113. package/lib/log.js +184 -86
  114. package/lib/mail-bounce.js +107 -62
  115. package/lib/mail.js +295 -58
  116. package/lib/mcp.js +108 -27
  117. package/lib/metrics.js +98 -89
  118. package/lib/middleware/age-gate.js +36 -0
  119. package/lib/middleware/ai-act-disclosure.js +37 -0
  120. package/lib/middleware/api-encrypt.js +45 -0
  121. package/lib/middleware/assetlinks.js +40 -0
  122. package/lib/middleware/asyncapi-serve.js +35 -0
  123. package/lib/middleware/attach-user.js +40 -0
  124. package/lib/middleware/bearer-auth.js +40 -0
  125. package/lib/middleware/body-parser.js +230 -0
  126. package/lib/middleware/bot-disclose.js +34 -0
  127. package/lib/middleware/bot-guard.js +39 -0
  128. package/lib/middleware/compression.js +37 -0
  129. package/lib/middleware/cookies.js +32 -0
  130. package/lib/middleware/cors.js +40 -0
  131. package/lib/middleware/csp-nonce.js +40 -0
  132. package/lib/middleware/csp-report.js +34 -0
  133. package/lib/middleware/csrf-protect.js +43 -0
  134. package/lib/middleware/daily-byte-quota.js +53 -85
  135. package/lib/middleware/db-role-for.js +40 -0
  136. package/lib/middleware/dpop.js +40 -0
  137. package/lib/middleware/error-handler.js +37 -14
  138. package/lib/middleware/fetch-metadata.js +39 -0
  139. package/lib/middleware/flag-context.js +34 -0
  140. package/lib/middleware/gpc.js +33 -0
  141. package/lib/middleware/headers.js +35 -0
  142. package/lib/middleware/health.js +46 -0
  143. package/lib/middleware/host-allowlist.js +30 -0
  144. package/lib/middleware/network-allowlist.js +38 -0
  145. package/lib/middleware/openapi-serve.js +34 -0
  146. package/lib/middleware/rate-limit.js +160 -18
  147. package/lib/middleware/request-id.js +36 -18
  148. package/lib/middleware/request-log.js +37 -0
  149. package/lib/middleware/require-aal.js +29 -0
  150. package/lib/middleware/require-auth.js +32 -0
  151. package/lib/middleware/require-bound-key.js +41 -0
  152. package/lib/middleware/require-content-type.js +32 -0
  153. package/lib/middleware/require-methods.js +27 -0
  154. package/lib/middleware/require-mtls.js +33 -0
  155. package/lib/middleware/require-step-up.js +37 -0
  156. package/lib/middleware/security-headers.js +44 -0
  157. package/lib/middleware/security-txt.js +38 -0
  158. package/lib/middleware/span-http-server.js +37 -0
  159. package/lib/middleware/sse.js +36 -0
  160. package/lib/middleware/trace-log-correlation.js +33 -0
  161. package/lib/middleware/trace-propagate.js +32 -0
  162. package/lib/middleware/tus-upload.js +90 -0
  163. package/lib/middleware/web-app-manifest.js +53 -0
  164. package/lib/mtls-ca.js +100 -70
  165. package/lib/network-byte-quota.js +308 -0
  166. package/lib/network-heartbeat.js +135 -0
  167. package/lib/network-tls.js +534 -4
  168. package/lib/network.js +103 -0
  169. package/lib/notify.js +114 -43
  170. package/lib/ntp-check.js +192 -51
  171. package/lib/observability.js +145 -47
  172. package/lib/openapi.js +90 -44
  173. package/lib/outbox.js +99 -1
  174. package/lib/pagination.js +168 -86
  175. package/lib/parsers/index.js +16 -5
  176. package/lib/permissions.js +93 -40
  177. package/lib/pqc-agent.js +84 -8
  178. package/lib/pqc-software.js +94 -60
  179. package/lib/process-spawn.js +95 -21
  180. package/lib/pubsub.js +96 -66
  181. package/lib/queue.js +375 -54
  182. package/lib/redact.js +793 -21
  183. package/lib/render.js +139 -47
  184. package/lib/request-helpers.js +485 -121
  185. package/lib/restore-bundle.js +142 -39
  186. package/lib/restore-rollback.js +136 -45
  187. package/lib/retention.js +178 -50
  188. package/lib/retry.js +116 -33
  189. package/lib/router.js +475 -23
  190. package/lib/safe-async.js +543 -94
  191. package/lib/safe-buffer.js +337 -41
  192. package/lib/safe-json.js +467 -62
  193. package/lib/safe-jsonpath.js +285 -0
  194. package/lib/safe-schema.js +631 -87
  195. package/lib/safe-sql.js +221 -59
  196. package/lib/safe-url.js +278 -46
  197. package/lib/sandbox-worker.js +135 -0
  198. package/lib/sandbox.js +358 -0
  199. package/lib/scheduler.js +135 -70
  200. package/lib/self-update.js +647 -0
  201. package/lib/session-device-binding.js +431 -0
  202. package/lib/session.js +259 -49
  203. package/lib/slug.js +138 -26
  204. package/lib/ssrf-guard.js +316 -56
  205. package/lib/storage.js +433 -70
  206. package/lib/subject.js +405 -23
  207. package/lib/template.js +148 -8
  208. package/lib/tenant-quota.js +545 -0
  209. package/lib/testing.js +440 -53
  210. package/lib/time.js +291 -23
  211. package/lib/tls-exporter.js +239 -0
  212. package/lib/tracing.js +90 -74
  213. package/lib/uuid.js +97 -22
  214. package/lib/vault/index.js +284 -22
  215. package/lib/vault/seal-pem-file.js +66 -0
  216. package/lib/watcher.js +368 -0
  217. package/lib/webhook.js +196 -63
  218. package/lib/websocket.js +393 -68
  219. package/lib/wiki-concepts.js +338 -0
  220. package/lib/worker-pool.js +464 -0
  221. package/package.json +3 -3
  222. package/sbom.cyclonedx.json +7 -7
package/lib/compliance.js CHANGED
@@ -1,31 +1,44 @@
1
1
  "use strict";
2
2
  /**
3
- * b.compliance — top-level compliance-posture coordinator.
3
+ * @module b.compliance
4
+ * @featured true
5
+ * @nav Compliance
6
+ * @title Compliance
4
7
  *
5
- * Sets a global posture (`hipaa` / `pci-dss` / `gdpr` / `soc2` /
6
- * `dora`) that primitives with a `compliancePosture` opt fall back to
7
- * when the operator hasn't passed one explicitly. Single source of
8
- * truth for "what regulatory posture is this deployment running
9
- * under?".
8
+ * @intro
9
+ * Top-level compliance-posture coordinator single source of truth
10
+ * for "what regulatory regime is this deployment running under?".
10
11
  *
11
- * b.compliance.set("hipaa");
12
- * b.compliance.current(); // "hipaa"
13
- * b.compliance.assert("hipaa"); // throws if not the named posture
14
- *
15
- * // Every primitive with a compliancePosture opt now picks "hipaa"
16
- * // by default:
17
- * var gate = b.guardCsv.gate({}); // hipaa overlay applied
18
- * var ttl = b.retention.complianceFloor("hipaa", customTtl);
19
- *
20
- * Boot-time only `set()` MUST run before the primitives it
21
- * coordinates are first used. Runtime switches are forbidden because
22
- * they would create a half-set state across primitives that have
23
- * already initialized.
24
- *
25
- * Audit emission: `compliance.posture.set` on every successful
26
- * `set()`, `compliance.posture.cleared` on `clear()`. Operators
27
- * tracking deploys can grep audit for these to reconstruct posture
28
- * history per deployment.
12
+ * `b.compliance.set("hipaa")` cascades the posture into every
13
+ * framework primitive that owns a posture-conditioned default:
14
+ * `b.retention` (TTL floors), `b.audit` (ML-DSA-87 chain-signing),
15
+ * `b.db` (column-policy enforcement), `b.cryptoField` (vacuum-after-
16
+ * erase). Each primitive merges the matching `POSTURE_DEFAULTS`
17
+ * entry into its own state and emits a
18
+ * `compliance.posture.cascade.applied` audit row so operators can
19
+ * confirm the cascade landed.
20
+ *
21
+ * Posture overlays follow a union-of-bars rule: when a primitive
22
+ * knob has different floors per regime (TLS minimum, retention
23
+ * ceiling, hash-algorithm minimum), the strictest applicable bar
24
+ * wins. Operators running under a single posture get that posture's
25
+ * floor; operators running multi-tenant deployments compose
26
+ * per-tenant by reading `postureDefault(posture, key)` per request
27
+ * instead of pinning a single global.
28
+ *
29
+ * Boot-time only `set()` MUST run before the primitives it
30
+ * coordinates are first used. Runtime switches throw
31
+ * `compliance/already-set` because partial cascades produce
32
+ * half-set state across already-initialized primitives.
33
+ *
34
+ * Audit emissions: `compliance.posture.set` on success,
35
+ * `compliance.posture.set_rejected` on unknown / already-set,
36
+ * `compliance.posture.cascade.applied` / `.skipped` per primitive,
37
+ * `compliance.posture.cleared` on `clear()`. Grep audit chain to
38
+ * reconstruct posture history per deployment.
39
+ *
40
+ * @card
41
+ * Top-level compliance-posture coordinator — single source of truth for "what regulatory regime is this deployment running under?".
29
42
  */
30
43
 
31
44
  var lazyRequire = require("./lazy-require");
@@ -34,6 +47,10 @@ var aiAct = require("./compliance-ai-act");
34
47
  var { ComplianceError } = require("./framework-error");
35
48
 
36
49
  var audit = lazyRequire(function () { return require("./audit"); });
50
+ var retentionMod = lazyRequire(function () { return require("./retention"); });
51
+ var auditFwk = lazyRequire(function () { return require("./audit"); });
52
+ var dbMod = lazyRequire(function () { return require("./db"); });
53
+ var cryptoFieldMod = lazyRequire(function () { return require("./crypto-field"); });
37
54
 
38
55
  // Recognised posture names. Aligns with the compliance-posture
39
56
  // vocabulary every guard / retention floor / etc. accepts. Operators
@@ -45,6 +62,8 @@ var KNOWN_POSTURES = Object.freeze([
45
62
  "pci-dss", // Payment Card Industry Data Security Standard
46
63
  "soc2", // System and Organization Controls 2
47
64
  "sox", // Sarbanes-Oxley
65
+ "sox-404", // Sarbanes-Oxley §404 ICFR (DDL change-control + segregation of duties)
66
+ "soc2-cc1.3", // SOC 2 Trust Services Criterion CC1.3 (segregation of duties)
48
67
  "wmhmda", // Washington My Health My Data Act (added 2026)
49
68
  "bipa", // Illinois Biometric Information Privacy Act (added 2026)
50
69
  // ---- US State Privacy ----
@@ -99,6 +118,47 @@ function _emitAudit(action, metadata, outcome) {
99
118
  } catch (_e) { /* audit best-effort */ }
100
119
  }
101
120
 
121
+ /**
122
+ * @primitive b.compliance.set
123
+ * @signature b.compliance.set(posture)
124
+ * @since 0.7.27
125
+ * @status stable
126
+ * @related b.compliance.current, b.compliance.assert, b.compliance.clear, b.compliance.postureDefault
127
+ *
128
+ * Pin the deployment's compliance posture and cascade the matching
129
+ * defaults into every primitive that owns posture-conditioned state
130
+ * (`b.retention`, `b.audit`, `b.db`, `b.cryptoField`). Throws
131
+ * `compliance/unknown-posture` for names outside `KNOWN_POSTURES`,
132
+ * `compliance/already-set` if a different posture is already pinned
133
+ * (runtime switches are forbidden — they create half-set state across
134
+ * already-initialized primitives). Idempotent for the same posture:
135
+ * calling `set("hipaa")` a second time after `set("hipaa")` is a
136
+ * no-op, no audit row, no cascade.
137
+ *
138
+ * Operators wiring multiple regimes pick the strictest single posture
139
+ * here and read per-regime knobs via `postureDefault(posture, key)`
140
+ * for tenant-level overrides — see the @intro union-of-bars note.
141
+ *
142
+ * Emits `compliance.posture.set` (success), `compliance.posture.set_rejected`
143
+ * (unknown/already-set), `compliance.posture.cascade.applied`/`.skipped`
144
+ * per primitive, `compliance.posture.tz_warning` when `process.env.TZ`
145
+ * is set to a non-UTC value under a regulated posture (HIPAA / PCI-DSS /
146
+ * SOX / GDPR / SOC2 / FDA 21 CFR 11).
147
+ *
148
+ * @example
149
+ * b.compliance.set("hipaa");
150
+ * b.compliance.current(); // → "hipaa"
151
+ *
152
+ * // Calling again with the same posture is idempotent:
153
+ * b.compliance.set("hipaa"); // no-op, no audit row
154
+ *
155
+ * // Switching to a different posture throws:
156
+ * try {
157
+ * b.compliance.set("pci-dss");
158
+ * } catch (e) {
159
+ * e.code; // → "compliance/already-set"
160
+ * }
161
+ */
102
162
  function set(posture) {
103
163
  if (typeof posture !== "string" || posture.length === 0) {
104
164
  throw new ComplianceError("compliance/bad-posture",
@@ -128,6 +188,19 @@ function set(posture) {
128
188
  STATE.posture = posture;
129
189
  STATE.setAt = Date.now();
130
190
  _emitAudit("compliance.posture.set", { posture: posture });
191
+
192
+ // F-POSTURE-1 — cascade the posture into every primitive that owns a
193
+ // posture-conditioned default. Each primitive exposes an
194
+ // `applyPosture(name)` that merges the POSTURE_DEFAULTS entry for the
195
+ // posture into its own state and emits
196
+ // `compliance.posture.cascade.applied` with { primitive, posture }
197
+ // metadata. Cascade is ATOMIC at the chain-emission level — every
198
+ // primitive emits success/skipped, and a single primitive's failure
199
+ // (DB not initialized, retention not wired) emits skipped without
200
+ // failing the cascade. Operators wire DB/retention before set();
201
+ // skipped rows surface in the audit chain so a forensic review can
202
+ // reconstruct the boot order.
203
+ _applyPostureCascade(posture);
131
204
  // F-AUD-5 — TZ awareness. Auditors expect timestamps in UTC.
132
205
  // process.env.TZ controls Node's local-time conversion for any
133
206
  // operator code that uses non-UTC formatters; under regulated
@@ -146,10 +219,88 @@ function set(posture) {
146
219
  }
147
220
  }
148
221
 
222
+ // _applyPostureCascade — F-POSTURE-1. Walks every primitive that
223
+ // participates in posture-conditioned defaults and asks it to merge
224
+ // the named posture into its state. Each step is best-effort at the
225
+ // audit-emission level (a primitive that isn't loaded yet emits
226
+ // 'skipped'); each step's success/skipped emits its own audit row so
227
+ // operators can confirm the cascade landed without re-reading
228
+ // state.posture per primitive.
229
+ function _applyPostureCascade(posture) {
230
+ var steps = [
231
+ { primitive: "retention", resolver: function () { return retentionMod(); } },
232
+ { primitive: "audit", resolver: function () { return auditFwk(); } },
233
+ { primitive: "db", resolver: function () { return dbMod(); } },
234
+ { primitive: "cryptoField", resolver: function () { return cryptoFieldMod(); } },
235
+ ];
236
+ for (var i = 0; i < steps.length; i += 1) {
237
+ var step = steps[i];
238
+ var mod;
239
+ try { mod = step.resolver(); }
240
+ catch (_loadErr) { mod = null; }
241
+ if (!mod || typeof mod.applyPosture !== "function") {
242
+ _emitAudit("compliance.posture.cascade.skipped",
243
+ { primitive: step.primitive, posture: posture, reason: "not-loaded-or-no-applyPosture" });
244
+ continue;
245
+ }
246
+ var result;
247
+ try { result = mod.applyPosture(posture); }
248
+ catch (e) {
249
+ _emitAudit("compliance.posture.cascade.skipped",
250
+ { primitive: step.primitive, posture: posture,
251
+ reason: (e && e.message) ? e.message : String(e) },
252
+ "warning");
253
+ continue;
254
+ }
255
+ _emitAudit("compliance.posture.cascade.applied",
256
+ { primitive: step.primitive, posture: posture, applied: result || null });
257
+ }
258
+ }
259
+
260
+ /**
261
+ * @primitive b.compliance.current
262
+ * @signature b.compliance.current()
263
+ * @since 0.7.27
264
+ * @status stable
265
+ * @related b.compliance.set, b.compliance.assert, b.compliance.describe
266
+ *
267
+ * Read the currently-pinned posture, or `null` if `set()` has not yet
268
+ * run. Cheap; pure read of internal state. Operators rendering an
269
+ * admin-UI banner ("running under HIPAA posture") call this once per
270
+ * page render — no caching needed.
271
+ *
272
+ * @example
273
+ * b.compliance.current(); // → null
274
+ * b.compliance.set("hipaa");
275
+ * b.compliance.current(); // → "hipaa"
276
+ */
149
277
  function current() {
150
278
  return STATE.posture;
151
279
  }
152
280
 
281
+ /**
282
+ * @primitive b.compliance.assert
283
+ * @signature b.compliance.assert(posture)
284
+ * @since 0.7.27
285
+ * @status stable
286
+ * @related b.compliance.current, b.compliance.set
287
+ *
288
+ * Throw `compliance/assertion-failed` if the currently-pinned posture
289
+ * differs from `posture`. Use at the top of a request handler that is
290
+ * only safe to run under a specific regime — fails closed with a
291
+ * stack trace that names the mismatch instead of silently serving
292
+ * under the wrong posture.
293
+ *
294
+ * @example
295
+ * b.compliance.set("hipaa");
296
+ * b.compliance.assert("hipaa"); // → no throw
297
+ *
298
+ * try {
299
+ * b.compliance.assert("pci-dss");
300
+ * } catch (e) {
301
+ * e.code; // → "compliance/assertion-failed"
302
+ * }
303
+ */
153
304
  function assert(posture) {
154
305
  if (STATE.posture !== posture) {
155
306
  throw new ComplianceError("compliance/assertion-failed",
@@ -158,6 +309,25 @@ function assert(posture) {
158
309
  }
159
310
  }
160
311
 
312
+ /**
313
+ * @primitive b.compliance.clear
314
+ * @signature b.compliance.clear()
315
+ * @since 0.7.27
316
+ * @status stable
317
+ * @related b.compliance.set, b.compliance.current
318
+ *
319
+ * Reset the pinned posture to `null` and emit a
320
+ * `compliance.posture.cleared` audit row carrying the previous
321
+ * posture. Reserved for tests + operator-controlled tear-down — the
322
+ * primitives that were cascaded into do not roll back their merged
323
+ * defaults, so production code that called `set()` should not call
324
+ * `clear()` mid-life.
325
+ *
326
+ * @example
327
+ * b.compliance.set("hipaa");
328
+ * b.compliance.clear();
329
+ * b.compliance.current(); // → null
330
+ */
161
331
  function clear() {
162
332
  // Reserved for tests + operator-controlled tear-down. Emits an audit
163
333
  // row so the chain shows the posture was intentionally cleared.
@@ -289,16 +459,189 @@ var REGIME_MAP = Object.freeze({
289
459
  },
290
460
  });
291
461
 
462
+ /**
463
+ * @primitive b.compliance.describe
464
+ * @signature b.compliance.describe(posture)
465
+ * @since 0.7.27
466
+ * @status stable
467
+ * @related b.compliance.list, b.compliance.posturesByJurisdiction, b.compliance.posturesByDomain
468
+ *
469
+ * Resolve a posture name to its human-readable record:
470
+ * `{ name, citation, jurisdiction, domain }`. Returns `null` for
471
+ * unknown postures. Operators rendering "we run under {name}
472
+ * ({citation})" in admin UI / generated audit reports reach for this
473
+ * instead of hand-rolling a lookup; the values track the regulatory
474
+ * text and update with the framework rather than going stale in
475
+ * operator code.
476
+ *
477
+ * @example
478
+ * var meta = b.compliance.describe("hipaa");
479
+ * meta.name; // → "Health Insurance Portability and Accountability Act"
480
+ * meta.citation; // → "Pub. L. 104-191; 45 CFR Parts 160, 162, 164"
481
+ * meta.jurisdiction; // → "US"
482
+ * meta.domain; // → "health"
483
+ *
484
+ * b.compliance.describe("not-a-real-posture"); // → null
485
+ */
292
486
  function describe(posture) {
293
487
  return REGIME_MAP[posture] || null;
294
488
  }
295
489
 
490
+ // POSTURE_DEFAULTS — per-posture configuration knobs that primitives
491
+ // (b.backup, b.retention, b.audit, b.cryptoField, b.db, etc.) consult
492
+ // when the operator hasn't passed an explicit value. Not user-facing
493
+ // config — primitives look up here at boot to enforce regulatory
494
+ // floors.
495
+ //
496
+ // Keys per posture:
497
+ // backupEncryptionRequired — backup.create refuses encrypt:false (F-BUDR-4)
498
+ // auditChainSignedRequired — audit emissions MUST be ML-DSA-87 chain-signed
499
+ // tlsMinVersion — minimum TLS version (string e.g. "TLSv1.3")
500
+ // sessionAbsoluteTimeoutMs — hard session expiry ceiling
501
+ // requireVacuumAfterErase — F-RTBF-2: cryptoField.eraseRow must call
502
+ // b.db.vacuumAfterErase({ mode: "full" })
503
+ // so freed B-tree index pages don't linger
504
+ // with sealed-column ciphertext readable
505
+ // from a forensic disk image. GDPR Art. 17
506
+ // + DPDP §12 + LGPD-BR Art. 18 + PIPL-CN
507
+ // Art. 47 all require effective erasure;
508
+ // leftover index residue defeats it.
509
+ //
510
+ // This table is the single source-of-truth — duplicating values into
511
+ // per-primitive defaults would drift the moment a regulator updates.
512
+ var POSTURE_DEFAULTS = Object.freeze({
513
+ "hipaa": Object.freeze({
514
+ backupEncryptionRequired: true,
515
+ auditChainSignedRequired: true,
516
+ tlsMinVersion: "TLSv1.3",
517
+ requireVacuumAfterErase: true,
518
+ }),
519
+ "pci-dss": Object.freeze({
520
+ backupEncryptionRequired: true,
521
+ auditChainSignedRequired: true,
522
+ tlsMinVersion: "TLSv1.3",
523
+ requireVacuumAfterErase: false,
524
+ }),
525
+ "gdpr": Object.freeze({
526
+ backupEncryptionRequired: false, // GDPR Art. 32 says "appropriate" — not mandatory floor // allow:protocol-constant — regulatory article number in prose
527
+ auditChainSignedRequired: true,
528
+ tlsMinVersion: "TLSv1.3",
529
+ // GDPR Art. 17 — "right to erasure" includes residual indexes; B-tree
530
+ // pages holding sealed-column ciphertext after a row-erase defeat
531
+ // the right unless followed by a full vacuum.
532
+ requireVacuumAfterErase: true,
533
+ }),
534
+ "soc2": Object.freeze({
535
+ backupEncryptionRequired: false,
536
+ auditChainSignedRequired: true,
537
+ tlsMinVersion: "TLSv1.3",
538
+ requireVacuumAfterErase: false,
539
+ }),
540
+ "dora": Object.freeze({
541
+ backupEncryptionRequired: true,
542
+ auditChainSignedRequired: true,
543
+ tlsMinVersion: "TLSv1.3",
544
+ requireVacuumAfterErase: false,
545
+ }),
546
+ // LGPD-BR Art. 18 — equivalent right to deletion + residue cleanup.
547
+ "lgpd-br": Object.freeze({
548
+ backupEncryptionRequired: false,
549
+ auditChainSignedRequired: true,
550
+ tlsMinVersion: "TLSv1.3",
551
+ requireVacuumAfterErase: true,
552
+ }),
553
+ // PIPL-CN Art. 47 — deletion right; cross-border residue concerns.
554
+ "pipl-cn": Object.freeze({
555
+ backupEncryptionRequired: true,
556
+ auditChainSignedRequired: true,
557
+ tlsMinVersion: "TLSv1.3",
558
+ requireVacuumAfterErase: true,
559
+ }),
560
+ // India DPDP Act 2023 §12 — right to erasure with effectiveness floor.
561
+ "dpdp": Object.freeze({
562
+ backupEncryptionRequired: false,
563
+ auditChainSignedRequired: true,
564
+ tlsMinVersion: "TLSv1.3",
565
+ requireVacuumAfterErase: true,
566
+ }),
567
+ });
568
+
569
+ /**
570
+ * @primitive b.compliance.postureDefault
571
+ * @signature b.compliance.postureDefault(posture, key)
572
+ * @since 0.7.27
573
+ * @status stable
574
+ * @related b.compliance.set, b.compliance.list
575
+ *
576
+ * Look up a single posture-conditioned default without pinning the
577
+ * posture globally. Returns `null` for unknown postures, unknown
578
+ * keys, or empty/non-string inputs. Used by primitives that need to
579
+ * read a regime's floor per-tenant in a multi-tenant deployment
580
+ * where `set()` would over-pin the process.
581
+ *
582
+ * Recognised keys per posture include `backupEncryptionRequired`,
583
+ * `auditChainSignedRequired`, `tlsMinVersion`, and
584
+ * `requireVacuumAfterErase` — the floors enforced by `b.backup`,
585
+ * `b.audit`, the TLS minimum-version gate, and `b.cryptoField`'s
586
+ * residual-erasure pass.
587
+ *
588
+ * @example
589
+ * b.compliance.postureDefault("hipaa", "tlsMinVersion");
590
+ * // → "TLSv1.3"
591
+ *
592
+ * b.compliance.postureDefault("hipaa", "backupEncryptionRequired");
593
+ * // → true
594
+ *
595
+ * b.compliance.postureDefault("soc2", "requireVacuumAfterErase");
596
+ * // → false
597
+ *
598
+ * b.compliance.postureDefault("hipaa", "no-such-key");
599
+ * // → null
600
+ *
601
+ * b.compliance.postureDefault("not-a-real-posture", "tlsMinVersion");
602
+ * // → null
603
+ */
604
+ function postureDefault(posture, key) {
605
+ if (typeof posture !== "string" || posture.length === 0) return null;
606
+ var d = POSTURE_DEFAULTS[posture];
607
+ if (!d) return null;
608
+ return Object.prototype.hasOwnProperty.call(d, key) ? d[key] : null;
609
+ }
610
+
296
611
  // posturesByDomain — list every posture that maps to the named
297
612
  // domain (privacy / health / payment / cybersecurity / etc.).
298
613
  // Operators rendering compliance dashboards grouped by domain pull
299
614
  // the per-domain posture list with this; admin UIs that show "we
300
615
  // satisfy the privacy regimes for {users.country}" use it to pick
301
616
  // the right posture name without hand-rolling the lookup.
617
+ /**
618
+ * @primitive b.compliance.posturesByDomain
619
+ * @signature b.compliance.posturesByDomain(domain)
620
+ * @since 0.7.27
621
+ * @status stable
622
+ * @related b.compliance.posturesByJurisdiction, b.compliance.list, b.compliance.describe
623
+ *
624
+ * Return every posture name whose `REGIME_MAP[p].domain` equals
625
+ * `domain`, in canonical `KNOWN_POSTURES` order. Returns `[]` for
626
+ * empty/non-string inputs and for domains with no matches.
627
+ * Operators rendering compliance dashboards grouped by domain
628
+ * (privacy / health / payment / cybersecurity / etc.) iterate the
629
+ * domain list once and read posture sets from here.
630
+ *
631
+ * @example
632
+ * b.compliance.posturesByDomain("privacy");
633
+ * // → ["ccpa", "gdpr", "lgpd-br", "pipl-cn", "appi-jp",
634
+ * // "pdpa-sg", "pipeda-ca", "uk-gdpr"]
635
+ *
636
+ * b.compliance.posturesByDomain("health");
637
+ * // → ["hipaa", "wmhmda"]
638
+ *
639
+ * b.compliance.posturesByDomain("payment");
640
+ * // → ["pci-dss"]
641
+ *
642
+ * b.compliance.posturesByDomain("not-a-domain");
643
+ * // → []
644
+ */
302
645
  function posturesByDomain(domain) {
303
646
  if (typeof domain !== "string" || domain.length === 0) return [];
304
647
  var out = [];
@@ -314,6 +657,33 @@ function posturesByDomain(domain) {
314
657
  // deployment (e.g. one that serves users in EU + CA + JP) iterate
315
658
  // over jurisdiction codes and resolve to per-jurisdiction posture
316
659
  // configs without hand-rolling the lookup table.
660
+ /**
661
+ * @primitive b.compliance.posturesByJurisdiction
662
+ * @signature b.compliance.posturesByJurisdiction(jurisdiction)
663
+ * @since 0.7.27
664
+ * @status stable
665
+ * @related b.compliance.posturesByDomain, b.compliance.list, b.compliance.describe
666
+ *
667
+ * Return every posture whose `REGIME_MAP[p].jurisdiction` equals
668
+ * `jurisdiction`, in canonical `KNOWN_POSTURES` order. Jurisdiction
669
+ * values are ISO 3166 alpha-2 codes (`US`, `BR`, `CA`, `JP`, `CN`,
670
+ * `SG`, `UK`) plus `EU` and `international`, and `US-`-prefixed
671
+ * state codes (`US-CA`, `US-IL`, `US-WA`). Returns `[]` for
672
+ * empty/non-string inputs and unknown jurisdictions.
673
+ *
674
+ * @example
675
+ * b.compliance.posturesByJurisdiction("EU");
676
+ * // → ["gdpr", "dora", "nis2", "cra", "ai-act"]
677
+ *
678
+ * b.compliance.posturesByJurisdiction("US");
679
+ * // → ["hipaa", "soc2", "sox"]
680
+ *
681
+ * b.compliance.posturesByJurisdiction("US-CA");
682
+ * // → ["ccpa"]
683
+ *
684
+ * b.compliance.posturesByJurisdiction("XX");
685
+ * // → []
686
+ */
317
687
  function posturesByJurisdiction(jurisdiction) {
318
688
  if (typeof jurisdiction !== "string" || jurisdiction.length === 0) return [];
319
689
  var out = [];
@@ -327,6 +697,34 @@ function posturesByJurisdiction(jurisdiction) {
327
697
  // list — returns every posture as a { name, ...regime-map-fields }
328
698
  // object array, in canonical KNOWN_POSTURES order. Useful for admin
329
699
  // UIs that render the full set as a dropdown / table.
700
+ /**
701
+ * @primitive b.compliance.list
702
+ * @signature b.compliance.list()
703
+ * @since 0.7.27
704
+ * @status stable
705
+ * @related b.compliance.describe, b.compliance.posturesByDomain, b.compliance.posturesByJurisdiction
706
+ *
707
+ * Return every documented posture as a
708
+ * `{ posture, name, citation, jurisdiction, domain }` record array,
709
+ * in canonical `KNOWN_POSTURES` order. Postures present in
710
+ * `KNOWN_POSTURES` but missing from `REGIME_MAP` (sectoral identifiers
711
+ * such as `fapi-2.0` or `ny-2-d`) are skipped — `list()` is the
712
+ * "regimes with full metadata" view; full naming awaits the regime
713
+ * map gaining those rows. Useful for admin UIs that render the full
714
+ * set as a dropdown / table without hand-rolling iteration over
715
+ * `REGIME_MAP`.
716
+ *
717
+ * @example
718
+ * var rows = b.compliance.list();
719
+ * rows[0].posture; // → "hipaa"
720
+ * rows[0].jurisdiction; // → "US"
721
+ * rows[0].domain; // → "health"
722
+ *
723
+ * // Render as a dropdown:
724
+ * var options = rows.map(function (r) {
725
+ * return { value: r.posture, label: r.name + " (" + r.jurisdiction + ")" };
726
+ * });
727
+ */
330
728
  function list() {
331
729
  var out = [];
332
730
  for (var i = 0; i < KNOWN_POSTURES.length; i++) {
@@ -353,9 +751,11 @@ module.exports = {
353
751
  posturesByDomain: posturesByDomain,
354
752
  posturesByJurisdiction: posturesByJurisdiction,
355
753
  list: list,
754
+ postureDefault: postureDefault,
356
755
  sanctions: sanctions,
357
756
  aiAct: aiAct,
358
757
  KNOWN_POSTURES: KNOWN_POSTURES,
758
+ POSTURE_DEFAULTS: POSTURE_DEFAULTS,
359
759
  REGIME_MAP: REGIME_MAP,
360
760
  ComplianceError: ComplianceError,
361
761
  _resetForTest: _resetForTest,