@blamejs/core 0.8.43 → 0.8.50

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 +93 -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/metrics.js CHANGED
@@ -1,99 +1,40 @@
1
1
  "use strict";
2
2
  /**
3
- * metrics — Prometheus-format counters, gauges, and histograms.
3
+ * @module b.metrics
4
+ * @nav Observability
5
+ * @title Metrics
4
6
  *
5
- * Production deployments need numbers. Without a metrics layer ops
6
- * teams are half-blind on every incident: "Was that p99 spike real?
7
- * What's the queue depth? How fast are audit emits going?". This
8
- * module ships the standard Prometheus types with framework
9
- * auto-instrumentation already wired into audit / vault / queue
10
- * hot paths so operators get the framework's vital signs for free.
7
+ * @intro
8
+ * Counter / gauge / histogram primitives in Prometheus 0.0.4 text
9
+ * format with OTLP-friendly labels, plus framework auto-instrumentation
10
+ * wired into audit / vault / queue hot paths.
11
11
  *
12
- * Public API:
12
+ * `b.metrics.create()` returns a registry — call `counter(name)` /
13
+ * `gauge(name)` / `histogram(name)` to register typed metrics, then
14
+ * `requestMiddleware()` for per-request counter+latency, and
15
+ * `expositionHandler()` for the `/metrics` scrape route. Every metric
16
+ * carries a per-instance `labelCardinalityCap` (default 10,000) — when
17
+ * the next label combination would push past the cap the increment
18
+ * drops and a single warning logs, so a runaway label (request-id,
19
+ * raw URL with query string, per-user dimension) can't OOM the
20
+ * process.
13
21
  *
14
- * var m = b.metrics.create({
15
- * namespace: "myapp", // prepended to every metric name
16
- * defaultLabels: { service: "api", version: "1.2.3" },
17
- * labelCardinalityCap: 10000, // per-metric ceiling
18
- * });
22
+ * Framework modules call `metrics.tap("audit.record", value, labels)`
23
+ * at hot paths. Until a registry is active the call is a zero-cost
24
+ * no-op; once `create()` runs, taps flow into pre-registered
25
+ * counters / gauges (`framework_audit_events_total`,
26
+ * `framework_vault_seal_total`, `framework_queue_depth`,
27
+ * `framework_jobs_inflight`, `framework_errors_total`,
28
+ * `framework_http_requests_total`,
29
+ * `framework_http_request_duration_seconds`).
19
30
  *
20
- * var requests = m.counter("http_requests_total", {
21
- * help: "Total HTTP requests",
22
- * labelNames: ["method", "route", "status"],
23
- * });
24
- * requests.inc({ method: "GET", route: "/users", status: "200" });
25
- * requests.inc({ method: "GET", route: "/users", status: "200" }, 5);
31
+ * Best-practice route labels are the route TEMPLATE
32
+ * (`/users/:id`), not the actual path — `requestMiddleware` reads
33
+ * `req.routePattern` when the matcher set one and falls back to the
34
+ * query-stripped URL otherwise.
26
35
  *
27
- * var latency = m.histogram("http_request_duration_seconds", {
28
- * help: "HTTP request latency",
29
- * labelNames: ["method", "route"],
30
- * buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
31
- * });
32
- * latency.observe({ method: "GET", route: "/users" }, 0.123);
33
- *
34
- * var queueDepth = m.gauge("queue_depth", { labelNames: ["queueName"] });
35
- * queueDepth.set({ queueName: "default" }, 42);
36
- * queueDepth.inc({ queueName: "default" });
37
- * queueDepth.dec({ queueName: "default" });
38
- *
39
- * router.use(m.requestMiddleware()); // auto-times every request
40
- * router.get("/metrics", m.expositionHandler());
41
- *
42
- * Framework auto-instrumentation:
43
- * When metrics.create() runs, framework hot paths (audit.record,
44
- * vault.seal, vault.unseal, queue ops) call metrics.tap() — a
45
- * global no-op stub that the active registry replaces with real
46
- * counters. Modules don't import the registry directly; the tap
47
- * pattern keeps them decoupled and lets operators with no metrics
48
- * pay zero cost.
49
- *
50
- * Built-in metrics surfaced:
51
- * framework_audit_events_total{action, outcome} counter
52
- * framework_vault_seal_total counter
53
- * framework_vault_unseal_total counter
54
- * framework_queue_enqueue_total{queueName} counter
55
- * framework_queue_complete_total{queueName} counter
56
- * framework_queue_fail_total{queueName} counter
57
- * framework_queue_depth{queueName} gauge
58
- * framework_jobs_inflight{queueName} gauge
59
- * framework_errors_total{class} counter
60
- * framework_http_requests_total{method,route,status} counter
61
- * framework_http_request_duration_seconds{method,route} histogram
62
- *
63
- * Cardinality control:
64
- * Every metric has a per-instance ceiling on distinct label
65
- * combinations (default 10,000). When a request's label set would
66
- * create the 10,001st unique combination, the increment is dropped
67
- * and a warning is logged ONCE per metric. The bound is high enough
68
- * that legitimate apps don't hit it; low enough that runaway
69
- * labels (a label per request id, per user id, per full URL with
70
- * query string) can't OOM the process. Operators size up via
71
- * labelCardinalityCap when they have a legitimate need.
72
- *
73
- * Best practice: route labels are the route TEMPLATE (`/users/:id`),
74
- * not the actual path (`/users/123`). The framework's
75
- * requestMiddleware uses req.routePattern when set; otherwise falls
76
- * back to req.url stripped of query string.
77
- *
78
- * Exposition format:
79
- * The text/plain exposition follows the Prometheus 0.0.4 text format:
80
- * `# HELP <name> <description>` and `# TYPE <name> <counter|gauge|
81
- * histogram>` headers, one sample per line with serialized labels
82
- * in `{key="value",key2="value2"}` form. Buckets and _sum / _count
83
- * for histograms.
84
- *
85
- * Out of scope (with structural reasons):
86
- * - Summary type (client-side quantiles): generally inferior to
87
- * histogram for aggregation across instances. Prometheus team
88
- * recommends histogram. Add later if a real demand emerges.
89
- * - Push gateway: pull-only monitoring is the simpler architecture.
90
- * Operators with batch jobs that want push wire it themselves.
91
- * - Native histogram (Prometheus 2.40+): not yet broadly supported
92
- * by tooling; classic histogram is universal.
93
- * - Per-process labels at scrape time (instance, hostname): operators
94
- * pass via defaultLabels. The framework doesn't auto-inject —
95
- * deploy environments differ on what to use (k8s pod name,
96
- * hostname, container id) and the operator knows best.
36
+ * @card
37
+ * Counter / gauge / histogram primitives in Prometheus 0.0.4 text format with OTLP-friendly labels, plus framework auto-instrumentation wired into audit / vault / queue hot paths.
97
38
  */
98
39
 
99
40
  var C = require("./constants");
@@ -137,6 +78,28 @@ var LABEL_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
137
78
 
138
79
  var _activeTap = null;
139
80
 
81
+ /**
82
+ * @primitive b.metrics.tap
83
+ * @signature b.metrics.tap(name, value, labels)
84
+ * @since 0.4.0
85
+ * @related b.metrics.create, b.observability.event
86
+ *
87
+ * Framework hot-path tap. Modules call `tap("audit.record", 1,
88
+ * { action, outcome })` without importing a registry. Until
89
+ * `b.metrics.create()` runs the call is a zero-cost no-op; afterwards
90
+ * the active registry routes the tap into pre-registered counters and
91
+ * gauges. Drop-silent on internal throws so a misconfigured metric
92
+ * cannot crash the request that triggered the tap.
93
+ *
94
+ * @example
95
+ * // Module-level — no registry yet, no-op:
96
+ * b.metrics.tap("audit.record", 1, { action: "auth.login", outcome: "success" });
97
+ *
98
+ * // After registry creation, the same tap call increments
99
+ * // framework_audit_events_total{action="auth.login", outcome="success"}.
100
+ * var registry = b.metrics.create({ namespace: "myapp" });
101
+ * b.metrics.tap("audit.record", 1, { action: "auth.login", outcome: "success" });
102
+ */
140
103
  function tap(name, value, labels) {
141
104
  if (_activeTap === null) return;
142
105
  try { _activeTap(name, value, labels); }
@@ -260,6 +223,52 @@ function _resolveLabels(defaultLabels, declaredNames, callLabels) {
260
223
 
261
224
  // ---- registry factory ----
262
225
 
226
+ /**
227
+ * @primitive b.metrics.create
228
+ * @signature b.metrics.create(opts)
229
+ * @since 0.4.0
230
+ * @status stable
231
+ * @related b.metrics.tap, b.observability.event, b.tracing.create
232
+ *
233
+ * Build a Prometheus-format metrics registry. The returned registry
234
+ * exposes `counter` / `gauge` / `histogram` factories,
235
+ * `requestMiddleware()` for per-route auto-instrumentation,
236
+ * `expositionHandler()` for the `/metrics` scrape route, and
237
+ * `exposition()` for direct rendering. Activates the framework
238
+ * auto-tap so audit / vault / queue / error events feed
239
+ * pre-registered framework counters.
240
+ *
241
+ * @opts
242
+ * namespace: string, // prepended to every metric name
243
+ * defaultLabels: object, // attached to every sample
244
+ * labelCardinalityCap: number, // per-metric distinct-label-set cap; default 10000
245
+ *
246
+ * @example
247
+ * var m = b.metrics.create({
248
+ * namespace: "myapp",
249
+ * defaultLabels: { service: "api", version: "1.2.3" },
250
+ * });
251
+ *
252
+ * var requests = m.counter("http_requests_total", {
253
+ * help: "Total HTTP requests",
254
+ * labelNames: ["method", "route", "status"],
255
+ * });
256
+ * requests.inc({ method: "GET", route: "/users", status: "200" });
257
+ *
258
+ * var latency = m.histogram("http_request_duration_seconds", {
259
+ * help: "HTTP request latency",
260
+ * labelNames: ["method", "route"],
261
+ * buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
262
+ * });
263
+ * latency.observe({ method: "GET", route: "/users" }, 0.123);
264
+ *
265
+ * var depth = m.gauge("queue_depth", { labelNames: ["queueName"] });
266
+ * depth.set({ queueName: "default" }, 42);
267
+ *
268
+ * // Wire into an HTTP server.
269
+ * router.use(m.requestMiddleware());
270
+ * router.get("/metrics", m.expositionHandler());
271
+ */
263
272
  function create(opts) {
264
273
  opts = opts || {};
265
274
  validateOpts(opts, [
@@ -43,6 +43,42 @@ var audit = lazyRequire(function () { return require("../audit"); });
43
43
 
44
44
  var AgeGateError = defineClass("AgeGateError", { alwaysPermanent: true });
45
45
 
46
+ /**
47
+ * @primitive b.middleware.ageGate
48
+ * @signature b.middleware.ageGate(opts)
49
+ * @since 0.1.0
50
+ * @compliance gdpr, ferpa, ccpa
51
+ * @related b.middleware.gpc
52
+ *
53
+ * Classifies the request against an operator-supplied age predicate
54
+ * and applies COPPA / UK Children's Code / California AADC defaults
55
+ * (no-store cache, no-referrer, X-Privacy-Posture header) for
56
+ * below-threshold + unknown-age requests. When `requireAge` is set
57
+ * and the request is below threshold without a parental-consent
58
+ * record, refuses with HTTP 451. Every classification decision is
59
+ * audited.
60
+ *
61
+ * @opts
62
+ * {
63
+ * getAge: function(req): number|null, // required
64
+ * requireAge: number|null, // 451 below this
65
+ * consentRequired: number|null, // require consent below
66
+ * hasParentalConsent: function(req): boolean,
67
+ * skipPaths: string[],
68
+ * errorMessage: string,
69
+ * audit: boolean, // default true
70
+ * }
71
+ *
72
+ * @example
73
+ * var b = require("@blamejs/core");
74
+ * var app = b.router.create();
75
+ * app.use(b.middleware.ageGate({
76
+ * getAge: function (req) { return (req.user && req.user.age) || null; },
77
+ * requireAge: null,
78
+ * consentRequired: 13,
79
+ * hasParentalConsent: function (req) { return req.user && req.user.parentalConsent === true; },
80
+ * }));
81
+ */
46
82
  function create(opts) {
47
83
  opts = opts || {};
48
84
  validateOpts(opts, [
@@ -42,6 +42,43 @@ var requestHelpers = require("../request-helpers");
42
42
  var aiActMod = lazyRequire(function () { return require("../compliance-ai-act"); });
43
43
  var audit = lazyRequire(function () { return require("../audit"); });
44
44
 
45
+ /**
46
+ * @primitive b.middleware.aiActDisclosure
47
+ * @signature b.middleware.aiActDisclosure(opts)
48
+ * @since 0.1.0
49
+ * @compliance eu-ai-act
50
+ * @related b.middleware.botDisclose
51
+ *
52
+ * Injects EU AI Act Article 50 transparency disclosures into outgoing
53
+ * responses. In `mode: "header"` (default) it sets `AI-Act-Notice` and
54
+ * `AI-Act-Article` response headers — cheapest, works for both JSON
55
+ * and HTML. In `mode: "html"` it additionally inserts a status banner
56
+ * after `<body>` and a `<meta>` inside `<head>` for HTML responses.
57
+ * Skips error pages, redirects, requests bearing the configured
58
+ * skip-header, and responses opted out via `res.locals.aiActSkip`.
59
+ * Emits `compliance.aiact.disclosed` audits on success.
60
+ *
61
+ * @opts
62
+ * {
63
+ * kind: "ai-interaction"|"deepfake"|"emotion-recognition"|"biometric-categorisation"|"synthetic-content",
64
+ * deployerName: string,
65
+ * policyUri: string,
66
+ * mode: "header"|"html", // default "header"
67
+ * lang: string, // default "en"
68
+ * skipHeader: string, // default "x-skip-ai-act"
69
+ * audit: boolean, // default true
70
+ * }
71
+ *
72
+ * @example
73
+ * var b = require("@blamejs/core");
74
+ * var app = b.router.create();
75
+ * app.use(b.middleware.aiActDisclosure({
76
+ * kind: "ai-interaction",
77
+ * deployerName: "myco",
78
+ * policyUri: "https://myco.example.com/ai-policy",
79
+ * mode: "html",
80
+ * }));
81
+ */
45
82
  function create(opts) {
46
83
  opts = opts || {};
47
84
  validateOpts(opts, [
@@ -232,6 +232,51 @@ function _writeRejection(res, code, body) {
232
232
 
233
233
  // ---- Server-side middleware ----
234
234
 
235
+ /**
236
+ * @primitive b.middleware.apiEncrypt
237
+ * @signature b.middleware.apiEncrypt(opts)
238
+ * @since 0.1.0
239
+ * @related b.middleware.csrfProtect
240
+ *
241
+ * End-to-end PQC payload encryption for operator-controlled clients.
242
+ * TLS protects browser to load-balancer; this middleware protects the
243
+ * request and response bodies through every intermediate hop (LB,
244
+ * sidecars, queues, log aggregators, APM tooling). A tampered byte
245
+ * anywhere downstream of the encrypted boundary fails the AEAD tag
246
+ * before the route handler runs. Defends against stripped TLS,
247
+ * body capture in observability tooling, and replay (timestamp +
248
+ * nonce window). Mount with a server keypair set; the configured
249
+ * client SDK encrypts to the keypair's public half.
250
+ *
251
+ * @opts
252
+ * {
253
+ * keypair: { publicKey, secretKey, ecPublicKey, ecSecretKey },
254
+ * keypairs: [...] // multi-key rotation set; first = active
255
+ * replayWindowMs: number,
256
+ * pruneIntervalMs: number,
257
+ * nonceStore: { has, add, prune },
258
+ * exemptPaths: string[],
259
+ * contentTypes: string[], // default ["application/json"]
260
+ * audit: boolean,
261
+ * maxDecryptedBytes: number,
262
+ * trustProxy: boolean|number,
263
+ * keying: "per-request"|"per-session",
264
+ * sessionStore: object,
265
+ * sessionTtlMs: number,
266
+ * sessionMaxResponses: number,
267
+ * observability: object,
268
+ * }
269
+ *
270
+ * @example
271
+ * var b = require("@blamejs/core");
272
+ * var app = b.router.create();
273
+ * var kp = b.crypto.keypair();
274
+ * app.use(b.middleware.apiEncrypt({
275
+ * keypair: kp,
276
+ * replayWindowMs: 30000,
277
+ * contentTypes: ["application/json"],
278
+ * }));
279
+ */
235
280
  function create(opts) {
236
281
  opts = opts || {};
237
282
  validateOpts(opts, [
@@ -34,6 +34,46 @@ var AssetlinksError = defineClass("AssetlinksError", { alwaysPermanent: true });
34
34
 
35
35
  var observability = lazyRequire(function () { return require("../observability"); });
36
36
 
37
+ /**
38
+ * @primitive b.middleware.assetlinks
39
+ * @signature b.middleware.assetlinks(opts)
40
+ * @since 0.1.0
41
+ * @related b.middleware.webAppManifest
42
+ *
43
+ * Serves Digital Asset Links at `/.well-known/assetlinks.json` per
44
+ * Google's spec (Trusted Web Activity, Android App Links, Smart Lock,
45
+ * WebAuthn-for-Android). The statements array is JSON-serialized once
46
+ * at create-time and emitted with `Content-Type: application/json`.
47
+ * Multi-app deployments include multiple statement entries.
48
+ *
49
+ * @opts
50
+ * {
51
+ * statements: Array<{
52
+ * relation: string[],
53
+ * target: {
54
+ * namespace: string,
55
+ * package_name?: string,
56
+ * sha256_cert_fingerprints?: string[],
57
+ * site?: string,
58
+ * },
59
+ * }>,
60
+ * audit: boolean, // default true
61
+ * }
62
+ *
63
+ * @example
64
+ * var b = require("@blamejs/core");
65
+ * var app = b.router.create();
66
+ * app.use(b.middleware.assetlinks({
67
+ * statements: [{
68
+ * relation: ["delegate_permission/common.handle_all_urls"],
69
+ * target: {
70
+ * namespace: "android_app",
71
+ * package_name: "com.example.app",
72
+ * sha256_cert_fingerprints: ["AB:CD:EF:01:23:45:67:89"],
73
+ * },
74
+ * }],
75
+ * }));
76
+ */
37
77
  function create(opts) {
38
78
  validateOpts.requireObject(opts, "middleware.assetlinks", AssetlinksError);
39
79
  validateOpts(opts, ["statements", "audit"], "middleware.assetlinks");
@@ -28,6 +28,41 @@ var AsyncApiError = defineClass("AsyncApiError", { alwaysPermanent: true });
28
28
  var openapiYaml = lazyRequire(function () { return require("../openapi-yaml"); });
29
29
  var audit = lazyRequire(function () { return require("../audit"); });
30
30
 
31
+ /**
32
+ * @primitive b.middleware.asyncapiServe
33
+ * @signature b.middleware.asyncapiServe(opts)
34
+ * @since 0.1.0
35
+ * @related b.middleware.openapiServe, b.asyncapi.create
36
+ *
37
+ * Serves an AsyncAPI 3.0 document built via `b.asyncapi.create` at
38
+ * configurable JSON + YAML mount points. Matches `openapiServe`
39
+ * behaviour: GET/HEAD only, SHA3-512 ETag with conditional 304,
40
+ * operator-controlled CORS gate, falls through on unmatched paths
41
+ * or methods. Use to publish channel + operation + message + schema
42
+ * specs for event-driven APIs (Kafka, AMQP, MQTT, WebSocket).
43
+ *
44
+ * @opts
45
+ * {
46
+ * document: object, // builder from b.asyncapi.create()
47
+ * pathJson: string, // default "/asyncapi.json"
48
+ * pathYaml: string, // default "/asyncapi.yaml"
49
+ * pretty: boolean, // default false → minified
50
+ * cacheControl: string, // default "public, max-age=300"
51
+ * accessControl: "public"|"same-origin"|{ allowOrigin: string },
52
+ * audit: boolean, // default true
53
+ * }
54
+ *
55
+ * @example
56
+ * var b = require("@blamejs/core");
57
+ * var app = b.router.create();
58
+ * var aapi = b.asyncapi.create({ title: "events", version: "1.0.0" });
59
+ * app.use(b.middleware.asyncapiServe({
60
+ * document: aapi,
61
+ * pathJson: "/asyncapi.json",
62
+ * pathYaml: "/asyncapi.yaml",
63
+ * accessControl: "public",
64
+ * }));
65
+ */
31
66
  function create(opts) {
32
67
  opts = opts || {};
33
68
  validateOpts(opts, [
@@ -63,6 +63,46 @@ function _readBearer(authHeader) {
63
63
  return m ? m[1].trim() : null;
64
64
  }
65
65
 
66
+ /**
67
+ * @primitive b.middleware.attachUser
68
+ * @signature b.middleware.attachUser(req, res, next)
69
+ * @since 0.1.0
70
+ * @related b.middleware.requireAuth, b.middleware.bearerAuth, b.session.verify
71
+ *
72
+ * Populates `req.user` and `req.session` from a verified session
73
+ * token. Constructed via `b.middleware.attachUser(opts)`; the
74
+ * resulting middleware has the `(req, res, next)` shape shown
75
+ * above. Tries the configured cookie first, then `Authorization:
76
+ * Bearer <token>`. Sealed cookies (vault-unwrapped) are supported so
77
+ * the cookie isn't reachable via curl-with-arbitrary-cookies. The
78
+ * framework can't know the operator's user schema; `userLoader`
79
+ * receives the verified session and returns the user record. Always
80
+ * calls `next()` — gating decisions live in
81
+ * `b.middleware.requireAuth`. Optional fingerprint-drift / IP-UA pin
82
+ * / anomaly-score enforcement threads through `session.verify`.
83
+ *
84
+ * @opts
85
+ * {
86
+ * userLoader: async function(session): user|null, // required
87
+ * cookieName: string, // default "blamejs_session"
88
+ * tokenFrom: "both"|"cookie"|"header", // default "both"
89
+ * sealed: boolean,
90
+ * vault: object, // required when sealed
91
+ * requireFingerprintMatch: boolean,
92
+ * maxAnomalyScore: number,
93
+ * scorer: function,
94
+ * audit: boolean, // default true
95
+ * }
96
+ *
97
+ * @example
98
+ * var b = require("@blamejs/core");
99
+ * var app = b.router.create();
100
+ * app.use(b.middleware.attachUser({
101
+ * userLoader: async function (session) {
102
+ * return { id: session.userId, name: "alice" };
103
+ * },
104
+ * }));
105
+ */
66
106
  function create(opts) {
67
107
  opts = opts || {};
68
108
  validateOpts(opts, [
@@ -78,6 +78,46 @@ function _extractToken(req, scheme) {
78
78
  return { state: "ok", token: token };
79
79
  }
80
80
 
81
+ /**
82
+ * @primitive b.middleware.bearerAuth
83
+ * @signature b.middleware.bearerAuth(req, res, next)
84
+ * @since 0.1.0
85
+ * @related b.middleware.attachUser, b.middleware.requireAuth, b.auth.jwt
86
+ *
87
+ * Extracts `Authorization: Bearer <token>`, calls an operator-supplied
88
+ * verifier, attaches the result to `req.user`. Constructed via
89
+ * `b.middleware.bearerAuth(opts)`; the resulting middleware has
90
+ * the `(req, res, next)` shape shown above. Distinct from
91
+ * `attachUser` (cookie sessions) — this is the API-token / JWT /
92
+ * OAuth-access-token path. When the header is absent the middleware
93
+ * defers to downstream auth; when it IS present but invalid it
94
+ * rejects with HTTP 401 + `WWW-Authenticate` immediately. Verifier
95
+ * returns the user object on success, null/false on rejection, or
96
+ * throws an Error with `code === "auth-bearer/expired"` to surface
97
+ * a token-expired challenge. Emits `auth.bearer.success` /
98
+ * `auth.bearer.failure` audit events with actor context.
99
+ *
100
+ * @opts
101
+ * {
102
+ * verify: async function(token): user|null, // required
103
+ * scheme: string, // default "Bearer"; some ops use "Token"
104
+ * realm: string,
105
+ * errorMessage: string,
106
+ * tokenAttachKey: string,
107
+ * userAttachKey: string,
108
+ * audit: boolean, // default true
109
+ * }
110
+ *
111
+ * @example
112
+ * var b = require("@blamejs/core");
113
+ * var app = b.router.create();
114
+ * app.use("/api", b.middleware.bearerAuth({
115
+ * verify: async function (token) {
116
+ * if (token === "valid-token") return { id: "user-1" };
117
+ * return null;
118
+ * },
119
+ * }));
120
+ */
81
121
  function create(opts) {
82
122
  opts = opts || {};
83
123
  validateOpts(opts, [