@blamejs/blamejs-shop 0.4.31 → 0.4.32

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 (336) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/lib/asset-manifest.json +1 -1
  3. package/lib/vendor/MANIFEST.json +392 -278
  4. package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
  5. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
  6. package/lib/vendor/blamejs/.gitignore +6 -0
  7. package/lib/vendor/blamejs/CHANGELOG.md +26 -0
  8. package/lib/vendor/blamejs/MIGRATING.md +43 -0
  9. package/lib/vendor/blamejs/README.md +8 -6
  10. package/lib/vendor/blamejs/SECURITY.md +19 -3
  11. package/lib/vendor/blamejs/api-snapshot.json +2190 -664
  12. package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
  13. package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
  14. package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
  15. package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
  16. package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
  17. package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
  18. package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
  19. package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
  20. package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
  21. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
  22. package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
  23. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
  24. package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
  25. package/lib/vendor/blamejs/index.js +4 -0
  26. package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
  27. package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
  28. package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
  29. package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
  30. package/lib/vendor/blamejs/lib/api-key.js +158 -77
  31. package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
  32. package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
  33. package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
  34. package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
  35. package/lib/vendor/blamejs/lib/audit.js +259 -123
  36. package/lib/vendor/blamejs/lib/auth/oauth.js +53 -9
  37. package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
  38. package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
  39. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +31 -5
  40. package/lib/vendor/blamejs/lib/backup/index.js +45 -10
  41. package/lib/vendor/blamejs/lib/break-glass.js +355 -147
  42. package/lib/vendor/blamejs/lib/cache.js +174 -105
  43. package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
  44. package/lib/vendor/blamejs/lib/cli.js +19 -14
  45. package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
  46. package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
  47. package/lib/vendor/blamejs/lib/cluster.js +119 -71
  48. package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
  49. package/lib/vendor/blamejs/lib/compliance.js +206 -4
  50. package/lib/vendor/blamejs/lib/consent.js +82 -29
  51. package/lib/vendor/blamejs/lib/constants.js +27 -11
  52. package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
  53. package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
  54. package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
  55. package/lib/vendor/blamejs/lib/db-query.js +882 -260
  56. package/lib/vendor/blamejs/lib/db-schema.js +228 -44
  57. package/lib/vendor/blamejs/lib/db.js +249 -99
  58. package/lib/vendor/blamejs/lib/dsr.js +385 -55
  59. package/lib/vendor/blamejs/lib/error-page.js +14 -1
  60. package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
  61. package/lib/vendor/blamejs/lib/external-db.js +549 -34
  62. package/lib/vendor/blamejs/lib/file-upload.js +52 -7
  63. package/lib/vendor/blamejs/lib/framework-error.js +20 -1
  64. package/lib/vendor/blamejs/lib/framework-files.js +73 -0
  65. package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
  66. package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
  67. package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
  68. package/lib/vendor/blamejs/lib/guard-all.js +1 -0
  69. package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
  70. package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
  71. package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
  72. package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
  73. package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
  74. package/lib/vendor/blamejs/lib/guard-email.js +47 -69
  75. package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
  76. package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
  77. package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
  78. package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
  79. package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
  80. package/lib/vendor/blamejs/lib/guard-html.js +53 -108
  81. package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
  82. package/lib/vendor/blamejs/lib/guard-image.js +46 -103
  83. package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
  84. package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
  85. package/lib/vendor/blamejs/lib/guard-json.js +38 -108
  86. package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
  87. package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
  88. package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
  89. package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
  90. package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
  91. package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
  92. package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
  93. package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
  94. package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
  95. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
  96. package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
  97. package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
  98. package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
  99. package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
  100. package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
  101. package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
  102. package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
  103. package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
  104. package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
  105. package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
  106. package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
  107. package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
  108. package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
  109. package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
  110. package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
  111. package/lib/vendor/blamejs/lib/guard-template.js +35 -172
  112. package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
  113. package/lib/vendor/blamejs/lib/guard-time.js +32 -154
  114. package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
  115. package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
  116. package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
  117. package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
  118. package/lib/vendor/blamejs/lib/http-client.js +37 -9
  119. package/lib/vendor/blamejs/lib/inbox.js +120 -107
  120. package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
  121. package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
  122. package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
  123. package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
  124. package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
  125. package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
  126. package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
  127. package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
  128. package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
  129. package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
  130. package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
  131. package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
  132. package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
  133. package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
  134. package/lib/vendor/blamejs/lib/mail-store.js +293 -154
  135. package/lib/vendor/blamejs/lib/mail.js +8 -4
  136. package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
  137. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
  138. package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
  139. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
  140. package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
  141. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
  142. package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
  143. package/lib/vendor/blamejs/lib/migrations.js +108 -66
  144. package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
  145. package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
  146. package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
  147. package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
  148. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
  149. package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
  150. package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
  151. package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
  152. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
  153. package/lib/vendor/blamejs/lib/observability.js +124 -0
  154. package/lib/vendor/blamejs/lib/otel-export.js +12 -3
  155. package/lib/vendor/blamejs/lib/outbox.js +184 -83
  156. package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
  157. package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
  158. package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
  159. package/lib/vendor/blamejs/lib/queue-local.js +225 -140
  160. package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
  161. package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
  162. package/lib/vendor/blamejs/lib/queue.js +7 -0
  163. package/lib/vendor/blamejs/lib/redact.js +68 -11
  164. package/lib/vendor/blamejs/lib/redis-client.js +160 -31
  165. package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
  166. package/lib/vendor/blamejs/lib/retention.js +101 -40
  167. package/lib/vendor/blamejs/lib/router.js +212 -5
  168. package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
  169. package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
  170. package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
  171. package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
  172. package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
  173. package/lib/vendor/blamejs/lib/safe-url.js +170 -3
  174. package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
  175. package/lib/vendor/blamejs/lib/scheduler.js +35 -12
  176. package/lib/vendor/blamejs/lib/seeders.js +122 -74
  177. package/lib/vendor/blamejs/lib/session-stores.js +42 -14
  178. package/lib/vendor/blamejs/lib/session.js +175 -77
  179. package/lib/vendor/blamejs/lib/sql.js +3842 -0
  180. package/lib/vendor/blamejs/lib/sse.js +26 -0
  181. package/lib/vendor/blamejs/lib/ssrf-guard.js +151 -4
  182. package/lib/vendor/blamejs/lib/static.js +177 -34
  183. package/lib/vendor/blamejs/lib/subject.js +96 -49
  184. package/lib/vendor/blamejs/lib/vault/index.js +3 -2
  185. package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
  186. package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
  187. package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
  188. package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
  189. package/lib/vendor/blamejs/lib/websocket.js +35 -5
  190. package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
  191. package/lib/vendor/blamejs/package.json +2 -2
  192. package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
  193. package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
  194. package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
  195. package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
  196. package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
  197. package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
  198. package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
  199. package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
  200. package/lib/vendor/blamejs/scripts/check-services.js +21 -0
  201. package/lib/vendor/blamejs/scripts/gen-migrating.js +51 -0
  202. package/lib/vendor/blamejs/scripts/release.js +398 -38
  203. package/lib/vendor/blamejs/test/00-primitives.js +117 -0
  204. package/lib/vendor/blamejs/test/10-state.js +140 -14
  205. package/lib/vendor/blamejs/test/20-db.js +65 -2
  206. package/lib/vendor/blamejs/test/helpers/db.js +9 -0
  207. package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
  208. package/lib/vendor/blamejs/test/helpers/services.js +21 -0
  209. package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
  210. package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
  211. package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
  212. package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
  213. package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
  214. package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
  215. package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
  216. package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
  217. package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
  218. package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
  219. package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
  220. package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
  221. package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
  222. package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
  223. package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
  224. package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
  225. package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
  226. package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
  227. package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
  228. package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
  229. package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
  230. package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
  231. package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
  232. package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
  233. package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
  234. package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
  235. package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
  236. package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
  237. package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
  238. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
  239. package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
  240. package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
  241. package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
  242. package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
  243. package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
  244. package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
  245. package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
  246. package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
  247. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
  248. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1120 -14
  249. package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
  250. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
  251. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
  252. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
  253. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
  254. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
  255. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
  256. package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
  257. package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
  258. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
  259. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
  260. package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
  261. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
  262. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
  263. package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
  264. package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
  265. package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
  266. package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
  267. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
  268. package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
  269. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
  270. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
  271. package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
  272. package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
  273. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
  274. package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
  275. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
  276. package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
  277. package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
  278. package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
  279. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
  280. package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
  281. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
  282. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
  283. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
  284. package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
  285. package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
  286. package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
  287. package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
  288. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
  289. package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
  290. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
  291. package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
  292. package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
  293. package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
  294. package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
  295. package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
  296. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +309 -0
  297. package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
  298. package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
  299. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
  300. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
  301. package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
  302. package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
  303. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
  304. package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
  305. package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
  306. package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
  307. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
  308. package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
  309. package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
  310. package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
  311. package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
  312. package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
  313. package/lib/vendor/blamejs/test/smoke.js +79 -21
  314. package/package.json +1 -1
  315. package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
  316. package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
  317. package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
  318. package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
  319. package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
  320. package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
  321. package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
  322. package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
  323. package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
  324. package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
  325. package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
  326. package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
  327. package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
  328. package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
  329. package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
  330. package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
  331. package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
  332. package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
  333. package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
  334. package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
  335. package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
  336. package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
@@ -0,0 +1,639 @@
1
+ "use strict";
2
+ /**
3
+ * Live MySQL coverage for the b.sql data layer of:
4
+ *
5
+ * lib/audit.js — record() → chain-writer, checkpoint(),
6
+ * _upsertAuditTip() fencing-token guard (the MySQL
7
+ * ON DUPLICATE KEY UPDATE + IF-fold + readback path),
8
+ * verify(), verifyCheckpoints()
9
+ * lib/audit-tools.js — exportSlice / verifyBundle reading the live MySQL
10
+ * audit_log + the purge-anchor UPSERT
11
+ * lib/chain-writer.js — _insertRow / counter primer / tip read on MySQL
12
+ * lib/break-glass.js — policy.set/get/list (ON DUPLICATE KEY UPSERT) +
13
+ * grant + unsealRow consume (the backtick-quoted
14
+ * rowsConsumed increment), routed to live MySQL
15
+ * lib/crypto-field.js — a K_row (vault.row:) sealed cell stored as TEXT in
16
+ * MySQL and read back + derived-hash dual-read
17
+ *
18
+ * Each of these files threads { dialect: clusterStorage.dialect() } into
19
+ * every framework-table b.sql call, so in cluster mode against a MySQL
20
+ * backend the emitted SQL is backtick-quoted with ON DUPLICATE KEY UPDATE —
21
+ * what MySQL accepts. Defaulting to "sqlite" emitted double-quoted
22
+ * identifiers (string literals on MySQL) + ON CONFLICT (a syntax error),
23
+ * which is what this file proves is no longer the case.
24
+ *
25
+ * The driver is a docker-exec mysql shim (per-statement, like the
26
+ * data-layer-cluster-mysql file). None of the five files under test use
27
+ * clusterStorage.transaction, so the per-statement driver is sufficient.
28
+ *
29
+ * RUN: node scripts/test-integration.js --skip-service-check audit-stack-mysql
30
+ */
31
+
32
+ var execFileSync = require("node:child_process").execFileSync;
33
+ var fs = require("node:fs");
34
+ var os = require("node:os");
35
+ var path = require("node:path");
36
+
37
+ var helpers = require("../helpers");
38
+ var check = helpers.check;
39
+ var services = require("../helpers/services");
40
+ var setupTestDb = require("../helpers/db").setupTestDb;
41
+ var teardownTestDb = require("../helpers/db").teardownTestDb;
42
+ var b = require("../../");
43
+
44
+ var CONTAINER = "blamejs-test-mysql";
45
+ var DB_NAME = "blamejs_audit_mysql_test";
46
+
47
+ // ---- one-shot mysql (setup / teardown / out-of-band assertions) ----
48
+ function _mysqlRoot(sql, dbName) {
49
+ var args = ["exec", "-i", CONTAINER, "mysql", "-uroot", "-pblamejs_test_root", "--batch", "--raw"];
50
+ if (dbName) args.push(dbName);
51
+ args.push("-e", sql);
52
+ return execFileSync("docker", args, { stdio: ["pipe", "pipe", "pipe"] }).toString("utf8");
53
+ }
54
+
55
+ // --batch output: tab-separated, header row, "NULL" sentinel. Every cell is
56
+ // text (BIGINT included) — coerceRow's job on the framework readback.
57
+ function _parseBatch(out) {
58
+ var lines = out.split(/\r?\n/).filter(function (l) { return l.length > 0; });
59
+ if (lines.length < 1) return { rows: [] };
60
+ var headers = lines[0].split("\t");
61
+ var rows = [];
62
+ for (var i = 1; i < lines.length; i++) {
63
+ var cells = lines[i].split("\t");
64
+ var row = {};
65
+ for (var j = 0; j < headers.length; j++) {
66
+ var v = cells[j];
67
+ row[headers[j]] = (v === "NULL" || v === undefined) ? null : v;
68
+ }
69
+ rows.push(row);
70
+ }
71
+ return { rows: rows };
72
+ }
73
+
74
+ function _countMysql(table, whereClause) {
75
+ var sql = "SELECT count(*) AS n FROM " + table + (whereClause ? " WHERE " + whereClause : "");
76
+ var parsed = _parseBatch(_mysqlRoot(sql, DB_NAME));
77
+ return parsed.rows[0] ? Number(parsed.rows[0].n) : 0;
78
+ }
79
+
80
+ function _scalar(sql) {
81
+ var parsed = _parseBatch(_mysqlRoot(sql, DB_NAME));
82
+ if (!parsed.rows[0]) return null;
83
+ var k = Object.keys(parsed.rows[0])[0];
84
+ return parsed.rows[0][k];
85
+ }
86
+
87
+ // ---- docker-exec mysql driver (faithful to a text-protocol driver) ----
88
+ // SQL is piped over STDIN, NOT passed as an `-e` argument: a sealed cell can
89
+ // push a single INSERT past the OS command-line length limit (ENAMETOOLONG),
90
+ // whereas a real protocol driver streams it. STDIN keeps the shim faithful at
91
+ // any statement size.
92
+ function _exec(sql) {
93
+ try {
94
+ return execFileSync("docker",
95
+ ["exec", "-i", CONTAINER, "mysql", "-uroot", "-pblamejs_test_root",
96
+ "--batch", "--raw", DB_NAME],
97
+ { input: sql + "\n", stdio: ["pipe", "pipe", "pipe"] }).toString("utf8");
98
+ } catch (e) {
99
+ var msg = e.stderr ? e.stderr.toString("utf8") : (e.message || String(e));
100
+ var errLine = (msg.split(/\r?\n/).filter(function (l) { return /ERROR \d+/.test(l); })[0]) || msg.trim();
101
+ var err = new Error(errLine.trim());
102
+ var m = /ERROR (\d+) \(([0-9A-Za-z]{5})\)/.exec(msg);
103
+ if (m) { err.errno = Number(m[1]); err.code = m[2]; err.sqlState = m[2]; }
104
+ throw err;
105
+ }
106
+ }
107
+
108
+ function _bindParams(sql, params) {
109
+ var i = 0;
110
+ return sql.replace(/\?/g, function () {
111
+ if (i >= params.length) throw new Error("placeholder/param count mismatch");
112
+ var p = params[i++];
113
+ if (p === null || p === undefined) return "NULL";
114
+ if (Buffer.isBuffer(p)) return "x'" + p.toString("hex") + "'"; // BLOB literal (nonce / signature)
115
+ if (typeof p === "number") return String(p);
116
+ if (typeof p === "boolean") return p ? "1" : "0";
117
+ return "'" + String(p).replace(/\\/g, "\\\\").replace(/'/g, "''") + "'";
118
+ });
119
+ }
120
+
121
+ // A real mysql2 driver parses the binary protocol, so a BLOB comes back as a
122
+ // Buffer and a TEXT cell carrying embedded newlines / tabs round-trips
123
+ // intact. The docker-exec `--batch --raw` shim emits cells verbatim into a
124
+ // TSV, so (a) binary BLOBs (nonce / signature) corrupt the stream and (b) a
125
+ // sealed `vault:` text cell containing literal newlines shatters one row
126
+ // across several TSV lines — column misalignment that breaks the chain-hash
127
+ // recompute. Emulate the live driver faithfully: for a `SELECT * FROM
128
+ // <framework table>`, expand the star to an explicit projection that
129
+ // HEX()-encodes EVERY column (so no cell can contain a tab or newline), then
130
+ // decode each cell by type — blob columns to Buffers, everything else to a
131
+ // UTF-8 string (coerceRows then turns BIGINT strings into numbers). Keeps the
132
+ // test honest (bytes in == bytes out, like mysql2) without touching any
133
+ // framework SQL.
134
+ // Per-column metadata: { name, kind } where kind is "blob" | "text" |
135
+ // "numeric". HEX() of a NUMERIC column returns the hex of the integer value
136
+ // (HEX(255) -> "FF"), NOT the hex of its ASCII digits — so numeric columns
137
+ // must NOT be HEX-wrapped (they carry no tabs/newlines and round-trip as
138
+ // plain text the framework's coerceRows turns back into numbers). Only
139
+ // text/blob columns get HEX-encoded (they may carry sealed bytes / embedded
140
+ // newlines that would shatter the TSV).
141
+ var _COLMETA_CACHE = {}; // table -> [{ name, kind }]
142
+ function _columnMeta(table) {
143
+ if (_COLMETA_CACHE[table] !== undefined) return _COLMETA_CACHE[table];
144
+ var out = _mysqlRoot(
145
+ "SELECT column_name, data_type FROM information_schema.columns " +
146
+ "WHERE table_schema = '" + DB_NAME + "' AND table_name = '" + table + "' " +
147
+ "ORDER BY ordinal_position", DB_NAME);
148
+ var meta = _parseBatch(out).rows.map(function (r) {
149
+ var name = r.column_name || r.COLUMN_NAME;
150
+ var dt = (r.data_type || r.DATA_TYPE || "").toLowerCase();
151
+ var kind;
152
+ if (/(longblob|mediumblob|tinyblob|blob|varbinary|binary)/.test(dt)) kind = "blob";
153
+ else if (/(int|decimal|numeric|float|double|bit|year)/.test(dt)) kind = "numeric";
154
+ else kind = "text";
155
+ return { name: name, kind: kind };
156
+ });
157
+ _COLMETA_CACHE[table] = meta;
158
+ return meta;
159
+ }
160
+
161
+ function _makeDockerMysqlDriver() {
162
+ return {
163
+ connect: async function () { return { id: 1 }; },
164
+ query: async function (_client, sql, params) {
165
+ params = params || [];
166
+ var bound = _bindParams(sql, params);
167
+ var t = bound.trim();
168
+ if (/^(CREATE|ALTER|INSERT|UPDATE|DELETE|DROP|REPLACE|TRUNCATE)\b/i.test(t)) {
169
+ var stmt = bound.replace(/;\s*$/, "");
170
+ var ar = _exec(stmt + "; SELECT ROW_COUNT() AS n");
171
+ var parsed = _parseBatch(ar);
172
+ var n = parsed.rows[0] ? Number(parsed.rows[0].n) : 0;
173
+ if (!isFinite(n) || n < 0) n = 0;
174
+ return { rows: [], affectedRows: n, rowCount: n };
175
+ }
176
+ // Newline/binary-safe SELECT *: HEX-encode every column so the TSV can
177
+ // never be shattered by an embedded tab/newline (sealed vault: cells),
178
+ // then decode by type. Only the `SELECT * FROM <table>` shape needs it;
179
+ // explicit-projection framework reads never project a blob/multiline
180
+ // cell.
181
+ var meta = null;
182
+ var starMatch = /^SELECT \* FROM `?([A-Za-z0-9_]+)`?\b/i.exec(bound);
183
+ if (starMatch) {
184
+ meta = _columnMeta(starMatch[1]);
185
+ if (meta.length > 0) {
186
+ var proj = meta.map(function (m) {
187
+ // Numeric columns pass through raw (HEX of a number is the hex of
188
+ // its value, not its digits); text/blob get HEX-encoded so binary
189
+ // / embedded newlines survive the TSV.
190
+ return m.kind === "numeric"
191
+ ? "`" + m.name + "`"
192
+ : "HEX(`" + m.name + "`) AS `" + m.name + "`";
193
+ }).join(", ");
194
+ bound = bound.replace(/^SELECT \*/i, "SELECT " + proj);
195
+ } else {
196
+ meta = null;
197
+ }
198
+ }
199
+ var parsedSel = _parseBatch(_exec(bound));
200
+ if (meta) {
201
+ var byName = {};
202
+ for (var mi = 0; mi < meta.length; mi++) byName[meta[mi].name] = meta[mi];
203
+ for (var i = 0; i < parsedSel.rows.length; i++) {
204
+ var row = parsedSel.rows[i];
205
+ for (var k in row) {
206
+ if (!Object.prototype.hasOwnProperty.call(row, k)) continue;
207
+ var cell = row[k];
208
+ if (cell === null || cell === undefined) continue;
209
+ var m = byName[k];
210
+ if (!m || m.kind === "numeric") continue; // numeric passes through (coerceRows handles it)
211
+ var buf = Buffer.from(String(cell), "hex");
212
+ row[k] = m.kind === "blob" ? buf : buf.toString("utf8");
213
+ }
214
+ }
215
+ }
216
+ return { rows: parsedSel.rows, rowCount: parsedSel.rows.length };
217
+ },
218
+ close: async function () { /* no-op */ },
219
+ dialect: "mysql",
220
+ };
221
+ }
222
+
223
+ // Framework + app tables this test owns. Dropped at setup + teardown so a
224
+ // re-run is clean and other live tests don't collide.
225
+ var OWNED_TABLES = [
226
+ "_blamejs_audit_log", "_blamejs_consent_log", "_blamejs_audit_checkpoints",
227
+ "_blamejs_audit_tip", "_blamejs_consent_tip", "_blamejs_audit_purge_anchor",
228
+ "_blamejs_break_glass_policies", "_blamejs_break_glass_grants",
229
+ "_blamejs_leader", "_blamejs_cluster_state",
230
+ "patients", "krow_demo",
231
+ ];
232
+
233
+ function _dropOwned() {
234
+ for (var i = 0; i < OWNED_TABLES.length; i++) {
235
+ try { _mysqlRoot("DROP TABLE IF EXISTS `" + OWNED_TABLES[i] + "`", DB_NAME); } catch (_e) {}
236
+ }
237
+ }
238
+
239
+ async function run() {
240
+ var svc = await services.requireService("mysql");
241
+ if (!svc.ok) throw new Error("mysql unreachable: " + svc.reason);
242
+
243
+ _mysqlRoot("CREATE DATABASE IF NOT EXISTS " + DB_NAME);
244
+ _dropOwned();
245
+
246
+ var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-audit-my-"));
247
+ var driver = _makeDockerMysqlDriver();
248
+
249
+ try {
250
+ // Boot the framework: vault + local SQLite + cryptoField schema
251
+ // registration for the framework tables. The glass-locked "patients"
252
+ // app table is sealed for the break-glass + crypto-field sections.
253
+ await setupTestDb(tmpDir, [
254
+ {
255
+ name: "patients",
256
+ columns: {
257
+ _id: "TEXT PRIMARY KEY", mrn: "TEXT", ssn: "TEXT",
258
+ residency: "TEXT", notes: "TEXT",
259
+ },
260
+ sealedFields: ["ssn", "notes"],
261
+ },
262
+ ]);
263
+
264
+ // External MySQL backend + cluster mode. ensureSchema now emits MySQL DDL
265
+ // (backtick identifiers, BIGINT, ON DUPLICATE KEY); cluster.init flips
266
+ // clusterStorage to route framework SQL to the external MySQL backend.
267
+ b.externalDb.init({
268
+ backends: {
269
+ ops: {
270
+ connect: driver.connect, query: driver.query, close: driver.close,
271
+ dialect: "mysql",
272
+ },
273
+ },
274
+ });
275
+ await b.frameworkSchema.ensureSchema({ externalDbBackend: "ops", dialect: "mysql" });
276
+ check("ensureSchema created the framework tables on real MySQL (audit + break-glass)",
277
+ _countMysql("information_schema.tables",
278
+ "table_schema = '" + DB_NAME + "' AND table_name = '_blamejs_break_glass_grants'") === 1);
279
+
280
+ await b.cluster.init({
281
+ nodeId: "audit-stack-my",
282
+ role: "leader",
283
+ externalDbBackend: "ops",
284
+ dialect: "mysql",
285
+ });
286
+ check("cluster.init acquired leadership on real MySQL (gates every chain append)",
287
+ b.cluster.isLeader() === true);
288
+ check("framework is in cluster mode → framework SQL routes to external MySQL",
289
+ b.clusterStorage.dialect() === "mysql" &&
290
+ b.clusterStorage.tableName("audit_log") === "_blamejs_audit_log");
291
+
292
+ await _testAuditRecordAndChain();
293
+ await _testCheckpointAndFence();
294
+ await _testCoercionFidelity();
295
+ await _testAuditToolsBundle(tmpDir);
296
+ await _testBreakGlass();
297
+ await _testCryptoFieldKRowRoundTrip();
298
+ await _testDerivedHashDualRead();
299
+ await _testTamperDetection();
300
+
301
+ } finally {
302
+ try { await b.cluster.shutdown(); } catch (_e) {}
303
+ try { await b.externalDb.shutdown(); } catch (_e) {}
304
+ try { await teardownTestDb(tmpDir); } catch (_e) {}
305
+ _dropOwned();
306
+ }
307
+ }
308
+
309
+ // ====================================================================
310
+ // 1. audit.record() → chain-writer._insertRow on real MySQL. The whole
311
+ // primitive: counter primer (MAX), tip read, seal, computeRowHash,
312
+ // backtick-quoted INSERT, then verify(). A correct chain verifies ok:true.
313
+ // ====================================================================
314
+ async function _testAuditRecordAndChain() {
315
+ var events = [
316
+ { action: "system.boot", outcome: "success" },
317
+ { action: "auth.login.success", outcome: "success", actor: { userId: "u-1", ip: "10.0.0.7" } },
318
+ { action: "consent.granted", outcome: "success",
319
+ actor: { userId: "u-2" }, resource: { kind: "purpose", id: "marketing" },
320
+ metadata: { region: "eu" } },
321
+ { action: "system.shutdown", outcome: "success" },
322
+ ];
323
+ var appended = [];
324
+ for (var i = 0; i < events.length; i++) appended.push(await b.audit.record(events[i]));
325
+ check("audit.record returned a monotonic counter per row (1..4) on MySQL",
326
+ appended[0].monotonicCounter === 1 && appended[3].monotonicCounter === 4);
327
+ check("audit.record landed 4 rows in _blamejs_audit_log on real MySQL",
328
+ _countMysql("`_blamejs_audit_log`", null) === 4);
329
+
330
+ var v = await b.audit.verify({});
331
+ check("audit.verify walks the live MySQL chain and returns ok:true", v.ok === true);
332
+ check("audit.verify counted every stored row (rowsVerified === 4)",
333
+ v.ok === true && v.rowsVerified === 4);
334
+ if (!v.ok) check("AUDIT-VERIFY DETAIL (mysql): '" + v.reason + "' at row " + v.breakAt, false);
335
+
336
+ // Counter primer: a fresh in-process chain-writer must read MAX from MySQL
337
+ // and continue at 5, not restart at 1.
338
+ b.audit._resetForTest();
339
+ var more = await b.audit.record({ action: "system.boot", outcome: "success" });
340
+ check("counter primer read MAX(monotonicCounter) from live MySQL (continued at 5)",
341
+ more.monotonicCounter === 5);
342
+ check("5 audit rows present after primer-continued append on MySQL",
343
+ _countMysql("`_blamejs_audit_log`", null) === 5);
344
+ }
345
+
346
+ // ====================================================================
347
+ // 2. audit.checkpoint() → _insertCheckpoint + _upsertAuditTip on MySQL.
348
+ // The tip UPSERT is the MySQL ON DUPLICATE KEY UPDATE + IF(<fence>,…)
349
+ // fold; FENCED_OUT detection reads the stored token back (no RETURNING on
350
+ // MySQL). A strictly-lower incoming token must be FENCED_OUT.
351
+ // ====================================================================
352
+ async function _testCheckpointAndFence() {
353
+ var ck = await b.audit.checkpoint({});
354
+ check("audit.checkpoint anchored the live MySQL chain tip (counter 5)",
355
+ ck && ck.atMonotonicCounter === 5);
356
+ check("checkpoint row landed in _blamejs_audit_checkpoints on MySQL",
357
+ _countMysql("`_blamejs_audit_checkpoints`", null) === 1);
358
+
359
+ check("_upsertAuditTip wrote the single audit-tip row on MySQL",
360
+ _countMysql("`_blamejs_audit_tip`", "`scope` = 'audit'") === 1);
361
+ check("audit-tip atMonotonicCounter matches the chain tip (5) on MySQL",
362
+ Number(_scalar("SELECT `atMonotonicCounter` FROM `_blamejs_audit_tip` WHERE `scope` = 'audit'")) === 5);
363
+
364
+ var vc = await b.audit.verifyCheckpoints();
365
+ check("audit.verifyCheckpoints returns ok:true against the live MySQL checkpoint",
366
+ vc.ok === true && vc.checkpointsVerified === 1);
367
+ if (!vc.ok) check("VERIFY-CHECKPOINTS DETAIL (mysql): '" + vc.reason + "'", false);
368
+
369
+ // Fencing guard. Raise the stored token directly to a high value, then a
370
+ // second checkpoint at the leader's (lower) fencing token must be
371
+ // FENCED_OUT — the MySQL IF-fold keeps the stored token, and the readback
372
+ // detection surfaces the FENCED_OUT.
373
+ var storedTok = Number(_scalar("SELECT `fencingToken` FROM `_blamejs_audit_tip` WHERE `scope`='audit'"));
374
+ var highTok = storedTok + 1000000;
375
+ _mysqlRoot("UPDATE `_blamejs_audit_tip` SET `fencingToken` = " + highTok + " WHERE `scope`='audit'", DB_NAME);
376
+ // Append a row so checkpoint has a new tip to anchor, then attempt the
377
+ // checkpoint — its _upsertAuditTip carries the leader's lower token.
378
+ await b.audit.record({ action: "system.boot", outcome: "success" });
379
+ var fencedErr = null;
380
+ try { await b.audit.checkpoint({}); } catch (e) { fencedErr = e; }
381
+ check("checkpoint at a lower fencing token is FENCED_OUT on MySQL (IF-fold + readback)",
382
+ fencedErr !== null && /FENCED_OUT/.test((fencedErr.code || "") + (fencedErr.message || "")));
383
+ check("the stored fencingToken stayed at the higher value (lower token did not overwrite)",
384
+ Number(_scalar("SELECT `fencingToken` FROM `_blamejs_audit_tip` WHERE `scope`='audit'")) === highTok);
385
+
386
+ // Restore the leader's token so subsequent sections can checkpoint again.
387
+ _mysqlRoot("UPDATE `_blamejs_audit_tip` SET `fencingToken` = " + storedTok + " WHERE `scope`='audit'", DB_NAME);
388
+ }
389
+
390
+ // ====================================================================
391
+ // 3. Coercion fidelity: read an audit row back THROUGH clusterStorage and
392
+ // assert the normalized JS shape — MySQL --batch hands BIGINT as text,
393
+ // coerceRows must turn the framework int columns into JS numbers.
394
+ // ====================================================================
395
+ async function _testCoercionFidelity() {
396
+ var built = require("../../lib/sql").select("audit_log", { dialect: b.clusterStorage.dialect() })
397
+ .orderBy("monotonicCounter", "asc").limit(1).toSql();
398
+ var rows = await b.clusterStorage.executeAll(built.sql, built.params);
399
+ check("clusterStorage.executeAll read an audit row back from live MySQL", rows.length === 1);
400
+ var row = rows[0];
401
+ check("coercion (mysql): monotonicCounter is a JS number === 1",
402
+ typeof row.monotonicCounter === "number" && row.monotonicCounter === 1);
403
+ check("coercion (mysql): recordedAt BIGINT coerced to a JS number",
404
+ typeof row.recordedAt === "number");
405
+ check("coercion (mysql): rowHash stays a string under the camelCase key",
406
+ typeof row.rowHash === "string" && row.rowHash.length > 0);
407
+ }
408
+
409
+ // ====================================================================
410
+ // 4. audit-tools exportSlice → verifyBundle over the live MySQL audit_log
411
+ // via the default clusterStorage readers, then the purge-anchor UPSERT.
412
+ // ====================================================================
413
+ async function _testAuditToolsBundle(tmpDir) {
414
+ var pass = Buffer.from("audit-bundle-passphrase-not-secret-1234567890", "utf8");
415
+ var nRows = _countMysql("`_blamejs_audit_log`", null);
416
+
417
+ var exDir = path.join(tmpDir, "export-bundle-my");
418
+ var ex = await b.auditTools.exportSlice({ out: exDir, passphrase: pass });
419
+ check("audit-tools.exportSlice read the live MySQL chain + wrote a bundle",
420
+ ex.rowCount === nRows);
421
+
422
+ var exVerify = await b.auditTools.verifyBundle({ in: exDir, passphrase: pass });
423
+ check("audit-tools.verifyBundle round-trips the exported live-MySQL slice (ok:true)",
424
+ exVerify.ok === true && exVerify.rowsVerified === nRows);
425
+ if (!exVerify.ok) check("EXPORT-VERIFY DETAIL (mysql): '" + exVerify.reason + "'", false);
426
+
427
+ // The purge-anchor UPSERT (the external-DB piece of purge's default apply)
428
+ // through b.sql + clusterStorage must land on MySQL (ON DUPLICATE KEY).
429
+ await b.clusterStorage.execute(
430
+ "INSERT INTO `_blamejs_audit_purge_anchor` " +
431
+ "(`scope`,`lastPurgedCounter`,`lastPurgedRowHash`,`archiveBundleId`,`purgedAt`) " +
432
+ "VALUES ('audit', ?, ?, ?, ?) " +
433
+ "ON DUPLICATE KEY UPDATE `lastPurgedCounter`=VALUES(`lastPurgedCounter`), " +
434
+ "`lastPurgedRowHash`=VALUES(`lastPurgedRowHash`), " +
435
+ "`archiveBundleId`=VALUES(`archiveBundleId`), `purgedAt`=VALUES(`purgedAt`)",
436
+ [3, "anchor-hash", "bundle-1", Date.now()]);
437
+ var anchor = await b.clusterStorage.executeOne(
438
+ "SELECT `lastPurgedCounter`, `lastPurgedRowHash` FROM `_blamejs_audit_purge_anchor` WHERE `scope` = ?",
439
+ ["audit"]);
440
+ check("purge anchor UPSERT through clusterStorage landed on MySQL + coerced counter BIGINT→number",
441
+ anchor && anchor.lastPurgedCounter === 3 && anchor.lastPurgedRowHash === "anchor-hash");
442
+ _mysqlRoot("DELETE FROM `_blamejs_audit_purge_anchor` WHERE `scope`='audit'", DB_NAME);
443
+ }
444
+
445
+ // ====================================================================
446
+ // 5. break-glass policy + grant + unsealRow consume — the whole flow on
447
+ // live MySQL: policy UPSERT (sealed, ON DUPLICATE KEY), policy.get/list,
448
+ // grant (TOTP verify → sealed grant INSERT with the derived hash NOT
449
+ // NULL), then unsealRow (grant fetch + backtick-quoted rowsConsumed++
450
+ // increment + glass-locked column unseal of a real MySQL-stored row).
451
+ // ====================================================================
452
+ async function _testBreakGlass() {
453
+ b.breakGlass.init({ trustProxy: false });
454
+
455
+ _mysqlRoot("CREATE TABLE IF NOT EXISTS `patients` (" +
456
+ "`_id` VARCHAR(64) PRIMARY KEY, `mrn` TEXT, `ssn` TEXT, " +
457
+ "`residency` TEXT, `notes` TEXT)", DB_NAME);
458
+
459
+ var patient = b.cryptoField.sealRow("patients", {
460
+ _id: "patient-001", mrn: "MRN-1", ssn: "123-45-6789",
461
+ residency: "eu", notes: "high blood pressure",
462
+ });
463
+ await b.clusterStorage.execute(
464
+ "INSERT INTO `patients` (`_id`,`mrn`,`ssn`,`residency`,`notes`) VALUES (?,?,?,?,?)",
465
+ [patient._id, patient.mrn, patient.ssn, patient.residency, patient.notes]);
466
+ check("break-glass (mysql): glass-locked ssn is stored SEALED (vault:-prefixed)",
467
+ /vault[:.]/.test(String(_scalar("SELECT `ssn` FROM `patients` WHERE `_id`='patient-001'"))));
468
+
469
+ var setRes = await b.breakGlass.policy.set("patients", {
470
+ columns: ["ssn", "notes"], factors: ["totp"],
471
+ grantTtl: b.constants.TIME.minutes(15), maxRowsPerGrant: 1,
472
+ reasonMinLength: 12, pinIp: false, sessionPin: false,
473
+ });
474
+ check("break-glass (mysql): policy.set UPSERT landed on MySQL", setRes.applied === true);
475
+ check("break-glass (mysql): one policy row physically present",
476
+ _countMysql("`_blamejs_break_glass_policies`", null) === 1);
477
+
478
+ var got = await b.breakGlass.policy.get("patients");
479
+ check("break-glass (mysql): policy.get reads + unseals the MySQL policy row",
480
+ got && got.table === "patients" && got.columns.length === 2 && got.columns.indexOf("ssn") !== -1);
481
+ check("break-glass (mysql): policy numeric fields coerced (grantTtl is a number)",
482
+ typeof got.grantTtl === "number" && got.grantTtl > 0);
483
+
484
+ var listed = await b.breakGlass.policy.list();
485
+ check("break-glass (mysql): policy.list enumerates the glass-locked table",
486
+ listed.length === 1 && listed[0].table === "patients");
487
+
488
+ var totpSecret = b.auth.totp.generateSecret();
489
+ var nowMs = Date.now();
490
+ var code = b.auth.totp.generate(totpSecret, { now: nowMs });
491
+ var req = {
492
+ user: { id: "dr-house", scopes: [] }, socket: { remoteAddress: "127.0.0.1" },
493
+ headers: { "user-agent": "test-agent" }, method: "POST", url: "/admin/break-glass",
494
+ };
495
+ var handle = await b.breakGlass.grant({
496
+ req: req, table: "patients", columns: ["ssn"],
497
+ reason: "ER admit verifying identity for patient-001",
498
+ factor: { type: "totp", secret: totpSecret, code: code, now: nowMs },
499
+ });
500
+ check("break-glass (mysql): grant minted after live TOTP verify",
501
+ handle && typeof handle.id === "string" && handle.id.indexOf("bg-") === 0);
502
+ check("break-glass (mysql): grant row physically landed",
503
+ _countMysql("`_blamejs_break_glass_grants`", null) === 1);
504
+ check("break-glass (mysql): issuedToActorHash NOT-NULL derived column populated",
505
+ String(_scalar("SELECT `issuedToActorHash` FROM `_blamejs_break_glass_grants` LIMIT 1") || "").length > 0);
506
+
507
+ var unsealed = await b.breakGlass.unsealRow(handle, "patients", "patient-001");
508
+ check("break-glass (mysql): unsealRow returned the decrypted glass-locked ssn",
509
+ unsealed && unsealed.ssn === "123-45-6789");
510
+ check("break-glass (mysql): atomic rowsConsumed++ persisted (backtick whereRaw fence)",
511
+ Number(_scalar("SELECT `rowsConsumed` FROM `_blamejs_break_glass_grants` LIMIT 1")) === 1);
512
+
513
+ var exhaustedErr = null;
514
+ try { await b.breakGlass.unsealRow(handle, "patients", "patient-001"); }
515
+ catch (e) { exhaustedErr = e; }
516
+ check("break-glass (mysql): second unseal refused — grant exhausted (row-by-row auth)",
517
+ exhaustedErr && /exhausted/i.test((exhaustedErr.code || "") + (exhaustedErr.message || "")));
518
+
519
+ // listActive / listActiveAll exercise the same backtick whereRaw fence.
520
+ var active = await b.breakGlass.listActiveAll({ table: "patients" });
521
+ check("break-glass (mysql): listActiveAll runs the backtick rowsConsumed<max fence (grant now exhausted → 0)",
522
+ Array.isArray(active) && active.length === 0);
523
+ }
524
+
525
+ // ====================================================================
526
+ // 6. crypto-field K_row (vault.row:) sealed cell stored on MySQL + read
527
+ // back, proving the typed codec survives a real TEXT round-trip. The
528
+ // wrapped row-secret lives in the LOCAL per-row-keys registry; the
529
+ // sealed CELL is what lands on MySQL.
530
+ // ====================================================================
531
+ async function _testCryptoFieldKRowRoundTrip() {
532
+ b.cryptoField.declarePerRowKey("krow_demo", { keySize: 32 });
533
+ b.cryptoField.registerTable("krow_demo", { sealedFields: ["secret", "blobCol", "objCol"] });
534
+
535
+ var rowId = "krow-row-1";
536
+ var kRow = b.cryptoField.materializePerRowKey("krow_demo", rowId, b.db);
537
+ check("crypto-field (mysql): materializePerRowKey produced a 32-byte K_row",
538
+ Buffer.isBuffer(kRow) && kRow.length === 32);
539
+
540
+ var origBuf = Buffer.from([0, 1, 2, 250, 251, 255]);
541
+ var origObj = { kind: "phi", level: 9 };
542
+ var sealed = b.cryptoField.sealRow("krow_demo",
543
+ { _id: rowId, secret: "top-secret-string", blobCol: origBuf, objCol: origObj },
544
+ { kRow: kRow, rowId: rowId });
545
+ check("crypto-field (mysql): sealRow under K_row emitted vault.row: cells",
546
+ b.cryptoField.isRowSealed(sealed.secret) && b.cryptoField.isRowSealed(sealed.blobCol) &&
547
+ b.cryptoField.isRowSealed(sealed.objCol));
548
+
549
+ _mysqlRoot("CREATE TABLE IF NOT EXISTS `krow_demo` (" +
550
+ "`_id` VARCHAR(64) PRIMARY KEY, `secret` TEXT, `blobCol` TEXT, `objCol` TEXT)", DB_NAME);
551
+ await b.clusterStorage.execute(
552
+ "INSERT INTO `krow_demo` (`_id`,`secret`,`blobCol`,`objCol`) VALUES (?,?,?,?)",
553
+ [rowId, sealed.secret, sealed.blobCol, sealed.objCol]);
554
+
555
+ var stored = await b.clusterStorage.executeOne(
556
+ "SELECT `_id`,`secret`,`blobCol`,`objCol` FROM `krow_demo` WHERE `_id` = ?", [rowId]);
557
+ check("crypto-field (mysql): vault.row: cells survived the MySQL TEXT round-trip intact",
558
+ stored.secret === sealed.secret && stored.blobCol === sealed.blobCol && stored.objCol === sealed.objCol);
559
+
560
+ // Unseal under K_row (the read path resolves the wrapped secret from the
561
+ // LOCAL per-row-keys registry) — typed codec restores original types.
562
+ var unsealed = b.cryptoField.unsealRow("krow_demo", stored, "svc", b.db);
563
+ check("crypto-field (mysql): K_row unseal restored the string value",
564
+ unsealed.secret === "top-secret-string");
565
+ check("crypto-field (mysql): K_row unseal restored the Buffer value byte-for-byte",
566
+ Buffer.isBuffer(unsealed.blobCol) && unsealed.blobCol.equals(origBuf));
567
+ check("crypto-field (mysql): K_row unseal restored the object value",
568
+ unsealed.objCol && unsealed.objCol.kind === "phi" && unsealed.objCol.level === 9);
569
+ }
570
+
571
+ // ====================================================================
572
+ // 7. crypto-field derived-hash dual-read on a row stored in MySQL. A row
573
+ // whose derived-hash column holds the LEGACY salted-sha3 digest is found
574
+ // via lookupHashCandidates' legacy member; reading it back through the
575
+ // framework leaves the keyed-MAC value the active lookup uses.
576
+ // ====================================================================
577
+ async function _testDerivedHashDualRead() {
578
+ b.cryptoField.registerTable("dh_my", {
579
+ sealedFields: ["email"],
580
+ derivedHashes: { emailHash: { from: "email", normalize: function (v) { return String(v).toLowerCase(); } } },
581
+ });
582
+ var email = "Carol@Example.com";
583
+ var lk = b.cryptoField.lookupHash("dh_my", "email", email);
584
+ check("derived-hash (mysql): active lookup is the keyed MAC (64 hex)", lk.value.length === 64);
585
+ check("derived-hash (mysql): legacyValue surfaced (128 hex)",
586
+ typeof lk.legacyValue === "string" && lk.legacyValue.length === 128);
587
+ var cands = b.cryptoField.lookupHashCandidates("dh_my", "email", email);
588
+ check("derived-hash (mysql): candidates carry BOTH digests (match-either)",
589
+ cands.values.length === 2 && cands.values.indexOf(lk.value) !== -1 &&
590
+ cands.values.indexOf(lk.legacyValue) !== -1);
591
+
592
+ // Forge a legacy-indexed row on MySQL.
593
+ _mysqlRoot("CREATE TABLE IF NOT EXISTS `dh_my` (" +
594
+ "`_id` VARCHAR(64) PRIMARY KEY, `email` TEXT, `emailHash` TEXT)", DB_NAME);
595
+ var sealed = b.cryptoField.sealRow("dh_my", { _id: "c-legacy", email: email });
596
+ sealed.emailHash = lk.legacyValue;
597
+ await b.clusterStorage.execute(
598
+ "INSERT INTO `dh_my` (`_id`,`email`,`emailHash`) VALUES (?,?,?)",
599
+ [sealed._id, sealed.email, sealed.emailHash]);
600
+ var foundLegacy = await b.clusterStorage.executeOne(
601
+ "SELECT `_id` FROM `dh_my` WHERE `emailHash` = ?", [lk.legacyValue]);
602
+ check("derived-hash (mysql): legacy-indexed row found via the legacy candidate hash",
603
+ foundLegacy && foundLegacy._id === "c-legacy");
604
+ _mysqlRoot("DROP TABLE IF EXISTS `dh_my`", DB_NAME);
605
+ b.cryptoField.clearForTest();
606
+ // Re-register the framework tables clearForTest dropped so later teardown
607
+ // (which seals/unseals through cryptoField) still has its schema.
608
+ // (setupTestDb registered them via db.init's FRAMEWORK_SCHEMA; clearForTest
609
+ // wiped the whole registry, so a fresh db.init-equivalent isn't available
610
+ // here — but teardown only closes the db, no further seal calls, so this is
611
+ // safe. Left as a note for maintainers.)
612
+ }
613
+
614
+ // ====================================================================
615
+ // 8. Tamper detection on the live chain — mutate a hashed column, confirm
616
+ // verify reports ok:false. Meaningful only because the clean chain
617
+ // verified ok:true.
618
+ // ====================================================================
619
+ async function _testTamperDetection() {
620
+ // Drop the append-only WORM triggers (the privileged-DB-write attacker the
621
+ // chain defends against), mutate a hashed column, confirm verify catches it.
622
+ // Trigger names follow the framework's `no_update_<table>` / `no_delete_<table>`
623
+ // convention; drop both so the tampering UPDATE can land.
624
+ try { _mysqlRoot("DROP TRIGGER IF EXISTS `no_update__blamejs_audit_log`", DB_NAME); } catch (_e) {}
625
+ try { _mysqlRoot("DROP TRIGGER IF EXISTS `no_delete__blamejs_audit_log`", DB_NAME); } catch (_e) {}
626
+ _mysqlRoot("UPDATE `_blamejs_audit_log` SET `action` = 'auth.login.tampered' " +
627
+ "WHERE `monotonicCounter` = 2", DB_NAME);
628
+ var v = await b.audit.verify({});
629
+ check("audit.verify returns ok:false after a hashed column is tampered on MySQL", v.ok === false);
630
+ }
631
+
632
+ module.exports = { run: run };
633
+
634
+ if (require.main === module) {
635
+ run().then(
636
+ function () { console.log("OK — " + helpers.getChecks() + " checks passed"); process.exit(0); },
637
+ function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
638
+ );
639
+ }