@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.
- package/CHANGELOG.md +92 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/compliance.js
CHANGED
|
@@ -1,31 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.compliance
|
|
3
|
+
* @module b.compliance
|
|
4
|
+
* @featured true
|
|
5
|
+
* @nav Compliance
|
|
6
|
+
* @title Compliance
|
|
4
7
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
*
|
|
13
|
-
* b.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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,
|