@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,770 @@
1
+ "use strict";
2
+ /**
3
+ * Live PG coverage for the b.sql-migrated cluster data layer — the parts
4
+ * that ONLY ran on sqlite host smoke before this file: cluster-storage
5
+ * coercion, pubsub-cluster publish/poll/prune, the cluster vault-key-
6
+ * consistency upsert, external-db-migrate up/down/status+lock, and the
7
+ * external-db pg_roles hardening scan. Each path is driven END-TO-END
8
+ * against real Postgres and the row / side-effect is asserted, with
9
+ * COERCION asserted on the real backend (node-postgres returns BIGINT as
10
+ * a JS STRING; the framework's coerceRow normalizes it back to a number).
11
+ *
12
+ * The "driver" is a persistent docker-exec psql shim that reproduces a
13
+ * real node-postgres driver's surface EXACTLY (the property the test
14
+ * leans on):
15
+ * - column identifiers come back verbatim as Postgres reports them —
16
+ * case-PRESERVED for the quoted camelCase the framework DDL + b.sql
17
+ * emit (so `publishedBy` / `vaultKeyFp` survive, not folded to
18
+ * `publishedby` / `vaultkeyfp`),
19
+ * - BIGINT (int8) comes back as a JS STRING (the precision-safe
20
+ * node-postgres default) — coerceRow's job is to turn the framework
21
+ * int columns back into numbers,
22
+ * - SQL travels on stdin, never argv (no shell parsing of SQL).
23
+ *
24
+ * RUN: node scripts/test-integration.js --skip-service-check data-layer-cluster-pg
25
+ */
26
+
27
+ var spawn = require("node:child_process").spawn;
28
+ var execFileSync = require("node:child_process").execFileSync;
29
+ var helpers = require("../helpers");
30
+ var check = helpers.check;
31
+ var services = require("../helpers/services");
32
+ var b = require("../../");
33
+
34
+ var CONTAINER = "blamejs-test-postgres";
35
+ var NULL_SENTINEL = "__BJNULL__";
36
+ var PSQL_ARGS = "psql -U blamejs -d blamejs_test -A " +
37
+ "-v ON_ERROR_STOP=0 -P null=__BJNULL__ 2>&1";
38
+
39
+ // ---- one-shot psql (setup / teardown / out-of-band assertions) ----
40
+ function _psql(sql) {
41
+ var prelude = "\\pset fieldsep '\\t'\n";
42
+ var out = execFileSync(
43
+ "docker",
44
+ ["exec", "-i", CONTAINER, "sh", "-c",
45
+ "psql -U blamejs -d blamejs_test -qtA -P null=__BJNULL__ 2>&1"],
46
+ { input: prelude + sql + "\n", stdio: ["pipe", "pipe", "pipe"] }
47
+ ).toString("utf8");
48
+ if (/^ERROR:/m.test(out)) {
49
+ throw new Error("psql setup failed for [" + sql + "]:\n" + out);
50
+ }
51
+ return out;
52
+ }
53
+
54
+ // ---- persistent-session docker-exec psql driver (faithful to node-pg) ----
55
+ var _seq = 0;
56
+ function _makeDockerPgDriver() {
57
+ return {
58
+ connect: function () {
59
+ return new Promise(function (resolve, reject) {
60
+ var child = spawn(
61
+ "docker",
62
+ ["exec", "-i", CONTAINER, "sh", "-c",
63
+ PSQL_ARGS + " ; echo __BLAMEJS_PSQL_EXIT__"],
64
+ { stdio: ["pipe", "pipe", "pipe"] }
65
+ );
66
+ var client = { child: child, buf: "", pending: null, closed: false };
67
+ child.on("error", function (e) {
68
+ if (client.pending) { var p = client.pending; client.pending = null; p.reject(e); }
69
+ });
70
+ child.on("close", function () {
71
+ client.closed = true;
72
+ if (client.pending) {
73
+ var p = client.pending; client.pending = null;
74
+ p.reject(new Error("psql session closed mid-statement"));
75
+ }
76
+ });
77
+ child.stdout.on("data", function (chunk) {
78
+ client.buf += chunk.toString("utf8");
79
+ _drain(client);
80
+ });
81
+ var primeSentinel = "__BJ_PRIME__";
82
+ client.pending = {
83
+ sentinel: primeSentinel,
84
+ resolve: function () { resolve(client); },
85
+ reject: reject,
86
+ };
87
+ client.child.stdin.write(
88
+ "\\pset fieldsep '\\t'\n\\pset footer off\n\\set VERBOSITY verbose\n" +
89
+ "\\echo " + primeSentinel + "\n");
90
+ });
91
+ },
92
+
93
+ query: function (client, sql, params) {
94
+ params = params || [];
95
+ var bound = _bindParams(sql, params);
96
+ var sentinel = "__BJ_EOR_" + (++_seq) + "__";
97
+ return new Promise(function (resolve, reject) {
98
+ if (client.closed) { reject(new Error("psql session is closed")); return; }
99
+ client.pending = { sentinel: sentinel, resolve: resolve, reject: reject };
100
+ client.child.stdin.write(bound + "\n;\n\\echo " + sentinel + "\n");
101
+ });
102
+ },
103
+
104
+ close: function (client) {
105
+ return new Promise(function (resolve) {
106
+ if (client.closed) { resolve(); return; }
107
+ try { client.child.stdin.end("\\q\n"); } catch (_e) { /* best effort */ }
108
+ var done = false;
109
+ client.child.on("close", function () { if (!done) { done = true; resolve(); } });
110
+ setTimeout(function () {
111
+ if (done) return;
112
+ done = true;
113
+ try { client.child.kill("SIGKILL"); } catch (_e) {}
114
+ resolve();
115
+ }, 2000);
116
+ });
117
+ },
118
+
119
+ dialect: "postgres",
120
+ };
121
+ }
122
+
123
+ function _drain(client) {
124
+ if (!client.pending) return;
125
+ var sentinel = client.pending.sentinel;
126
+ var marker = "\n" + sentinel + "\n";
127
+ var idx = client.buf.indexOf(marker);
128
+ var startAtZero = client.buf.indexOf(sentinel + "\n") === 0;
129
+ var block;
130
+ if (idx !== -1) {
131
+ block = client.buf.slice(0, idx);
132
+ client.buf = client.buf.slice(idx + marker.length);
133
+ } else if (startAtZero) {
134
+ block = "";
135
+ client.buf = client.buf.slice((sentinel + "\n").length);
136
+ } else {
137
+ return;
138
+ }
139
+ var p = client.pending;
140
+ client.pending = null;
141
+ var parsed;
142
+ try { parsed = _parseBlock(block); }
143
+ catch (e) { return p.reject(e); }
144
+ if (parsed.error) return p.reject(parsed.error);
145
+ p.resolve({ rows: parsed.rows, rowCount: parsed.rowCount });
146
+ }
147
+
148
+ function _bindParams(sql, params) {
149
+ return sql.replace(/\$(\d+)/g, function (_m, n) {
150
+ var i = Number(n) - 1;
151
+ if (i < 0 || i >= params.length) {
152
+ throw new Error("placeholder $" + n + " has no matching param");
153
+ }
154
+ var v = params[i];
155
+ if (v === null || v === undefined) return "NULL";
156
+ if (typeof v === "number") return String(v);
157
+ if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
158
+ return "'" + String(v).replace(/'/g, "''") + "'";
159
+ });
160
+ }
161
+
162
+ var _CMD_TAG_RE = /^(INSERT|UPDATE|DELETE|MERGE|SELECT|COPY|MOVE)\b(?:\s+\d+)*\s*$/;
163
+ 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/;
164
+
165
+ function _parseBlock(block) {
166
+ var lines = block.split(/\r?\n/);
167
+ while (lines.length && lines[lines.length - 1] === "") lines.pop();
168
+
169
+ for (var i = 0; i < lines.length; i++) {
170
+ var em = /^ERROR:\s+([0-9A-Za-z]{5}):\s*(.*)$/.exec(lines[i]);
171
+ if (em) {
172
+ var err = new Error("Postgres " + em[1] + ": " + em[2]);
173
+ err.code = em[1];
174
+ return { error: err };
175
+ }
176
+ }
177
+
178
+ var affected = null;
179
+ var dataLines = [];
180
+ for (var j = 0; j < lines.length; j++) {
181
+ var ln = lines[j];
182
+ if (/^(NOTICE|WARNING|DETAIL|HINT|LINE|LOCATION|CONTEXT|STATEMENT):/.test(ln)) continue;
183
+ var tm = _CMD_TAG_RE.exec(ln);
184
+ if (tm) {
185
+ var nums = ln.trim().split(/\s+/).slice(1).map(Number);
186
+ if (nums.length) affected = nums[nums.length - 1];
187
+ continue;
188
+ }
189
+ if (_CTRL_TAG_RE.test(ln) && ln.indexOf("\t") === -1) continue;
190
+ dataLines.push(ln);
191
+ }
192
+
193
+ var rows = [];
194
+ if (dataLines.length >= 1) {
195
+ // Header row first — column names AS POSTGRES REPORTS THEM. The
196
+ // framework DDL + b.sql quote every identifier, so the camelCase is
197
+ // case-PRESERVED here, exactly as node-postgres keys the row object.
198
+ var headers = dataLines[0].split("\t");
199
+ for (var k = 1; k < dataLines.length; k++) {
200
+ var cells = dataLines[k].split("\t");
201
+ var row = {};
202
+ for (var c = 0; c < headers.length; c++) {
203
+ var cell = cells[c];
204
+ // Every non-null cell is a STRING — including BIGINT columns,
205
+ // mirroring node-postgres's int8-as-string default. The
206
+ // framework's coerceRow is what turns the int framework columns
207
+ // back into JS numbers; leaving them as strings here is the
208
+ // faithful pre-coercion shape.
209
+ row[headers[c]] = (cell === NULL_SENTINEL || cell === undefined) ? null : cell;
210
+ }
211
+ rows.push(row);
212
+ }
213
+ }
214
+ var rowCount = (affected !== null) ? affected : rows.length;
215
+ return { rows: rows, rowCount: rowCount, error: null };
216
+ }
217
+
218
+ // Tables this file owns, dropped in setup + teardown.
219
+ var OWNED_TABLES = [
220
+ "_blamejs_pubsub_messages",
221
+ "_blamejs_cluster_state",
222
+ "_blamejs_leader",
223
+ "_blamejs_externaldb_migrations",
224
+ "_blamejs_externaldb_migrations_lock",
225
+ "_blamejs_schema_version_history",
226
+ ];
227
+
228
+ function _dropOwned() {
229
+ _psql(OWNED_TABLES.map(function (t) {
230
+ return "DROP TABLE IF EXISTS " + t + " CASCADE;";
231
+ }).join("\n"));
232
+ }
233
+
234
+ // Soft findings — a recorded lib-bug surfaced live that must NOT halt the
235
+ // rest of the suite (the remaining sections are independent coverage). Each
236
+ // is printed at the end and makes the file exit non-zero, so the release
237
+ // gate still fails until the bug is fixed.
238
+ var _softFindings = [];
239
+ function _softCheck(label, ok) {
240
+ if (ok) { check(label, true); return; }
241
+ _softFindings.push(label);
242
+ console.error("[SOFT-FAIL] " + label);
243
+ }
244
+
245
+ async function run() {
246
+ var pg = await services.requireService("postgres");
247
+ if (!pg.ok) throw new Error("postgres unreachable: " + pg.reason);
248
+
249
+ _dropOwned();
250
+
251
+ var driver = _makeDockerPgDriver();
252
+ b.cluster._resetForTest();
253
+ b.externalDb._resetForTest();
254
+ b.externalDb.init({
255
+ backends: {
256
+ ops: {
257
+ connect: driver.connect, query: driver.query, close: driver.close,
258
+ dialect: "postgres",
259
+ },
260
+ },
261
+ });
262
+
263
+ try {
264
+ await _proveClusterStorageCoercion();
265
+ await _provePubsubCluster();
266
+ await _proveVaultKeyConsistency();
267
+ await _proveExternalDbMigrate();
268
+ await _proveRoleHardening();
269
+ } finally {
270
+ try { await b.cluster.shutdown(); } catch (_e) {}
271
+ b.cluster._resetForTest();
272
+ try { await b.externalDb.shutdown(); } catch (_e) {}
273
+ _dropOwned();
274
+ }
275
+
276
+ // Surface any recorded soft findings as a hard suite failure so the
277
+ // release gate stays red until the live-surfaced lib bug is fixed.
278
+ if (_softFindings.length > 0) {
279
+ throw new Error("data-layer-cluster-pg: " + _softFindings.length +
280
+ " live-surfaced lib bug(s):\n - " + _softFindings.join("\n - "));
281
+ }
282
+ }
283
+
284
+ // ======================================================================
285
+ // 1. cluster-storage coercion on real PG. b.clusterStorage.execute runs
286
+ // framework-state SQL against the external DB in cluster mode, then
287
+ // coerceRows-normalizes the driver-native shape (node-pg BIGINT→string)
288
+ // back to the framework's canonical JS type. Drive a real round-trip
289
+ // through a framework table (the pubsub fan-out table — BIGSERIAL id +
290
+ // BIGINT publishedAt are the int columns) and assert the readback is a
291
+ // JS NUMBER, not the string a raw node-pg driver would hand back.
292
+ // ======================================================================
293
+ async function _proveClusterStorageCoercion() {
294
+ // cluster.init wires isClusterMode()→true so clusterStorage routes to PG.
295
+ await b.cluster.init({
296
+ nodeId: "cs-node",
297
+ role: "leader",
298
+ leaseTtl: b.constants.TIME.seconds(30),
299
+ heartbeatInterval: b.constants.TIME.seconds(10),
300
+ externalDbBackend: "ops",
301
+ dialect: "postgres",
302
+ });
303
+ check("cluster-storage: cluster mode routes framework state to PG",
304
+ b.cluster.isClusterMode() === true);
305
+
306
+ // The pubsub fan-out table is created by the framework DDL with QUOTED
307
+ // (case-preserving) camelCase columns. Emit the canonical quoted DDL
308
+ // (mirrors framework-schema _pubsubMessagesDDL for postgres) so the
309
+ // columns read back case-preserved exactly like the shipped DDL.
310
+ _psql(
311
+ 'CREATE TABLE IF NOT EXISTS _blamejs_pubsub_messages (' +
312
+ ' "id" BIGSERIAL PRIMARY KEY,' +
313
+ ' "topic" TEXT NOT NULL,' +
314
+ ' "payload" TEXT NOT NULL,' +
315
+ ' "publishedAt" BIGINT NOT NULL,' +
316
+ ' "publishedBy" TEXT NOT NULL' +
317
+ ')');
318
+
319
+ // INSERT through clusterStorage.execute (the path the framework uses).
320
+ var bigAt = 1700000000000; // > 2^31, exercises BIGINT not INT
321
+ var insRes = await b.clusterStorage.execute(
322
+ 'INSERT INTO _blamejs_pubsub_messages ("topic","payload","publishedAt","publishedBy") ' +
323
+ 'VALUES (?, ?, ?, ?)',
324
+ ["coerce-topic", '{"k":1}', bigAt, "cs-node"]);
325
+ check("cluster-storage: INSERT through execute() affected 1 row on real PG",
326
+ insRes.rowCount === 1);
327
+
328
+ // Read it back through executeOne — coerceRows runs here.
329
+ var row = await b.clusterStorage.executeOne(
330
+ 'SELECT "id","topic","payload","publishedAt","publishedBy" ' +
331
+ 'FROM _blamejs_pubsub_messages WHERE "publishedBy" = ?',
332
+ ["cs-node"]);
333
+ check("cluster-storage: round-tripped the row by case-preserved camelCase key",
334
+ row !== null && row.publishedBy === "cs-node" && row.topic === "coerce-topic");
335
+ // COERCION: BIGINT publishedAt + BIGSERIAL id come back as JS NUMBERS,
336
+ // not the decimal strings a raw node-pg driver hands over.
337
+ check("cluster-storage COERCION: BIGINT publishedAt coerced string→number",
338
+ typeof row.publishedAt === "number" && row.publishedAt === bigAt);
339
+ check("cluster-storage COERCION: BIGSERIAL id coerced string→number",
340
+ typeof row.id === "number" && row.id >= 1);
341
+ // text columns pass through unchanged.
342
+ check("cluster-storage COERCION: text payload left as the string it is",
343
+ typeof row.payload === "string" && row.payload === '{"k":1}');
344
+
345
+ await b.cluster.shutdown();
346
+ b.cluster._resetForTest();
347
+ }
348
+
349
+ // ======================================================================
350
+ // 2. pubsub-cluster publish / poll / prune on real PG. The polling backend
351
+ // reads rows back by camelCase keys (row.publishedBy / row.publishedAt /
352
+ // row.id / row.topic / row.payload); on real PG those only resolve if
353
+ // the DDL + b.sql kept the identifiers case-preserved. Drive a publish
354
+ // from one "node" and a poll-dispatch on another, asserting the remote
355
+ // row is delivered with the right topic + payload + publishedAt (as a
356
+ // NUMBER), then prove the prune DELETE removes an expired row.
357
+ // ======================================================================
358
+ async function _provePubsubCluster() {
359
+ // Fresh table (the coercion section left a row in it).
360
+ _psql("DROP TABLE IF EXISTS _blamejs_pubsub_messages CASCADE;");
361
+ _psql(
362
+ 'CREATE TABLE IF NOT EXISTS _blamejs_pubsub_messages (' +
363
+ ' "id" BIGSERIAL PRIMARY KEY,' +
364
+ ' "topic" TEXT NOT NULL,' +
365
+ ' "payload" TEXT NOT NULL,' +
366
+ ' "publishedAt" BIGINT NOT NULL,' +
367
+ ' "publishedBy" TEXT NOT NULL' +
368
+ ')');
369
+
370
+ // Cluster mode again so pubsub-cluster's clusterStorage.execute hits PG.
371
+ await b.cluster.init({
372
+ nodeId: "node-pub",
373
+ role: "leader",
374
+ leaseTtl: b.constants.TIME.seconds(30),
375
+ heartbeatInterval: b.constants.TIME.seconds(10),
376
+ externalDbBackend: "ops",
377
+ dialect: "postgres",
378
+ });
379
+
380
+ var clusterClusterBackend = require("../../lib/pubsub-cluster");
381
+
382
+ // Publisher "node" view + subscriber "node" view — distinct nodeIds so
383
+ // the poll's `publishedBy <> nodeId` self-skip is what filters, exactly
384
+ // like a real two-node deploy.
385
+ var pubView = { currentNodeId: function () { return "node-pub"; } };
386
+ var subView = { currentNodeId: function () { return "node-sub"; } };
387
+
388
+ var publisher = clusterClusterBackend.create({
389
+ cluster: pubView, pollIntervalMs: 25, retentionMs: b.constants.TIME.minutes(1),
390
+ });
391
+ var subscriber = clusterClusterBackend.create({
392
+ cluster: subView, pollIntervalMs: 25, retentionMs: b.constants.TIME.minutes(1),
393
+ });
394
+
395
+ // publishRemote writes a row (publishedBy=node-pub). Returns { remote: 1 }.
396
+ var pr = await publisher.publishRemote("orders:created", { orderId: "o-7", amount: 42 });
397
+ check("pubsub-cluster: publishRemote reports remote:1", pr && pr.remote === 1);
398
+
399
+ // The row physically landed on the server with the right shape.
400
+ var landed = _psql(
401
+ 'SELECT "topic","publishedBy" FROM _blamejs_pubsub_messages ' +
402
+ 'WHERE "topic" = \'orders:created\';');
403
+ check("pubsub-cluster: publish row physically present on real PG",
404
+ /orders:created/.test(landed) && /node-pub/.test(landed));
405
+
406
+ // Subscriber polls: first poll primes lastSeenId to MAX(id); subsequent
407
+ // polls dispatch rows past it from OTHER nodes. Start the poll loop and
408
+ // assert the remote message is delivered with the camelCase metadata.
409
+ var received = [];
410
+ subscriber.start(function (topic, payload, meta) {
411
+ received.push({ topic: topic, payload: payload, meta: meta });
412
+ });
413
+
414
+ // The first poll primes (no dispatch); publish a SECOND row AFTER priming
415
+ // so it has an id strictly greater than the primed lastSeenId and gets
416
+ // dispatched. Poll until the subscriber observes it.
417
+ await helpers.waitUntil(function () {
418
+ // Re-publish on each tick until the subscriber's loop has primed and
419
+ // then delivered. Cheap + deterministic: once primed, the next new row
420
+ // (id > lastSeen, publishedBy != node-sub) dispatches.
421
+ return received.length >= 1;
422
+ }, {
423
+ timeoutMs: 15000,
424
+ label: "pubsub-cluster: subscriber dispatched the first remote row",
425
+ }).catch(function () { /* fall through to a publish-then-wait retry below */ });
426
+
427
+ if (received.length === 0) {
428
+ // Priming consumed the only row; publish a fresh one strictly after the
429
+ // prime and wait for delivery.
430
+ await publisher.publishRemote("orders:created", { orderId: "o-8", amount: 99 });
431
+ await helpers.waitUntil(function () { return received.length >= 1; }, {
432
+ timeoutMs: 15000,
433
+ label: "pubsub-cluster: subscriber dispatched a post-prime remote row",
434
+ });
435
+ }
436
+
437
+ var first = received[0];
438
+ check("pubsub-cluster: subscriber received the remote topic verbatim",
439
+ first.topic === "orders:created");
440
+ check("pubsub-cluster: subscriber received the remote payload (JSON string)",
441
+ typeof first.payload === "string" && /"orderId"/.test(first.payload));
442
+ // The poll reads row.publishedBy / row.publishedAt by camelCase key; meta
443
+ // surfaces them. publishedAt is coerced to a NUMBER (Number(row.publishedAt)
444
+ // in pubsub-cluster, atop coerceRows).
445
+ check("pubsub-cluster: meta.publishedBy is the PUBLISHER node (not the subscriber)",
446
+ first.meta && first.meta.publishedBy === "node-pub");
447
+ check("pubsub-cluster COERCION: meta.publishedAt resolved to a finite number",
448
+ typeof first.meta.publishedAt === "number" && isFinite(first.meta.publishedAt));
449
+
450
+ subscriber.stop();
451
+ publisher.stop();
452
+
453
+ // ---- prune ----
454
+ // Insert a deliberately-expired row (publishedAt far in the past) and an
455
+ // un-expired row, then drive a poll with pruneEveryMs=1 so the prune fires
456
+ // on the first tick (lastPruneAt starts at 0, so Date.now()-0 >= 1) and
457
+ // DELETEs only the expired one. retentionMs=1ms so "now - 1ms" is the
458
+ // cutoff — the past row is older than that, the fresh row is not.
459
+ _psql("DELETE FROM _blamejs_pubsub_messages;");
460
+ _psql(
461
+ 'INSERT INTO _blamejs_pubsub_messages ("topic","payload","publishedAt","publishedBy") ' +
462
+ "VALUES ('expired','{}',1,'node-other'), " +
463
+ "('fresh','{}'," + (Date.now() + 60000) + ",'node-other');");
464
+
465
+ var pruner = clusterClusterBackend.create({
466
+ cluster: subView, pollIntervalMs: 25, retentionMs: 1, pruneEveryMs: 1,
467
+ });
468
+ var pruneSeen = [];
469
+ pruner.start(function (topic) { pruneSeen.push(topic); });
470
+ // Wait until the expired row is gone (the prune DELETE landed) while the
471
+ // fresh row survives. Poll the server directly.
472
+ await helpers.waitUntil(function () {
473
+ var n = _psql("SELECT count(*) AS n FROM _blamejs_pubsub_messages WHERE \"topic\" = 'expired';");
474
+ return /^0$/m.test(n.trim());
475
+ }, { timeoutMs: 15000, label: "pubsub-cluster: prune DELETE removed the expired row" });
476
+ var freshLeft = _psql("SELECT count(*) AS n FROM _blamejs_pubsub_messages WHERE \"topic\" = 'fresh';");
477
+ check("pubsub-cluster: prune removed the expired row", true);
478
+ check("pubsub-cluster: prune left the un-expired row intact",
479
+ /^1$/m.test(freshLeft.trim()));
480
+ pruner.stop();
481
+
482
+ await b.cluster.shutdown();
483
+ b.cluster._resetForTest();
484
+ }
485
+
486
+ // ======================================================================
487
+ // 3. cluster vault-key-consistency upsert on real PG. cluster.init runs
488
+ // _checkVaultKeyConsistency: an INSERT ... ON CONFLICT (scope) DO
489
+ // NOTHING into _blamejs_cluster_state, then a SELECT reading
490
+ // vaultKeyFp / recordedByNode / recordedAt / rotationEpoch back by
491
+ // camelCase key. The whole path only works if the quoted DDL + b.sql
492
+ // keep those identifiers case-preserved on real PG — a fold-to-lower
493
+ // would make canonical.vaultKeyFp read undefined and either FATAL
494
+ // (mismatch vs the local fingerprint) or silently mis-handle rotation.
495
+ // Prove: first boot RECORDS this node's fingerprint, a second boot with
496
+ // the SAME vault key reads it back and AGREES (no VAULT_KEY_DRIFT).
497
+ // ======================================================================
498
+ async function _proveVaultKeyConsistency() {
499
+ var fs = require("node:fs");
500
+ var os = require("node:os");
501
+ var path = require("node:path");
502
+ // The consistency check needs a real vault (it fingerprints the vault
503
+ // public keys). setupVaultOnly stands one up without the full db; it
504
+ // takes the data dir the keypair persists to.
505
+ var vaultDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-vk-pg-"));
506
+ await helpers.setupVaultOnly(vaultDir);
507
+
508
+ // Fresh coordination + leader tables. The provider's ensureSchema creates
509
+ // them; drop first so the first boot is genuinely a first boot.
510
+ _psql([
511
+ "DROP TABLE IF EXISTS _blamejs_cluster_state CASCADE;",
512
+ "DROP TABLE IF EXISTS _blamejs_leader CASCADE;",
513
+ ].join("\n"));
514
+
515
+ // ---- first boot: records this node's vault-key fingerprint ----
516
+ await b.cluster.init({
517
+ nodeId: "vk-node-A",
518
+ role: "leader",
519
+ leaseTtl: b.constants.TIME.seconds(30),
520
+ heartbeatInterval: b.constants.TIME.seconds(10),
521
+ externalDbBackend: "ops",
522
+ dialect: "postgres",
523
+ });
524
+ check("vault-key: first boot completed (recorded fingerprint, no drift)", true);
525
+
526
+ // The cluster-state row physically carries THIS node as recorder + a
527
+ // 128-hex fingerprint under the case-preserved camelCase columns.
528
+ var stateRow = _psql(
529
+ 'SELECT "vaultKeyFp","recordedByNode" FROM _blamejs_cluster_state ' +
530
+ "WHERE \"scope\" = 'state';");
531
+ check("vault-key: cluster-state row recorded by this node on real PG",
532
+ /vk-node-A/.test(stateRow));
533
+ check("vault-key: recorded fingerprint is a 128-hex SHA3-512 digest",
534
+ /\b[0-9a-f]{128}\b/.test(stateRow));
535
+
536
+ await b.cluster.shutdown();
537
+ b.cluster._resetForTest();
538
+
539
+ // ---- second boot: SAME vault key → reads back + AGREES (no drift) ----
540
+ // The cluster-state row persists (not dropped). A second node booting with
541
+ // the same vault key reads the canonical fingerprint via the camelCase
542
+ // SELECT and finds it equals its own → no VAULT_KEY_DRIFT throw.
543
+ var secondBootErr = null;
544
+ try {
545
+ await b.cluster.init({
546
+ nodeId: "vk-node-B",
547
+ role: "follower", // follower still runs the consistency check
548
+ leaseTtl: b.constants.TIME.seconds(30),
549
+ heartbeatInterval: b.constants.TIME.seconds(10),
550
+ externalDbBackend: "ops",
551
+ dialect: "postgres",
552
+ });
553
+ } catch (e) { secondBootErr = e; }
554
+ check("vault-key: second boot with the SAME key did NOT throw VAULT_KEY_DRIFT " +
555
+ "(canonical fingerprint read back by camelCase key + matched)",
556
+ secondBootErr === null);
557
+ if (secondBootErr) {
558
+ check("VAULT-KEY DETAIL: " + (secondBootErr.code || "") + " " +
559
+ (secondBootErr.message || String(secondBootErr)).slice(0, 200), false);
560
+ }
561
+
562
+ // The canonical recorder is STILL node-A (DO NOTHING preserved the first
563
+ // writer; node-B read + agreed rather than overwriting). Confirms the
564
+ // ON CONFLICT DO NOTHING upsert behaved, and the read resolved.
565
+ var stillA = _psql(
566
+ 'SELECT "recordedByNode" FROM _blamejs_cluster_state WHERE "scope" = \'state\';');
567
+ check("vault-key: ON CONFLICT DO NOTHING preserved the first recorder (node-A)",
568
+ /vk-node-A/.test(stillA));
569
+
570
+ await b.cluster.shutdown();
571
+ b.cluster._resetForTest();
572
+
573
+ try { helpers.teardownVaultOnly(vaultDir); } catch (_e) {}
574
+ }
575
+
576
+ // ======================================================================
577
+ // 4. external-db-migrate up / down / status + the advisory lock on real
578
+ // PG. Build a migrate runner over a temp migrations dir, run up() (which
579
+ // runs each migration inside externalDb.transaction, records tracking +
580
+ // signed history rows, and holds the single-row advisory lock), assert
581
+ // the migration's table landed + the tracking/history rows are present,
582
+ // then status() and down() and assert the rollback removed the table +
583
+ // the tracking row.
584
+ // ======================================================================
585
+ async function _proveExternalDbMigrate() {
586
+ var fs = require("node:fs");
587
+ var os = require("node:os");
588
+ var path = require("node:path");
589
+
590
+ // Fresh runner bookkeeping tables.
591
+ _psql([
592
+ "DROP TABLE IF EXISTS _blamejs_externaldb_migrations CASCADE;",
593
+ "DROP TABLE IF EXISTS _blamejs_externaldb_migrations_lock CASCADE;",
594
+ "DROP TABLE IF EXISTS _blamejs_schema_version_history CASCADE;",
595
+ "DROP TABLE IF EXISTS mig_demo_widgets CASCADE;",
596
+ ].join("\n"));
597
+
598
+ // Operator migration dir with one migration that CREATEs + DROPs a table.
599
+ var dir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-mig-pg-"));
600
+ fs.writeFileSync(path.join(dir, "0001-create-widgets.js"),
601
+ "module.exports = {\n" +
602
+ " description: 'create widgets',\n" +
603
+ " up: async function (xdb) {\n" +
604
+ " await xdb.query('CREATE TABLE IF NOT EXISTS mig_demo_widgets (\"id\" TEXT PRIMARY KEY, \"n\" BIGINT)', []);\n" +
605
+ " },\n" +
606
+ " down: async function (xdb) {\n" +
607
+ " await xdb.query('DROP TABLE IF EXISTS mig_demo_widgets', []);\n" +
608
+ " },\n" +
609
+ "};\n");
610
+
611
+ var migrate = b.externalDb.migrate.create({
612
+ dir: dir, backend: "ops", signHistory: false,
613
+ });
614
+
615
+ // ---- status() before up: 1 pending, 0 applied ----
616
+ var pre = await migrate.status();
617
+ check("migrate: status() before up reports the migration pending",
618
+ pre.pending.indexOf("0001-create-widgets.js") !== -1 && pre.applied.length === 0);
619
+
620
+ // ---- up(): applies the migration + records tracking row ----
621
+ var upRes = await migrate.up();
622
+ check("migrate: up() applied 0001-create-widgets.js",
623
+ upRes.applied.indexOf("0001-create-widgets.js") !== -1);
624
+
625
+ // The migration's table physically landed on real PG.
626
+ var tblOk = _psql(
627
+ "SELECT count(*) AS n FROM information_schema.tables " +
628
+ "WHERE table_name = 'mig_demo_widgets';");
629
+ check("migrate: the migration's CREATE TABLE landed on real PG",
630
+ /^1$/m.test(tblOk.trim()));
631
+
632
+ // The tracking row is present (case-preserved camelCase columns).
633
+ var trackRow = _psql(
634
+ 'SELECT "name","description" FROM _blamejs_externaldb_migrations ' +
635
+ "WHERE \"name\" = '0001-create-widgets.js';");
636
+ check("migrate: tracking row recorded the applied migration on real PG",
637
+ /0001-create-widgets\.js/.test(trackRow) && /create widgets/.test(trackRow));
638
+
639
+ // The advisory lock table exists and the lock was RELEASED after up()
640
+ // (the finally block deletes the holder's row).
641
+ var lockCount = _psql("SELECT count(*) AS n FROM _blamejs_externaldb_migrations_lock;");
642
+ check("migrate: advisory lock released after up() (0 lock rows remain)",
643
+ /^0$/m.test(lockCount.trim()));
644
+
645
+ // status() after up: 0 pending, 1 applied.
646
+ var post = await migrate.status();
647
+ check("migrate: status() after up reports it applied, none pending",
648
+ post.applied.length === 1 && post.pending.length === 0 &&
649
+ post.applied[0].name === "0001-create-widgets.js");
650
+
651
+ // Re-running up() is idempotent: the already-applied migration is skipped.
652
+ var upAgain = await migrate.up();
653
+ check("migrate: re-running up() skips the already-applied migration",
654
+ upAgain.skipped.indexOf("0001-create-widgets.js") !== -1 &&
655
+ upAgain.applied.length === 0);
656
+
657
+ // ---- down(): rolls back, drops the table, removes the tracking row ----
658
+ var downRes = await migrate.down({ steps: 1 });
659
+ check("migrate: down() reverted the migration",
660
+ downRes.reverted.indexOf("0001-create-widgets.js") !== -1);
661
+ var tblGone = _psql(
662
+ "SELECT count(*) AS n FROM information_schema.tables " +
663
+ "WHERE table_name = 'mig_demo_widgets';");
664
+ check("migrate: down() DROPped the migration's table on real PG",
665
+ /^0$/m.test(tblGone.trim()));
666
+ var trackGone = _psql(
667
+ "SELECT count(*) AS n FROM _blamejs_externaldb_migrations " +
668
+ "WHERE \"name\" = '0001-create-widgets.js';");
669
+ check("migrate: down() removed the tracking row",
670
+ /^0$/m.test(trackGone.trim()));
671
+
672
+ // ---- lock contention: a held lock blocks a second acquire ----
673
+ // Manually plant a lock row (a different holder), then run up() — it must
674
+ // refuse with the OPERATOR-FACING lock-held error ("migration lock is held
675
+ // by <holder>") rather than running migrations OR surfacing a raw Postgres
676
+ // SQLSTATE. _acquireLock acquires with `INSERT ... ON CONFLICT (scope) DO
677
+ // NOTHING` so the PK conflict is a 0-row no-op rather than a 23505 that
678
+ // would ABORT the surrounding transaction (SQLSTATE 25P02). Because the
679
+ // transaction is NOT aborted, the holder-naming SELECT runs cleanly and
680
+ // the operator gets the clear "migration lock is held by <holder>" message.
681
+ _psql(
682
+ 'INSERT INTO _blamejs_externaldb_migrations_lock ("scope","lockedAt","lockedBy") ' +
683
+ "VALUES ('lock'," + Date.now() + ",'other-process@host@deadbeef');");
684
+ // Re-apply the migration so there is pending work the lock would gate.
685
+ var migrate2 = b.externalDb.migrate.create({ dir: dir, backend: "ops", signHistory: false });
686
+ var lockErr = null;
687
+ try { await migrate2.up(); } catch (e) { lockErr = e; }
688
+ var lockMsg = (lockErr && lockErr.message) || "";
689
+ var lockCode = (lockErr && lockErr.code) || "";
690
+ // Always surface the captured lock error so the evidence is in the run log.
691
+ console.error("[migrate-lock-contention] code=" + lockCode +
692
+ " | message=" + lockMsg.slice(0, 220));
693
+ check("migrate: up() threw when the advisory lock is held (did not run migrations)",
694
+ lockErr !== null);
695
+ check("migrate: lock-contention surfaces the operator-facing lock-held " +
696
+ "message naming the holding process — NOT a raw Postgres " +
697
+ "aborted-transaction error (got code=" + lockCode + ")",
698
+ /lock.held|lock is held/i.test(lockMsg) &&
699
+ /other-process@host@deadbeef/.test(lockMsg));
700
+ // Clean the planted lock (DELETE direct via psql — the lib may have left
701
+ // the connection's view aborted, but psql is a fresh session).
702
+ _psql("DELETE FROM _blamejs_externaldb_migrations_lock;");
703
+
704
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) {}
705
+ }
706
+
707
+ // ======================================================================
708
+ // 5. external-db pg_roles hardening scan on real PG. assertRoleHardening
709
+ // SELECTs rolname from the live pg_roles catalog through b.sql and
710
+ // compares to an operator allowlist. Prove: (a) a declaredRoles list
711
+ // that omits a real, freshly-created role surfaces it as unrecognized
712
+ // in audit mode, and (b) mode:"throw" raises ROLE_HARDENING_FAIL for
713
+ // that same unrecognized role. The scan reads the REAL system catalog,
714
+ // so this is a genuine live read, not a fixture.
715
+ // ======================================================================
716
+ async function _proveRoleHardening() {
717
+ // Create a recognizable test role on the server so the scan has a known
718
+ // non-system name to classify.
719
+ _psql("DROP ROLE IF EXISTS blamejs_harden_probe;");
720
+ _psql("CREATE ROLE blamejs_harden_probe NOLOGIN;");
721
+
722
+ // ---- audit mode: the probe role is unrecognized (not in the allowlist) ----
723
+ var report = await b.externalDb.assertRoleHardening({
724
+ backend: "ops",
725
+ declaredRoles: ["blamejs"], // the app role; NOT the probe
726
+ mode: "audit",
727
+ ignoreSystem: true,
728
+ });
729
+ check("role-hardening: scan read pg_roles and observed the live app role",
730
+ report.observed.indexOf("blamejs") !== -1);
731
+ check("role-hardening: the freshly-created probe role surfaces as unrecognized",
732
+ report.unrecognized.indexOf("blamejs_harden_probe") !== -1);
733
+ check("role-hardening: system roles (postgres / pg_*) are filtered by ignoreSystem",
734
+ report.observed.indexOf("postgres") === -1 &&
735
+ report.observed.every(function (n) { return n.indexOf("pg_") !== 0; }));
736
+
737
+ // ---- declaredRoles that includes the probe → it is no longer unrecognized ----
738
+ var clean = await b.externalDb.assertRoleHardening({
739
+ backend: "ops",
740
+ declaredRoles: ["blamejs", "blamejs_harden_probe"],
741
+ mode: "audit",
742
+ ignoreSystem: true,
743
+ });
744
+ check("role-hardening: declaring the probe clears it from unrecognized",
745
+ clean.unrecognized.indexOf("blamejs_harden_probe") === -1);
746
+
747
+ // ---- throw mode: an unrecognized role fails closed ----
748
+ var threw = null;
749
+ try {
750
+ await b.externalDb.assertRoleHardening({
751
+ backend: "ops",
752
+ declaredRoles: ["blamejs"],
753
+ mode: "throw",
754
+ ignoreSystem: true,
755
+ });
756
+ } catch (e) { threw = e; }
757
+ check("role-hardening: mode:'throw' raises ROLE_HARDENING_FAIL on an unrecognized role",
758
+ threw !== null && threw.code === "ROLE_HARDENING_FAIL");
759
+
760
+ _psql("DROP ROLE IF EXISTS blamejs_harden_probe;");
761
+ }
762
+
763
+ module.exports = { run: run };
764
+
765
+ if (require.main === module) {
766
+ run().then(
767
+ function () { console.log("OK — " + helpers.getChecks() + " checks passed"); process.exit(0); },
768
+ function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
769
+ );
770
+ }