@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
@@ -70,6 +70,7 @@ var lazyRequire = require("./lazy-require");
70
70
  var safeAsync = require("./safe-async");
71
71
  var safeJson = require("./safe-json");
72
72
  var safeSql = require("./safe-sql");
73
+ var sql = require("./sql");
73
74
  var validateOpts = require("./validate-opts");
74
75
  var { defineClass } = require("./framework-error");
75
76
 
@@ -84,6 +85,12 @@ var DEFAULT_MAX_ATTEMPTS = 10;
84
85
  var DEFAULT_BACKOFF_INITIAL = C.TIME.seconds(1);
85
86
  var DEFAULT_BACKOFF_MAX = C.TIME.minutes(5);
86
87
  var DEFAULT_BACKOFF_FACTOR = 2; // multiplier, not bytes
88
+ // Lease after which an in-flight row is treated as stranded by a crashed
89
+ // publisher and reclaimed to 'pending'. Must exceed the longest expected
90
+ // publish so a slow-but-live publish isn't reclaimed mid-flight (a reclaim
91
+ // then re-publish is a duplicate, which at-least-once tolerates, but a tight
92
+ // lease makes duplicates routine). Default 5 min, matching backoff.maxMs.
93
+ var DEFAULT_CLAIM_RECLAIM_MS = C.TIME.minutes(5);
87
94
  var TOPIC_MAX_LEN = C.BYTES.bytes(255);
88
95
  var KEY_MAX_LEN = C.BYTES.bytes(255);
89
96
 
@@ -93,6 +100,15 @@ function _validateTableName(name) {
93
100
  return safeSql.quoteIdentifier(name);
94
101
  }
95
102
 
103
+ // Map the operator backend's dialect tag to the b.sql dialect vocabulary.
104
+ // b.sql's terminal toExternalSql() then emits $1..$N for postgres and `?`
105
+ // for sqlite / mysql, matching what the operator-supplied driver expects.
106
+ function _sqlDialect(externalDb) {
107
+ var d = externalDb && externalDb.dialect;
108
+ if (d === "postgres" || d === "mysql") return d;
109
+ return "sqlite";
110
+ }
111
+
96
112
  function _utcNowExpr(externalDb) {
97
113
  // The framework's externalDb backends wrap Postgres + SQLite. Both
98
114
  // accept a parameterized timestamp via JS Date → ISO string for
@@ -187,7 +203,7 @@ function create(opts) {
187
203
  validateOpts.requireObject(opts, "outbox", OutboxError);
188
204
  validateOpts(opts, [
189
205
  "externalDb", "table", "publisher",
190
- "pollIntervalMs", "batchSize", "maxAttempts",
206
+ "pollIntervalMs", "batchSize", "maxAttempts", "claimReclaimMs",
191
207
  "retryBackoff", "audit", "name",
192
208
  "envelope", "connectorName", "connectorVersion", "dbName",
193
209
  ], "outbox.create");
@@ -198,7 +214,10 @@ function create(opts) {
198
214
  }
199
215
  validateOpts.requireNonEmptyString(opts.table,
200
216
  "outbox.create: table", OutboxError, "outbox/bad-table");
201
- var quotedTable = _validateTableName(opts.table);
217
+ // Validate the table identifier at create-time so a bad name throws at
218
+ // boot, not at first query. b.sql re-quotes the name by construction on
219
+ // every emitted statement (the builder owns identifier quoting now).
220
+ _validateTableName(opts.table);
202
221
 
203
222
  if (typeof opts.publisher !== "function") {
204
223
  throw new OutboxError("outbox/bad-publisher",
@@ -210,10 +229,13 @@ function create(opts) {
210
229
  "outbox.create: batchSize", OutboxError, "outbox/bad-opts");
211
230
  validateOpts.optionalPositiveFinite(opts.maxAttempts,
212
231
  "outbox.create: maxAttempts", OutboxError, "outbox/bad-opts");
232
+ validateOpts.optionalPositiveFinite(opts.claimReclaimMs,
233
+ "outbox.create: claimReclaimMs", OutboxError, "outbox/bad-opts");
213
234
 
214
235
  var pollIntervalMs = opts.pollIntervalMs || DEFAULT_POLL_MS;
215
236
  var batchSize = opts.batchSize || DEFAULT_BATCH_SIZE;
216
237
  var maxAttempts = opts.maxAttempts || DEFAULT_MAX_ATTEMPTS;
238
+ var claimReclaimMs = opts.claimReclaimMs || DEFAULT_CLAIM_RECLAIM_MS;
217
239
  var name = opts.name || "outbox";
218
240
 
219
241
  var backoff = opts.retryBackoff || {};
@@ -302,38 +324,69 @@ function create(opts) {
302
324
  "outbox.enqueue: payload/headers must be JSON-serializable: " + e.message);
303
325
  }
304
326
 
305
- var sql = "INSERT INTO " + quotedTable +
306
- " (topic, payload, key, headers, enqueued_at, next_attempt_at, attempts, status)" +
307
- " VALUES ($1, $2, $3, $4, $5, $5, 0, 'pending')";
308
327
  var now = _utcNowExpr(externalDb);
309
- await txn.query(sql, [
310
- event.topic, payloadJson, event.key || null, headersJson, now,
311
- ]);
328
+ // enqueued_at and next_attempt_at both take the same publisher-clock
329
+ // moment; b.sql binds it as two separate `?` so the placeholder/param
330
+ // parity gate holds (no $5-reused-twice shorthand).
331
+ var stmt = sql.insert(opts.table, { dialect: _sqlDialect(externalDb) })
332
+ .values({
333
+ topic: event.topic,
334
+ payload: payloadJson,
335
+ key: event.key || null,
336
+ headers: headersJson,
337
+ enqueued_at: now,
338
+ next_attempt_at: now,
339
+ attempts: 0,
340
+ status: "pending",
341
+ })
342
+ .toExternalSql(_sqlDialect(externalDb));
343
+ await txn.query(stmt.sql, stmt.params);
312
344
  _emitMetric("enqueued", 1);
313
345
  }
314
346
 
315
347
  async function declareSchema(xdb) {
316
348
  var target = xdb || externalDb;
317
- var ddl =
318
- "CREATE TABLE IF NOT EXISTS " + quotedTable + " (" +
319
- "id BIGSERIAL PRIMARY KEY, " +
320
- "topic VARCHAR(255) NOT NULL, " +
321
- "payload TEXT NOT NULL, " +
322
- "key VARCHAR(255), " +
323
- "headers TEXT, " +
324
- "enqueued_at TIMESTAMPTZ NOT NULL, " +
325
- "next_attempt_at TIMESTAMPTZ NOT NULL, " +
326
- "published_at TIMESTAMPTZ, " +
327
- "attempts INTEGER NOT NULL DEFAULT 0, " +
328
- "last_error TEXT, " +
329
- "status VARCHAR(16) NOT NULL DEFAULT 'pending'" +
330
- ")";
331
- var idxName = _validateTableName(opts.table + "_pending_idx");
332
- var idx =
333
- "CREATE INDEX IF NOT EXISTS " + idxName + " ON " + quotedTable +
334
- " (next_attempt_at) WHERE status = 'pending'";
335
- await target.query(ddl, []);
336
- await target.query(idx, []);
349
+ var dialect = _sqlDialect(target);
350
+ // The identity PK renders dialect-correct (BIGSERIAL on postgres,
351
+ // INTEGER PRIMARY KEY AUTOINCREMENT on sqlite, BIGINT AUTO_INCREMENT
352
+ // on mysql) - the prior hand-rolled DDL hardcoded Postgres BIGSERIAL /
353
+ // TIMESTAMPTZ even on a sqlite backend, which the dialect-aware type
354
+ // map now corrects. A varchar-with-length / timestamp-with-zone is
355
+ // passed verbatim by the type map (it sits in type position after a
356
+ // quoted column name, so no identifier injection is possible).
357
+ var tsType = dialect === "postgres" ? "TIMESTAMPTZ" : "TIMESTAMP";
358
+ var ddl = sql.toExternalSql(sql.createTable(opts.table, [
359
+ { name: "id", serial: true },
360
+ { name: "topic", type: "VARCHAR(255)", notNull: true },
361
+ { name: "payload", type: "TEXT", notNull: true },
362
+ { name: "key", type: "VARCHAR(255)" },
363
+ { name: "headers", type: "TEXT" },
364
+ { name: "enqueued_at", type: tsType, notNull: true },
365
+ { name: "next_attempt_at", type: tsType, notNull: true },
366
+ { name: "published_at", type: tsType },
367
+ { name: "claimed_at", type: tsType },
368
+ { name: "attempts", type: "INTEGER", notNull: true, default: 0 },
369
+ { name: "last_error", type: "TEXT" },
370
+ { name: "status", type: "VARCHAR(16)", notNull: true, default: "pending" },
371
+ ], { dialect: dialect }), dialect);
372
+ // Partial index on the pending pool (the publisher's claim path scans
373
+ // status='pending' ORDER BY next_attempt_at). The 'pending' literal is
374
+ // a builder-emitted static predicate, opted in via allowLiterals.
375
+ var idx = sql.toExternalSql(sql.createIndex(opts.table + "_pending_idx", opts.table,
376
+ ["next_attempt_at"], { dialect: dialect, where: "status = 'pending'" }), dialect);
377
+ await target.query(ddl.sql, ddl.params);
378
+ await target.query(idx.sql, idx.params);
379
+ // Back-compat: an outbox table created before the claimed_at column
380
+ // existed predates the stale-in-flight reaper. CREATE TABLE above is
381
+ // IF NOT EXISTS, so it won't add the column to an existing table — add it
382
+ // idempotently here (every dialect errors if the column already exists,
383
+ // which a fresh table from the CREATE will; swallow that). Without
384
+ // claimed_at the reaper can't tell a stranded claim from a live one.
385
+ try {
386
+ var alter = sql.toExternalSql(sql.alterTable(opts.table,
387
+ { addColumn: { name: "claimed_at", type: tsType } }, { dialect: dialect }), dialect);
388
+ await target.query(alter.sql, alter.params);
389
+ } catch (_e) { /* column already present — idempotent add */ }
337
390
  }
338
391
 
339
392
  // ---- Publisher worker ----
@@ -363,18 +416,24 @@ function create(opts) {
363
416
 
364
417
  async function _claimBatch() {
365
418
  var supportsSkipLocked = _supportsForUpdateSkipLocked();
419
+ var dialect = _sqlDialect(externalDb);
420
+ var CLAIM_COLS = ["id", "topic", "payload", "key", "headers", "attempts"];
366
421
  return await externalDb.transaction(async function (xdb) {
367
422
  var nowExpr = _utcNowExpr(externalDb);
368
- var selectSql =
369
- "SELECT id, topic, payload, key, headers, attempts" +
370
- " FROM " + quotedTable +
371
- " WHERE status = 'pending' AND next_attempt_at <= $1" +
372
- " ORDER BY next_attempt_at" +
373
- " LIMIT $2";
374
- if (supportsSkipLocked) {
375
- selectSql += " FOR UPDATE SKIP LOCKED";
376
- }
377
- var rows = await xdb.query(selectSql, [nowExpr, batchSize]);
423
+ // status='pending' is a builder-emitted static predicate (opted in
424
+ // via allowLiterals); next_attempt_at <= ? + the LIMIT both bind.
425
+ var selectBuilder = sql.select(opts.table, { dialect: dialect })
426
+ .columns(CLAIM_COLS)
427
+ .whereRaw("status = 'pending'", [], { allowLiterals: true })
428
+ .whereRaw("next_attempt_at <= ?", [nowExpr])
429
+ .orderBy("next_attempt_at")
430
+ .limit(batchSize);
431
+ // FOR UPDATE SKIP LOCKED on postgres / mysql; sqlite is a single
432
+ // writer with no row lock, so the claim there is the conservative
433
+ // mark-then-reselect path below (b.sql refuses forUpdate on sqlite).
434
+ if (supportsSkipLocked) selectBuilder.forUpdate({ skipLocked: true });
435
+ var selectSql = selectBuilder.toExternalSql(dialect);
436
+ var rows = await xdb.query(selectSql.sql, selectSql.params);
378
437
  if (!rows || !rows.rows || rows.rows.length === 0) return [];
379
438
  var ids = rows.rows.map(function (r) { return r.id; });
380
439
  // Atomic claim: when the dialect lacks SKIP LOCKED, the UPDATE
@@ -386,30 +445,33 @@ function create(opts) {
386
445
  // way Postgres does).
387
446
  var actuallyClaimed;
388
447
  if (supportsSkipLocked) {
389
- // Postgres/MySQL: row lock held; ANY($1) update is safe.
390
- await xdb.query(
391
- "UPDATE " + quotedTable + " SET status = 'in-flight' WHERE id = ANY($1)",
392
- [ids]
393
- );
448
+ // Postgres/MySQL: row lock held; whereInArray emits `id = ANY(?)`
449
+ // on postgres (the whole id set as one bound array) / expanded
450
+ // `IN (?, ?, ...)` on mysql.
451
+ var claimUpdate = sql.update(opts.table, { dialect: dialect })
452
+ .set({ status: "in-flight", claimed_at: _utcNowExpr(externalDb) })
453
+ .whereInArray("id", ids)
454
+ .toExternalSql(dialect);
455
+ await xdb.query(claimUpdate.sql, claimUpdate.params);
394
456
  actuallyClaimed = rows.rows;
395
457
  } else {
396
458
  // SQLite (or "other") path: emit a portable UPDATE that
397
459
  // refuses overlap by gating on status='pending'. After the
398
460
  // update we re-read the in-flight rows we own; rows that
399
- // another publisher beat us to are skipped.
400
- // Use placeholders so the SQL stays parameterized regardless
401
- // of dialect array semantics.
402
- var placeholders = ids.map(function (_, i) { return "$" + (i + 1); }).join(",");
403
- await xdb.query(
404
- "UPDATE " + quotedTable +
405
- " SET status = 'in-flight' WHERE status = 'pending' AND id IN (" + placeholders + ")",
406
- ids
407
- );
408
- var afterRows = await xdb.query(
409
- "SELECT id, topic, payload, key, headers, attempts FROM " + quotedTable +
410
- " WHERE status = 'in-flight' AND id IN (" + placeholders + ")",
411
- ids
412
- );
461
+ // another publisher beat us to are skipped. whereInArray expands
462
+ // to an `IN (?, ?, ...)` placeholder list on sqlite.
463
+ var markUpdate = sql.update(opts.table, { dialect: dialect })
464
+ .set({ status: "in-flight", claimed_at: _utcNowExpr(externalDb) })
465
+ .whereRaw("status = 'pending'", [], { allowLiterals: true })
466
+ .whereInArray("id", ids)
467
+ .toExternalSql(dialect);
468
+ await xdb.query(markUpdate.sql, markUpdate.params);
469
+ var afterSelect = sql.select(opts.table, { dialect: dialect })
470
+ .columns(CLAIM_COLS)
471
+ .whereRaw("status = 'in-flight'", [], { allowLiterals: true })
472
+ .whereInArray("id", ids)
473
+ .toExternalSql(dialect);
474
+ var afterRows = await xdb.query(afterSelect.sql, afterSelect.params);
413
475
  actuallyClaimed = (afterRows && afterRows.rows) || [];
414
476
  }
415
477
  return actuallyClaimed.map(function (r) {
@@ -425,35 +487,72 @@ function create(opts) {
425
487
  });
426
488
  }
427
489
 
490
+ // Reclaim rows stranded in 'in-flight' by a crashed publisher. A claim
491
+ // flips status pending → in-flight and stamps claimed_at; if the process
492
+ // dies before the row is marked published / retry / dead, it sits in-flight
493
+ // forever, because the claim path only selects status='pending'. That
494
+ // silently drops the event and violates the at-least-once guarantee. Reset
495
+ // any in-flight row whose claim is older than the lease — or that predates
496
+ // the claimed_at column (NULL) — back to 'pending' so the next poll
497
+ // re-publishes it. The lease bounds how long a legitimately slow publish is
498
+ // protected from reclaim; a reclaim+re-publish is a duplicate, which
499
+ // at-least-once tolerates. Best-effort: a failed sweep retries next poll.
500
+ async function _reapStaleInflight() {
501
+ var dialect = _sqlDialect(externalDb);
502
+ var cutoff = new Date(Date.now() - claimReclaimMs);
503
+ var stmt = sql.update(opts.table, { dialect: dialect })
504
+ .set({ status: "pending", claimed_at: null })
505
+ .whereRaw("status = 'in-flight'", [], { allowLiterals: true })
506
+ .whereRaw("(claimed_at IS NULL OR claimed_at <= ?)", [cutoff])
507
+ .toExternalSql(dialect);
508
+ var res = await externalDb.query(stmt.sql, stmt.params);
509
+ return res;
510
+ }
511
+
428
512
  async function _markPublished(id) {
429
- await externalDb.query(
430
- "UPDATE " + quotedTable +
431
- " SET status = 'published', published_at = $1 WHERE id = $2",
432
- [_utcNowExpr(externalDb), id]
433
- );
513
+ var dialect = _sqlDialect(externalDb);
514
+ var stmt = sql.update(opts.table, { dialect: dialect })
515
+ .set({ status: "published", published_at: _utcNowExpr(externalDb) })
516
+ .where("id", id)
517
+ .toExternalSql(dialect);
518
+ await externalDb.query(stmt.sql, stmt.params);
434
519
  }
435
520
 
436
521
  async function _markRetry(id, attempts, errMsg) {
522
+ var dialect = _sqlDialect(externalDb);
437
523
  var nextAt = new Date(Date.now() + _backoffMs(attempts + 1));
438
- await externalDb.query(
439
- "UPDATE " + quotedTable +
440
- " SET status = 'pending', attempts = $1, last_error = $2, next_attempt_at = $3" +
441
- " WHERE id = $4",
442
- [attempts + 1, String(errMsg).slice(0, 1024), nextAt, id] // error-message char cap
443
- );
524
+ var stmt = sql.update(opts.table, { dialect: dialect })
525
+ .set({
526
+ status: "pending",
527
+ attempts: attempts + 1,
528
+ last_error: String(errMsg).slice(0, 1024), // error-message char cap
529
+ next_attempt_at: nextAt,
530
+ })
531
+ .where("id", id)
532
+ .toExternalSql(dialect);
533
+ await externalDb.query(stmt.sql, stmt.params);
444
534
  }
445
535
 
446
536
  async function _markDead(id, attempts, errMsg) {
447
- await externalDb.query(
448
- "UPDATE " + quotedTable +
449
- " SET status = 'dead', attempts = $1, last_error = $2 WHERE id = $3",
450
- [attempts + 1, String(errMsg).slice(0, 1024), id] // error-message char cap
451
- );
537
+ var dialect = _sqlDialect(externalDb);
538
+ var stmt = sql.update(opts.table, { dialect: dialect })
539
+ .set({
540
+ status: "dead",
541
+ attempts: attempts + 1,
542
+ last_error: String(errMsg).slice(0, 1024), // error-message char cap
543
+ })
544
+ .where("id", id)
545
+ .toExternalSql(dialect);
546
+ await externalDb.query(stmt.sql, stmt.params);
452
547
  _emitAudit("system.outbox.deadletter", "failure", { id: id, attempts: attempts + 1 });
453
548
  _emitMetric("dead-letter", 1);
454
549
  }
455
550
 
456
551
  async function _processOnce() {
552
+ // Reclaim crashed-publisher rows before claiming new work, so a stranded
553
+ // in-flight row re-enters the pending pool and is published this cycle.
554
+ try { await _reapStaleInflight(); }
555
+ catch (_e) { /* drop-silent — reaper retries next poll */ }
457
556
  var batch = await _claimBatch();
458
557
  if (batch.length === 0) return 0;
459
558
  for (var i = 0; i < batch.length; i++) {
@@ -516,19 +615,21 @@ function create(opts) {
516
615
  _emitAudit("system.outbox.stopped", "success", { name: name });
517
616
  }
518
617
 
519
- async function pendingCount() {
520
- var res = await externalDb.query(
521
- "SELECT COUNT(*) AS n FROM " + quotedTable + " WHERE status = 'pending'", []
522
- );
618
+ async function _statusCount(status) {
619
+ var dialect = _sqlDialect(externalDb);
620
+ // status is a fixed builder-internal literal ('pending' / 'dead'),
621
+ // never operator input; opted in via allowLiterals. COUNT(*) AS n is
622
+ // the count aggregate with an alias.
623
+ var stmt = sql.select(opts.table, { dialect: dialect })
624
+ .count("*", "n")
625
+ .whereRaw("status = '" + status + "'", [], { allowLiterals: true })
626
+ .toExternalSql(dialect);
627
+ var res = await externalDb.query(stmt.sql, stmt.params);
523
628
  return Number((res && res.rows && res.rows[0] && res.rows[0].n) || 0);
524
629
  }
525
630
 
526
- async function deadCount() {
527
- var res = await externalDb.query(
528
- "SELECT COUNT(*) AS n FROM " + quotedTable + " WHERE status = 'dead'", []
529
- );
530
- return Number((res && res.rows && res.rows[0] && res.rows[0].n) || 0);
531
- }
631
+ async function pendingCount() { return await _statusCount("pending"); }
632
+ async function deadCount() { return await _statusCount("dead"); }
532
633
 
533
634
  return {
534
635
  enqueue: enqueue,
@@ -13,10 +13,18 @@
13
13
  * - Processing instructions referencing external resources
14
14
  * - Unbounded recursion / element count / attribute count
15
15
  * - CDATA sections of arbitrary length
16
+ * - Prototype pollution: an element or attribute named __proto__,
17
+ * constructor, or prototype landing as a key in the result tree
18
+ * (CWE-1321 / OWASP prototype-pollution)
16
19
  *
17
20
  * This parser closes all of them by default. DOCTYPE, external entities,
18
21
  * and processing instructions other than '<?xml ?>' are REJECTED — apps
19
- * that need them are using the wrong parser.
22
+ * that need them are using the wrong parser. Element and attribute names
23
+ * equal to __proto__ / constructor / prototype are REJECTED with
24
+ * xml/forbidden-name so they can never collide with an inherited member
25
+ * or reassign an accumulator's prototype; the result tree and every
26
+ * nested object it contains have a null prototype, so a consumer reading
27
+ * an absent key sees undefined rather than an inherited Object member.
20
28
  *
21
29
  * Output: a plain JS object. Element with attributes + children:
22
30
  * <root id="x"><child>text</child></root>
@@ -75,6 +83,18 @@ var ABSOLUTE_MAX_ATTRIBUTES = 1_000;
75
83
  // XML built-in entities (the ONLY entities allowed)
76
84
  var BUILT_IN_ENTITIES = { lt: "<", gt: ">", amp: "&", quot: "\"", apos: "'" };
77
85
 
86
+ // Names that must never become a key in the result tree. A plain object
87
+ // inherits these from Object.prototype; an element/attribute named after
88
+ // one of them would otherwise collide with the inherited member (a
89
+ // consumer sees a function/object instead of undefined) or — for a
90
+ // computed-member write of an object value — reassign the accumulator's
91
+ // prototype (CWE-1321 / OWASP prototype-pollution). The accumulators are
92
+ // built with a null prototype, and these names are rejected outright so
93
+ // the result is always a clean key→value map. Mirrors the
94
+ // __proto__/constructor/prototype rejection the toml / yaml / ini
95
+ // parsers in this family already apply.
96
+ var FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
97
+
78
98
  function _validateAndCap(name, value, defaultValue, ceiling) {
79
99
  if (value === undefined) return defaultValue;
80
100
  if (!numericBounds.isPositiveFiniteInt(value)) {
@@ -178,7 +198,12 @@ function parse(input, opts) {
178
198
  } else break;
179
199
  }
180
200
  if (pos === start) throw _err("expected name", "xml/bad-name");
181
- return input.substring(start, pos);
201
+ var parsed = input.substring(start, pos);
202
+ if (FORBIDDEN_KEYS.has(parsed)) {
203
+ throw _err("element/attribute name '" + parsed +
204
+ "' is reserved (prototype-pollution defense)", "xml/forbidden-name");
205
+ }
206
+ return parsed;
182
207
  }
183
208
 
184
209
  // Parse an attribute value (single- or double-quoted)
@@ -267,7 +292,12 @@ function parse(input, opts) {
267
292
 
268
293
  expectChar("<");
269
294
  var name = parseName();
270
- var attrs = {};
295
+ // Null-prototype accumulator keyed by attacker-influenced attribute
296
+ // names — no inherited Object member can shadow a missing key, and the
297
+ // duplicate-attribute check below can't be fooled by an inherited
298
+ // function (CWE-1321). Forbidden names are already rejected in
299
+ // parseName.
300
+ var attrs = Object.create(null);
271
301
  var attrCount = 0;
272
302
 
273
303
  while (pos < len) {
@@ -351,10 +381,15 @@ function parse(input, opts) {
351
381
  // Pure-text element → string
352
382
  return _make(name, textParts.join("").trim() === "" ? textParts.join("") : textParts.join(""));
353
383
  }
354
- // Mixed / attributed element → object
355
- var obj = {};
384
+ // Mixed / attributed element → object. Both accumulators carry a null
385
+ // prototype: `grouped` is keyed by attacker-influenced child element
386
+ // names and `obj` receives them via Object.assign, so neither may
387
+ // expose an inherited Object member or be prototype-poisoned by a
388
+ // computed-member write (CWE-1321). Forbidden child names were already
389
+ // rejected in parseName.
390
+ var obj = Object.create(null);
356
391
  if (hasAttrs) obj["@attrs"] = attrs;
357
- var grouped = {};
392
+ var grouped = Object.create(null);
358
393
  for (var i = 0; i < elementChildren.length; i++) {
359
394
  var childWrap = elementChildren[i].value;
360
395
  var childName = Object.keys(childWrap)[0];
@@ -374,7 +409,12 @@ function parse(input, opts) {
374
409
  }
375
410
 
376
411
  function _make(name, value) {
377
- var out = {};
412
+ // Null-prototype wrapper keyed by the element name (parser-controlled,
413
+ // attacker-influenced). `out[name] = value` with a forbidden name
414
+ // would otherwise reassign the wrapper's prototype when value is an
415
+ // object; the name is already rejected in parseName and the null
416
+ // prototype removes the inherited-member surface entirely (CWE-1321).
417
+ var out = Object.create(null);
378
418
  out[name] = value;
379
419
  return out;
380
420
  }
@@ -41,6 +41,33 @@ var PqcAgentError = defineClass("PqcAgentError", { alwaysPermanent: true });
41
41
  // cycles when pqc-agent is required during framework bootstrap.
42
42
  var audit = lazyRequire(function () { return require("./audit"); });
43
43
 
44
+ // Observe an outbound socket's negotiated TLS key-exchange group and audit a
45
+ // classical (non-PQC) downgrade. node:tls reports getEphemeralKeyInfo() as
46
+ // { type:"ECDH", name:"X25519", ... } for a classical group and as {} for an
47
+ // ML-KEM hybrid (it doesn't model the hybrid as ECDH). So a NON-empty name
48
+ // that doesn't carry "MLKEM" means the peer offered no hybrid and the
49
+ // handshake fell back to classical X25519 (the framework's last-resort
50
+ // group) — emit the downgrade so operators can see which dependencies are
51
+ // not yet PQC-ready. Best-effort + drop-silent: an audit failure must never
52
+ // break the request that triggered it.
53
+ function auditClassicalDowngrade(socket, meta) {
54
+ try {
55
+ if (!socket || typeof socket.getEphemeralKeyInfo !== "function") return;
56
+ var info = socket.getEphemeralKeyInfo() || {};
57
+ var group = info.name;
58
+ if (!group || /MLKEM/i.test(group)) return; // hybrid (or unreported) — not a downgrade
59
+ audit().safeEmit({
60
+ action: "tls.classical_downgrade",
61
+ outcome: "success",
62
+ metadata: {
63
+ group: group,
64
+ host: (meta && (meta.host || meta.servername)) || null,
65
+ port: (meta && meta.port) || null,
66
+ },
67
+ });
68
+ } catch (_e) { /* drop-silent — audit is best-effort; never break TLS */ }
69
+ }
70
+
44
71
  // IANA TLS Supported Groups Registry — every named-group identifier
45
72
  // the framework knows by name. Operators with `allowOperatorGroups:
46
73
  // true` may pass any entry from this registry; entries outside it
@@ -186,6 +213,19 @@ function create(opts) {
186
213
  var built = _buildAgentOpts(opts);
187
214
  var agent = new https.Agent(built);
188
215
  agent._builtOpts = built;
216
+ // Observe each NEW outbound socket's negotiated group (createConnection
217
+ // runs per fresh connection, not per keep-alive reuse). A classical
218
+ // negotiation means the peer offered no ML-KEM hybrid — audit the
219
+ // downgrade. Hybrid stays preferred on every handshake; this only fires on
220
+ // the classical fallback.
221
+ var _origCreateConnection = agent.createConnection.bind(agent);
222
+ agent.createConnection = function (options, cb) {
223
+ var socket = _origCreateConnection(options, cb);
224
+ if (socket && typeof socket.once === "function") {
225
+ socket.once("secureConnect", function () { auditClassicalDowngrade(socket, options); });
226
+ }
227
+ return socket;
228
+ };
189
229
  // Per-instance cert rotation. The pre-v0.10.9 path required process
190
230
  // restart for cert rotation on agents built via explicit `create()`
191
231
  // (only the framework's lazy default had `b.pqcAgent.reload()`).
@@ -345,6 +385,10 @@ module.exports = {
345
385
  create: create,
346
386
  createHttp: createHttp,
347
387
  reload: reload,
388
+ // Internal — shared with lib/http-client.js's h2 transport, which connects
389
+ // via node:http2 (not this agent) and so needs the same downgrade
390
+ // observation. Underscore-prefixed: not a public operator primitive.
391
+ _auditClassicalDowngrade: auditClassicalDowngrade,
348
392
  DEFAULT_OPTS: DEFAULT_OPTS,
349
393
  KNOWN_TLS_GROUPS: KNOWN_TLS_GROUPS,
350
394
  enforced: true,
@@ -22,6 +22,8 @@
22
22
  * publishedBy)`. Created by `lib/cluster-storage.js` migrations.
23
23
  */
24
24
  var clusterStorage = require("./cluster-storage");
25
+ var frameworkSchema = require("./framework-schema");
26
+ var sql = require("./sql");
25
27
  var C = require("./constants");
26
28
  var lazyRequire = require("./lazy-require");
27
29
  var validateOpts = require("./validate-opts");
@@ -31,6 +33,24 @@ var logger = lazyRequire(function () { return require("./log").boot("pubsub-clus
31
33
 
32
34
  var PubsubError = defineClass("PubsubError");
33
35
 
36
+ // Resolved once: the fan-out table's concrete name, honoring the
37
+ // configurable framework-table prefix. clusterStorage.execute leaves
38
+ // this self-prefixed name unrewritten (its rewrite map is identity-
39
+ // filtered for already-prefixed tables) and translates `?` to `$N` for
40
+ // Postgres, so b.sql emits the bare resolved name + `?` placeholders.
41
+ var MESSAGES_TABLE = frameworkSchema.tableName("_blamejs_pubsub_messages"); // allow:hand-rolled-sql — single canonical logical-name reference
42
+
43
+ // b.sql opts for every fan-out statement: thread the ACTIVE backend
44
+ // dialect (clusterStorage.dialect() — "sqlite" single-node, "postgres" |
45
+ // "mysql" in cluster mode) so the emitted identifier quoting matches the
46
+ // backend the SQL dispatches to. Without it b.sql defaults to "sqlite"
47
+ // and double-quotes identifiers — correct on Postgres (both double-quote)
48
+ // but read as STRING LITERALS by MySQL (no ANSI_QUOTES), turning the
49
+ // INSERT/SELECT/DELETE into syntax errors. clusterStorage.execute still
50
+ // rewrites table names + translates `?` placeholders at dispatch; this
51
+ // controls only the builder-side identifier quoting.
52
+ function _sqlOpts() { return { dialect: clusterStorage.dialect() }; }
53
+
34
54
  var DEFAULT_POLL_INTERVAL_MS = 100;
35
55
  var DEFAULT_RETENTION_MS = C.TIME.minutes(1);
36
56
  var DEFAULT_PRUNE_EVERY_MS = C.TIME.minutes(5);
@@ -65,11 +85,13 @@ function create(opts) {
65
85
 
66
86
  async function publishRemote(scopedChannel, payload) {
67
87
  var serialized = JSON.stringify(payload);
68
- await clusterStorage.execute(
69
- "INSERT INTO _blamejs_pubsub_messages " +
70
- "(topic, payload, publishedAt, publishedBy) VALUES (?, ?, ?, ?)",
71
- [scopedChannel, serialized, Date.now(), _nodeId()]
72
- );
88
+ var built = sql.insert(MESSAGES_TABLE, _sqlOpts()).values({
89
+ topic: scopedChannel,
90
+ payload: serialized,
91
+ publishedAt: Date.now(),
92
+ publishedBy: _nodeId(),
93
+ }).toSql();
94
+ await clusterStorage.execute(built.sql, built.params);
73
95
  return { remote: 1 };
74
96
  }
75
97
 
@@ -78,24 +100,25 @@ function create(opts) {
78
100
  var nodeId = _nodeId();
79
101
  try {
80
102
  // First poll: prime lastSeenId to the current MAX so we don't
81
- // re-dispatch every historical row on startup.
103
+ // re-dispatch every historical row on startup. MAX(id) is NULL on
104
+ // an empty table; Number(null) || 0 below maps that to 0 (the same
105
+ // result the prior COALESCE(MAX(id), 0) produced).
82
106
  if (!primed) {
83
- var primer = await clusterStorage.execute(
84
- "SELECT COALESCE(MAX(id), 0) AS maxId FROM _blamejs_pubsub_messages",
85
- []
86
- );
107
+ var primerBuilt = sql.select(MESSAGES_TABLE, _sqlOpts()).max("id", "maxId").toSql();
108
+ var primer = await clusterStorage.execute(primerBuilt.sql, primerBuilt.params);
87
109
  if (primer.rows && primer.rows[0]) {
88
110
  lastSeenId = Number(primer.rows[0].maxId) || 0;
89
111
  }
90
112
  primed = true;
91
113
  return;
92
114
  }
93
- var result = await clusterStorage.execute(
94
- "SELECT id, topic, payload, publishedAt, publishedBy " +
95
- "FROM _blamejs_pubsub_messages " +
96
- "WHERE id > ? AND publishedBy <> ? ORDER BY id ASC",
97
- [lastSeenId, nodeId]
98
- );
115
+ var pollBuilt = sql.select(MESSAGES_TABLE, _sqlOpts())
116
+ .columns(["id", "topic", "payload", "publishedAt", "publishedBy"])
117
+ .where("id", ">", lastSeenId)
118
+ .where("publishedBy", "<>", nodeId)
119
+ .orderBy("id", "asc")
120
+ .toSql();
121
+ var result = await clusterStorage.execute(pollBuilt.sql, pollBuilt.params);
99
122
  var rows = result.rows || [];
100
123
  for (var i = 0; i < rows.length; i++) {
101
124
  var row = rows[i];
@@ -116,10 +139,9 @@ function create(opts) {
116
139
  var now = Date.now();
117
140
  if (now - lastPruneAt >= pruneEveryMs) {
118
141
  lastPruneAt = now;
119
- await clusterStorage.execute(
120
- "DELETE FROM _blamejs_pubsub_messages WHERE publishedAt < ?",
121
- [now - retentionMs]
122
- );
142
+ var pruneBuilt = sql.delete(MESSAGES_TABLE, _sqlOpts())
143
+ .where("publishedAt", "<", now - retentionMs).toSql();
144
+ await clusterStorage.execute(pruneBuilt.sql, pruneBuilt.params);
123
145
  }
124
146
  } catch (e) {
125
147
  try { logger().warn("pubsub-cluster poll failed: " +