@blamejs/blamejs-shop 0.4.30 → 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 (338) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/asset-manifest.json +1 -1
  3. package/lib/checkout.js +8 -0
  4. package/lib/order.js +71 -11
  5. package/lib/vendor/MANIFEST.json +392 -278
  6. package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
  7. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
  8. package/lib/vendor/blamejs/.gitignore +6 -0
  9. package/lib/vendor/blamejs/CHANGELOG.md +26 -0
  10. package/lib/vendor/blamejs/MIGRATING.md +43 -0
  11. package/lib/vendor/blamejs/README.md +8 -6
  12. package/lib/vendor/blamejs/SECURITY.md +19 -3
  13. package/lib/vendor/blamejs/api-snapshot.json +2190 -664
  14. package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
  15. package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
  16. package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
  17. package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
  18. package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
  19. package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
  20. package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
  21. package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
  22. package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
  23. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
  24. package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
  25. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
  26. package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
  27. package/lib/vendor/blamejs/index.js +4 -0
  28. package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
  29. package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
  30. package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
  31. package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
  32. package/lib/vendor/blamejs/lib/api-key.js +158 -77
  33. package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
  34. package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
  35. package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
  36. package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
  37. package/lib/vendor/blamejs/lib/audit.js +259 -123
  38. package/lib/vendor/blamejs/lib/auth/oauth.js +53 -9
  39. package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
  40. package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
  41. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +31 -5
  42. package/lib/vendor/blamejs/lib/backup/index.js +45 -10
  43. package/lib/vendor/blamejs/lib/break-glass.js +355 -147
  44. package/lib/vendor/blamejs/lib/cache.js +174 -105
  45. package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
  46. package/lib/vendor/blamejs/lib/cli.js +19 -14
  47. package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
  48. package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
  49. package/lib/vendor/blamejs/lib/cluster.js +119 -71
  50. package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
  51. package/lib/vendor/blamejs/lib/compliance.js +206 -4
  52. package/lib/vendor/blamejs/lib/consent.js +82 -29
  53. package/lib/vendor/blamejs/lib/constants.js +27 -11
  54. package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
  55. package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
  56. package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
  57. package/lib/vendor/blamejs/lib/db-query.js +882 -260
  58. package/lib/vendor/blamejs/lib/db-schema.js +228 -44
  59. package/lib/vendor/blamejs/lib/db.js +249 -99
  60. package/lib/vendor/blamejs/lib/dsr.js +385 -55
  61. package/lib/vendor/blamejs/lib/error-page.js +14 -1
  62. package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
  63. package/lib/vendor/blamejs/lib/external-db.js +549 -34
  64. package/lib/vendor/blamejs/lib/file-upload.js +52 -7
  65. package/lib/vendor/blamejs/lib/framework-error.js +20 -1
  66. package/lib/vendor/blamejs/lib/framework-files.js +73 -0
  67. package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
  68. package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
  69. package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
  70. package/lib/vendor/blamejs/lib/guard-all.js +1 -0
  71. package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
  72. package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
  73. package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
  74. package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
  75. package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
  76. package/lib/vendor/blamejs/lib/guard-email.js +47 -69
  77. package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
  78. package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
  79. package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
  80. package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
  81. package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
  82. package/lib/vendor/blamejs/lib/guard-html.js +53 -108
  83. package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
  84. package/lib/vendor/blamejs/lib/guard-image.js +46 -103
  85. package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
  86. package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
  87. package/lib/vendor/blamejs/lib/guard-json.js +38 -108
  88. package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
  89. package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
  90. package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
  91. package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
  92. package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
  93. package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
  94. package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
  95. package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
  96. package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
  97. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
  98. package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
  99. package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
  100. package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
  101. package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
  102. package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
  103. package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
  104. package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
  105. package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
  106. package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
  107. package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
  108. package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
  109. package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
  110. package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
  111. package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
  112. package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
  113. package/lib/vendor/blamejs/lib/guard-template.js +35 -172
  114. package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
  115. package/lib/vendor/blamejs/lib/guard-time.js +32 -154
  116. package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
  117. package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
  118. package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
  119. package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
  120. package/lib/vendor/blamejs/lib/http-client.js +37 -9
  121. package/lib/vendor/blamejs/lib/inbox.js +120 -107
  122. package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
  123. package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
  124. package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
  125. package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
  126. package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
  127. package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
  128. package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
  129. package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
  130. package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
  131. package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
  132. package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
  133. package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
  134. package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
  135. package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
  136. package/lib/vendor/blamejs/lib/mail-store.js +293 -154
  137. package/lib/vendor/blamejs/lib/mail.js +8 -4
  138. package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
  139. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
  140. package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
  141. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
  142. package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
  143. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
  144. package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
  145. package/lib/vendor/blamejs/lib/migrations.js +108 -66
  146. package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
  147. package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
  148. package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
  149. package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
  150. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
  151. package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
  152. package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
  153. package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
  154. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
  155. package/lib/vendor/blamejs/lib/observability.js +124 -0
  156. package/lib/vendor/blamejs/lib/otel-export.js +12 -3
  157. package/lib/vendor/blamejs/lib/outbox.js +184 -83
  158. package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
  159. package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
  160. package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
  161. package/lib/vendor/blamejs/lib/queue-local.js +225 -140
  162. package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
  163. package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
  164. package/lib/vendor/blamejs/lib/queue.js +7 -0
  165. package/lib/vendor/blamejs/lib/redact.js +68 -11
  166. package/lib/vendor/blamejs/lib/redis-client.js +160 -31
  167. package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
  168. package/lib/vendor/blamejs/lib/retention.js +101 -40
  169. package/lib/vendor/blamejs/lib/router.js +212 -5
  170. package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
  171. package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
  172. package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
  173. package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
  174. package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
  175. package/lib/vendor/blamejs/lib/safe-url.js +170 -3
  176. package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
  177. package/lib/vendor/blamejs/lib/scheduler.js +35 -12
  178. package/lib/vendor/blamejs/lib/seeders.js +122 -74
  179. package/lib/vendor/blamejs/lib/session-stores.js +42 -14
  180. package/lib/vendor/blamejs/lib/session.js +175 -77
  181. package/lib/vendor/blamejs/lib/sql.js +3842 -0
  182. package/lib/vendor/blamejs/lib/sse.js +26 -0
  183. package/lib/vendor/blamejs/lib/ssrf-guard.js +151 -4
  184. package/lib/vendor/blamejs/lib/static.js +177 -34
  185. package/lib/vendor/blamejs/lib/subject.js +96 -49
  186. package/lib/vendor/blamejs/lib/vault/index.js +3 -2
  187. package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
  188. package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
  189. package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
  190. package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
  191. package/lib/vendor/blamejs/lib/websocket.js +35 -5
  192. package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
  193. package/lib/vendor/blamejs/package.json +2 -2
  194. package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
  195. package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
  196. package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
  197. package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
  198. package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
  199. package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
  200. package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
  201. package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
  202. package/lib/vendor/blamejs/scripts/check-services.js +21 -0
  203. package/lib/vendor/blamejs/scripts/gen-migrating.js +51 -0
  204. package/lib/vendor/blamejs/scripts/release.js +398 -38
  205. package/lib/vendor/blamejs/test/00-primitives.js +117 -0
  206. package/lib/vendor/blamejs/test/10-state.js +140 -14
  207. package/lib/vendor/blamejs/test/20-db.js +65 -2
  208. package/lib/vendor/blamejs/test/helpers/db.js +9 -0
  209. package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
  210. package/lib/vendor/blamejs/test/helpers/services.js +21 -0
  211. package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
  212. package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
  213. package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
  214. package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
  215. package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
  216. package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
  217. package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
  218. package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
  219. package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
  220. package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
  221. package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
  222. package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
  223. package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
  224. package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
  225. package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
  226. package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
  227. package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
  228. package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
  229. package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
  230. package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
  231. package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
  232. package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
  233. package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
  234. package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
  235. package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
  236. package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
  237. package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
  238. package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
  239. package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
  240. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
  241. package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
  242. package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
  243. package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
  244. package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
  245. package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
  246. package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
  247. package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
  248. package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
  249. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
  250. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1120 -14
  251. package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
  252. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
  253. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
  254. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
  255. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
  256. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
  257. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
  258. package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
  259. package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
  260. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
  261. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
  262. package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
  263. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
  264. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
  265. package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
  266. package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
  267. package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
  268. package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
  269. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
  270. package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
  271. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
  272. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
  273. package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
  274. package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
  275. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
  276. package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
  277. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
  278. package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
  279. package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
  280. package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
  281. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
  282. package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
  283. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
  284. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
  285. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
  286. package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
  287. package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
  288. package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
  289. package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
  290. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
  291. package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
  292. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
  293. package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
  294. package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
  295. package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
  296. package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
  297. package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
  298. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +309 -0
  299. package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
  300. package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
  301. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
  302. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
  303. package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
  304. package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
  305. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
  306. package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
  307. package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
  308. package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
  309. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
  310. package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
  311. package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
  312. package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
  313. package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
  314. package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
  315. package/lib/vendor/blamejs/test/smoke.js +79 -21
  316. package/package.json +1 -1
  317. package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
  318. package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
  319. package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
  320. package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
  321. package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
  322. package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
  323. package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
  324. package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
  325. package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
  326. package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
  327. package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
  328. package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
  329. package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
  330. package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
  331. package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
  332. package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
  333. package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
  334. package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
  335. package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
  336. package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
  337. package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
  338. package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
@@ -0,0 +1,832 @@
1
+ "use strict";
2
+ /**
3
+ * Live PostgreSQL coverage for the migrated b.sql data layer that today
4
+ * is only exercised against single-node SQLite host smoke:
5
+ *
6
+ * lib/audit.js — record() → chain-writer, checkpoint(),
7
+ * _upsertAuditTip() fencing-token guard, verify(),
8
+ * verifyCheckpoints()
9
+ * lib/audit-tools.js — exportSlice / archive / verifyBundle / purge
10
+ * lib/chain-writer.js — _insertRow / counter primer / tip read on a real
11
+ * backend
12
+ * lib/break-glass.js — policy.set/get/list + grant + unsealRow consume,
13
+ * all routed through clusterStorage to live Postgres
14
+ * lib/crypto-field.js — a K_row (vault.row:) sealed cell stored as TEXT in
15
+ * Postgres and read back, proving the typed codec
16
+ * (Buffer/object) survives a real round-trip
17
+ *
18
+ * The driver is a docker-exec psql shim that replicates a real
19
+ * node-postgres driver's coercions: BIGINT (int8) → JS STRING, BYTEA →
20
+ * Node Buffer, and unquoted identifiers folded to lowercase. The
21
+ * framework's own clusterStorage.coerceRows (frameworkSchema.COLUMN_TYPES)
22
+ * then normalizes those back to numbers / Buffers — this test asserts that
23
+ * normalization end-to-end against the live server, NOT a hand-coerced
24
+ * fake.
25
+ *
26
+ * Flow: setupTestDb (vault + local SQLite + cryptoField schema
27
+ * registration for the framework tables) → frameworkSchema.ensureSchema on
28
+ * Postgres → cluster.init (leader) flips the framework into cluster mode so
29
+ * every audit / break-glass / consent write dispatches to the external
30
+ * Postgres backend through the SAME b.sql + clusterStorage path operators
31
+ * run in production.
32
+ *
33
+ * Tables are namespaced under the default _blamejs_ prefix; setup drops and
34
+ * recreates them so a re-run is clean and concurrent integration tests in
35
+ * other databases don't collide (this test owns blamejs_test).
36
+ */
37
+
38
+ var spawn = require("node:child_process").spawn;
39
+ var execFileSync = require("node:child_process").execFileSync;
40
+ var fs = require("node:fs");
41
+ var os = require("node:os");
42
+ var path = require("node:path");
43
+
44
+ var helpers = require("../helpers");
45
+ var check = helpers.check;
46
+ var services = require("../helpers/services");
47
+ var setupTestDb = require("../helpers/db").setupTestDb;
48
+ var teardownTestDb = require("../helpers/db").teardownTestDb;
49
+ var b = require("../../");
50
+
51
+ var CONTAINER = "blamejs-test-postgres";
52
+ var NULL_SENTINEL = "__BJNULL__";
53
+ var PSQL_ARGS = "psql -U blamejs -d blamejs_test -A " +
54
+ "-v ON_ERROR_STOP=0 -P null=__BJNULL__ 2>&1";
55
+
56
+ // ---- one-shot psql (setup / teardown / out-of-band assertions) ----
57
+ function _psql(sql) {
58
+ var prelude = "\\pset fieldsep '\\t'\n";
59
+ var out = execFileSync(
60
+ "docker",
61
+ ["exec", "-i", CONTAINER, "sh", "-c",
62
+ "psql -U blamejs -d blamejs_test -qtA -P null=__BJNULL__ 2>&1"],
63
+ { input: prelude + sql + "\n", stdio: ["pipe", "pipe", "pipe"] }
64
+ ).toString("utf8");
65
+ if (/^ERROR:/m.test(out)) {
66
+ throw new Error("psql setup failed for [" + sql + "]:\n" + out);
67
+ }
68
+ return out;
69
+ }
70
+
71
+ // ---- persistent-session docker-exec psql driver (faithful to node-postgres) ----
72
+ var _seq = 0;
73
+ function _makeDockerPgDriver() {
74
+ return {
75
+ connect: function () {
76
+ return new Promise(function (resolve, reject) {
77
+ var child = spawn(
78
+ "docker",
79
+ ["exec", "-i", CONTAINER, "sh", "-c",
80
+ PSQL_ARGS + " ; echo __BLAMEJS_PSQL_EXIT__"],
81
+ { stdio: ["pipe", "pipe", "pipe"] }
82
+ );
83
+ var client = { child: child, buf: "", pending: null, closed: false };
84
+ child.on("error", function (e) {
85
+ if (client.pending) { var p = client.pending; client.pending = null; p.reject(e); }
86
+ });
87
+ child.on("close", function () {
88
+ client.closed = true;
89
+ if (client.pending) {
90
+ var p = client.pending; client.pending = null;
91
+ p.reject(new Error("psql session closed mid-statement"));
92
+ }
93
+ });
94
+ child.stdout.on("data", function (chunk) {
95
+ client.buf += chunk.toString("utf8");
96
+ _drain(client);
97
+ });
98
+ var primeSentinel = "__BJ_PRIME__";
99
+ client.pending = {
100
+ sentinel: primeSentinel,
101
+ resolve: function () { resolve(client); },
102
+ reject: reject,
103
+ };
104
+ client.child.stdin.write(
105
+ "\\pset fieldsep '\\t'\n\\pset footer off\n\\set VERBOSITY verbose\n" +
106
+ "SET bytea_output = 'hex';\n" +
107
+ "\\echo " + primeSentinel + "\n");
108
+ });
109
+ },
110
+
111
+ query: function (client, sql, params) {
112
+ params = params || [];
113
+ if (process.env.BJ_TRACE_SQL === "1") { try { process.stderr.write("[SQL] " + sql + "\n"); } catch (_e) {} }
114
+ var bound = _bindParams(sql, params);
115
+ var sentinel = "__BJ_EOR_" + (++_seq) + "__";
116
+ return new Promise(function (resolve, reject) {
117
+ if (client.closed) { reject(new Error("psql session is closed")); return; }
118
+ client.pending = { sentinel: sentinel, resolve: resolve, reject: reject };
119
+ client.child.stdin.write(bound + "\n;\n\\echo " + sentinel + "\n");
120
+ });
121
+ },
122
+
123
+ close: function (client) {
124
+ return new Promise(function (resolve) {
125
+ if (client.closed) { resolve(); return; }
126
+ try { client.child.stdin.end("\\q\n"); } catch (_e) { /* best effort */ }
127
+ var done = false;
128
+ client.child.on("close", function () { if (!done) { done = true; resolve(); } });
129
+ setTimeout(function () {
130
+ if (done) return;
131
+ done = true;
132
+ try { client.child.kill("SIGKILL"); } catch (_e) {}
133
+ resolve();
134
+ }, 2000);
135
+ });
136
+ },
137
+
138
+ dialect: "postgres",
139
+ };
140
+ }
141
+
142
+ function _drain(client) {
143
+ if (!client.pending) return;
144
+ var sentinel = client.pending.sentinel;
145
+ var marker = "\n" + sentinel + "\n";
146
+ var idx = client.buf.indexOf(marker);
147
+ var startAtZero = client.buf.indexOf(sentinel + "\n") === 0;
148
+ var block;
149
+ if (idx !== -1) {
150
+ block = client.buf.slice(0, idx);
151
+ client.buf = client.buf.slice(idx + marker.length);
152
+ } else if (startAtZero) {
153
+ block = "";
154
+ client.buf = client.buf.slice((sentinel + "\n").length);
155
+ } else {
156
+ return;
157
+ }
158
+ var p = client.pending;
159
+ client.pending = null;
160
+ var parsed;
161
+ try { parsed = _parseBlock(block); }
162
+ catch (e) { return p.reject(e); }
163
+ if (parsed.error) return p.reject(parsed.error);
164
+ p.resolve({ rows: parsed.rows, rowCount: parsed.rowCount });
165
+ }
166
+
167
+ // Inline params: Buffers as bytea hex literals (byte-faithful round-trip),
168
+ // numbers raw, booleans TRUE/FALSE, everything else single-quote-escaped.
169
+ var BYTEA_LITERAL_PREFIX = "'" + "\\" + "x";
170
+ function _bindParams(sql, params) {
171
+ return sql.replace(/\$(\d+)/g, function (_m, n) {
172
+ var i = Number(n) - 1;
173
+ if (i < 0 || i >= params.length) {
174
+ throw new Error("placeholder $" + n + " has no matching param");
175
+ }
176
+ var v = params[i];
177
+ if (v === null || v === undefined) return "NULL";
178
+ if (Buffer.isBuffer(v)) return BYTEA_LITERAL_PREFIX + v.toString("hex") + "'::bytea";
179
+ if (typeof v === "number") return String(v);
180
+ if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
181
+ return "'" + String(v).replace(/'/g, "''") + "'";
182
+ });
183
+ }
184
+
185
+ var _CMD_TAG_RE = /^(INSERT|UPDATE|DELETE|MERGE|SELECT|COPY|MOVE)\b(?:\s+\d+)*\s*$/;
186
+ var _CTRL_TAG_RE = /^(BEGIN|COMMIT|ROLLBACK|SET|RESET|SAVEPOINT|RELEASE|START|CREATE|DROP|ALTER|GRANT|REVOKE|TRUNCATE|COMMENT|DO|CALL|VACUUM|ANALYZE|EXPLAIN|TABLE|SHOW|DISCARD)\b/;
187
+
188
+ // Columns the framework's audit / break-glass tables store as BYTEA — a real
189
+ // pg driver returns these as Buffers. clusterStorage.coerceRows then keeps
190
+ // them Buffers (idempotent), so the driver MUST hand Buffers up to match.
191
+ var _BYTEA_COLUMNS = { nonce: true, signature: true };
192
+
193
+ function _parseBlock(block) {
194
+ var lines = block.split(/\r?\n/);
195
+ while (lines.length && lines[lines.length - 1] === "") lines.pop();
196
+
197
+ for (var i = 0; i < lines.length; i++) {
198
+ var em = /^ERROR:\s+([0-9A-Za-z]{5}):\s*(.*)$/.exec(lines[i]);
199
+ if (em) {
200
+ var err = new Error("Postgres " + em[1] + ": " + em[2]);
201
+ err.code = em[1];
202
+ return { error: err };
203
+ }
204
+ }
205
+
206
+ var affected = null;
207
+ var dataLines = [];
208
+ for (var j = 0; j < lines.length; j++) {
209
+ var ln = lines[j];
210
+ if (/^(NOTICE|WARNING|DETAIL|HINT|LINE|LOCATION|CONTEXT|STATEMENT):/.test(ln)) continue;
211
+ var tm = _CMD_TAG_RE.exec(ln);
212
+ if (tm) {
213
+ var nums = ln.trim().split(/\s+/).slice(1).map(Number);
214
+ if (nums.length) affected = nums[nums.length - 1];
215
+ continue;
216
+ }
217
+ if (_CTRL_TAG_RE.test(ln) && ln.indexOf("\t") === -1) continue;
218
+ dataLines.push(ln);
219
+ }
220
+
221
+ var rows = [];
222
+ if (dataLines.length >= 1) {
223
+ // Header row keyed AS POSTGRES REPORTS IT — lowercase for unquoted
224
+ // identifiers, case-preserving for the double-quoted camelCase columns
225
+ // the framework's DDL created. Kept verbatim, exactly as node-postgres
226
+ // would key the row object.
227
+ var headers = dataLines[0].split("\t");
228
+ for (var k = 1; k < dataLines.length; k++) {
229
+ var cells = dataLines[k].split("\t");
230
+ var row = {};
231
+ for (var c = 0; c < headers.length; c++) {
232
+ var cell = cells[c];
233
+ var hdr = headers[c];
234
+ if (cell === NULL_SENTINEL || cell === undefined) { row[hdr] = null; continue; }
235
+ if (_BYTEA_COLUMNS[hdr] === true) {
236
+ var hex = cell.charAt(0) === "\\" && cell.charAt(1) === "x"
237
+ ? cell.slice(2) : cell;
238
+ row[hdr] = Buffer.from(hex, "hex");
239
+ } else {
240
+ // STRING — including BIGINT columns (node-postgres int8 default).
241
+ row[hdr] = cell;
242
+ }
243
+ }
244
+ rows.push(row);
245
+ }
246
+ }
247
+ var rowCount = (affected !== null) ? affected : rows.length;
248
+ return { rows: rows, rowCount: rowCount, error: null };
249
+ }
250
+
251
+ // Drop every framework table this test touches so a re-run is clean.
252
+ var FRAMEWORK_TABLES = [
253
+ "_blamejs_audit_log", "_blamejs_consent_log", "_blamejs_audit_checkpoints",
254
+ "_blamejs_audit_tip", "_blamejs_consent_tip", "_blamejs_audit_purge_anchor",
255
+ "_blamejs_scheduler_ticks", "_blamejs_rate_limit_counters",
256
+ "_blamejs_pubsub_messages", "_blamejs_api_encrypt_nonces", "_blamejs_api_keys",
257
+ "_blamejs_sessions", "_blamejs_jobs", "_blamejs_cache", "_blamejs_cache_tags",
258
+ "_blamejs_seeders", "_blamejs_seeders_lock", "_blamejs_break_glass_policies",
259
+ "_blamejs_break_glass_grants", "_blamejs_leader", "_blamejs_cluster_state",
260
+ // app-side table for break-glass + K_row storage round-trips
261
+ "patients",
262
+ ];
263
+
264
+ function _dropFrameworkTables() {
265
+ _psql(FRAMEWORK_TABLES.map(function (t) {
266
+ return "DROP TABLE IF EXISTS " + t + " CASCADE;";
267
+ }).join("\n"));
268
+ }
269
+
270
+ async function run() {
271
+ var pg = await services.requireService("postgres");
272
+ if (!pg.ok) throw new Error("postgres unreachable: " + pg.reason);
273
+
274
+ var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-audit-pg-"));
275
+ var driver = _makeDockerPgDriver();
276
+ var driverClient = null;
277
+
278
+ try {
279
+ _dropFrameworkTables();
280
+
281
+ // ---- Boot the framework: vault + local SQLite + cryptoField schema
282
+ // registration for the framework tables (audit_log /
283
+ // break_glass_* sealed-column declarations come from db.init's
284
+ // FRAMEWORK_SCHEMA). The "patients" app table is glass-locked +
285
+ // sealed for the break-glass + crypto-field portions. ----
286
+ await setupTestDb(tmpDir, [
287
+ {
288
+ name: "patients",
289
+ columns: {
290
+ _id: "TEXT PRIMARY KEY",
291
+ mrn: "TEXT",
292
+ ssn: "TEXT",
293
+ residency: "TEXT",
294
+ notes: "TEXT",
295
+ },
296
+ sealedFields: ["ssn", "notes"],
297
+ },
298
+ ]);
299
+
300
+ // External backend + cluster mode. cluster.init flips
301
+ // cluster.isClusterMode() true so clusterStorage routes the audit /
302
+ // break-glass / consent SQL to the external Postgres backend.
303
+ b.externalDb.init({
304
+ backends: {
305
+ ops: {
306
+ connect: driver.connect, query: driver.query, close: driver.close,
307
+ dialect: "postgres",
308
+ },
309
+ },
310
+ });
311
+ await b.frameworkSchema.ensureSchema({ externalDbBackend: "ops", dialect: "postgres" });
312
+ check("ensureSchema created _blamejs_audit_log + break-glass tables on real Postgres",
313
+ /\b1\b/.test(_psql(
314
+ "SELECT count(*) AS n FROM information_schema.tables " +
315
+ "WHERE table_name = '_blamejs_break_glass_grants';").trim()));
316
+
317
+ await b.cluster.init({
318
+ nodeId: "audit-stack-node",
319
+ role: "leader",
320
+ externalDbBackend: "ops",
321
+ dialect: "postgres",
322
+ });
323
+ check("cluster.init acquired leadership on real Postgres (gates every chain append)",
324
+ b.cluster.isLeader() === true);
325
+ check("framework is in cluster mode → framework SQL routes to external Postgres",
326
+ b.clusterStorage.tableName("audit_log") === "_blamejs_audit_log");
327
+
328
+ // A dedicated driver session for out-of-band readback assertions
329
+ // keyed exactly as node-postgres keys rows.
330
+ driverClient = await driver.connect();
331
+ async function liveQueryAll(sql, params) {
332
+ var r = await driver.query(driverClient, sql, params || []);
333
+ return r.rows;
334
+ }
335
+
336
+ await _testAuditRecordAndChain(liveQueryAll);
337
+ await _testCheckpointAndFence(liveQueryAll);
338
+ await _testCoercionFidelity(liveQueryAll);
339
+ await _testAuditToolsBundleAndPurge(tmpDir);
340
+ await _testBreakGlass();
341
+ await _testCryptoFieldKRowRoundTrip(liveQueryAll);
342
+ await _testTamperDetection();
343
+
344
+ } finally {
345
+ try { if (driverClient) await driver.close(driverClient); } catch (_e) {}
346
+ try { await b.cluster.shutdown(); } catch (_e) {}
347
+ try { await b.externalDb.shutdown(); } catch (_e) {}
348
+ try { await teardownTestDb(tmpDir); } catch (_e) {}
349
+ try { _dropFrameworkTables(); } catch (_e) {}
350
+ }
351
+ }
352
+
353
+ // ====================================================================
354
+ // 1. audit.record() → chain-writer._insertRow on real Postgres. Drives
355
+ // the FULL primitive: counter primer (MAX(monotonicCounter)), tip read,
356
+ // cryptoField seal, null-fill, computeRowHash, INSERT with prevHash /
357
+ // rowHash / nonce / fencingToken, then verify() (the reader b.audit
358
+ // ships). A correct framework verifies ok:true — the CORE proof.
359
+ // ====================================================================
360
+ async function _testAuditRecordAndChain(liveQueryAll) {
361
+ var events = [
362
+ { action: "system.boot", outcome: "success" },
363
+ { action: "auth.login.success", outcome: "success",
364
+ actor: { userId: "u-1", ip: "10.0.0.7" } },
365
+ { action: "consent.granted", outcome: "success",
366
+ actor: { userId: "u-2" },
367
+ resource: { kind: "purpose", id: "marketing" },
368
+ metadata: { region: "eu" } },
369
+ { action: "system.shutdown", outcome: "success" },
370
+ ];
371
+ var appended = [];
372
+ for (var i = 0; i < events.length; i++) {
373
+ appended.push(await b.audit.record(events[i]));
374
+ }
375
+ check("audit.record returned a monotonic counter per row (1..4)",
376
+ appended[0].monotonicCounter === 1 && appended[3].monotonicCounter === 4);
377
+
378
+ var count = _psql("SELECT count(*) AS n FROM _blamejs_audit_log;");
379
+ check("audit.record landed 4 rows in _blamejs_audit_log on real Postgres",
380
+ /\b4\b/.test(count.trim()));
381
+
382
+ // The reader b.audit.verify uses, against the live table.
383
+ var v = await b.audit.verify({});
384
+ check("audit.verify walks the live Postgres chain and returns ok:true " +
385
+ "(a valid chain on the operator's external DB must verify)", v.ok === true);
386
+ check("audit.verify counted every stored row (rowsVerified === 4)",
387
+ v.ok === true && v.rowsVerified === 4);
388
+ if (!v.ok) {
389
+ check("AUDIT-VERIFY DETAIL: verify reports '" + v.reason + "' at row " +
390
+ v.breakAt + " on an untampered live chain", false);
391
+ }
392
+
393
+ // The second row's prevHash must equal the first row's rowHash — chain
394
+ // linkage actually persisted, not a per-row island.
395
+ var linked = await liveQueryAll(
396
+ 'SELECT "monotonicCounter", "prevHash", "rowHash" FROM _blamejs_audit_log ' +
397
+ 'ORDER BY "monotonicCounter" ASC', []);
398
+ check("chain links across rows on Postgres (row2.prevHash === row1.rowHash)",
399
+ linked.length === 4 && linked[1].prevHash === linked[0].rowHash);
400
+
401
+ // Counter primer correctness: a brand-new chain-writer (fresh in-process
402
+ // state via record after flush) must read MAX(monotonicCounter) from the
403
+ // live table and continue, not restart at 1. Reset the audit module's
404
+ // chain-writer in-process counter and append once more.
405
+ b.audit._resetForTest();
406
+ // _resetForTest tore down cluster wiring's audit ties but cluster mode
407
+ // and externalDb remain; the counter primer re-reads MAX from Postgres.
408
+ var more = await b.audit.record({ action: "system.boot", outcome: "success" });
409
+ check("counter primer read MAX(monotonicCounter) from live Postgres on a " +
410
+ "fresh chain-writer (continued at 5, did not restart at 1)",
411
+ more.monotonicCounter === 5);
412
+ var count2 = _psql("SELECT count(*) AS n FROM _blamejs_audit_log;");
413
+ check("5 audit rows now present after primer-continued append",
414
+ /\b5\b/.test(count2.trim()));
415
+ }
416
+
417
+ // ====================================================================
418
+ // 2. audit.checkpoint() → _insertCheckpoint + _upsertAuditTip fencing
419
+ // guard on real Postgres. The tip UPSERT's WHERE clause
420
+ // (storedToken <= EXCLUDED.token) is the canonical fencing-token guard;
421
+ // a strictly-lower incoming token must be FENCED_OUT.
422
+ // ====================================================================
423
+ async function _testCheckpointAndFence(liveQueryAll) {
424
+ var ck = await b.audit.checkpoint({});
425
+ check("audit.checkpoint anchored the live chain tip", ck && ck.atMonotonicCounter === 5);
426
+
427
+ var ckCount = _psql("SELECT count(*) AS n FROM _blamejs_audit_checkpoints;");
428
+ check("checkpoint row landed in _blamejs_audit_checkpoints on Postgres",
429
+ /\b1\b/.test(ckCount.trim()));
430
+
431
+ var tip = await liveQueryAll(
432
+ 'SELECT "scope", "atMonotonicCounter", "fencingToken" FROM _blamejs_audit_tip ' +
433
+ "WHERE scope = 'audit'", []);
434
+ check("_upsertAuditTip wrote the single audit-tip row on Postgres",
435
+ tip.length === 1 && tip[0].scope === "audit");
436
+ check("audit-tip atMonotonicCounter coerced BIGINT→number and matches the chain tip",
437
+ Number(tip[0].atMonotonicCounter) === 5);
438
+
439
+ // verifyCheckpoints walks the live checkpoints + confirms the anchored
440
+ // row still has its rowHash (ML-DSA signature verify + row match).
441
+ var vc = await b.audit.verifyCheckpoints();
442
+ check("audit.verifyCheckpoints returns ok:true against the live Postgres checkpoint",
443
+ vc.ok === true && vc.checkpointsVerified === 1);
444
+ if (!vc.ok) {
445
+ check("VERIFY-CHECKPOINTS DETAIL: '" + vc.reason + "' at " + vc.breakAt, false);
446
+ }
447
+
448
+ // Fencing-token guard. The audit-tip currently holds the leader's
449
+ // fencingToken. Directly UPSERT a HIGHER token (accepted) then attempt a
450
+ // LOWER token (must be fenced out → 0 RETURNING rows). We exercise the
451
+ // exact b.sql UPSERT shape the framework emits via a raw psql round-trip
452
+ // so the DB-level guard is what's tested, not application state.
453
+ var curTok = Number(tip[0].fencingToken);
454
+ // Higher token accepted: storedToken <= EXCLUDED.token.
455
+ var higher = _psql(
456
+ 'INSERT INTO _blamejs_audit_tip ("scope","atMonotonicCounter","rowHash","signedAt","fencingToken") ' +
457
+ "VALUES ('audit', 5, 'h', 's', " + (curTok + 10) + ") " +
458
+ 'ON CONFLICT ("scope") DO UPDATE SET "fencingToken" = EXCLUDED."fencingToken" ' +
459
+ 'WHERE _blamejs_audit_tip."fencingToken" <= EXCLUDED."fencingToken" ' +
460
+ 'RETURNING "fencingToken";');
461
+ check("fencing guard ACCEPTS a higher incoming token (RETURNING produced a row)",
462
+ new RegExp("\\b" + (curTok + 10) + "\\b").test(higher));
463
+ // Lower token rejected: WHERE storedToken(curTok+10) <= EXCLUDED(curTok+1) is false.
464
+ var lower = _psql(
465
+ 'INSERT INTO _blamejs_audit_tip ("scope","atMonotonicCounter","rowHash","signedAt","fencingToken") ' +
466
+ "VALUES ('audit', 5, 'h2', 's2', " + (curTok + 1) + ") " +
467
+ 'ON CONFLICT ("scope") DO UPDATE SET "fencingToken" = EXCLUDED."fencingToken" ' +
468
+ 'WHERE _blamejs_audit_tip."fencingToken" <= EXCLUDED."fencingToken" ' +
469
+ 'RETURNING "fencingToken";');
470
+ // The DO UPDATE ... WHERE that filters out the row yields no RETURNING
471
+ // output (no data line; only the "INSERT 0 0" tag).
472
+ check("fencing guard REJECTS a strictly-lower incoming token (0 RETURNING rows → FENCED_OUT)",
473
+ lower.indexOf(String(curTok + 1)) === -1);
474
+ var stillHigh = _psql('SELECT "fencingToken" FROM _blamejs_audit_tip WHERE scope=\'audit\';');
475
+ check("stored fencingToken stayed at the higher value (lower token did not overwrite)",
476
+ new RegExp("\\b" + (curTok + 10) + "\\b").test(stillHigh.trim()));
477
+ }
478
+
479
+ // ====================================================================
480
+ // 3. Coercion fidelity on the live readback: the framework reader expects
481
+ // camelCase keys + number counters + Buffer nonces, but Postgres hands
482
+ // BIGINT back as a STRING and BYTEA as a Buffer. clusterStorage.coerceRows
483
+ // must normalize. We read THROUGH clusterStorage (the framework path) and
484
+ // assert the normalized JS shape — not the raw driver shape.
485
+ // ====================================================================
486
+ async function _testCoercionFidelity() {
487
+ // Compose via b.sql so the camelCase column is double-quoted (Postgres
488
+ // folds an unquoted identifier to lowercase). Bare logical table name —
489
+ // clusterStorage rewrites audit_log → _blamejs_audit_log + coerces the row.
490
+ var built = require("../../lib/sql").select("audit_log", { dialect: "sqlite" })
491
+ .orderBy("monotonicCounter", "asc")
492
+ .limit(1)
493
+ .toSql();
494
+ var rows = await b.clusterStorage.executeAll(built.sql, built.params);
495
+ check("clusterStorage.executeAll read an audit row back from live Postgres",
496
+ rows.length === 1);
497
+ var row = rows[0];
498
+ check("coercion: monotonicCounter is a JS number after clusterStorage.coerceRows " +
499
+ "(node-postgres handed it back as a BIGINT string)",
500
+ typeof row.monotonicCounter === "number" && row.monotonicCounter === 1);
501
+ check("coercion: recordedAt BIGINT coerced to a JS number",
502
+ typeof row.recordedAt === "number");
503
+ check("coercion: nonce BYTEA coerced to a Node Buffer",
504
+ Buffer.isBuffer(row.nonce));
505
+ check("coercion: rowHash stays a string under the camelCase key the reader uses",
506
+ typeof row.rowHash === "string" && row.rowHash.length > 0);
507
+ }
508
+
509
+ // ====================================================================
510
+ // 4. audit-tools exportSlice → verifyBundle round-trip reading from the
511
+ // live Postgres audit_log via the DEFAULT clusterStorage readers, then
512
+ // archive (needs a covering checkpoint) → verifyBundle, then the purge
513
+ // monotonic gate + the live anchor UPSERT through clusterStorage.
514
+ // ====================================================================
515
+ async function _testAuditToolsBundleAndPurge(tmpDir) {
516
+ var pass = Buffer.from("audit-bundle-passphrase-not-secret-1234567890", "utf8");
517
+
518
+ // exportSlice reads rows from the live Postgres audit_log (default
519
+ // _defaultReadRows → clusterStorage.executeAll) and writes an encrypted
520
+ // bundle to disk. audit-tools refuses an existing out dir — pass a fresh
521
+ // (non-existent) path under tmpDir.
522
+ var exDir = path.join(tmpDir, "export-bundle");
523
+ var ex = await b.auditTools.exportSlice({ out: exDir, passphrase: pass });
524
+ check("audit-tools.exportSlice read the live Postgres chain + wrote a bundle " +
525
+ "(rowCount === 5)", ex.rowCount === 5);
526
+
527
+ var exVerify = await b.auditTools.verifyBundle({ in: exDir, passphrase: pass });
528
+ check("audit-tools.verifyBundle round-trips the exported live-Postgres slice " +
529
+ "(ok:true, walks the prevHash→rowHash chain)",
530
+ exVerify.ok === true && exVerify.rowsVerified === 5);
531
+ if (!exVerify.ok) {
532
+ check("EXPORT-VERIFY DETAIL: '" + exVerify.reason + "'", false);
533
+ }
534
+
535
+ // archive needs a covering checkpoint (we wrote one at counter 5) and a
536
+ // `before` boundary newer than every row. recordedAt is Date.now()-based,
537
+ // so a `before` of now+1h covers all rows.
538
+ var arDir = path.join(tmpDir, "archive-bundle");
539
+ var ar = await b.auditTools.archive({
540
+ out: arDir,
541
+ before: Date.now() + b.constants.TIME.hours(1),
542
+ passphrase: pass,
543
+ });
544
+ check("audit-tools.archive bundled every live-Postgres row under a covering " +
545
+ "checkpoint (rowCount === 5)", ar.rowCount === 5);
546
+
547
+ var arVerify = await b.auditTools.verifyBundle({ in: arDir, passphrase: pass });
548
+ check("audit-tools.verifyBundle confirms the archive chain + checkpoint signature " +
549
+ "over live-Postgres rows (ok:true)", arVerify.ok === true);
550
+ check("archive bundle is kind 'archive' (carries the off-chain checkpoint anchor)",
551
+ arVerify.kind === "archive");
552
+
553
+ // purge monotonic gate. Drive the real purge() flow but inject a no-op
554
+ // apply so we exercise the verifyBundle + monotonic-anchor logic against
555
+ // the live archive WITHOUT physically deleting the chain mid-test (the
556
+ // local purgeAuditChain path is single-node-only). Then separately
557
+ // exercise the LIVE anchor UPSERT through clusterStorage so the
558
+ // _blamejs_audit_purge_anchor table's b.sql UPSERT is proven on Postgres.
559
+ var applied = null;
560
+ var purgeRes = await b.auditTools.purge({
561
+ confirm: true,
562
+ archive: arDir,
563
+ passphrase: pass,
564
+ readAnchor: function () { return Promise.resolve(null); }, // origin: first purge
565
+ apply: function (args) {
566
+ applied = args;
567
+ return Promise.resolve({
568
+ rowsDeleted: ar.rowCount, checkpointsDeleted: 0,
569
+ archiveBundleId: args.archiveBundleId,
570
+ });
571
+ },
572
+ });
573
+ check("audit-tools.purge verified the archive + passed the monotonic gate " +
574
+ "(firstCounter===1 from origin) and reported rowsDeleted",
575
+ purgeRes.purged === true && purgeRes.rowsDeleted === 5 &&
576
+ applied && Number(applied.lastPurgedCounter) === 5);
577
+
578
+ // Now prove the live anchor UPSERT (the only piece of purge's default
579
+ // apply that targets the external DB via clusterStorage) actually runs on
580
+ // Postgres: run _defaultApplyPurge's anchor write shape directly.
581
+ await b.clusterStorage.execute(
582
+ 'INSERT INTO _blamejs_audit_purge_anchor ' +
583
+ '("scope","lastPurgedCounter","lastPurgedRowHash","archiveBundleId","purgedAt") ' +
584
+ "VALUES ('audit', ?, ?, ?, ?) " +
585
+ 'ON CONFLICT ("scope") DO UPDATE SET ' +
586
+ '"lastPurgedCounter" = EXCLUDED."lastPurgedCounter", ' +
587
+ '"lastPurgedRowHash" = EXCLUDED."lastPurgedRowHash", ' +
588
+ '"archiveBundleId" = EXCLUDED."archiveBundleId", ' +
589
+ '"purgedAt" = EXCLUDED."purgedAt"',
590
+ [5, "anchor-hash", "bundle-1", Date.now()]);
591
+ var anchorReadBack = await b.clusterStorage.executeOne(
592
+ 'SELECT "lastPurgedCounter", "lastPurgedRowHash" FROM _blamejs_audit_purge_anchor WHERE "scope" = ?',
593
+ ["audit"]);
594
+ check("purge anchor UPSERT through b.sql + clusterStorage landed on Postgres + " +
595
+ "coerced lastPurgedCounter BIGINT→number",
596
+ anchorReadBack && anchorReadBack.lastPurgedCounter === 5 &&
597
+ anchorReadBack.lastPurgedRowHash === "anchor-hash");
598
+ }
599
+
600
+ // ====================================================================
601
+ // 5. break-glass policy + grant + unsealRow consume — the WHOLE flow
602
+ // routed through clusterStorage to live Postgres: policy UPSERT (sealed),
603
+ // policy.get/list (unseal + decode), grant (TOTP factor verify → sealed
604
+ // grant row INSERT, with the issuedToActorHash derived hash NOT NULL
605
+ // populated), then unsealRow (grant fetch + atomic rowsConsumed++ +
606
+ // glass-locked column unseal of a real app row stored on Postgres).
607
+ // ====================================================================
608
+ async function _testBreakGlass() {
609
+ b.breakGlass.init({ trustProxy: false });
610
+
611
+ // The glass-locked app table is an OPERATOR table — the framework does
612
+ // not own its DDL. In cluster mode break-glass reads it from the external
613
+ // Postgres, so create it there with the same column shape db.init
614
+ // registered for cryptoField sealing.
615
+ _psql('CREATE TABLE IF NOT EXISTS patients (' +
616
+ '"_id" TEXT PRIMARY KEY, "mrn" TEXT, "ssn" TEXT, ' +
617
+ '"residency" TEXT, "notes" TEXT);');
618
+
619
+ // Seed a glass-locked app row in Postgres via the framework's own write
620
+ // path (clusterStorage) so the SSN column is cryptoField-sealed on disk.
621
+ var patient = b.cryptoField.sealRow("patients", {
622
+ _id: "patient-001", mrn: "MRN-1", ssn: "123-45-6789",
623
+ residency: "eu", notes: "high blood pressure",
624
+ });
625
+ await b.clusterStorage.execute(
626
+ 'INSERT INTO patients ("_id","mrn","ssn","residency","notes") VALUES (?,?,?,?,?)',
627
+ [patient._id, patient.mrn, patient.ssn, patient.residency, patient.notes]);
628
+ var sealedOnDisk = _psql("SELECT ssn FROM patients WHERE _id = 'patient-001';");
629
+ check("break-glass: glass-locked ssn is stored SEALED (vault:-prefixed) on Postgres",
630
+ /vault[:.]/.test(sealedOnDisk.trim()));
631
+
632
+ // policy.set → UPSERT into _blamejs_break_glass_policies (sealed columns).
633
+ var setRes = await b.breakGlass.policy.set("patients", {
634
+ columns: ["ssn", "notes"],
635
+ factors: ["totp"],
636
+ grantTtl: b.constants.TIME.minutes(15),
637
+ maxRowsPerGrant: 1,
638
+ reasonMinLength: 12,
639
+ pinIp: false,
640
+ sessionPin: false,
641
+ });
642
+ check("break-glass: policy.set UPSERT landed on Postgres", setRes.applied === true);
643
+ var polCount = _psql("SELECT count(*) AS n FROM _blamejs_break_glass_policies;");
644
+ check("break-glass: one policy row physically present on Postgres",
645
+ /\b1\b/.test(polCount.trim()));
646
+
647
+ // policy.get round-trips the sealed/encoded policy from Postgres.
648
+ var got = await b.breakGlass.policy.get("patients");
649
+ check("break-glass: policy.get reads + unseals the Postgres policy row",
650
+ got && got.table === "patients" &&
651
+ got.columns.length === 2 && got.columns.indexOf("ssn") !== -1);
652
+ check("break-glass: policy numeric fields coerced (grantTtl is a number)",
653
+ typeof got.grantTtl === "number" && got.grantTtl > 0);
654
+
655
+ var listed = await b.breakGlass.policy.list();
656
+ check("break-glass: policy.list enumerates the glass-locked table from Postgres",
657
+ listed.length === 1 && listed[0].table === "patients");
658
+
659
+ // grant — mint a TOTP-backed grant. Generate a real TOTP secret + a code
660
+ // valid at a fixed clock the verifier is threaded with.
661
+ var totpSecret = b.auth.totp.generateSecret();
662
+ var nowMs = Date.now();
663
+ var code = b.auth.totp.generate(totpSecret, { now: nowMs });
664
+ var req = {
665
+ user: { id: "dr-house", scopes: [] },
666
+ socket: { remoteAddress: "127.0.0.1" },
667
+ headers: { "user-agent": "test-agent" },
668
+ method: "POST",
669
+ url: "/admin/break-glass",
670
+ };
671
+ var handle = await b.breakGlass.grant({
672
+ req: req,
673
+ table: "patients",
674
+ columns: ["ssn"],
675
+ reason: "ER admit verifying identity for patient-001",
676
+ factor: { type: "totp", secret: totpSecret, code: code, now: nowMs },
677
+ });
678
+ check("break-glass: grant minted after live TOTP verify (handle has an id)",
679
+ handle && typeof handle.id === "string" && handle.id.indexOf("bg-") === 0);
680
+
681
+ var grantCount = _psql("SELECT count(*) AS n FROM _blamejs_break_glass_grants;");
682
+ check("break-glass: grant row physically landed on Postgres", /\b1\b/.test(grantCount.trim()));
683
+ // The grants DDL declares issuedToActorHash TEXT NOT NULL; the cryptoField
684
+ // derived hash must have populated it, or the INSERT would have failed the
685
+ // NOT NULL constraint on Postgres (strict) — assert it's non-null.
686
+ var hashCell = _psql('SELECT "issuedToActorHash" FROM _blamejs_break_glass_grants LIMIT 1;');
687
+ check("break-glass: issuedToActorHash NOT-NULL derived column populated on Postgres",
688
+ hashCell.trim().length > 0 && !/__BJNULL__/.test(hashCell));
689
+
690
+ // unsealRow consume — fetch grant from Postgres, atomic rowsConsumed++,
691
+ // unseal the glass-locked ssn of the real Postgres-stored row.
692
+ var unsealed = await b.breakGlass.unsealRow(handle, "patients", "patient-001");
693
+ check("break-glass: unsealRow returned the decrypted glass-locked ssn",
694
+ unsealed && unsealed.ssn === "123-45-6789");
695
+
696
+ var consumed = _psql('SELECT "rowsConsumed" FROM _blamejs_break_glass_grants LIMIT 1;');
697
+ check("break-glass: atomic rowsConsumed++ persisted on Postgres (now 1)",
698
+ /\b1\b/.test(consumed.trim()));
699
+
700
+ // Second consume on a maxRowsPerGrant:1 grant must be refused (exhausted).
701
+ var exhaustedErr = null;
702
+ try { await b.breakGlass.unsealRow(handle, "patients", "patient-001"); }
703
+ catch (e) { exhaustedErr = e; }
704
+ check("break-glass: second unseal refused — grant exhausted (row-by-row auth on Postgres)",
705
+ exhaustedErr && /exhausted/i.test((exhaustedErr.code || "") + (exhaustedErr.message || "")));
706
+ }
707
+
708
+ // ====================================================================
709
+ // 6. crypto-field K_row (vault.row:) sealed cell stored on Postgres + read
710
+ // back, proving the typed codec (Buffer / object / string) survives a
711
+ // real TEXT round-trip + clusterStorage coercion. The wrapped row-secret
712
+ // lives in the LOCAL per-row-keys registry (its by-design home); the
713
+ // sealed CELL is what lands on Postgres.
714
+ // ====================================================================
715
+ async function _testCryptoFieldKRowRoundTrip(liveQueryAll) {
716
+ b.cryptoField.declarePerRowKey("krow_demo", { keySize: 32 });
717
+ b.cryptoField.registerTable("krow_demo", { sealedFields: ["secret", "blobCol", "objCol"] });
718
+
719
+ // b.db itself is the local-db handle (exposes .prepare()); the per-row-key
720
+ // registry (_blamejs_per_row_keys) is a LOCAL-only table by design — the
721
+ // wrapped row-secret never leaves the framework's own db, while the sealed
722
+ // CELL is what lands on Postgres.
723
+ var dbHandle = b.db;
724
+ check("crypto-field: local db handle (b.db) exposes .prepare() for the per-row-key registry",
725
+ typeof dbHandle.prepare === "function");
726
+
727
+ var rowId = "krow-row-1";
728
+ var kRow = b.cryptoField.materializePerRowKey("krow_demo", rowId, dbHandle);
729
+ check("crypto-field: materializePerRowKey produced a 32-byte K_row",
730
+ Buffer.isBuffer(kRow) && kRow.length === 32);
731
+
732
+ var origBuf = Buffer.from([0, 1, 2, 250, 251, 255]);
733
+ var origObj = { kind: "phi", level: 9 };
734
+ var sealed = b.cryptoField.sealRow("krow_demo",
735
+ { _id: rowId, secret: "top-secret-string", blobCol: origBuf, objCol: origObj },
736
+ { kRow: kRow, rowId: rowId });
737
+ check("crypto-field: sealRow under K_row emitted vault.row: cells",
738
+ b.cryptoField.isRowSealed(sealed.secret) &&
739
+ b.cryptoField.isRowSealed(sealed.blobCol) &&
740
+ b.cryptoField.isRowSealed(sealed.objCol));
741
+
742
+ // Store the sealed cells on Postgres as TEXT, then read back.
743
+ _psql('CREATE TABLE IF NOT EXISTS krow_demo (' +
744
+ '"_id" TEXT PRIMARY KEY, "secret" TEXT, "blobCol" TEXT, "objCol" TEXT);');
745
+ await b.clusterStorage.execute(
746
+ 'INSERT INTO krow_demo ("_id","secret","blobCol","objCol") VALUES (?,?,?,?)',
747
+ [rowId, sealed.secret, sealed.blobCol, sealed.objCol]);
748
+
749
+ var stored = (await liveQueryAll(
750
+ 'SELECT "_id","secret","blobCol","objCol" FROM krow_demo WHERE "_id" = $1', [rowId]))[0];
751
+ check("crypto-field: vault.row: cells survived the Postgres TEXT round-trip intact",
752
+ stored.secret === sealed.secret && stored.blobCol === sealed.blobCol &&
753
+ stored.objCol === sealed.objCol);
754
+
755
+ // Unseal the read-back row under K_row — the typed codec must restore the
756
+ // ORIGINAL types (string / Buffer / object), proving no String() mangling
757
+ // across the real backend round-trip.
758
+ var unsealed = b.cryptoField.sealRow ? _unsealKRow("krow_demo", stored, kRow, rowId) : null;
759
+ check("crypto-field: K_row unseal restored the string value byte-for-byte",
760
+ unsealed.secret === "top-secret-string");
761
+ check("crypto-field: K_row unseal restored the Buffer value byte-for-byte " +
762
+ "(typed codec, NOT String()-mangled)",
763
+ Buffer.isBuffer(unsealed.blobCol) && unsealed.blobCol.equals(origBuf));
764
+ check("crypto-field: K_row unseal restored the object value (typed codec)",
765
+ unsealed.objCol && unsealed.objCol.kind === "phi" && unsealed.objCol.level === 9);
766
+
767
+ _psql("DROP TABLE IF EXISTS krow_demo;");
768
+ }
769
+
770
+ // Unseal vault.row: cells under a known K_row without going through the
771
+ // dbHandle fetch path (we already hold kRow). Mirrors the framework's
772
+ // K_row decrypt: decryptPacked under the (table,rowId,column,schemaVersion)
773
+ // AAD + the typed-codec decode. Uses only exported framework crypto so the
774
+ // AAD bytes match the seal side.
775
+ function _unsealKRow(table, row, kRow, rowId) {
776
+ var vaultAad = require("../../lib/vault-aad");
777
+ var crypto = require("../../lib/crypto");
778
+ var ROW_PREFIX = require("../../lib/constants").ROW_PREFIX;
779
+ var out = Object.assign({}, row);
780
+ var cols = ["secret", "blobCol", "objCol"];
781
+ for (var i = 0; i < cols.length; i++) {
782
+ var col = cols[i];
783
+ var v = row[col];
784
+ if (typeof v !== "string" || v.indexOf(ROW_PREFIX) !== 0) continue;
785
+ var aad = vaultAad.canonicalizeAad(vaultAad.buildColumnAad({
786
+ table: table, rowId: rowId, column: col, schemaVersion: "1",
787
+ }));
788
+ var packed = Buffer.from(v.slice(ROW_PREFIX.length), "base64");
789
+ var plain = crypto.decryptPacked(packed, kRow, aad).toString("utf8");
790
+ out[col] = _decodeTyped(plain);
791
+ }
792
+ return out;
793
+ }
794
+
795
+ // Mirror of crypto-field's _decodeTyped for the test-side K_row unseal.
796
+ var TYPED_SENTINEL = String.fromCharCode(0) + "bjsv1:";
797
+ function _decodeTyped(str) {
798
+ if (typeof str !== "string" || str.indexOf(TYPED_SENTINEL) !== 0) return str;
799
+ var body = str.slice(TYPED_SENTINEL.length);
800
+ var tag = body.slice(0, 2);
801
+ var payload = body.slice(2);
802
+ if (tag === "B:") return Buffer.from(payload, "base64");
803
+ if (tag === "J:") return JSON.parse(payload);
804
+ if (tag === "S:") return payload;
805
+ return str;
806
+ }
807
+
808
+ // ====================================================================
809
+ // 7. Tamper detection on the live chain — drop the WORM triggers (privileged
810
+ // DB-write attacker), mutate a hashed column, confirm verify reports
811
+ // ok:false. Meaningful only because the clean chain verified ok:true.
812
+ // ====================================================================
813
+ async function _testTamperDetection() {
814
+ _psql([
815
+ "DROP TRIGGER IF EXISTS no_update__blamejs_audit_log ON _blamejs_audit_log;",
816
+ "DROP TRIGGER IF EXISTS no_delete__blamejs_audit_log ON _blamejs_audit_log;",
817
+ ].join("\n"));
818
+ _psql("UPDATE _blamejs_audit_log SET action = 'auth.login.tampered' " +
819
+ 'WHERE "monotonicCounter" = 2;');
820
+ var v = await b.audit.verify({});
821
+ check("audit.verify returns ok:false after a hashed column is tampered on Postgres",
822
+ v.ok === false);
823
+ }
824
+
825
+ module.exports = { run: run };
826
+
827
+ if (require.main === module) {
828
+ run().then(
829
+ function () { console.log("OK — " + helpers.getChecks() + " checks passed"); process.exit(0); },
830
+ function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
831
+ );
832
+ }