@blamejs/blamejs-shop 0.4.31 → 0.4.33

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 (343) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -1
  3. package/lib/asset-manifest.json +1 -1
  4. package/lib/vendor/MANIFEST.json +400 -282
  5. package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
  6. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
  7. package/lib/vendor/blamejs/.gitignore +6 -0
  8. package/lib/vendor/blamejs/CHANGELOG.md +28 -0
  9. package/lib/vendor/blamejs/MIGRATING.md +55 -0
  10. package/lib/vendor/blamejs/README.md +8 -6
  11. package/lib/vendor/blamejs/SECURITY.md +19 -3
  12. package/lib/vendor/blamejs/api-snapshot.json +2190 -664
  13. package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
  14. package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
  15. package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
  16. package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
  17. package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
  18. package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
  19. package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
  20. package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
  21. package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
  22. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
  23. package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
  24. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
  25. package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
  26. package/lib/vendor/blamejs/index.js +4 -0
  27. package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
  28. package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
  29. package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
  30. package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
  31. package/lib/vendor/blamejs/lib/api-key.js +158 -77
  32. package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
  33. package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
  34. package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
  35. package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
  36. package/lib/vendor/blamejs/lib/audit.js +259 -123
  37. package/lib/vendor/blamejs/lib/auth/elevation-grant.js +6 -2
  38. package/lib/vendor/blamejs/lib/auth/oauth.js +66 -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 +36 -7
  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 +210 -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/credential-hash.js +9 -0
  55. package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
  56. package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
  57. package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
  58. package/lib/vendor/blamejs/lib/db-query.js +882 -260
  59. package/lib/vendor/blamejs/lib/db-schema.js +228 -44
  60. package/lib/vendor/blamejs/lib/db.js +249 -99
  61. package/lib/vendor/blamejs/lib/dsr.js +385 -55
  62. package/lib/vendor/blamejs/lib/error-page.js +14 -1
  63. package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
  64. package/lib/vendor/blamejs/lib/external-db.js +549 -34
  65. package/lib/vendor/blamejs/lib/file-upload.js +52 -7
  66. package/lib/vendor/blamejs/lib/framework-error.js +20 -1
  67. package/lib/vendor/blamejs/lib/framework-files.js +73 -0
  68. package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
  69. package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
  70. package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
  71. package/lib/vendor/blamejs/lib/guard-all.js +1 -0
  72. package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
  73. package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
  74. package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
  75. package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
  76. package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
  77. package/lib/vendor/blamejs/lib/guard-email.js +47 -69
  78. package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
  79. package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
  80. package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
  81. package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
  82. package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
  83. package/lib/vendor/blamejs/lib/guard-html.js +53 -108
  84. package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
  85. package/lib/vendor/blamejs/lib/guard-image.js +46 -103
  86. package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
  87. package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
  88. package/lib/vendor/blamejs/lib/guard-json.js +38 -108
  89. package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
  90. package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
  91. package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
  92. package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
  93. package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
  94. package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
  95. package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
  96. package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
  97. package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
  98. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
  99. package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
  100. package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
  101. package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
  102. package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
  103. package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
  104. package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
  105. package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
  106. package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
  107. package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
  108. package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
  109. package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
  110. package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
  111. package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
  112. package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
  113. package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
  114. package/lib/vendor/blamejs/lib/guard-template.js +35 -172
  115. package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
  116. package/lib/vendor/blamejs/lib/guard-time.js +32 -154
  117. package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
  118. package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
  119. package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
  120. package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
  121. package/lib/vendor/blamejs/lib/http-client.js +37 -9
  122. package/lib/vendor/blamejs/lib/inbox.js +120 -107
  123. package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
  124. package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
  125. package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
  126. package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
  127. package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
  128. package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
  129. package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
  130. package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
  131. package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
  132. package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
  133. package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
  134. package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
  135. package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
  136. package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
  137. package/lib/vendor/blamejs/lib/mail-store.js +293 -154
  138. package/lib/vendor/blamejs/lib/mail.js +8 -4
  139. package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
  140. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
  141. package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
  142. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
  143. package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
  144. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
  145. package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
  146. package/lib/vendor/blamejs/lib/migrations.js +108 -66
  147. package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
  148. package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
  149. package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
  150. package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
  151. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
  152. package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
  153. package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
  154. package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
  155. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
  156. package/lib/vendor/blamejs/lib/observability.js +124 -0
  157. package/lib/vendor/blamejs/lib/otel-export.js +12 -3
  158. package/lib/vendor/blamejs/lib/outbox.js +184 -83
  159. package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
  160. package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
  161. package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
  162. package/lib/vendor/blamejs/lib/queue-local.js +225 -140
  163. package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
  164. package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
  165. package/lib/vendor/blamejs/lib/queue.js +7 -0
  166. package/lib/vendor/blamejs/lib/redact.js +68 -11
  167. package/lib/vendor/blamejs/lib/redis-client.js +160 -31
  168. package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
  169. package/lib/vendor/blamejs/lib/retention.js +117 -42
  170. package/lib/vendor/blamejs/lib/router.js +212 -5
  171. package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
  172. package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
  173. package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
  174. package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
  175. package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
  176. package/lib/vendor/blamejs/lib/safe-url.js +170 -3
  177. package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
  178. package/lib/vendor/blamejs/lib/scheduler.js +47 -12
  179. package/lib/vendor/blamejs/lib/seeders.js +122 -74
  180. package/lib/vendor/blamejs/lib/session-stores.js +42 -14
  181. package/lib/vendor/blamejs/lib/session.js +175 -77
  182. package/lib/vendor/blamejs/lib/sql.js +3842 -0
  183. package/lib/vendor/blamejs/lib/sse.js +26 -0
  184. package/lib/vendor/blamejs/lib/ssrf-guard.js +169 -4
  185. package/lib/vendor/blamejs/lib/static.js +177 -34
  186. package/lib/vendor/blamejs/lib/subject.js +96 -49
  187. package/lib/vendor/blamejs/lib/vault/index.js +3 -2
  188. package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
  189. package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
  190. package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
  191. package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
  192. package/lib/vendor/blamejs/lib/websocket.js +35 -5
  193. package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
  194. package/lib/vendor/blamejs/package.json +2 -2
  195. package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
  196. package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
  197. package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
  198. package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
  199. package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
  200. package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
  201. package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
  202. package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
  203. package/lib/vendor/blamejs/release-notes/v0.15.7.json +43 -0
  204. package/lib/vendor/blamejs/scripts/check-services.js +21 -0
  205. package/lib/vendor/blamejs/scripts/gen-migrating.js +67 -0
  206. package/lib/vendor/blamejs/scripts/release.js +398 -38
  207. package/lib/vendor/blamejs/test/00-primitives.js +168 -0
  208. package/lib/vendor/blamejs/test/10-state.js +140 -14
  209. package/lib/vendor/blamejs/test/20-db.js +65 -2
  210. package/lib/vendor/blamejs/test/helpers/db.js +9 -0
  211. package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
  212. package/lib/vendor/blamejs/test/helpers/services.js +21 -0
  213. package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
  214. package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
  215. package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
  216. package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
  217. package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
  218. package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
  219. package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
  220. package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
  221. package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
  222. package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
  223. package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
  224. package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
  225. package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
  226. package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
  227. package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
  228. package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
  229. package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
  230. package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
  231. package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
  232. package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
  233. package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
  234. package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
  235. package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
  236. package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
  237. package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
  238. package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
  239. package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
  240. package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
  241. package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
  242. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
  243. package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
  244. package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
  245. package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
  246. package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
  247. package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
  248. package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
  249. package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
  250. package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
  251. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
  252. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1196 -14
  253. package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
  254. package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +18 -0
  255. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
  256. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
  257. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
  258. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
  259. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
  260. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
  261. package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
  262. package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
  263. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
  264. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
  265. package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
  266. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
  267. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
  268. package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
  269. package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
  270. package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
  271. package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
  272. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
  273. package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
  274. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
  275. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
  276. package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
  277. package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
  278. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
  279. package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
  280. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
  281. package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
  282. package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
  283. package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
  284. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
  285. package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
  286. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
  287. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
  288. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
  289. package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
  290. package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
  291. package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
  292. package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
  293. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
  294. package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
  295. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
  296. package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
  297. package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
  298. package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
  299. package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
  300. package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +59 -0
  301. package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
  302. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +362 -0
  303. package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
  304. package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
  305. package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-watchdog-stale-settle.test.js +71 -0
  306. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
  307. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
  308. package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
  309. package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
  310. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
  311. package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
  312. package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
  313. package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
  314. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
  315. package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
  316. package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
  317. package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
  318. package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
  319. package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
  320. package/lib/vendor/blamejs/test/smoke.js +79 -21
  321. package/package.json +2 -2
  322. package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
  323. package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
  324. package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
  325. package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
  326. package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
  327. package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
  328. package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
  329. package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
  330. package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
  331. package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
  332. package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
  333. package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
  334. package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
  335. package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
  336. package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
  337. package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
  338. package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
  339. package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
  340. package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
  341. package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
  342. package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
  343. package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
@@ -52,44 +52,303 @@ var log = boot("external-db");
52
52
  var audit = lazyRequire(function () { return require("./audit"); });
53
53
  var db = lazyRequire(function () { return require("./db"); });
54
54
  var observability = lazyRequire(function () { return require("./observability"); });
55
+ // b.sql composes the framework's own internal queries against a backend
56
+ // (e.g. the pg_roles hardening scan). Lazy because b.sql -> framework-schema
57
+ // -> external-db would cycle at module load; resolved when a query runs.
58
+ var sql = lazyRequire(function () { return require("./sql"); });
55
59
 
56
60
  function _emitMetric(name, value, labels) {
57
61
  try { observability().event(name, value, labels || {}); }
58
62
  catch (_e) { /* hot-path observability sink — drop silent by design */ }
59
63
  }
60
64
 
61
- // Statement-class classifier for auth-failure forensics. Inspects
62
- // the leading keyword only so an attacker-controlled trailing fragment
63
- // can't smuggle a false classification. Skips leading whitespace plus
64
- // SQL line / block comments before reading the keyword.
65
- // Linear (non-backtracking) comment/whitespace skip: each iteration of
66
- // the outer group consumes exactly one whitespace char, one complete
67
- // block comment (matched with the star-not-slash form, never a lazy
68
- // `[\s\S]*?`), or one complete line comment disjoint by first char,
69
- // so there is no ambiguous repetition for a crafted SQL string of
70
- // nested `/**/` or `*/--` runs to backtrack on (CWE-1333 ReDoS).
65
+ // Statement-class classifier for auth-failure forensics AND the
66
+ // residency write gate. Reads the leading keyword but a leading
67
+ // keyword is not the whole story: `WITH ... INSERT`, `EXPLAIN ANALYZE
68
+ // INSERT`, `CALL`/`EXECUTE`/`DO`, `COPY ... FROM`, and `REPLACE` all
69
+ // place rows while their leading (or only) keyword reads as harmless.
70
+ // _classifyStatement unwraps WITH (CTE) and EXPLAIN [ANALYZE] prefixes
71
+ // to the effective verb so the gate sees the real statement class; an
72
+ // attacker-controlled trailing fragment still can't smuggle a false
73
+ // class because the multi-statement form is refused upstream
74
+ // (_hasTrailingStatement) and an unresolvable prefix classifies
75
+ // UNKNOWN (fail-closed on the gate's enforced path).
76
+ //
77
+ // Skips leading whitespace plus SQL line / block comments before
78
+ // reading the keyword. Linear (non-backtracking) comment/whitespace
79
+ // skip: each iteration of the outer group consumes exactly one
80
+ // whitespace char, one complete block comment (star-not-slash form,
81
+ // never a lazy `[\s\S]*?`), or one complete line comment — disjoint by
82
+ // first char, so a crafted SQL string of nested `/**/` or `*/--` runs
83
+ // cannot backtrack polynomially (CWE-1333 ReDoS).
71
84
  var _STATEMENT_CLASS_RE = /^(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/|--[^\n]*\n)*([A-Za-z]+)/;
72
85
  var _STATEMENT_CLASS_MAP = Object.freeze({
73
- SELECT: "SELECT", WITH: "SELECT", VALUES: "SELECT", TABLE: "SELECT",
74
- INSERT: "DML", UPDATE: "DML", DELETE: "DML", MERGE: "DML", UPSERT: "DML",
86
+ SELECT: "SELECT", VALUES: "SELECT", TABLE: "SELECT", // allow:hand-rolled-sql — leading-keyword classifier table, not composed SQL
87
+ SHOW: "READ_INFO", DESCRIBE: "READ_INFO", DESC: "READ_INFO",
88
+ PRAGMA: "READ_INFO", USE: "READ_INFO",
89
+ INSERT: "DML", UPDATE: "DML", DELETE: "DML", MERGE: "DML",
90
+ UPSERT: "DML", REPLACE: "DML",
75
91
  CREATE: "DDL", DROP: "DDL", ALTER: "DDL", TRUNCATE: "DDL",
76
92
  RENAME: "DDL", COMMENT: "DDL",
77
93
  GRANT: "DCL", REVOKE: "DCL",
78
94
  SET: "SESSION", RESET: "SESSION",
79
95
  BEGIN: "TX", START: "TX", COMMIT: "TX", ROLLBACK: "TX",
80
96
  SAVEPOINT: "TX", RELEASE: "TX",
81
- CALL: "ROUTINE", EXECUTE: "ROUTINE",
97
+ CALL: "ROUTINE", EXECUTE: "ROUTINE", DO: "ROUTINE",
82
98
  COPY: "BULK",
83
99
  EXPLAIN: "META", ANALYZE: "META", VACUUM: "META",
84
100
  });
85
101
 
102
+ // Main-statement keywords that may follow a WITH (CTE) clause list —
103
+ // the verb that decides the statement's effect. `WITH src AS (...)
104
+ // INSERT INTO ...` is a write; classifying it by its leading keyword
105
+ // would label it a read and wave it past the residency write gate.
106
+ var _CTE_MAIN_VERBS = Object.freeze({
107
+ SELECT: true, VALUES: true, TABLE: true,
108
+ INSERT: true, UPDATE: true, DELETE: true,
109
+ MERGE: true, UPSERT: true, REPLACE: true,
110
+ });
111
+
112
+ // SQL identifier character classes, as char-range predicates rather
113
+ // than `_isIdentChar(ch)` regex literals — faster per-char in
114
+ // the tight statement-scan loops and keeps the trivial word-char class
115
+ // out of the cross-file duplicate-regex catalog.
116
+ function _isIdentStart(ch) {
117
+ return (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z") || ch === "_";
118
+ }
119
+ function _isIdentChar(ch) {
120
+ return _isIdentStart(ch) || (ch >= "0" && ch <= "9");
121
+ }
122
+
123
+ // If sql[i] begins an opaque span — a string literal ('...' with
124
+ // doubled-quote escapes), a quoted identifier ("..." / `...` / [...]),
125
+ // a Postgres dollar-quoted body ($tag$...$tag$), or a SQL line / block
126
+ // comment — return the index just past its close; -1 if the span is
127
+ // unterminated; i unchanged if sql[i] does not begin a span. One
128
+ // linear scan per call, no backtracking regex (CWE-1333). Shared by
129
+ // every SQL leading-/effective-verb scanner below so the multi-
130
+ // statement, CTE, EXPLAIN, and COPY walkers all agree on what counts
131
+ // as data vs. structure across the Postgres / MySQL / SQLite dialects
132
+ // external-db targets. A doubled closing quote ('it''s') re-enters as
133
+ // a fresh empty span on the caller's next iteration, staying opaque.
134
+ function _skipOpaqueSpan(sql, i) {
135
+ var n = sql.length;
136
+ var ch = sql.charAt(i);
137
+ if (ch === "'" || ch === "\"" || ch === "`") {
138
+ var close = sql.indexOf(ch, i + 1);
139
+ return close === -1 ? -1 : close + 1;
140
+ }
141
+ if (ch === "[") { // SQLite / MSSQL bracket identifier
142
+ var rb = sql.indexOf("]", i + 1);
143
+ return rb === -1 ? -1 : rb + 1;
144
+ }
145
+ if (ch === "$") {
146
+ // $tag$ ... $tag$ dollar-quoted body; a bare $n placeholder has no
147
+ // second `$` after the run of word chars and is not a span.
148
+ var tagEnd = i + 1;
149
+ while (tagEnd < n && _isIdentChar(sql.charAt(tagEnd))) tagEnd += 1;
150
+ if (tagEnd < n && sql.charAt(tagEnd) === "$") {
151
+ var tag = sql.slice(i, tagEnd + 1);
152
+ var closeTag = sql.indexOf(tag, tagEnd + 1);
153
+ return closeTag === -1 ? -1 : closeTag + tag.length;
154
+ }
155
+ return i;
156
+ }
157
+ if (ch === "-" && sql.charAt(i + 1) === "-") {
158
+ var nl = sql.indexOf("\n", i + 2);
159
+ return nl === -1 ? -1 : nl + 1;
160
+ }
161
+ if (ch === "/" && sql.charAt(i + 1) === "*") {
162
+ var ce = sql.indexOf("*/", i + 2);
163
+ return ce === -1 ? -1 : ce + 2;
164
+ }
165
+ return i;
166
+ }
167
+
168
+ // Resolve the main-statement keyword of a WITH-prefixed (CTE)
169
+ // statement: walk past the CTE definition list to the first top-level
170
+ // main verb (opaque spans skipped via _skipOpaqueSpan, parens tracked
171
+ // by depth). `WITH src AS (...) INSERT INTO ...` is a write; the
172
+ // leading keyword would label it a read and wave it past the residency
173
+ // write gate. Returns the uppercased verb, or null when unresolvable
174
+ // (unterminated span, parenthesized main statement, stray close-paren,
175
+ // no verb found) — the gate REFUSES unresolvable statements on its
176
+ // enforced path, so a parse miss fails closed.
177
+ function _cteMainKeyword(sql, start) {
178
+ var n = sql.length;
179
+ var depth = 0;
180
+ var i = start;
181
+ while (i < n) {
182
+ var ch = sql.charAt(i);
183
+ var skipped = _skipOpaqueSpan(sql, i);
184
+ if (skipped === -1) return null;
185
+ if (skipped !== i) { i = skipped; continue; }
186
+ if (ch === "(") { depth += 1; i += 1; continue; }
187
+ if (ch === ")") { depth -= 1; i += 1; continue; }
188
+ if (_isIdentStart(ch)) {
189
+ var we = i + 1;
190
+ while (we < n && _isIdentChar(sql.charAt(we))) we += 1;
191
+ if (depth === 0) {
192
+ var word = sql.slice(i, we).toUpperCase();
193
+ if (_CTE_MAIN_VERBS[word] === true) return word;
194
+ // Top-level word that is not a main verb: CTE name, AS,
195
+ // RECURSIVE, [NOT] MATERIALIZED, or a SEARCH / CYCLE clause
196
+ // word — keep walking.
197
+ }
198
+ i = we;
199
+ continue;
200
+ }
201
+ i += 1;
202
+ }
203
+ return null;
204
+ }
205
+
206
+ // EXPLAIN option words (Postgres parenthesized + legacy bare; MySQL
207
+ // bare). These precede the inner statement and never terminate the
208
+ // option list, so the verb scanner skips them; only ANALYZE flips the
209
+ // "this EXPLAIN actually executes the statement" bit.
210
+ var _EXPLAIN_OPTION_WORDS = Object.freeze({
211
+ ANALYZE: true, VERBOSE: true, COSTS: true, BUFFERS: true,
212
+ SETTINGS: true, WAL: true, TIMING: true, SUMMARY: true,
213
+ SERIALIZE: true, MEMORY: true, GENERIC_PLAN: true, FORMAT: true,
214
+ TEXT: true, JSON: true, YAML: true, XML: true, TREE: true,
215
+ EXTENDED: true, PARTITIONS: true,
216
+ ON: true, OFF: true, TRUE: true, FALSE: true,
217
+ });
218
+
219
+ // Resolve an EXPLAIN prefix: skip the option list (a parenthesized
220
+ // `( ANALYZE, FORMAT JSON )` group and/or bare option words), noting
221
+ // whether ANALYZE is present, and return { hasAnalyze, innerStart }
222
+ // pointing at the wrapped statement's leading keyword. null when
223
+ // unresolvable (unterminated span, unbalanced option parens, no inner
224
+ // statement). EXPLAIN ANALYZE EXECUTES the wrapped statement, so an
225
+ // `EXPLAIN ANALYZE INSERT ...` is a real write the gate must see.
226
+ function _explainResolve(sql, start) {
227
+ var n = sql.length;
228
+ var hasAnalyze = false;
229
+ var i = start;
230
+ while (i < n) {
231
+ var ch = sql.charAt(i);
232
+ var skipped = _skipOpaqueSpan(sql, i);
233
+ if (skipped === -1) return null;
234
+ if (skipped !== i) { i = skipped; continue; }
235
+ if (ch === "(") {
236
+ var depth = 0;
237
+ var j = i;
238
+ while (j < n) {
239
+ var c2 = sql.charAt(j);
240
+ var s2 = _skipOpaqueSpan(sql, j);
241
+ if (s2 === -1) return null;
242
+ if (s2 !== j) { j = s2; continue; }
243
+ if (c2 === "(") { depth += 1; j += 1; continue; }
244
+ if (c2 === ")") { depth -= 1; j += 1; if (depth === 0) break; continue; }
245
+ if (_isIdentStart(c2)) {
246
+ var oe = j + 1;
247
+ while (oe < n && _isIdentChar(sql.charAt(oe))) oe += 1;
248
+ if (sql.slice(j, oe).toUpperCase() === "ANALYZE") hasAnalyze = true;
249
+ j = oe;
250
+ continue;
251
+ }
252
+ j += 1;
253
+ }
254
+ if (depth !== 0) return null;
255
+ i = j;
256
+ continue;
257
+ }
258
+ if (_isIdentStart(ch)) {
259
+ var we = i + 1;
260
+ while (we < n && _isIdentChar(sql.charAt(we))) we += 1;
261
+ var word = sql.slice(i, we).toUpperCase();
262
+ if (word === "ANALYZE") { hasAnalyze = true; i = we; continue; }
263
+ if (_EXPLAIN_OPTION_WORDS[word] === true) { i = we; continue; }
264
+ return { hasAnalyze: hasAnalyze, innerStart: i };
265
+ }
266
+ i += 1;
267
+ }
268
+ return null;
269
+ }
270
+
271
+ // COPY <target> FROM <source> LOADS rows (a write); COPY <target> /
272
+ // COPY (query) TO <dest> EXPORTS rows (a read). Find the first
273
+ // top-level FROM / TO keyword after COPY, skipping a parenthesized
274
+ // source query and opaque spans. FROM → true (load); TO → false
275
+ // (export); unresolvable or neither → true (fail-closed write).
276
+ function _copyLoadsRows(sql) {
277
+ var m = _STATEMENT_CLASS_RE.exec(sql);
278
+ if (!m) return true;
279
+ var n = sql.length;
280
+ var i = m.index + m[0].length;
281
+ while (i < n) {
282
+ var ch = sql.charAt(i);
283
+ var skipped = _skipOpaqueSpan(sql, i);
284
+ if (skipped === -1) return true;
285
+ if (skipped !== i) { i = skipped; continue; }
286
+ if (ch === "(") {
287
+ var depth = 0;
288
+ var j = i;
289
+ while (j < n) {
290
+ var c2 = sql.charAt(j);
291
+ var s2 = _skipOpaqueSpan(sql, j);
292
+ if (s2 === -1) return true;
293
+ if (s2 !== j) { j = s2; continue; }
294
+ if (c2 === "(") { depth += 1; j += 1; continue; }
295
+ if (c2 === ")") { depth -= 1; j += 1; if (depth === 0) break; continue; }
296
+ j += 1;
297
+ }
298
+ i = j;
299
+ continue;
300
+ }
301
+ if (_isIdentStart(ch)) {
302
+ var we = i + 1;
303
+ while (we < n && _isIdentChar(sql.charAt(we))) we += 1;
304
+ var word = sql.slice(i, we).toUpperCase();
305
+ if (word === "FROM") return true;
306
+ if (word === "TO") return false;
307
+ i = we;
308
+ continue;
309
+ }
310
+ i += 1;
311
+ }
312
+ return true;
313
+ }
314
+
315
+ // Forensic / gate statement class. Resolves WITH (CTE) and EXPLAIN
316
+ // [ANALYZE] prefixes to the effective verb so a write wearing a
317
+ // harmless leading keyword classifies as the write it is; unresolvable
318
+ // prefixes classify UNKNOWN (fail-closed at the gate).
86
319
  function _classifyStatement(sql) {
87
320
  if (typeof sql !== "string" || sql.length === 0) return "UNKNOWN";
88
321
  var m = _STATEMENT_CLASS_RE.exec(sql);
89
322
  if (!m) return "UNKNOWN";
90
- return _STATEMENT_CLASS_MAP[m[1].toUpperCase()] || "OTHER";
323
+ var kw = m[1].toUpperCase();
324
+ if (kw === "WITH") {
325
+ var main = _cteMainKeyword(sql, m.index + m[0].length);
326
+ return main === null ? "UNKNOWN" : (_STATEMENT_CLASS_MAP[main] || "OTHER");
327
+ }
328
+ if (kw === "EXPLAIN") {
329
+ // Plan-only EXPLAIN reads (META). EXPLAIN ANALYZE executes the
330
+ // wrapped statement, so its effective class is the inner one's.
331
+ var ex = _explainResolve(sql, m.index + m[0].length);
332
+ if (ex === null) return "UNKNOWN";
333
+ if (!ex.hasAnalyze) return "META";
334
+ return _classifyStatement(sql.slice(ex.innerStart));
335
+ }
336
+ return _STATEMENT_CLASS_MAP[kw] || "OTHER";
91
337
  }
92
338
 
339
+ // Statement classes that place no rows on the backend, so the cross-
340
+ // border residency write gate lets them pass without a row tag. Every
341
+ // other class — DML, ROUTINE (CALL / EXECUTE / DO), a COPY ... FROM
342
+ // load, or an unmapped/unresolved statement — is treated as a write
343
+ // and must carry a residency tag on the enforced path. DDL (schema
344
+ // changes) and DCL (grants) move no row data across a border; the one
345
+ // edge they don't cover, CREATE TABLE AS SELECT / SELECT INTO, is a
346
+ // documented residency limitation rather than a silent bypass.
347
+ var _RESIDENCY_READ_CLASS = Object.freeze({
348
+ SELECT: true, READ_INFO: true, SESSION: true,
349
+ TX: true, DCL: true, DDL: true, META: true,
350
+ });
351
+
93
352
  // ---- OpenTelemetry database-client semantic conventions ----
94
353
  //
95
354
  // db.* span / metric attributes on the query / transaction / read emit
@@ -507,7 +766,7 @@ function init(opts) {
507
766
  return async function () {
508
767
  var client = await cn();
509
768
  try {
510
- await qn(client, "SET application_name TO " + quotedAppName, []);
769
+ await qn(client, "SET application_name TO " + quotedAppName, []); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
511
770
  } catch (_e) {
512
771
  // Best-effort. Real Postgres always supports SET
513
772
  // application_name; a driver that refuses it is a shim
@@ -738,6 +997,7 @@ function _servesClassification(b, cls) {
738
997
  * backend?: string, // explicit backend name; bypasses classification + role pick
739
998
  * classification?: string, // route to first backend whose classifications include this value
740
999
  * includeSqlInAudit?: boolean, // emit SQL text in audit metadata (off by default — may carry literal PII)
1000
+ * rowResidencyTag?: string, // the row's residency region tag; required for a write (DML, CALL/EXECUTE/DO, COPY ... FROM, REPLACE, or a WITH/EXPLAIN-ANALYZE wrapping one) to a residency-tagged backend under a cross-border regulated posture (pass "global"/"unrestricted" for region-neutral rows)
741
1001
  *
742
1002
  * @example
743
1003
  * var res = await b.externalDb.query(
@@ -754,6 +1014,14 @@ async function query(sql, params, opts) {
754
1014
  var b = _pickBackend(opts);
755
1015
  var role = dbRoleContext.getRole();
756
1016
 
1017
+ // Per-row residency write gate — refuses a cross-border write before
1018
+ // the statement reaches the wire (see _assertRowResidency).
1019
+ var _resRefusal = _assertRowResidency(sql, opts, b);
1020
+ if (_resRefusal) {
1021
+ _emit("db.residency.gate.rejected", "denied", _resRefusal.metadata, _resRefusal.code);
1022
+ throw _err(_resRefusal.code, _resRefusal.message, true);
1023
+ }
1024
+
757
1025
  var t0 = Date.now();
758
1026
  try {
759
1027
  var result = await retryHelper.withRetry(function () {
@@ -854,6 +1122,7 @@ async function query(sql, params, opts) {
854
1122
  * statementTimeoutMs?: number, // SET LOCAL statement_timeout
855
1123
  * idleInTransactionTimeoutMs?: number, // SET LOCAL idle_in_transaction_session_timeout
856
1124
  * deadlockRetries?: number, // retries for 40P01 / 40001 (default 3)
1125
+ * rowResidencyTag?: string, // residency tag applied to every statement; a per-call tx.query(sql, params, { rowResidencyTag }) overrides it for that statement
857
1126
  *
858
1127
  * @example
859
1128
  * var summary = await b.externalDb.transaction(async function (tx) {
@@ -907,10 +1176,34 @@ async function transaction(fn, opts) {
907
1176
  }
908
1177
  var maxRetries = (typeof opts.deadlockRetries === "number")
909
1178
  ? Math.floor(opts.deadlockRetries) : 3; // allow:numeric-opt-Infinity
1179
+ // Validate the transaction-level residency tag shape at entry (the
1180
+ // sessionGucs / deadlockRetries discipline) so an empty-string tag
1181
+ // fails before BEGIN rather than at the first statement.
1182
+ if (opts.rowResidencyTag !== undefined && opts.rowResidencyTag !== null &&
1183
+ (typeof opts.rowResidencyTag !== "string" || opts.rowResidencyTag.length === 0)) {
1184
+ throw _err("INVALID_OPT",
1185
+ "transaction: opts.rowResidencyTag must be a non-empty string when supplied", true);
1186
+ }
910
1187
  return await b.breaker.wrap(async function () {
911
1188
  var client = await b.pool.acquire();
912
1189
  var txClient = {
913
- query: function (sql, params) { return b.query(client, sql, params || []); },
1190
+ // Per-statement residency gate inside the transaction: a
1191
+ // transaction-level opts.rowResidencyTag applies to every
1192
+ // statement; an optional per-call third argument overrides it
1193
+ // for that statement. A refusal throws into the operator's tx
1194
+ // body, which rolls the transaction back — no partial commit of
1195
+ // a cross-border write.
1196
+ query: function (sql, params, perCallOpts) {
1197
+ var effOpts = (perCallOpts && perCallOpts.rowResidencyTag !== undefined)
1198
+ ? perCallOpts
1199
+ : { rowResidencyTag: opts.rowResidencyTag };
1200
+ var refusal = _assertRowResidency(sql, effOpts, b);
1201
+ if (refusal) {
1202
+ _emit("db.residency.gate.rejected", "denied", refusal.metadata, refusal.code);
1203
+ throw _err(refusal.code, refusal.message, true);
1204
+ }
1205
+ return b.query(client, sql, params || []);
1206
+ },
914
1207
  };
915
1208
  var committed = false;
916
1209
  var attempt = 0;
@@ -921,10 +1214,10 @@ async function transaction(fn, opts) {
921
1214
  try {
922
1215
  await b.beginTx(client);
923
1216
  if (typeof stmtTimeoutMs === "number" && isFinite(stmtTimeoutMs) && stmtTimeoutMs > 0) {
924
- await b.query(client, "SET LOCAL statement_timeout = " + Math.floor(stmtTimeoutMs), []);
1217
+ await b.query(client, "SET LOCAL statement_timeout = " + Math.floor(stmtTimeoutMs), []); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
925
1218
  }
926
1219
  if (typeof idleTimeoutMs === "number" && isFinite(idleTimeoutMs) && idleTimeoutMs > 0) {
927
- await b.query(client, "SET LOCAL idle_in_transaction_session_timeout = " + Math.floor(idleTimeoutMs), []);
1220
+ await b.query(client, "SET LOCAL idle_in_transaction_session_timeout = " + Math.floor(idleTimeoutMs), []); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
928
1221
  }
929
1222
  for (var gi = 0; gi < prebuiltGucs.length; gi++) {
930
1223
  await b.query(client, prebuiltGucs[gi], []);
@@ -1023,7 +1316,7 @@ async function _pingBackend(b) {
1023
1316
  var client = await b.pool.acquire();
1024
1317
  try {
1025
1318
  if (b.ping) await b.ping(client);
1026
- else await b.query(client, "SELECT 1", []);
1319
+ else await b.query(client, "SELECT 1", []); // allow:hand-rolled-sql — fixed connectivity ping (no table / b.sql verb)
1027
1320
  b.pool.release(client);
1028
1321
  return { ok: true, breakerState: b.breaker.getState(), pool: b.pool.stats() };
1029
1322
  } catch (e) {
@@ -1162,7 +1455,7 @@ function _buildSessionGucsStatements(sessionGucs) {
1162
1455
  "sessionGucs['" + name + "']: value must be a string, finite number, or boolean (got " +
1163
1456
  typeof value + ")", true);
1164
1457
  }
1165
- out.push("SET LOCAL " + qName + " = " + literal);
1458
+ out.push("SET LOCAL " + qName + " = " + literal); // allow:hand-rolled-sql — Postgres session GUC SET (no table; not a b.sql verb)
1166
1459
  }
1167
1460
  return out;
1168
1461
  }
@@ -1229,9 +1522,17 @@ var REPLICA_UNHEALTHY_COOLDOWN_MS = C.TIME.seconds(30);
1229
1522
  // - "unrestricted" tag on either side: compatible (operator
1230
1523
  // declared no constraint).
1231
1524
  // - Different tags: compatible only when allowCrossBorder is true.
1232
- var CROSS_BORDER_REGULATED_POSTURES = Object.freeze([
1233
- "gdpr", "uk-gdpr", "dpdp", "pipl-cn", "lgpd-br", "appi-jp", "pdpa-sg",
1234
- ]);
1525
+ //
1526
+ // The regulated-posture set itself lives on b.compliance
1527
+ // (CROSS_BORDER_REGULATED_POSTURES) — one vocabulary shared with the
1528
+ // local db-query residency gate.
1529
+ function _crossBorderRegulated(posture) {
1530
+ if (posture === null || posture === undefined) return false;
1531
+ try {
1532
+ var compliance = require("./compliance"); // allow:inline-require — defensive against optional load
1533
+ return compliance.isCrossBorderRegulated(posture);
1534
+ } catch (_e) { return false; }
1535
+ }
1235
1536
 
1236
1537
  function _residencyCompatible(primaryTag, replicaTag) {
1237
1538
  if (!primaryTag || !replicaTag) return true;
@@ -1240,6 +1541,54 @@ function _residencyCompatible(primaryTag, replicaTag) {
1240
1541
  return false;
1241
1542
  }
1242
1543
 
1544
+ // True when `sql` carries a non-comment, non-whitespace statement after
1545
+ // the first top-level semicolon — the multi-statement shape that would
1546
+ // let a trailing DML hide behind a harmless leading keyword. A `;`
1547
+ // inside any opaque span (string literal, quoted identifier, dollar-
1548
+ // quoted body, comment) is data, not a separator: the main scan skips
1549
+ // every span via _skipOpaqueSpan, so a `;` inside `$$ ... ; ... $$` or
1550
+ // a doubled-quote run can't be mistaken for a top-level separator (and
1551
+ // can't desync the scanner into missing a real one). Single linear
1552
+ // pass, no backtracking regex (CWE-1333).
1553
+ function _hasTrailingStatement(sql) {
1554
+ if (typeof sql !== "string") return false;
1555
+ var n = sql.length;
1556
+ var i = 0;
1557
+ while (i < n) {
1558
+ var ch = sql.charAt(i);
1559
+ var skipped = _skipOpaqueSpan(sql, i);
1560
+ if (skipped === -1) return false; // unterminated span — no top-level content beyond it
1561
+ if (skipped !== i) { i = skipped; continue; }
1562
+ if (ch !== ";") { i += 1; continue; }
1563
+ // First top-level `;` decides: if everything after it is only
1564
+ // whitespace and SQL comments there is no second statement (a
1565
+ // single statement may end with `;`); any other character is one.
1566
+ // Comments-only skip here (not _skipOpaqueSpan) — a string / ident
1567
+ // after the `;` IS trailing content, so it must count, not be
1568
+ // skipped as a span.
1569
+ var j = i + 1;
1570
+ while (j < n) {
1571
+ var c = sql.charAt(j);
1572
+ if (c === " " || c === "\t" || c === "\r" || c === "\n") { j += 1; continue; }
1573
+ if (c === "/" && sql.charAt(j + 1) === "*") {
1574
+ var end = sql.indexOf("*/", j + 2);
1575
+ if (end === -1) return false; // unterminated comment — no content
1576
+ j = end + 2;
1577
+ continue;
1578
+ }
1579
+ if (c === "-" && sql.charAt(j + 1) === "-") {
1580
+ var nl = sql.indexOf("\n", j + 2);
1581
+ if (nl === -1) return false; // line comment to EOF — no content
1582
+ j = nl + 1;
1583
+ continue;
1584
+ }
1585
+ return true;
1586
+ }
1587
+ return false;
1588
+ }
1589
+ return false;
1590
+ }
1591
+
1243
1592
  function _activePosture() {
1244
1593
  try {
1245
1594
  var compliance = require("./compliance"); // allow:inline-require — defensive against optional load
@@ -1247,6 +1596,115 @@ function _activePosture() {
1247
1596
  } catch (_e) { return null; }
1248
1597
  }
1249
1598
 
1599
+ // Per-row residency write gate (GDPR Art 44-46 / PIPL Art 38 / DPDP
1600
+ // §16 cross-border transfer restrictions). External-db takes raw SQL,
1601
+ // not row objects, so the row's residency tag travels as
1602
+ // `opts.rowResidencyTag` — the operator computes it from app logic
1603
+ // (session / declared user region), never inferred from request
1604
+ // metadata. Under a cross-border regulated posture, DML to a
1605
+ // residency-tagged backend REQUIRES the tag and refuses a mismatch;
1606
+ // untagged backends, unregulated postures, and non-DML statements
1607
+ // pass (with an advisory audit when a tag was supplied anyway).
1608
+ // Returns null on pass or { code, message, metadata } — the caller
1609
+ // throws via _err so the refusal carries permanent=true.
1610
+ function _assertRowResidency(sql, opts, backend) {
1611
+ var tag = opts && opts.rowResidencyTag;
1612
+ if (tag !== undefined && tag !== null &&
1613
+ (typeof tag !== "string" || tag.length === 0)) {
1614
+ return {
1615
+ code: "INVALID_OPT",
1616
+ message: "rowResidencyTag must be a non-empty string when supplied",
1617
+ metadata: { backend: backend.name, statementClass: _classifyStatement(sql) },
1618
+ };
1619
+ }
1620
+ var backendTag = backend.residencyTag || "unrestricted";
1621
+ var posture = _activePosture();
1622
+ var regulated = _crossBorderRegulated(posture);
1623
+ // The gate only enforces on the cross-border-regulated + residency-
1624
+ // tagged-backend path. Everywhere else (unregulated posture,
1625
+ // unrestricted backend — including the framework's own coordination
1626
+ // stores) statements pass untouched; multi-statement SQL stays the
1627
+ // operator's business there.
1628
+ if (regulated && backendTag !== "unrestricted") {
1629
+ // A trailing statement after a top-level `;` could hide a write
1630
+ // behind a harmless prefix — refuse multi-statement SQL on the
1631
+ // enforced path so a residency-bound write cannot ride a SELECT.
1632
+ if (_hasTrailingStatement(sql)) {
1633
+ return {
1634
+ code: "MULTI_STATEMENT_REFUSED",
1635
+ message: "multi-statement SQL is not supported on the residency-gated " +
1636
+ "write path; pass one statement per query()",
1637
+ metadata: { backend: backend.name, backendTag: backendTag, posture: posture,
1638
+ statementClass: _classifyStatement(sql), scope: "external" },
1639
+ };
1640
+ }
1641
+ var cls = _classifyStatement(sql);
1642
+ // Fail-closed: a statement whose effective class can't be resolved
1643
+ // (an unparseable WITH / EXPLAIN prefix or pathological quoting)
1644
+ // could be hiding a write, so refuse it rather than wave it through
1645
+ // as a read.
1646
+ if (cls === "UNKNOWN") {
1647
+ return {
1648
+ code: "STATEMENT_UNRESOLVED_REFUSED",
1649
+ message: "could not resolve the effective statement class on the " +
1650
+ "residency-gated write path; pass one plain statement per " +
1651
+ "query() (an unparseable WITH/EXPLAIN prefix or quoting)",
1652
+ metadata: { backend: backend.name, backendTag: backendTag, posture: posture,
1653
+ statementClass: cls, scope: "external" },
1654
+ };
1655
+ }
1656
+ // The gate enforces on writes, not just DML: ROUTINE (CALL /
1657
+ // EXECUTE / DO), a COPY ... FROM load, and an unmapped (OTHER)
1658
+ // verb all place rows and must carry a tag. Recognized pure reads
1659
+ // (and a COPY ... TO export) place none and pass untagged.
1660
+ var isWrite = !(_RESIDENCY_READ_CLASS[cls] === true ||
1661
+ (cls === "BULK" && !_copyLoadsRows(sql)));
1662
+ if (!isWrite) return null;
1663
+ if (!tag) {
1664
+ return {
1665
+ code: "RESIDENCY_GATE_REQUIRED",
1666
+ message: "write to backend '" + backend.name + "' (residencyTag='" +
1667
+ backendTag + "') under '" + posture + "' posture requires " +
1668
+ "opts.rowResidencyTag. Pass { rowResidencyTag: \"" + backendTag +
1669
+ "\" } for rows belonging to this region, or declare per-row " +
1670
+ "residency via b.cryptoField.declarePerRowResidency for local tables",
1671
+ metadata: { backend: backend.name, backendTag: backendTag,
1672
+ rowTag: null, posture: posture, statementClass: cls,
1673
+ scope: "external" },
1674
+ };
1675
+ }
1676
+ if (tag !== "global" && tag !== "unrestricted" &&
1677
+ !_residencyCompatible(tag, backendTag)) {
1678
+ return {
1679
+ code: "RESIDENCY_TAG_MISMATCH",
1680
+ message: "row residencyTag '" + tag + "' is not compatible with backend '" +
1681
+ backend.name + "' residencyTag '" + backendTag + "' under '" + posture +
1682
+ "' posture (cross-border transfer refused)",
1683
+ metadata: { backend: backend.name, backendTag: backendTag,
1684
+ rowTag: tag, posture: posture, statementClass: cls,
1685
+ scope: "external" },
1686
+ };
1687
+ }
1688
+ return null;
1689
+ }
1690
+ if (tag) {
1691
+ // Unregulated posture or untagged backend with a tag supplied on a
1692
+ // write — pass, but surface the advisory so operators staging a
1693
+ // posture flip can see what WOULD be evaluated.
1694
+ var advisoryCls = _classifyStatement(sql);
1695
+ var advisoryWrite = advisoryCls !== "UNKNOWN" &&
1696
+ !(_RESIDENCY_READ_CLASS[advisoryCls] === true ||
1697
+ (advisoryCls === "BULK" && !_copyLoadsRows(sql)));
1698
+ if (advisoryWrite) {
1699
+ _emit("db.residency.gate.advisory", "info", {
1700
+ backend: backend.name, backendTag: backendTag, rowTag: tag,
1701
+ posture: posture || null, statementClass: advisoryCls, scope: "external",
1702
+ });
1703
+ }
1704
+ }
1705
+ return null;
1706
+ }
1707
+
1250
1708
  function _buildReplicas(backendName, cfg) {
1251
1709
  if (!cfg.replicas) return null;
1252
1710
  if (!Array.isArray(cfg.replicas) || cfg.replicas.length === 0) {
@@ -1275,7 +1733,7 @@ function _buildReplicas(backendName, cfg) {
1275
1733
  var replicaTag = r.residencyTag || "unrestricted";
1276
1734
  var allowCrossBorder = r.allowCrossBorder === true;
1277
1735
  if (!_residencyCompatible(primaryTag, replicaTag) && !allowCrossBorder) {
1278
- var underPosture = posture && CROSS_BORDER_REGULATED_POSTURES.indexOf(posture) !== -1;
1736
+ var underPosture = _crossBorderRegulated(posture);
1279
1737
  throw _err("RESIDENCY_MISMATCH",
1280
1738
  "backend '" + backendName + "': replica[" + i +
1281
1739
  "] residencyTag '" + replicaTag +
@@ -1286,7 +1744,12 @@ function _buildReplicas(backendName, cfg) {
1286
1744
  "documented legal basis (SCCs / BCRs / adequacy decision) to suppress.", true);
1287
1745
  }
1288
1746
  if (!_residencyCompatible(primaryTag, replicaTag) && allowCrossBorder) {
1289
- _emit("externalDb.replica.cross_border_allowed", "warning",
1747
+ // The action name MUST stay in the registered `db.` namespace and
1748
+ // lowercase — the audit validator refuses "externalDb.*" (the old
1749
+ // name silently dropped every cross-border-allowed event through
1750
+ // safeEmit, leaving no audit-chain record of the operator's
1751
+ // conscious opt-in). Mirrors the read-path event name below.
1752
+ _emit("db.residency.replica.cross_border_allowed", "warning",
1290
1753
  { backend: backendName, replicaIndex: i,
1291
1754
  primaryTag: primaryTag, replicaTag: replicaTag,
1292
1755
  legalBasis: r.legalBasis || null,
@@ -1344,6 +1807,52 @@ async function _readQuery(sql, params, opts) {
1344
1807
  throw _err("ALL_REPLICAS_UNHEALTHY",
1345
1808
  "backend '" + b.name + "': all replicas unhealthy and fallback disabled", true);
1346
1809
  }
1810
+ // Replica residency check — when the caller identifies the row's
1811
+ // region (opts.rowResidencyTag) under a regulated posture, a read
1812
+ // routed to an incompatible replica is refused unless the replica
1813
+ // was explicitly configured allowCrossBorder (which is audited).
1814
+ var _readPosture = _activePosture();
1815
+ var _tagPresent = opts.rowResidencyTag && typeof opts.rowResidencyTag === "string";
1816
+ // Fail CLOSED when the row's region is not identified: a regulated read to a
1817
+ // residency-tagged replica without opts.rowResidencyTag would otherwise route
1818
+ // residency-restricted rows to an arbitrary-region replica with no check at
1819
+ // all (symmetric with the write gate's RESIDENCY_GATE_REQUIRED).
1820
+ if (!_tagPresent && _crossBorderRegulated(_readPosture) &&
1821
+ replica.residencyTag && !replica.allowCrossBorder) {
1822
+ _emit("db.residency.replica.tag_required", "denied", {
1823
+ backend: b.name, replicaIdx: replica.index,
1824
+ replicaTag: replica.residencyTag, posture: _readPosture,
1825
+ });
1826
+ throw _err("REPLICA_RESIDENCY_TAG_REQUIRED",
1827
+ "read routed to residency-tagged replica " + replica.index + " of backend '" +
1828
+ b.name + "' (residencyTag='" + replica.residencyTag + "') under '" + _readPosture +
1829
+ "' posture without opts.rowResidencyTag - identify the row's region or set " +
1830
+ "allowCrossBorder on the replica (audited)", true);
1831
+ }
1832
+ if (_tagPresent) {
1833
+ if (_crossBorderRegulated(_readPosture) &&
1834
+ opts.rowResidencyTag !== "global" && opts.rowResidencyTag !== "unrestricted" &&
1835
+ !_residencyCompatible(opts.rowResidencyTag, replica.residencyTag)) {
1836
+ if (replica.allowCrossBorder) {
1837
+ _emit("db.residency.replica.cross_border", "warning", {
1838
+ backend: b.name, replicaIdx: replica.index,
1839
+ rowTag: opts.rowResidencyTag, replicaTag: replica.residencyTag,
1840
+ posture: _readPosture,
1841
+ });
1842
+ } else {
1843
+ _emit("db.residency.replica.incompatible", "denied", {
1844
+ backend: b.name, replicaIdx: replica.index,
1845
+ rowTag: opts.rowResidencyTag, replicaTag: replica.residencyTag,
1846
+ posture: _readPosture,
1847
+ });
1848
+ throw _err("REPLICA_RESIDENCY_INCOMPATIBLE",
1849
+ "read for row residencyTag '" + opts.rowResidencyTag + "' routed to replica " +
1850
+ replica.index + " of backend '" + b.name + "' (residencyTag='" +
1851
+ replica.residencyTag + "') under '" + _readPosture +
1852
+ "' posture; set allowCrossBorder on the replica to permit (audited)", true);
1853
+ }
1854
+ }
1855
+ }
1347
1856
  var role = dbRoleContext.getRole();
1348
1857
  var t0 = Date.now();
1349
1858
  try {
@@ -1655,22 +2164,27 @@ function _connectAs(rawConnect, query, opts) {
1655
2164
 
1656
2165
  // Pre-compute the SET statements once — every fresh client runs the
1657
2166
  // same list, so building it per-connect would burn microbenchmarks.
2167
+ // Postgres session-config statements (SET ROLE / search_path /
2168
+ // application_name / statement_timeout / GUCs). These are session-state
2169
+ // commands, not table DML — b.sql has no SET verb, so they stay
2170
+ // hand-composed (identifiers double-quoted, string values single-quote
2171
+ // escaped, numerics rendered after a finite-check below).
1658
2172
  var stmts = [];
1659
2173
  if (opts.role) {
1660
- stmts.push('SET ROLE "' + opts.role + '"');
2174
+ stmts.push('SET ROLE "' + opts.role + '"'); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
1661
2175
  }
1662
2176
  if (pathSegments) {
1663
2177
  var pathSql = pathSegments.map(function (s) { return '"' + s + '"'; }).join(", ");
1664
- stmts.push("SET search_path TO " + pathSql);
2178
+ stmts.push("SET search_path TO " + pathSql); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
1665
2179
  }
1666
2180
  if (opts.applicationName !== undefined) {
1667
2181
  // Single-quoted string literal — SQL-standard escape doubles embedded
1668
2182
  // single quotes.
1669
2183
  var an = String(opts.applicationName).replace(/'/g, "''");
1670
- stmts.push("SET application_name TO '" + an + "'");
2184
+ stmts.push("SET application_name TO '" + an + "'"); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
1671
2185
  }
1672
2186
  if (opts.statementTimeoutMs !== undefined) {
1673
- stmts.push("SET statement_timeout TO " + opts.statementTimeoutMs);
2187
+ stmts.push("SET statement_timeout TO " + opts.statementTimeoutMs); // allow:hand-rolled-sql — Postgres session SET (no table; not a b.sql verb)
1674
2188
  }
1675
2189
  if (opts.gucs) {
1676
2190
  for (var gn in opts.gucs) {
@@ -1979,11 +2493,12 @@ async function assertRoleHardening(opts) {
1979
2493
  var ignoreSystem = opts.ignoreSystem !== false; // default true
1980
2494
  var rows;
1981
2495
  try {
1982
- var res = await query(
1983
- "SELECT rolname FROM pg_roles ORDER BY rolname",
1984
- [],
1985
- { backend: backendName }
1986
- );
2496
+ // pg_roles is a Postgres system catalog (this path is Postgres-only —
2497
+ // guarded above), so b.sql emits the bare unquoted catalog name with a
2498
+ // quoted projection. No params, no `?`, so no placeholder translation.
2499
+ var rolesBuilt = sql().select("pg_roles", { dialect: "postgres" })
2500
+ .columns(["rolname"]).orderBy("rolname", "asc").toSql();
2501
+ var res = await query(rolesBuilt.sql, rolesBuilt.params, { backend: backendName });
1987
2502
  rows = (res && res.rows) || [];
1988
2503
  } catch (e) {
1989
2504
  audit().safeEmit({