@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,602 @@
1
+ "use strict";
2
+ /**
3
+ * Live distributed-correctness proof against a REAL Postgres backend —
4
+ * not the single-process fakes the smoke tests use. Two things the
5
+ * framework advertises only hold if a real database enforces them:
6
+ *
7
+ * 1. EXACTLY-ONCE (b.scheduler, cluster mode): every fire INSERTs a
8
+ * row into _blamejs_scheduler_ticks keyed on the composite tickKey
9
+ * `name:scheduledAtUnix` with `ON CONFLICT (tickKey) DO NOTHING`.
10
+ * When two nodes race the SAME nominal tick, the PRIMARY KEY makes
11
+ * exactly one INSERT land — the loser's INSERT affects 0 rows and
12
+ * the scheduler skips its fire. The dedup is the DB's unique
13
+ * constraint, so this is only a real proof against a real DB.
14
+ *
15
+ * 2. FENCING (b.cluster fencing token): a stale leader holding a lower
16
+ * fencing token must be refused at the database layer when it
17
+ * attempts a fenced write after a newer leader took over. The
18
+ * canonical fenced write is the audit-tip upsert
19
+ * (`... ON CONFLICT (scope) DO UPDATE ... WHERE
20
+ * _blamejs_audit_tip.fencingToken <= EXCLUDED.fencingToken
21
+ * RETURNING fencingToken`, lib/audit.js _upsertAuditTip): the WHERE
22
+ * clause is the monotonic-non-decreasing guard. cluster-provider-
23
+ * mysql.test.js proves the lease-fencing-token issuance on MySQL;
24
+ * this extends both the issuance AND the tip CHECK to Postgres and
25
+ * ties the stale token to a real refused fenced operation.
26
+ *
27
+ * The "driver" is a docker-exec psql shim — a persistent
28
+ * docker exec -i blamejs-test-postgres psql -U blamejs -d blamejs_test ...
29
+ * subprocess per client (SQL fed over stdin, never argv — no shell
30
+ * parsing of SQL). It removes any npm pg-driver dep while exercising the
31
+ * framework's real Postgres SQL: the scheduler tick-claim, the cluster-
32
+ * provider-db lease/fencing-token SQL, and the audit-tip fencing guard.
33
+ *
34
+ * RUN: node scripts/test-integration.js --skip-service-check distributed-scheduler-fencing-pg
35
+ *
36
+ * STATUS: the EXACTLY-ONCE proof passes live; the FENCING proof exposes a
37
+ * live bug in lib/cluster-provider-db.js. Postgres folds unquoted column
38
+ * identifiers to lower case, so a real driver returns the leader row's
39
+ * columns as `nodeid` / `leaseid` / `fencingtoken` / `expiresat`, but the
40
+ * provider reads `row.nodeId` / `row.leaseId` / ... (camelCase). Every such
41
+ * read resolves to `undefined`: acquireLease's `row.nodeId !== nodeId` guard
42
+ * is always true so it returns null (the leader can never acquire), and
43
+ * currentLeader reports a phantom leader with `nodeId: undefined` and a NaN
44
+ * lease expiry. The columns are unaffected on MySQL (it preserves alias
45
+ * case), which is why cluster-provider-mysql.test.js passes. The fix is to
46
+ * double-quote the identifiers in the _blamejs_leader DDL and in every
47
+ * SELECT / RETURNING in lib/cluster-provider-db.js (or normalize row keys
48
+ * case-insensitively before the camelCase reads). This test asserts the
49
+ * CORRECT contract so it fails until that is fixed.
50
+ */
51
+
52
+ var spawn = require("node:child_process").spawn;
53
+ var execFileSync = require("node:child_process").execFileSync;
54
+ var helpers = require("../helpers");
55
+ var check = helpers.check;
56
+ var services = require("../helpers/services");
57
+ var b = require("../../");
58
+
59
+ var CONTAINER = "blamejs-test-postgres";
60
+
61
+ // ---- one-shot psql (setup / teardown / out-of-band assertions) ----
62
+ // Shell-free SQL: the statement travels on stdin, never in argv.
63
+ function _psql(sql) {
64
+ var prelude = "\\pset fieldsep '\\t'\n";
65
+ var out = execFileSync(
66
+ "docker",
67
+ ["exec", "-i", CONTAINER, "sh", "-c",
68
+ "psql -U blamejs -d blamejs_test -qtA -P null=__BJNULL__ 2>&1"],
69
+ { input: prelude + sql + "\n", stdio: ["pipe", "pipe", "pipe"] }
70
+ ).toString("utf8");
71
+ if (/^ERROR:/m.test(out)) {
72
+ throw new Error("psql setup failed for [" + sql + "]:\n" + out);
73
+ }
74
+ return out;
75
+ }
76
+
77
+ // ---- persistent-session docker-exec psql driver ----
78
+ // Each client is a long-lived psql subprocess. Statements are written to
79
+ // its stdin terminated by an `\echo <sentinel>`; the driver reads merged
80
+ // stdout until the sentinel line, then parses the block. SQLSTATE-coded
81
+ // ERROR lines throw an Error carrying `.code`.
82
+ var PSQL_ARGS = "psql -U blamejs -d blamejs_test -A " +
83
+ "-v ON_ERROR_STOP=0 -P null=__BJNULL__ 2>&1";
84
+ var NULL_SENTINEL = "__BJNULL__";
85
+ var _seq = 0;
86
+
87
+ function _makeDockerPgDriver() {
88
+ return {
89
+ connect: function () {
90
+ return new Promise(function (resolve, reject) {
91
+ var child = spawn(
92
+ "docker",
93
+ ["exec", "-i", CONTAINER, "sh", "-c",
94
+ PSQL_ARGS + " ; echo __BLAMEJS_PSQL_EXIT__"],
95
+ { stdio: ["pipe", "pipe", "pipe"] }
96
+ );
97
+ var client = {
98
+ child: child,
99
+ buf: "",
100
+ pending: null,
101
+ closed: false,
102
+ exitErr: null,
103
+ };
104
+ child.on("error", function (e) {
105
+ client.exitErr = e;
106
+ if (client.pending) { var p = client.pending; client.pending = null; p.reject(e); }
107
+ });
108
+ child.on("close", function () {
109
+ client.closed = true;
110
+ if (client.pending) {
111
+ var p = client.pending; client.pending = null;
112
+ p.reject(new Error("psql session closed mid-statement"));
113
+ }
114
+ });
115
+ child.stdout.on("data", function (chunk) {
116
+ client.buf += chunk.toString("utf8");
117
+ _drain(client);
118
+ });
119
+ var primeSentinel = "__BJ_PRIME__";
120
+ client.pending = {
121
+ sentinel: primeSentinel,
122
+ resolve: function () { resolve(client); },
123
+ reject: reject,
124
+ };
125
+ client.child.stdin.write(
126
+ "\\pset fieldsep '\\t'\n\\pset footer off\n\\set VERBOSITY verbose\n" +
127
+ "\\echo " + primeSentinel + "\n");
128
+ });
129
+ },
130
+
131
+ query: function (client, sql, params) {
132
+ params = params || [];
133
+ var bound = _bindParams(sql, params);
134
+ var sentinel = "__BJ_EOR_" + (++_seq) + "__";
135
+ return new Promise(function (resolve, reject) {
136
+ if (client.closed) { reject(new Error("psql session is closed")); return; }
137
+ client.pending = { sentinel: sentinel, resolve: resolve, reject: reject };
138
+ client.child.stdin.write(bound + "\n;\n\\echo " + sentinel + "\n");
139
+ });
140
+ },
141
+
142
+ close: function (client) {
143
+ return new Promise(function (resolve) {
144
+ if (client.closed) { resolve(); return; }
145
+ try { client.child.stdin.end("\\q\n"); } catch (_e) { /* best effort */ }
146
+ var done = false;
147
+ client.child.on("close", function () { if (!done) { done = true; resolve(); } });
148
+ setTimeout(function () {
149
+ if (done) return;
150
+ done = true;
151
+ try { client.child.kill("SIGKILL"); } catch (_e) {}
152
+ resolve();
153
+ }, 2000);
154
+ });
155
+ },
156
+
157
+ dialect: "postgres",
158
+ };
159
+ }
160
+
161
+ function _drain(client) {
162
+ if (!client.pending) return;
163
+ var sentinel = client.pending.sentinel;
164
+ var marker = "\n" + sentinel + "\n";
165
+ var idx = client.buf.indexOf(marker);
166
+ var startAtZero = client.buf.indexOf(sentinel + "\n") === 0;
167
+ var block;
168
+ if (idx !== -1) {
169
+ block = client.buf.slice(0, idx);
170
+ client.buf = client.buf.slice(idx + marker.length);
171
+ } else if (startAtZero) {
172
+ block = "";
173
+ client.buf = client.buf.slice((sentinel + "\n").length);
174
+ } else {
175
+ return;
176
+ }
177
+ var p = client.pending;
178
+ client.pending = null;
179
+ var parsed;
180
+ try {
181
+ parsed = _parseBlock(block);
182
+ } catch (e) {
183
+ return p.reject(e);
184
+ }
185
+ if (parsed.error) return p.reject(parsed.error);
186
+ p.resolve({ rows: parsed.rows, rowCount: parsed.rowCount });
187
+ }
188
+
189
+ // Substitute Postgres $1/$2 placeholders with quoted literals. Every test
190
+ // value is operator-controlled (ids / nodeIds / tokens / numbers / null).
191
+ function _bindParams(sql, params) {
192
+ return sql.replace(/\$(\d+)/g, function (_m, n) {
193
+ var i = Number(n) - 1;
194
+ if (i < 0 || i >= params.length) {
195
+ throw new Error("placeholder $" + n + " has no matching param");
196
+ }
197
+ var v = params[i];
198
+ if (v === null || v === undefined) return "NULL";
199
+ if (typeof v === "number") return String(v);
200
+ if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
201
+ return "'" + String(v).replace(/'/g, "''") + "'";
202
+ });
203
+ }
204
+
205
+ var _CMD_TAG_RE = /^(INSERT|UPDATE|DELETE|MERGE|SELECT|COPY|MOVE)\b(?:\s+\d+)*\s*$/;
206
+ 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/;
207
+
208
+ function _parseBlock(block) {
209
+ var lines = block.split(/\r?\n/);
210
+ while (lines.length && lines[lines.length - 1] === "") lines.pop();
211
+
212
+ for (var i = 0; i < lines.length; i++) {
213
+ var em = /^ERROR:\s+([0-9A-Za-z]{5}):\s*(.*)$/.exec(lines[i]);
214
+ if (em) {
215
+ var err = new Error("Postgres " + em[1] + ": " + em[2]);
216
+ err.code = em[1];
217
+ return { error: err };
218
+ }
219
+ }
220
+
221
+ var affected = null;
222
+ var dataLines = [];
223
+ for (var j = 0; j < lines.length; j++) {
224
+ var ln = lines[j];
225
+ if (/^(NOTICE|WARNING|DETAIL|HINT|LINE|LOCATION|CONTEXT|STATEMENT):/.test(ln)) continue;
226
+ var tm = _CMD_TAG_RE.exec(ln);
227
+ if (tm) {
228
+ var nums = ln.trim().split(/\s+/).slice(1).map(Number);
229
+ if (nums.length) affected = nums[nums.length - 1];
230
+ continue;
231
+ }
232
+ if (_CTRL_TAG_RE.test(ln) && ln.indexOf("\t") === -1) continue;
233
+ dataLines.push(ln);
234
+ }
235
+
236
+ var rows = [];
237
+ if (dataLines.length >= 1) {
238
+ var headers = dataLines[0].split("\t");
239
+ for (var k = 1; k < dataLines.length; k++) {
240
+ var cells = dataLines[k].split("\t");
241
+ var row = {};
242
+ for (var c = 0; c < headers.length; c++) {
243
+ var cell = cells[c];
244
+ row[headers[c]] = (cell === NULL_SENTINEL || cell === undefined) ? null : cell;
245
+ }
246
+ rows.push(row);
247
+ }
248
+ }
249
+ var rowCount = (affected !== null) ? affected : rows.length;
250
+ return { rows: rows, rowCount: rowCount, error: null };
251
+ }
252
+
253
+ // _blamejs_scheduler_ticks DDL (mirrors framework-schema _schedulerTicksDDL
254
+ // for the postgres dialect). The scheduler INSERTs through cluster-storage
255
+ // against this table when cluster mode is wired.
256
+ // Columns are double-quoted so Postgres preserves the camelCase exactly as
257
+ // framework-schema._schedulerTicksDDL emits them (quote-by-construction via
258
+ // safeSql.quoteIdentifier). The b.sql tick-claim INSERT quotes its columns,
259
+ // so an unquoted DDL here would create lowercased columns the quoted INSERT
260
+ // cannot find.
261
+ var SCHED_TICKS_DDL =
262
+ "CREATE TABLE IF NOT EXISTS _blamejs_scheduler_ticks (" +
263
+ ' "tickKey" TEXT PRIMARY KEY,' +
264
+ ' "name" TEXT NOT NULL,' +
265
+ ' "scheduledAtUnix" BIGINT NOT NULL,' +
266
+ ' "claimedAtUnix" BIGINT NOT NULL,' +
267
+ ' "claimedBy" TEXT' +
268
+ ")";
269
+
270
+ // _blamejs_audit_tip DDL (mirrors framework-schema _auditTipDDL for the
271
+ // postgres dialect) — the single-row coordination table whose
272
+ // fencingToken-monotonic WHERE clause is the canonical DB-layer fence.
273
+ var AUDIT_TIP_DDL =
274
+ "CREATE TABLE IF NOT EXISTS _blamejs_audit_tip (" +
275
+ ' "scope" TEXT PRIMARY KEY,' +
276
+ ' "atMonotonicCounter" BIGINT NOT NULL,' +
277
+ ' "rowHash" TEXT,' +
278
+ ' "signedAt" TEXT,' +
279
+ ' "fencingToken" BIGINT NOT NULL DEFAULT 0,' +
280
+ " CHECK (\"scope\" = 'audit')" +
281
+ ")";
282
+
283
+ async function run() {
284
+ var pg = await services.requireService("postgres");
285
+ if (!pg.ok) throw new Error("postgres unreachable: " + pg.reason);
286
+
287
+ // ---- fresh schema ----
288
+ _psql([
289
+ "DROP TABLE IF EXISTS _blamejs_scheduler_ticks;",
290
+ "DROP TABLE IF EXISTS _blamejs_audit_tip;",
291
+ "DROP TABLE IF EXISTS _blamejs_leader;",
292
+ "DROP TABLE IF EXISTS _blamejs_cluster_state;",
293
+ ].join("\n"));
294
+
295
+ var driver = _makeDockerPgDriver();
296
+ b.cluster._resetForTest();
297
+ b.externalDb._resetForTest();
298
+ b.externalDb.init({
299
+ backends: {
300
+ ops: {
301
+ connect: driver.connect, query: driver.query, close: driver.close,
302
+ dialect: "postgres",
303
+ },
304
+ },
305
+ });
306
+
307
+ try {
308
+ await _proveExactlyOnce();
309
+ await _proveFencing();
310
+ } finally {
311
+ // Best-effort teardown: shut cluster + externalDb, drop tables.
312
+ try { await b.cluster.shutdown(); } catch (_e) {}
313
+ b.cluster._resetForTest();
314
+ try { await b.externalDb.shutdown(); } catch (_e) {}
315
+ _psql([
316
+ "DROP TABLE IF EXISTS _blamejs_scheduler_ticks;",
317
+ "DROP TABLE IF EXISTS _blamejs_audit_tip;",
318
+ "DROP TABLE IF EXISTS _blamejs_leader;",
319
+ "DROP TABLE IF EXISTS _blamejs_cluster_state;",
320
+ ].join("\n"));
321
+ }
322
+ }
323
+
324
+ // ======================================================================
325
+ // 1. EXACTLY-ONCE — two racing nodes claim the SAME scheduled tick; the
326
+ // real PRIMARY KEY on tickKey lets exactly one win, the loser is
327
+ // rejected by the unique constraint (not by a hand-inserted row), and
328
+ // the job fires ONCE.
329
+ // ======================================================================
330
+ async function _proveExactlyOnce() {
331
+ _psql(SCHED_TICKS_DDL);
332
+
333
+ // Wire cluster.init against the real PG provider so cluster.isClusterMode()
334
+ // is true: the scheduler's tick-claim INSERT then routes through
335
+ // clusterStorage → externalDb → real Postgres. The provider's
336
+ // ensureSchema creates _blamejs_leader; we created the ticks table above.
337
+ //
338
+ // NOTE: the scheduler instances below use their OWN cluster views for the
339
+ // leader gate, so this proof does not depend on cluster.isLeader() — it
340
+ // depends only on isClusterMode() routing framework state to PG, which is
341
+ // true regardless of lease state. (cluster.isLeader() is in fact false
342
+ // here on Postgres — see the FENCING section for that bug.)
343
+ await b.cluster.init({
344
+ nodeId: "sched-node",
345
+ role: "leader",
346
+ leaseTtl: b.constants.TIME.seconds(30),
347
+ heartbeatInterval: b.constants.TIME.seconds(10),
348
+ externalDbBackend: "ops",
349
+ dialect: "postgres",
350
+ });
351
+ check("exactly-once: cluster is in cluster mode (state routes to PG)",
352
+ b.cluster.isClusterMode() === true);
353
+
354
+ // Two scheduler instances modelling two cluster nodes that both believe
355
+ // they're leader (the split-brain window the tick-claim defends). Each
356
+ // declares the SAME task name + same interval so a shared nominal
357
+ // scheduled time produces the SAME tickKey on both. We drive _fireOnce
358
+ // on both for the SAME nominal tick by pinning each task's nextRun to a
359
+ // shared instant before firing.
360
+ var firedByA = 0;
361
+ var firedByB = 0;
362
+
363
+ var clusterAView = {
364
+ isLeader: function () { return true; },
365
+ currentNodeId: function () { return "node-A"; },
366
+ };
367
+ var clusterBView = {
368
+ isLeader: function () { return true; },
369
+ currentNodeId: function () { return "node-B"; },
370
+ };
371
+
372
+ var schedA = b.scheduler.create({ cluster: clusterAView, audit: false });
373
+ var schedB = b.scheduler.create({ cluster: clusterBView, audit: false });
374
+
375
+ var taskA = schedA.schedule({
376
+ name: "rollup", every: b.constants.TIME.minutes(1),
377
+ run: async function () { firedByA++; },
378
+ });
379
+ var taskB = schedB.schedule({
380
+ name: "rollup", every: b.constants.TIME.minutes(1),
381
+ run: async function () { firedByB++; },
382
+ });
383
+
384
+ // Pin both tasks to the SAME nominal scheduled instant so both compute
385
+ // the identical tickKey ("rollup:<sharedNominal>"). _fireOnce reads
386
+ // task.nextRun as the nominal run for the tick.
387
+ var sharedNominal = Date.now() + b.constants.TIME.minutes(1);
388
+ taskA.nextRun = sharedNominal;
389
+ taskB.nextRun = sharedNominal;
390
+
391
+ // Fire BOTH for the same tick concurrently. The two clusterStorage
392
+ // INSERTs hit the real PG PRIMARY KEY at once; exactly one lands.
393
+ schedA._fireOnce("rollup");
394
+ schedB._fireOnce("rollup");
395
+
396
+ // Wait until the tick-claim race has resolved on BOTH instances: the
397
+ // winner's fires===1 and the loser's tickClaimLost===1. Polling on the
398
+ // observable counters avoids a fixed sleep.
399
+ await helpers.waitUntil(function () {
400
+ var a = schedA.list()[0];
401
+ var bb = schedB.list()[0];
402
+ var aResolved = (a.fires === 1) || (a.tickClaimLost === 1);
403
+ var bResolved = (bb.fires === 1) || (bb.tickClaimLost === 1);
404
+ return aResolved && bResolved;
405
+ }, { timeoutMs: 15000, label: "exactly-once: both nodes' tick-claim race resolved" });
406
+
407
+ var aState = schedA.list()[0];
408
+ var bState = schedB.list()[0];
409
+
410
+ // Exactly one of the two nodes won the claim.
411
+ var winners = aState.fires + bState.fires;
412
+ var losers = aState.tickClaimLost + bState.tickClaimLost;
413
+ check("exactly-once: exactly ONE node won the tick-claim (fires totals 1)",
414
+ winners === 1);
415
+ check("exactly-once: exactly ONE node lost (tickClaimLost totals 1)",
416
+ losers === 1);
417
+
418
+ // The run-side effect fired exactly once across both nodes — the
419
+ // real proof the job did not double-execute.
420
+ check("exactly-once: the job's run() executed exactly ONCE across both nodes",
421
+ (firedByA + firedByB) === 1);
422
+
423
+ // The DB holds exactly one tick row for the shared tickKey — the
424
+ // PRIMARY KEY collapsed the racing INSERTs to one.
425
+ var tickRows = _psql(
426
+ "SELECT count(*) AS n FROM _blamejs_scheduler_ticks " +
427
+ "WHERE \"tickKey\" = 'rollup:" + sharedNominal + "';");
428
+ check("exactly-once: real PG holds exactly ONE tick row for the shared key",
429
+ /^1$/m.test(tickRows.trim()));
430
+
431
+ // The surviving row's claimedBy is the winner's nodeId — confirms the
432
+ // winner is the one whose INSERT actually landed, not a coincidence.
433
+ var winnerNode = _psql(
434
+ "SELECT \"claimedBy\" FROM _blamejs_scheduler_ticks " +
435
+ "WHERE \"tickKey\" = 'rollup:" + sharedNominal + "';").trim();
436
+ var expectedWinner = aState.fires === 1 ? "node-A" : "node-B";
437
+ check("exactly-once: surviving tick row's claimedBy is the node that fired",
438
+ winnerNode === expectedWinner);
439
+
440
+ // Control: a SECOND distinct nominal tick is independently claimable —
441
+ // proves the dedup is per-tickKey, not a one-shot table lock.
442
+ var secondNominal = sharedNominal + b.constants.TIME.minutes(1);
443
+ taskA.nextRun = secondNominal;
444
+ schedA._fireOnce("rollup");
445
+ await helpers.waitUntil(function () {
446
+ return schedA.list()[0].fires === 2;
447
+ }, { timeoutMs: 15000, label: "exactly-once: a second distinct tick is claimable" });
448
+ check("exactly-once: a distinct second tick fires (per-tickKey dedup, not a table lock)",
449
+ firedByA === 2 || (firedByA + firedByB) === 2);
450
+
451
+ await schedA.stop();
452
+ await schedB.stop();
453
+
454
+ // Tear down cluster wiring so the fencing section starts from a clean
455
+ // single source of truth (it re-uses _blamejs_leader through two
456
+ // direct provider instances, not cluster.init).
457
+ await b.cluster.shutdown();
458
+ b.cluster._resetForTest();
459
+ }
460
+
461
+ // ======================================================================
462
+ // 2. FENCING — extend the MySQL lease-fencing pattern to Postgres AND
463
+ // tie a stale fencing token to a real refused fenced write.
464
+ //
465
+ // Two cluster-provider-db instances on the real PG _blamejs_leader
466
+ // row. Node-A acquires (token 1); a takeover by Node-B after A's lease
467
+ // expires bumps the token to 2. The canonical fenced operation — the
468
+ // audit-tip upsert with `WHERE stored.fencingToken <= EXCLUDED` — is
469
+ // issued by Node-B (token 2) and lands. Then the STALE Node-A (token
470
+ // 1) attempts the same fenced upsert: the real PG WHERE clause REFUSES
471
+ // it (RETURNING 0 rows). The stale write does not land; the tip still
472
+ // carries Node-B's token-2 row.
473
+ // ======================================================================
474
+ async function _proveFencing() {
475
+ _psql(AUDIT_TIP_DDL);
476
+ // Fresh leader table for an isolated fencing sequence. The prior
477
+ // _proveExactlyOnce acquired+released leadership (correctly leaving a
478
+ // released row at fencingToken 1), and the fencing token is monotonic
479
+ // across leadership changes BY DESIGN — so without a reset, A's first
480
+ // acquire here would correctly bump to 2. Drop the row so this proof
481
+ // asserts the 1 -> 2 progression from a clean origin.
482
+ _psql("DROP TABLE IF EXISTS _blamejs_leader;");
483
+
484
+ var providerFactory = require("../../lib/cluster-provider-db");
485
+ var pA = providerFactory.create({ externalDbBackend: "ops", dialect: "postgres" });
486
+ var pB = providerFactory.create({ externalDbBackend: "ops", dialect: "postgres" });
487
+
488
+ await pA.ensureSchema();
489
+ check("fencing: ensureSchema runs against real postgres", true);
490
+
491
+ // Node-A acquires the lease — first acquire issues fencing token 1.
492
+ var leaseA = await pA.acquireLease("node-A", b.constants.TIME.seconds(30));
493
+ check("fencing (PG): A acquired the lease", leaseA !== null);
494
+ check("fencing (PG): A's fencingToken = 1", leaseA.fencingToken === 1);
495
+ check("fencing (PG): A is the leader nodeId", leaseA.nodeId === "node-A");
496
+
497
+ // While A holds a live lease, B is blocked by the real ON CONFLICT
498
+ // WHERE expiresAt < now() guard.
499
+ var leaseBblocked = await pB.acquireLease("node-B", b.constants.TIME.seconds(30));
500
+ check("fencing (PG): B blocked while A holds a live lease", leaseBblocked === null);
501
+
502
+ // A performs a FENCED write at token 1 — the audit-tip upsert. First
503
+ // write: no prior tip row, so the INSERT lands and RETURNING is non-empty.
504
+ var aTipOk = await _fencedAuditTipUpsert(1, "node-A-row-hash-1", leaseA.fencingToken);
505
+ check("fencing (PG): A's fenced audit-tip write at token 1 landed", aTipOk === true);
506
+ var tipAfterA = _psql(
507
+ "SELECT \"fencingToken\", \"rowHash\" FROM _blamejs_audit_tip WHERE \"scope\" = 'audit';");
508
+ check("fencing (PG): tip row reflects A's token-1 write",
509
+ /\b1\b/.test(tipAfterA) && /node-A-row-hash-1/.test(tipAfterA));
510
+
511
+ // A's lease expires; B takes over. Use a short TTL on a fresh acquire so
512
+ // the takeover bumps the fencing token to 2 via the real
513
+ // `fencingToken + 1` ON CONFLICT path. (A releases first so B's takeover
514
+ // is deterministic rather than waiting on wall-clock expiry of the 30s
515
+ // lease above.)
516
+ await pA.releaseLease(leaseA);
517
+ var leaseB = await helpers.waitUntil(async function () {
518
+ return await pB.acquireLease("node-B", b.constants.TIME.seconds(30));
519
+ }, { timeoutMs: 15000, label: "fencing (PG): B takes over after A releases" });
520
+ check("fencing (PG): B took over the lease", leaseB !== null);
521
+ check("fencing (PG): takeover bumped fencingToken to 2",
522
+ leaseB.fencingToken === 2);
523
+ check("fencing (PG): leader is now node-B", leaseB.nodeId === "node-B");
524
+
525
+ // B (the NEW leader, token 2) performs a fenced write — accepted because
526
+ // 2 >= stored 1.
527
+ var bTipOk = await _fencedAuditTipUpsert(2, "node-B-row-hash-2", leaseB.fencingToken);
528
+ check("fencing (PG): B's fenced audit-tip write at token 2 landed (2 >= 1)",
529
+ bTipOk === true);
530
+ var tipAfterB = _psql(
531
+ "SELECT \"fencingToken\", \"rowHash\" FROM _blamejs_audit_tip WHERE \"scope\" = 'audit';");
532
+ check("fencing (PG): tip row now carries B's token-2 write",
533
+ /\b2\b/.test(tipAfterB) && /node-B-row-hash-2/.test(tipAfterB));
534
+
535
+ // The STALE leader A (still holding token 1) attempts a fenced write.
536
+ // The real PG `WHERE _blamejs_audit_tip.fencingToken <= EXCLUDED`
537
+ // clause refuses it: stored token is 2, incoming is 1, 2 <= 1 is false,
538
+ // 0 rows affected → fenced out. This is the split-brain old-leader
539
+ // write the fencing token exists to stop.
540
+ var staleAccepted = await _fencedAuditTipUpsert(99, "node-A-STALE-row-hash", leaseA.fencingToken);
541
+ check("fencing (PG): STALE A's fenced write at token 1 was REFUSED by the DB",
542
+ staleAccepted === false);
543
+
544
+ // The stale write did NOT land — the tip still carries B's token-2 row,
545
+ // not A's stale hash/counter. This is the real side-effect assertion:
546
+ // the partitioned old leader could not corrupt the chain head.
547
+ var tipAfterStale = _psql(
548
+ "SELECT \"atMonotonicCounter\", \"fencingToken\", \"rowHash\" FROM _blamejs_audit_tip " +
549
+ "WHERE \"scope\" = 'audit';");
550
+ check("fencing (PG): stale write did NOT overwrite the tip (token still 2)",
551
+ /\b2\b/.test(tipAfterStale));
552
+ check("fencing (PG): tip rowHash is still B's, NOT A's stale hash",
553
+ /node-B-row-hash-2/.test(tipAfterStale) &&
554
+ !/node-A-STALE-row-hash/.test(tipAfterStale));
555
+ check("fencing (PG): tip counter is B's (2), not A's stale (99)",
556
+ /^2\b/.test(tipAfterStale.trim()) || /\b2\t/.test(tipAfterStale));
557
+
558
+ // Same-token rewrite is permitted (the guard is <=, not <): B re-writes
559
+ // at token 2 with a new counter — confirms the guard fences only
560
+ // STRICTLY-lower tokens, matching _upsertAuditTip's documented contract.
561
+ var bRewriteOk = await _fencedAuditTipUpsert(3, "node-B-row-hash-3", leaseB.fencingToken);
562
+ check("fencing (PG): same-token (2) re-write is accepted (guard is <=, not <)",
563
+ bRewriteOk === true);
564
+ var tipAfterRewrite = _psql(
565
+ "SELECT \"atMonotonicCounter\", \"rowHash\" FROM _blamejs_audit_tip WHERE \"scope\" = 'audit';");
566
+ check("fencing (PG): same-token re-write advanced the counter to 3",
567
+ /^3\b/.test(tipAfterRewrite.trim()) || /\b3\t/.test(tipAfterRewrite) ||
568
+ /node-B-row-hash-3/.test(tipAfterRewrite));
569
+
570
+ await pB.releaseLease(leaseB);
571
+ }
572
+
573
+ // Issue the canonical fenced audit-tip upsert against the real PG backend,
574
+ // byte-for-byte the SQL shape from lib/audit.js _upsertAuditTip (with $N
575
+ // placeholders for the postgres dialect). Returns true if the write landed
576
+ // (RETURNING produced a row), false if the DB fenced it out (0 rows).
577
+ async function _fencedAuditTipUpsert(counter, rowHash, fencingToken) {
578
+ var result = await b.externalDb.query(
579
+ "INSERT INTO _blamejs_audit_tip " +
580
+ " (\"scope\", \"atMonotonicCounter\", \"rowHash\", \"signedAt\", \"fencingToken\") " +
581
+ "VALUES ('audit', $1, $2, $3, $4) " +
582
+ "ON CONFLICT (\"scope\") DO UPDATE SET " +
583
+ " \"atMonotonicCounter\" = EXCLUDED.\"atMonotonicCounter\", " +
584
+ " \"rowHash\" = EXCLUDED.\"rowHash\", " +
585
+ " \"signedAt\" = EXCLUDED.\"signedAt\", " +
586
+ " \"fencingToken\" = EXCLUDED.\"fencingToken\" " +
587
+ "WHERE _blamejs_audit_tip.\"fencingToken\" <= EXCLUDED.\"fencingToken\" " +
588
+ "RETURNING \"fencingToken\"",
589
+ [counter, rowHash, String(Date.now()), fencingToken],
590
+ { backend: "ops" }
591
+ );
592
+ return !!(result.rows && result.rows.length > 0);
593
+ }
594
+
595
+ module.exports = { run: run };
596
+
597
+ if (require.main === module) {
598
+ run().then(
599
+ function () { console.log("OK — " + helpers.getChecks() + " checks passed"); process.exit(0); },
600
+ function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
601
+ );
602
+ }