@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,630 @@
1
+ "use strict";
2
+ /**
3
+ * Live MySQL proof that the b.sql data-layer migration of the six
4
+ * privacy/identity/production modules (consent / api-key / legal-hold /
5
+ * subject / retention / scheduler) emits valid MySQL — backtick
6
+ * identifiers, `ON DUPLICATE KEY UPDATE` upserts, the IF()-based fence
7
+ * rewrite the builder synthesizes for MySQL (which has no `WHERE` on
8
+ * upsert and no `RETURNING`), and the unsigned/BIGINT coercion at the
9
+ * driver boundary.
10
+ *
11
+ * MySQL is NOT a framework cluster backend (frameworkSchema /
12
+ * clusterStorage support postgres + sqlite only — see
13
+ * audit-chain-external-db.test.js). So api-key / consent never dispatch
14
+ * to MySQL in production; the framework table shapes here are exercised
15
+ * as b.sql operator-app-schema targets — proving the SAME builders that
16
+ * back the migrated modules emit MySQL that runs. The headline MySQL-
17
+ * specific surface:
18
+ *
19
+ * - consent fenced tip: the builder rewrites `conflictWhere(fence)` +
20
+ * `RETURNING` into per-column `IF(stored.fencingToken <=
21
+ * VALUES(fencingToken), VALUES(col), col)` (no WHERE / no RETURNING
22
+ * on MySQL). This test proves the IF-fence actually PRESERVES the old
23
+ * tip on a lower fencing token against a real MySQL server (the
24
+ * security property), and documents that the RETURNING-0-rows
25
+ * fenced-out signal consent.js depends on does NOT exist on MySQL.
26
+ * - api-key: issue/verify/rotate/revoke/purge SQL shapes (INSERT /
27
+ * SELECT / the lastUsedAt UPDATE / graceful+cutover
28
+ * rotate UPDATE / the whereGroup-OR purge SELECT+DELETE)
29
+ * + BIGINT-as-string -> JS-number coercion via the
30
+ * framework's coerceRows on the real MySQL readback.
31
+ * - legal-hold: place INSERT / release DELETE / whereLike prefix history.
32
+ * - subject: the INSERT-OR-REPLACE upsert + restrict INSERT/DELETE.
33
+ * - retention: hard / soft / erase NULL-set / cascade DELETE / candidate.
34
+ * - scheduler: the tick-claim upsert `onConflict(tickKey).doNothing()`
35
+ * — on MySQL this folds to `ON DUPLICATE KEY UPDATE
36
+ * tickKey = tickKey`, so a duplicate tickKey (the split-
37
+ * brain replay) affects 0 rows = the loser skips, exactly
38
+ * the dedup signal `_fireOnce` reads — plus the prune
39
+ * DELETE and the BIGINT-as-string -> JS-number coercion of
40
+ * the claimed-at readback.
41
+ *
42
+ * Distinct from data-layer-mysql.test.js (cache / nonce / rate-limit
43
+ * cluster backends). Driver: a minimal docker-exec `mysql -e` shim (no
44
+ * shell parse of SQL beyond the -e arg). `?` placeholders bound inline
45
+ * (operator-controlled values only). Tables namespaced + DROP/recreated.
46
+ *
47
+ * RUN: node scripts/test-integration.js --skip-service-check data-layer-mysql-privacy
48
+ */
49
+
50
+ var execFileSync = require("node:child_process").execFileSync;
51
+ var fs = require("node:fs");
52
+ var os = require("node:os");
53
+ var path = require("node:path");
54
+ var helpers = require("../helpers");
55
+ var check = helpers.check;
56
+ var services = require("../helpers/services");
57
+ var cryptoField = require("../../lib/crypto-field");
58
+ var b = require("../../");
59
+
60
+ var CONTAINER = "blamejs-test-mysql";
61
+ var DB_NAME = "blamejs_test";
62
+
63
+ function _mysql(sql) {
64
+ var out;
65
+ try {
66
+ out = execFileSync("docker",
67
+ ["exec", "-i", CONTAINER, "mysql", "-uroot", "-pblamejs_test_root",
68
+ "--batch", "--raw", DB_NAME, "-e", sql],
69
+ { stdio: ["pipe", "pipe", "pipe"], maxBuffer: 16 * 1024 * 1024 }
70
+ ).toString("utf8");
71
+ } catch (e) {
72
+ var err = new Error(e.stderr ? e.stderr.toString("utf8") : (e.message || String(e)));
73
+ err.cause = e;
74
+ throw err;
75
+ }
76
+ return out;
77
+ }
78
+
79
+ // Inline `?` binding. Values are operator-controlled (ids / hashes /
80
+ // numbers / null). MySQL: backslash IS a string escape by default, so
81
+ // escape both backslash and single-quote.
82
+ function _bindParams(sql, params) {
83
+ var i = 0;
84
+ return sql.replace(/\?/g, function () {
85
+ if (i >= params.length) throw new Error("placeholder/param count mismatch");
86
+ var p = params[i++];
87
+ if (p === null || p === undefined) return "NULL";
88
+ if (typeof p === "number") return String(p);
89
+ if (typeof p === "boolean") return p ? "1" : "0";
90
+ if (Buffer.isBuffer(p)) return "x'" + p.toString("hex") + "'";
91
+ return "'" + String(p).replace(/\\/g, "\\\\").replace(/'/g, "''") + "'";
92
+ });
93
+ }
94
+
95
+ // Parse a --batch tab-separated result block. NULL prints as the literal
96
+ // "NULL"; numeric columns come back as STRINGS (faithful to a real driver
97
+ // returning BIGINT as a JS string), so the framework's coerceRows must
98
+ // turn them back to numbers.
99
+ function _parseBatch(out) {
100
+ var lines = out.split(/\r?\n/).filter(function (l) { return l.length > 0; });
101
+ if (lines.length < 1) return { rows: [] };
102
+ var headers = lines[0].split("\t");
103
+ var rows = [];
104
+ for (var i = 1; i < lines.length; i++) {
105
+ var cells = lines[i].split("\t");
106
+ var row = {};
107
+ for (var j = 0; j < headers.length; j++) {
108
+ var v = cells[j];
109
+ row[headers[j]] = (v === "NULL" || v === undefined) ? null : v;
110
+ }
111
+ rows.push(row);
112
+ }
113
+ return { rows: rows };
114
+ }
115
+
116
+ // A MySQL external-db driver: every query() shells `mysql -e`. Writes
117
+ // report ROW_COUNT() (read on the SAME exec/connection so the count is
118
+ // faithful); reads return parsed rows.
119
+ function _makeDockerMysqlDriver() {
120
+ return {
121
+ connect: async function () { return { id: 1 }; },
122
+ query: async function (_client, sql, params) {
123
+ params = params || [];
124
+ var bound = _bindParams(sql, params);
125
+ var t = bound.trim();
126
+ if (/^(SELECT|SHOW|WITH)/i.test(t)) {
127
+ var sel = _parseBatch(_mysql(bound));
128
+ return { rows: sel.rows, rowCount: sel.rows.length };
129
+ }
130
+ var combined = _mysql(bound + ";\nSELECT ROW_COUNT() AS `__rc`;");
131
+ var rc = _parseBatch(combined);
132
+ var n = (rc.rows[0] && rc.rows[0].__rc != null) ? Number(rc.rows[0].__rc) : 0;
133
+ if (!Number.isFinite(n) || n < 0) n = 0; // CREATE/etc return -1
134
+ return { rows: [], rowCount: n };
135
+ },
136
+ close: async function () { /* no-op */ },
137
+ dialect: "mysql",
138
+ };
139
+ }
140
+
141
+ function _resetState() {
142
+ try { b.cluster._resetForTest(); } catch (_e) {}
143
+ try { b.consent._resetForTest(); } catch (_e) {}
144
+ try { b.externalDb._resetForTest(); } catch (_e) {}
145
+ }
146
+
147
+ async function run() {
148
+ var mysqlSvc = await services.requireService("mysql");
149
+ if (!mysqlSvc.ok) throw new Error("mysql unreachable: " + mysqlSvc.reason);
150
+
151
+ var TABLES = [
152
+ "dl_myp_api_keys", "dl_myp_consent_tip",
153
+ "dl_myp_hold", "dl_myp_erasures", "dl_myp_restrictions",
154
+ "dl_myp_audit", "dl_myp_orders", "dl_myp_order_lines",
155
+ "dl_myp_sched_ticks",
156
+ ];
157
+ _mysql(TABLES.map(function (t) { return "DROP TABLE IF EXISTS `" + t + "`;"; }).join(" "));
158
+
159
+ _resetState();
160
+
161
+ // Vault + api-key cryptoField schema, so sealRow seals ownerId/scopes/
162
+ // metadata and derives ownerIdHash (db.js FRAMEWORK_SCHEMA wires this
163
+ // for the local path).
164
+ var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-dl-myp-"));
165
+ if (typeof b.vault._resetForTest === "function") b.vault._resetForTest();
166
+ await b.vault.init({ dataDir: dataDir, mode: "plaintext" });
167
+ cryptoField.registerTable("dl_myp_api_keys", {
168
+ sealedFields: ["ownerId", "scopes", "metadata"],
169
+ derivedHashes: { ownerIdHash: { from: "ownerId" } },
170
+ });
171
+
172
+ var driver = _makeDockerMysqlDriver();
173
+ b.externalDb.init({
174
+ backends: { ops: { connect: driver.connect, query: driver.query, close: driver.close, dialect: "mysql" } },
175
+ });
176
+
177
+ // b.sql emits `?`; MySQL keeps `?` (placeholderize is passthrough for
178
+ // mysql), so the driver binds inline — no translation needed.
179
+ function _q(built) {
180
+ return b.externalDb.query(built.sql, built.params, { backend: "ops" });
181
+ }
182
+
183
+ var nowMs = Date.now();
184
+
185
+ // ====================================================================
186
+ // api-key SQL shapes on MySQL. The api-key registry's clusterStorage
187
+ // dispatch only targets postgres/sqlite, so here we drive the SAME
188
+ // b.sql shapes the module emits (built at { dialect: "mysql" }) directly
189
+ // against MySQL — proving the migrated statements are valid MySQL and
190
+ // the coercion round-trips. We hand-seal the row with cryptoField the
191
+ // same way issue() does.
192
+ // ====================================================================
193
+ var apiKeysDdl = b.sql.createTable("dl_myp_api_keys", [
194
+ { name: "id", type: "text", primaryKey: true },
195
+ { name: "namespace", type: "text", notNull: true },
196
+ { name: "ownerId", type: "text", notNull: true },
197
+ { name: "ownerIdHash", type: "text", notNull: true },
198
+ { name: "secretHash", type: "text", notNull: true },
199
+ { name: "secondarySecretHash", type: "text" },
200
+ { name: "secondaryExpiresAt", type: "int" },
201
+ { name: "scopes", type: "text" },
202
+ { name: "metadata", type: "text" },
203
+ { name: "createdAt", type: "int", notNull: true },
204
+ { name: "expiresAt", type: "int" },
205
+ { name: "revokedAt", type: "int" },
206
+ { name: "lastUsedAt", type: "int" },
207
+ { name: "prefix", type: "text", notNull: true },
208
+ ], { dialect: "mysql", quoteName: true });
209
+ // MySQL needs a key length on a TEXT PRIMARY KEY. Patch the emitted DDL
210
+ // so `id` is a bounded VARCHAR PK (operator-app-schema concern, not a
211
+ // framework-shape concern — the framework only ships pg/sqlite DDL).
212
+ _mysql(apiKeysDdl.sql.replace("`id` TEXT PRIMARY KEY", "`id` VARCHAR(190) PRIMARY KEY"));
213
+ check("api-key: b.sql createTable DDL (MySQL backticks) ran on real MySQL", true);
214
+
215
+ var COLS = ["id", "namespace", "ownerId", "ownerIdHash", "secretHash",
216
+ "secondarySecretHash", "secondaryExpiresAt", "scopes", "metadata",
217
+ "createdAt", "expiresAt", "revokedAt", "lastUsedAt", "prefix"];
218
+
219
+ function _sealApiRow(plain) {
220
+ var sealed = cryptoField.sealRow("dl_myp_api_keys", plain);
221
+ for (var i = 0; i < COLS.length; i++) if (!(COLS[i] in sealed)) sealed[COLS[i]] = null;
222
+ return sealed;
223
+ }
224
+ function _insApiRow(plain) {
225
+ var sealed = _sealApiRow(plain);
226
+ var insertRow = {};
227
+ for (var ci = 0; ci < COLS.length; ci++) insertRow[COLS[ci]] = sealed[COLS[ci]];
228
+ return _q(b.sql.insert("dl_myp_api_keys", { dialect: "mysql", quoteName: true }).columns(COLS).values(insertRow).toSql());
229
+ }
230
+ var credentialHash = require("../../lib/credential-hash");
231
+ var bCrypto = require("../../lib/crypto");
232
+ var frameworkSchema = require("../../lib/framework-schema");
233
+
234
+ var idHex = bCrypto.generateToken(8);
235
+ var compositeId = "live:" + idHex;
236
+ var secretEnvelope = await credentialHash.hash(bCrypto.generateToken(16), { algo: "shake256" });
237
+ await _insApiRow({
238
+ id: compositeId, namespace: "live", ownerId: "owner-1",
239
+ secretHash: secretEnvelope, secondarySecretHash: null, secondaryExpiresAt: null,
240
+ scopes: JSON.stringify(["read:x"]), metadata: JSON.stringify({ name: "dev" }),
241
+ createdAt: nowMs, expiresAt: nowMs + b.constants.TIME.days(30),
242
+ revokedAt: null, lastUsedAt: null, prefix: "bk",
243
+ });
244
+ // ownerIdHash must be a real non-null derived hash (sealRow populated it).
245
+ var ownerHashRow = _parseBatch(_mysql("SELECT ownerIdHash FROM dl_myp_api_keys WHERE id = '" + compositeId + "';"));
246
+ check("api-key: sealRow populated a non-null derived ownerIdHash on MySQL",
247
+ ownerHashRow.rows.length === 1 && !!ownerHashRow.rows[0].ownerIdHash);
248
+
249
+ // SELECT-back + coercion: read the row through coerceRows so
250
+ // createdAt/expiresAt (TEXT-from-MySQL) become JS numbers.
251
+ var apiSel = await _q(b.sql.select("dl_myp_api_keys", { dialect: "mysql", quoteName: true })
252
+ .columns(COLS).where("id", compositeId).toSql());
253
+ check("api-key: SELECT round-trips the row on MySQL", apiSel.rows.length === 1);
254
+ var coercedApi = frameworkSchema.coerceRows(apiSel.rows);
255
+ check("api-key: coerceRows turns createdAt BIGINT-string -> JS number on MySQL",
256
+ typeof coercedApi[0].createdAt === "number" && coercedApi[0].createdAt === nowMs);
257
+ check("api-key: coerceRows turns expiresAt BIGINT-string -> JS number on MySQL",
258
+ typeof coercedApi[0].expiresAt === "number" &&
259
+ coercedApi[0].expiresAt === nowMs + b.constants.TIME.days(30));
260
+
261
+ // lastUsedAt UPDATE (verify's trackLastUsedAt touch).
262
+ var luRes = await _q(b.sql.update("dl_myp_api_keys", { dialect: "mysql", quoteName: true })
263
+ .set({ lastUsedAt: nowMs }).where("id", compositeId).toSql());
264
+ check("api-key: lastUsedAt UPDATE affected 1 row on MySQL", luRes.rowCount === 1);
265
+
266
+ // graceful rotate UPDATE — move secret to secondary slot with TTL.
267
+ var newHash = await credentialHash.hash(bCrypto.generateToken(16), { algo: "shake256" });
268
+ await _q(b.sql.update("dl_myp_api_keys", { dialect: "mysql", quoteName: true })
269
+ .set({ secretHash: newHash, secondarySecretHash: secretEnvelope, secondaryExpiresAt: nowMs + b.constants.TIME.days(7) })
270
+ .where("id", compositeId).toSql());
271
+ var graceRow = _parseBatch(_mysql("SELECT secondaryExpiresAt FROM dl_myp_api_keys WHERE id = '" + compositeId + "';"));
272
+ check("api-key: graceful rotate UPDATE persisted secondaryExpiresAt on MySQL",
273
+ graceRow.rows.length === 1 && /\d{6,}/.test(String(graceRow.rows[0].secondaryExpiresAt)));
274
+
275
+ // hard cutover UPDATE — clear the secondary slot.
276
+ await _q(b.sql.update("dl_myp_api_keys", { dialect: "mysql", quoteName: true })
277
+ .set({ secretHash: newHash, secondarySecretHash: null, secondaryExpiresAt: null })
278
+ .where("id", compositeId).toSql());
279
+ var cutRow = _parseBatch(_mysql("SELECT secondarySecretHash FROM dl_myp_api_keys WHERE id = '" + compositeId + "';"));
280
+ check("api-key: hard cutover NULLed secondarySecretHash on MySQL",
281
+ cutRow.rows.length === 1 && cutRow.rows[0].secondarySecretHash === null);
282
+
283
+ // revoke UPDATE (set revokedAt where revokedAt IS NULL).
284
+ var revRes = await _q(b.sql.update("dl_myp_api_keys", { dialect: "mysql", quoteName: true })
285
+ .set({ revokedAt: nowMs }).where("id", compositeId).whereNull("revokedAt").toSql());
286
+ check("api-key: revoke UPDATE (set where revokedAt IS NULL) affected 1 row on MySQL",
287
+ revRes.rowCount === 1);
288
+
289
+ // Seed an expired + a fresh key for the whereGroup-OR purge predicate.
290
+ await _insApiRow({
291
+ id: "live:purge-0", namespace: "live", ownerId: "owner-p0",
292
+ secretHash: await credentialHash.hash(bCrypto.generateToken(16), { algo: "shake256" }),
293
+ secondarySecretHash: null, secondaryExpiresAt: null, scopes: null, metadata: null,
294
+ createdAt: nowMs, expiresAt: 1, revokedAt: null, lastUsedAt: null, prefix: "bk", // expired
295
+ });
296
+ await _insApiRow({
297
+ id: "live:purge-1", namespace: "live", ownerId: "owner-p1",
298
+ secretHash: await credentialHash.hash(bCrypto.generateToken(16), { algo: "shake256" }),
299
+ secondarySecretHash: null, secondaryExpiresAt: null, scopes: null, metadata: null,
300
+ createdAt: nowMs, expiresAt: nowMs + b.constants.TIME.days(365), revokedAt: null, lastUsedAt: null, prefix: "bk", // fresh
301
+ });
302
+ var threshold = nowMs - b.constants.TIME.days(90);
303
+ function _purgeWhere(qb) {
304
+ return qb.where("namespace", "live").whereGroup(function (g) {
305
+ g.whereGroup(function (a) { a.whereNotNull("revokedAt").where("revokedAt", "<", threshold); })
306
+ .orWhereGroup(function (b2) { b2.whereNotNull("expiresAt").where("expiresAt", "<", threshold); });
307
+ });
308
+ }
309
+ var purgeSel = await _q(_purgeWhere(b.sql.select("dl_myp_api_keys", { dialect: "mysql", quoteName: true }).columns(["id"])).toSql());
310
+ // owner-1 (revokedAt=now, NOT < threshold), purge-0 (expiresAt=1 < threshold) → exactly 1.
311
+ check("api-key: whereGroup-OR purge SELECT found the 1 expired key on MySQL",
312
+ purgeSel.rows.length === 1 && purgeSel.rows[0].id === "live:purge-0");
313
+ var purgeDel = await _q(_purgeWhere(b.sql.delete("dl_myp_api_keys", { dialect: "mysql", quoteName: true })).toSql());
314
+ check("api-key: whereGroup-OR purge DELETE removed exactly 1 row on MySQL", purgeDel.rowCount === 1);
315
+
316
+ // ====================================================================
317
+ // consent fenced tip on MySQL — the headline MySQL-specific surface.
318
+ // The builder rewrites conflictWhere(fence) + RETURNING into per-column
319
+ // IF(stored.fencingToken <= VALUES(fencingToken), VALUES(col), col).
320
+ // Prove the IF-fence PRESERVES the old tip when a lower fencing token
321
+ // arrives, and document that the RETURNING 0-rows fenced-out signal
322
+ // consent.js uses does NOT exist here.
323
+ // ====================================================================
324
+ var consentTipDdl = b.sql.createTable("dl_myp_consent_tip", [
325
+ { name: "scope", type: "text", primaryKey: true },
326
+ { name: "atMonotonicCounter", type: "int", notNull: true },
327
+ { name: "rowHash", type: "text" },
328
+ { name: "signedAt", type: "text" },
329
+ { name: "fencingToken", type: "int", notNull: true },
330
+ ], { dialect: "mysql", quoteName: true });
331
+ _mysql(consentTipDdl.sql.replace("`scope` TEXT PRIMARY KEY", "`scope` VARCHAR(64) PRIMARY KEY"));
332
+
333
+ var safeSql = require("../../lib/safe-sql");
334
+ function _tipUpsert(counter, rowHash, signedAt, fencingToken) {
335
+ var tipFence = "dl_myp_consent_tip." + safeSql.quoteIdentifier("fencingToken", "mysql") +
336
+ " <= VALUES(" + safeSql.quoteIdentifier("fencingToken", "mysql") + ")";
337
+ return b.sql.upsert("dl_myp_consent_tip", { dialect: "mysql", quoteName: true })
338
+ .columns(["scope", "atMonotonicCounter", "rowHash", "signedAt", "fencingToken"])
339
+ .values({ scope: "consent", atMonotonicCounter: counter, rowHash: rowHash, signedAt: signedAt, fencingToken: fencingToken })
340
+ .onConflict(["scope"])
341
+ .doUpdateFromExcluded(["atMonotonicCounter", "rowHash", "signedAt", "fencingToken"])
342
+ .conflictWhere(tipFence, [])
343
+ .toSql();
344
+ }
345
+ // Confirm the builder produced the MySQL IF()-fence rewrite (no WHERE,
346
+ // no RETURNING — MySQL upsert has neither).
347
+ var tipSqlText = _tipUpsert(1, "h", "s", 1).sql;
348
+ check("consent-tip: builder rewrote the fence into MySQL IF() conditional updates",
349
+ /ON DUPLICATE KEY UPDATE/.test(tipSqlText) &&
350
+ /IF\(dl_myp_consent_tip\.`fencingToken` <= VALUES\(`fencingToken`\)/.test(tipSqlText) &&
351
+ !/RETURNING/.test(tipSqlText));
352
+
353
+ await _q(_tipUpsert(1, "hash-1", "1700000000001", 1)); // initial
354
+ await _q(_tipUpsert(2, "hash-2", "1700000000002", 2)); // higher token — advances
355
+ var tipAfterHigher = _parseBatch(_mysql("SELECT atMonotonicCounter, rowHash, fencingToken FROM dl_myp_consent_tip WHERE scope='consent';"));
356
+ check("consent-tip: higher fencingToken advanced the tip to counter 2 on MySQL",
357
+ tipAfterHigher.rows[0].atMonotonicCounter === "2" && tipAfterHigher.rows[0].rowHash === "hash-2");
358
+
359
+ // LOWER token (1) with a would-be newer counter/hash — the IF-fence must
360
+ // keep the OLD values (stored token 2 <= incoming 1 is FALSE).
361
+ await _q(_tipUpsert(99, "hash-evil", "1700000000099", 1));
362
+ var tipAfterFence = _parseBatch(_mysql("SELECT atMonotonicCounter, rowHash, fencingToken FROM dl_myp_consent_tip WHERE scope='consent';"));
363
+ check("consent-tip: MySQL IF-fence PRESERVED the tip against a lower fencing token " +
364
+ "(no hash-evil, still counter 2)",
365
+ tipAfterFence.rows[0].rowHash === "hash-2" &&
366
+ tipAfterFence.rows[0].atMonotonicCounter === "2" &&
367
+ tipAfterFence.rows[0].fencingToken === "2");
368
+
369
+ // EQUAL token (2) — the <= fence accepts; the tip advances.
370
+ await _q(_tipUpsert(3, "hash-3", "1700000000003", 2));
371
+ var tipAfterEqual = _parseBatch(_mysql("SELECT atMonotonicCounter, rowHash FROM dl_myp_consent_tip WHERE scope='consent';"));
372
+ check("consent-tip: equal fencingToken accepted by the <= IF-fence on MySQL (advanced to hash-3)",
373
+ tipAfterEqual.rows[0].rowHash === "hash-3" && tipAfterEqual.rows[0].atMonotonicCounter === "3");
374
+
375
+ // ====================================================================
376
+ // legal-hold — place INSERT / release DELETE / whereLike prefix history.
377
+ // ====================================================================
378
+ var holdDdl = b.sql.createTable("dl_myp_hold", [
379
+ { name: "subjectIdHash", type: "text", primaryKey: true },
380
+ { name: "placedAt", type: "int", notNull: true },
381
+ { name: "placedBy", type: "text" },
382
+ { name: "reason", type: "text", notNull: true },
383
+ { name: "custodian", type: "text" },
384
+ { name: "citation", type: "text" },
385
+ { name: "retainUntil", type: "int" },
386
+ ], { dialect: "mysql", quoteName: true });
387
+ _mysql(holdDdl.sql.replace("`subjectIdHash` TEXT PRIMARY KEY", "`subjectIdHash` VARCHAR(190) PRIMARY KEY"));
388
+ var hash = b.crypto.sha3Hash("bj-legal-hold:subject-42");
389
+ await _q(b.sql.insert("dl_myp_hold", { dialect: "mysql", quoteName: true })
390
+ .values({ subjectIdHash: hash, placedAt: nowMs, placedBy: "legal@x", reason: "SEC subpoena", custodian: "c@x", citation: "SEC-Rule-17a-4", retainUntil: null })
391
+ .toSql());
392
+ var holdSel = await _q(b.sql.select("dl_myp_hold", { dialect: "mysql", quoteName: true }).columns(["placedAt"]).where("subjectIdHash", hash).toSql());
393
+ check("legal-hold: place INSERT + existence SELECT round-trip on MySQL",
394
+ holdSel.rows.length === 1 && Number(holdSel.rows[0].placedAt) === nowMs);
395
+ var holdDel = await _q(b.sql.delete("dl_myp_hold", { dialect: "mysql", quoteName: true }).where("subjectIdHash", hash).toSql());
396
+ check("legal-hold: release DELETE affected 1 row on MySQL", holdDel.rowCount === 1);
397
+
398
+ var auditDdl = b.sql.createTable("dl_myp_audit", [
399
+ { name: "recordedAt", type: "int", notNull: true },
400
+ { name: "action", type: "text", notNull: true },
401
+ { name: "metadata", type: "text" },
402
+ { name: "outcome", type: "text" },
403
+ { name: "resourceKind", type: "text" },
404
+ ], { dialect: "mysql", quoteName: true });
405
+ _mysql(auditDdl.sql);
406
+ var seedRows = [
407
+ [1, "legalhold.placed", "{}", "success", "legal-hold"],
408
+ [2, "legalhold.released", "{}", "success", "legal-hold"],
409
+ [3, "auth.legalhold.x", "{}", "success", "legal-hold"], // NOT a prefix match
410
+ [4, "legalhold.100%done", "{}", "success", "legal-hold"], // literal % — must stay literal
411
+ ];
412
+ for (var sr = 0; sr < seedRows.length; sr++) {
413
+ await _q(b.sql.insert("dl_myp_audit", { dialect: "mysql", quoteName: true })
414
+ .values({ recordedAt: seedRows[sr][0], action: seedRows[sr][1], metadata: seedRows[sr][2], outcome: seedRows[sr][3], resourceKind: seedRows[sr][4] })
415
+ .toSql());
416
+ }
417
+ var hist = await _q(b.sql.select("dl_myp_audit", { dialect: "mysql", quoteName: true })
418
+ .columns(["recordedAt", "action"])
419
+ .whereLike("action", "legalhold.", "prefix")
420
+ .where("resourceKind", "legal-hold")
421
+ .orderBy("recordedAt", "asc")
422
+ .toSql());
423
+ check("legal-hold: whereLike prefix selects exactly the legalhold.* rows on MySQL " +
424
+ "(placed/released/100%done, NOT auth.legalhold.x)",
425
+ hist.rows.length === 3 &&
426
+ hist.rows.every(function (r) { return r.action.indexOf("legalhold.") === 0; }));
427
+ var esc = await _q(b.sql.select("dl_myp_audit", { dialect: "mysql", quoteName: true })
428
+ .columns(["action"]).whereLike("action", "legalhold.100%", "prefix").toSql());
429
+ check("legal-hold: whereLike escapes a literal % in the term on MySQL (matches only the literal row)",
430
+ esc.rows.length === 1 && esc.rows[0].action === "legalhold.100%done");
431
+
432
+ // ====================================================================
433
+ // subject — INSERT-OR-REPLACE upsert (_markErased) + restrict INSERT/DELETE.
434
+ // ====================================================================
435
+ var erasuresDdl = b.sql.createTable("dl_myp_erasures", [
436
+ { name: "subjectIdHash", type: "text", primaryKey: true },
437
+ { name: "erasedAt", type: "int", notNull: true },
438
+ ], { dialect: "mysql", quoteName: true });
439
+ _mysql(erasuresDdl.sql.replace("`subjectIdHash` TEXT PRIMARY KEY", "`subjectIdHash` VARCHAR(190) PRIMARY KEY"));
440
+ function _markErased(subjectHash, erasedAt) {
441
+ return b.sql.upsert("dl_myp_erasures", { dialect: "mysql", quoteName: true })
442
+ .values({ subjectIdHash: subjectHash, erasedAt: erasedAt })
443
+ .onConflict(["subjectIdHash"])
444
+ .doUpdateFromExcluded(["erasedAt"])
445
+ .toSql();
446
+ }
447
+ var shash = b.crypto.sha3Hash("bj-subject:user-99");
448
+ await _q(_markErased(shash, 1700000000000));
449
+ await _q(_markErased(shash, 1700000009999)); // re-erase refreshes timestamp, no dup-key
450
+ var erasureState = _parseBatch(_mysql("SELECT COUNT(*) AS c, MAX(erasedAt) AS m FROM dl_myp_erasures;"));
451
+ check("subject: INSERT-OR-REPLACE upsert kept ONE row (no dup-key) on MySQL",
452
+ erasureState.rows[0].c === "1");
453
+ check("subject: INSERT-OR-REPLACE refreshed erasedAt to the newest value on MySQL",
454
+ erasureState.rows[0].m === "1700000009999");
455
+
456
+ var restrictDdl = b.sql.createTable("dl_myp_restrictions", [
457
+ { name: "subjectIdHash", type: "text", primaryKey: true },
458
+ { name: "since", type: "int", notNull: true },
459
+ { name: "reason", type: "text" },
460
+ ], { dialect: "mysql", quoteName: true });
461
+ _mysql(restrictDdl.sql.replace("`subjectIdHash` TEXT PRIMARY KEY", "`subjectIdHash` VARCHAR(190) PRIMARY KEY"));
462
+ await _q(b.sql.insert("dl_myp_restrictions", { dialect: "mysql", quoteName: true })
463
+ .values({ subjectIdHash: shash, since: nowMs, reason: "art-18 hold" }).toSql());
464
+ var rPres = await _q(b.sql.select("dl_myp_restrictions", { dialect: "mysql", quoteName: true })
465
+ .columns(["subjectIdHash"]).where("subjectIdHash", shash).limit(1).toSql());
466
+ check("subject: restrict INSERT + presence SELECT round-trip on MySQL", rPres.rows.length === 1);
467
+ var rDel = await _q(b.sql.delete("dl_myp_restrictions", { dialect: "mysql", quoteName: true })
468
+ .where("subjectIdHash", shash).toSql());
469
+ check("subject: restrict DELETE (lift) affected 1 row on MySQL", rDel.rowCount === 1);
470
+
471
+ // ====================================================================
472
+ // retention — hard / soft / erase NULL-set / cascade / candidate WHERE.
473
+ // __erasedAt is TEXT (the `= ''` sentinel column shape).
474
+ // ====================================================================
475
+ var ordersDdl = b.sql.createTable("dl_myp_orders", [
476
+ { name: "_id", type: "text", primaryKey: true },
477
+ { name: "createdAt", type: "int", notNull: true },
478
+ { name: "secretCol", type: "text" },
479
+ { name: "secretColHash", type: "text" },
480
+ { name: "softAt", type: "int" },
481
+ { name: "__erasedAt", type: "text" },
482
+ ], { dialect: "mysql", quoteName: true });
483
+ _mysql(ordersDdl.sql.replace("`_id` TEXT PRIMARY KEY", "`_id` VARCHAR(190) PRIMARY KEY"));
484
+ var linesDdl = b.sql.createTable("dl_myp_order_lines", [
485
+ { name: "_id", type: "text", primaryKey: true },
486
+ { name: "orderId", type: "text", notNull: true },
487
+ ], { dialect: "mysql", quoteName: true });
488
+ _mysql(linesDdl.sql.replace("`_id` TEXT PRIMARY KEY", "`_id` VARCHAR(190) PRIMARY KEY"));
489
+
490
+ var oldAt = nowMs - b.constants.TIME.days(400);
491
+ for (var oi = 1; oi <= 4; oi++) {
492
+ var oid = "o-" + oi;
493
+ await _q(b.sql.insert("dl_myp_orders", { dialect: "mysql", quoteName: true })
494
+ .values({ _id: oid, createdAt: oldAt, secretCol: "secret-" + oi, secretColHash: "h-" + oi, softAt: null, __erasedAt: null })
495
+ .toSql());
496
+ await _q(b.sql.insert("dl_myp_order_lines", { dialect: "mysql", quoteName: true })
497
+ .values({ _id: "l-" + oi, orderId: oid }).toSql());
498
+ }
499
+ var cutoff = nowMs - b.constants.TIME.days(365);
500
+ var cand = await _q(b.sql.select("dl_myp_orders", { dialect: "mysql", quoteName: true })
501
+ .where("createdAt", "<=", cutoff)
502
+ .whereNull("softAt")
503
+ .whereGroup(function (g) { g.whereNull("__erasedAt").orWhereOp("__erasedAt", "=", ""); })
504
+ .limit(500)
505
+ .toSql());
506
+ check("retention: candidate whereGroup-OR WHERE selects the 4 aged rows on MySQL",
507
+ cand.rows.length === 4);
508
+ var softRes = await _q(b.sql.update("dl_myp_orders", { dialect: "mysql", quoteName: true })
509
+ .set("softAt", nowMs).where("_id", "o-1").toSql());
510
+ check("retention: soft-delete UPDATE set softAt on MySQL", softRes.rowCount === 1);
511
+ await _q(b.sql.update("dl_myp_orders", { dialect: "mysql", quoteName: true })
512
+ .set({ secretCol: null, secretColHash: null }).where("_id", "o-2").toSql());
513
+ var erasedRow = _parseBatch(_mysql("SELECT secretCol, secretColHash FROM dl_myp_orders WHERE _id = 'o-2';"));
514
+ check("retention: erase NULL-set wiped the sealed col + derived hash on MySQL",
515
+ erasedRow.rows[0].secretCol === null && erasedRow.rows[0].secretColHash === null);
516
+ var hardRes = await _q(b.sql.delete("dl_myp_orders", { dialect: "mysql", quoteName: true }).where("_id", "o-3").toSql());
517
+ check("retention: hard delete removed o-3 on MySQL", hardRes.rowCount === 1);
518
+ var cascRes = await _q(b.sql.delete("dl_myp_order_lines", { dialect: "mysql", quoteName: true }).where("orderId", "o-3").toSql());
519
+ check("retention: cascade DELETE removed o-3's order_lines on MySQL", cascRes.rowCount === 1);
520
+ var cnt = await _q(b.sql.select("dl_myp_order_lines", { dialect: "mysql", quoteName: true })
521
+ .count("*", "n").where("orderId", "o-4").toSql());
522
+ check("retention: cascade dry-run COUNT(*) returns 1 for o-4's lines on MySQL",
523
+ cnt.rows.length === 1 && Number(cnt.rows[0].n) === 1);
524
+
525
+ // ====================================================================
526
+ // scheduler — the cluster tick-claim. _fireOnce builds
527
+ // sql.upsert("_blamejs_scheduler_ticks", { dialect })
528
+ // .columns([tickKey, name, scheduledAtUnix, claimedAtUnix, claimedBy])
529
+ // .values({...}).onConflict(["tickKey"]).doNothing()
530
+ // and reads result.rowCount: a fresh tickKey wins (rowCount 1 = fire),
531
+ // a duplicate tickKey loses (rowCount 0 = skip, task.tickClaimLost++).
532
+ // On MySQL doNothing() folds to `ON DUPLICATE KEY UPDATE tickKey =
533
+ // tickKey`, so the PRIMARY KEY on `tickKey` makes the first INSERT land
534
+ // (ROW_COUNT 1) and the split-brain replay a no-op (ROW_COUNT 0) — the
535
+ // dedup is the DB's unique constraint, the security property a real
536
+ // server must enforce. The prune is delete().where("scheduledAtUnix",
537
+ // "<", threshold). claimedAtUnix is a BIGINT read back + coerced.
538
+ // ====================================================================
539
+ var schedTicksDdl = b.sql.createTable("dl_myp_sched_ticks", [
540
+ { name: "tickKey", type: "text", primaryKey: true },
541
+ { name: "name", type: "text", notNull: true },
542
+ { name: "scheduledAtUnix", type: "int", notNull: true },
543
+ { name: "claimedAtUnix", type: "int", notNull: true },
544
+ { name: "claimedBy", type: "text" },
545
+ ], { dialect: "mysql", quoteName: true });
546
+ _mysql(schedTicksDdl.sql.replace("`tickKey` TEXT PRIMARY KEY", "`tickKey` VARCHAR(190) PRIMARY KEY"));
547
+
548
+ var SCHED_COLS = ["tickKey", "name", "scheduledAtUnix", "claimedAtUnix", "claimedBy"];
549
+ function _claimTick(tickKey, name, scheduledAtUnix, claimedAtUnix, claimedBy) {
550
+ return b.sql.upsert("dl_myp_sched_ticks", { dialect: "mysql", quoteName: true })
551
+ .columns(SCHED_COLS)
552
+ .values({
553
+ tickKey: tickKey,
554
+ name: name,
555
+ scheduledAtUnix: scheduledAtUnix,
556
+ claimedAtUnix: claimedAtUnix,
557
+ claimedBy: claimedBy,
558
+ })
559
+ .onConflict(["tickKey"])
560
+ .doNothing()
561
+ .toSql();
562
+ }
563
+ // Confirm the builder emitted the MySQL no-op fold (no ON CONFLICT, no
564
+ // RETURNING — the doNothing() rewrite to `ON DUPLICATE KEY UPDATE
565
+ // tickKey = tickKey`).
566
+ var claimSqlText = _claimTick("rollup:1", "rollup", 1, 2, "node-A").sql;
567
+ check("scheduler: doNothing() emitted the MySQL ON DUPLICATE KEY UPDATE no-op fold",
568
+ /ON DUPLICATE KEY UPDATE `tickKey` = `tickKey`/.test(claimSqlText) &&
569
+ !/ON CONFLICT/.test(claimSqlText) && !/RETURNING/.test(claimSqlText));
570
+
571
+ var nominal = nowMs + b.constants.TIME.minutes(1);
572
+ var tickKey = "rollup:" + nominal;
573
+ // node-A claims the tick first — fresh tickKey, ROW_COUNT 1 (won).
574
+ var claimA = await _q(_claimTick(tickKey, "rollup", nominal, nowMs, "node-A"));
575
+ check("scheduler: first tick-claim INSERT won (rowCount 1 = the leader fires) on MySQL",
576
+ claimA.rowCount === 1);
577
+ // node-B races the SAME nominal tick (split-brain) — duplicate tickKey,
578
+ // the DUPLICATE-KEY no-op fold affects 0 rows (lost the claim, skips).
579
+ var claimB = await _q(_claimTick(tickKey, "rollup", nominal, nowMs + 5, "node-B"));
580
+ check("scheduler: racing tick-claim on the SAME tickKey lost (rowCount 0 = loser skips) on MySQL",
581
+ claimB.rowCount === 0);
582
+ // The DB holds exactly one tick row for the shared key — the PRIMARY KEY
583
+ // collapsed the racing INSERTs to one. claimedBy is still node-A's (the
584
+ // no-op fold left the winner's row untouched).
585
+ var tickRow = _parseBatch(_mysql(
586
+ "SELECT claimedBy, claimedAtUnix FROM dl_myp_sched_ticks WHERE tickKey = '" + tickKey + "';"));
587
+ check("scheduler: real MySQL holds exactly ONE tick row for the shared key, claimedBy=node-A",
588
+ tickRow.rows.length === 1 && tickRow.rows[0].claimedBy === "node-A");
589
+ // claimedAtUnix is a BIGINT column — coerceRows turns the string the
590
+ // driver returns back into a JS number (the same path the readback uses).
591
+ var coercedTick = frameworkSchema.coerceRows(tickRow.rows);
592
+ check("scheduler: coerceRows turns claimedAtUnix BIGINT-string -> JS number on MySQL",
593
+ typeof coercedTick[0].claimedAtUnix === "number" && coercedTick[0].claimedAtUnix === nowMs);
594
+
595
+ // A DISTINCT nominal tick is independently claimable (per-tickKey dedup,
596
+ // not a one-shot table lock).
597
+ var nominal2 = nominal + b.constants.TIME.minutes(1);
598
+ var claim2 = await _q(_claimTick("rollup:" + nominal2, "rollup", nominal2, nowMs + 9, "node-A"));
599
+ check("scheduler: a distinct second tick is independently claimable (rowCount 1) on MySQL",
600
+ claim2.rowCount === 1);
601
+
602
+ // pruneTickClaims: delete().where("scheduledAtUnix", "<", threshold).
603
+ // Seed an old tick + keep the two fresh ones; prune below a cutoff and
604
+ // assert only the aged row is removed.
605
+ await _q(_claimTick("rollup:" + (nominal - b.constants.TIME.days(30)), "rollup",
606
+ nominal - b.constants.TIME.days(30), nowMs, "node-A"));
607
+ var pruneThreshold = nominal - b.constants.TIME.days(7);
608
+ var pruneDel = await _q(b.sql.delete("dl_myp_sched_ticks", { dialect: "mysql", quoteName: true })
609
+ .where("scheduledAtUnix", "<", pruneThreshold)
610
+ .toSql());
611
+ check("scheduler: pruneTickClaims DELETE removed exactly the aged tick row on MySQL",
612
+ pruneDel.rowCount === 1);
613
+ var remaining = _parseBatch(_mysql("SELECT COUNT(*) AS n FROM dl_myp_sched_ticks;"));
614
+ check("scheduler: prune left the two un-aged tick rows intact on MySQL",
615
+ Number(remaining.rows[0].n) === 2);
616
+
617
+ // ---- teardown ----
618
+ await b.externalDb.shutdown();
619
+ _resetState();
620
+ _mysql(TABLES.map(function (t) { return "DROP TABLE IF EXISTS `" + t + "`;"; }).join(" "));
621
+ }
622
+
623
+ module.exports = { run: run };
624
+
625
+ if (require.main === module) {
626
+ run().then(
627
+ function () { console.log("OK — " + helpers.getChecks() + " checks passed"); process.exit(0); },
628
+ function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
629
+ );
630
+ }