@blamejs/blamejs-shop 0.0.44

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 (1220) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/LICENSE +17 -0
  3. package/README.md +117 -0
  4. package/SECURITY.md +139 -0
  5. package/lib/admin.js +952 -0
  6. package/lib/analytics.js +267 -0
  7. package/lib/cart.js +279 -0
  8. package/lib/catalog-import.js +344 -0
  9. package/lib/catalog.js +769 -0
  10. package/lib/checkout.js +320 -0
  11. package/lib/config.js +151 -0
  12. package/lib/customers.js +322 -0
  13. package/lib/email.js +242 -0
  14. package/lib/externaldb-d1.js +283 -0
  15. package/lib/index.js +57 -0
  16. package/lib/inventory-alerts.js +198 -0
  17. package/lib/newsletter.js +142 -0
  18. package/lib/order.js +380 -0
  19. package/lib/payment.js +318 -0
  20. package/lib/pricing.js +185 -0
  21. package/lib/r2-bridge.js +169 -0
  22. package/lib/shipping.js +185 -0
  23. package/lib/storefront.js +2160 -0
  24. package/lib/subscriptions.js +410 -0
  25. package/lib/tax.js +161 -0
  26. package/lib/theme.js +194 -0
  27. package/lib/vendor/MANIFEST.json +19 -0
  28. package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +23 -0
  29. package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +34 -0
  30. package/lib/vendor/blamejs/.clusterfuzzlite/project.yaml +16 -0
  31. package/lib/vendor/blamejs/.dockerignore +45 -0
  32. package/lib/vendor/blamejs/.gitattributes +42 -0
  33. package/lib/vendor/blamejs/.github/CODEOWNERS +4 -0
  34. package/lib/vendor/blamejs/.github/FUNDING.yml +2 -0
  35. package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/bug_report.md +58 -0
  36. package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/config.yml +8 -0
  37. package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/feature_request.md +99 -0
  38. package/lib/vendor/blamejs/.github/PULL_REQUEST_TEMPLATE.md +77 -0
  39. package/lib/vendor/blamejs/.github/dependabot.yml +37 -0
  40. package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +148 -0
  41. package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +107 -0
  42. package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +122 -0
  43. package/lib/vendor/blamejs/.github/workflows/ci.yml +511 -0
  44. package/lib/vendor/blamejs/.github/workflows/codeql.yml +50 -0
  45. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +655 -0
  46. package/lib/vendor/blamejs/.github/workflows/release-container.yml +406 -0
  47. package/lib/vendor/blamejs/.github/workflows/scorecard.yml +101 -0
  48. package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +134 -0
  49. package/lib/vendor/blamejs/.gitignore +102 -0
  50. package/lib/vendor/blamejs/.gitleaks.toml +166 -0
  51. package/lib/vendor/blamejs/.hadolint.yaml +18 -0
  52. package/lib/vendor/blamejs/.npmrc +5 -0
  53. package/lib/vendor/blamejs/.pinact.yaml +17 -0
  54. package/lib/vendor/blamejs/ARCHITECTURE.md +158 -0
  55. package/lib/vendor/blamejs/CHANGELOG.md +1351 -0
  56. package/lib/vendor/blamejs/CODE_OF_CONDUCT.md +86 -0
  57. package/lib/vendor/blamejs/CONTRIBUTING.md +156 -0
  58. package/lib/vendor/blamejs/GOVERNANCE.md +201 -0
  59. package/lib/vendor/blamejs/LICENSE +201 -0
  60. package/lib/vendor/blamejs/LTS-CALENDAR.md +29 -0
  61. package/lib/vendor/blamejs/MIGRATING.md +29 -0
  62. package/lib/vendor/blamejs/NOTICE +81 -0
  63. package/lib/vendor/blamejs/README.md +304 -0
  64. package/lib/vendor/blamejs/SECURITY.md +432 -0
  65. package/lib/vendor/blamejs/api-snapshot.json +48709 -0
  66. package/lib/vendor/blamejs/assets/BlameJS_Logo.png +0 -0
  67. package/lib/vendor/blamejs/assets/BlameJS_Logo.svg +129 -0
  68. package/lib/vendor/blamejs/bench/README.md +77 -0
  69. package/lib/vendor/blamejs/bench/_helpers.js +70 -0
  70. package/lib/vendor/blamejs/bench/baseline.json +183 -0
  71. package/lib/vendor/blamejs/bench/crypto-hash.bench.js +19 -0
  72. package/lib/vendor/blamejs/bench/crypto-symmetric.bench.js +28 -0
  73. package/lib/vendor/blamejs/bench/run.js +140 -0
  74. package/lib/vendor/blamejs/bench/safe-json.bench.js +31 -0
  75. package/lib/vendor/blamejs/bin/blamejs.js +13 -0
  76. package/lib/vendor/blamejs/docker/caddy/Caddyfile +46 -0
  77. package/lib/vendor/blamejs/docker/coredns/Corefile +37 -0
  78. package/lib/vendor/blamejs/docker/haproxy/haproxy.cfg +52 -0
  79. package/lib/vendor/blamejs/docker/init/generate-certs.sh +118 -0
  80. package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +87 -0
  81. package/lib/vendor/blamejs/docker/mitmproxy/config.yaml +16 -0
  82. package/lib/vendor/blamejs/docker/mongo/init-tls.sh +17 -0
  83. package/lib/vendor/blamejs/docker/mysql/my.cnf +12 -0
  84. package/lib/vendor/blamejs/docker/nats/nats.conf +33 -0
  85. package/lib/vendor/blamejs/docker/postgres/init-tls.sh +17 -0
  86. package/lib/vendor/blamejs/docker/postgres/postgresql.conf +18 -0
  87. package/lib/vendor/blamejs/docker/rabbitmq/rabbitmq.conf +18 -0
  88. package/lib/vendor/blamejs/docker/redis/redis.conf +15 -0
  89. package/lib/vendor/blamejs/docker/squid/squid.conf +24 -0
  90. package/lib/vendor/blamejs/docker/syslog/syslog-ng.conf +34 -0
  91. package/lib/vendor/blamejs/docker-compose.test.yml +545 -0
  92. package/lib/vendor/blamejs/docs/cis-postgres-crosswalk.md +102 -0
  93. package/lib/vendor/blamejs/docs/cis-sqlite-equivalent.md +92 -0
  94. package/lib/vendor/blamejs/eslint.config.mjs +204 -0
  95. package/lib/vendor/blamejs/examples/wiki/Caddyfile +40 -0
  96. package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +218 -0
  97. package/lib/vendor/blamejs/examples/wiki/Dockerfile +120 -0
  98. package/lib/vendor/blamejs/examples/wiki/README.md +157 -0
  99. package/lib/vendor/blamejs/examples/wiki/cli-snapshot.json +250 -0
  100. package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +231 -0
  101. package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +166 -0
  102. package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +217 -0
  103. package/lib/vendor/blamejs/examples/wiki/lib/auto-site-entries.js +139 -0
  104. package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +555 -0
  105. package/lib/vendor/blamejs/examples/wiki/lib/harvest-cli.js +507 -0
  106. package/lib/vendor/blamejs/examples/wiki/lib/harvest-env-vars.js +435 -0
  107. package/lib/vendor/blamejs/examples/wiki/lib/harvest-errors.js +282 -0
  108. package/lib/vendor/blamejs/examples/wiki/lib/harvest-vendored-deps.js +321 -0
  109. package/lib/vendor/blamejs/examples/wiki/lib/nav.js +15 -0
  110. package/lib/vendor/blamejs/examples/wiki/lib/opts-resolver.js +75 -0
  111. package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +508 -0
  112. package/lib/vendor/blamejs/examples/wiki/lib/section.js +276 -0
  113. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +587 -0
  114. package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +318 -0
  115. package/lib/vendor/blamejs/examples/wiki/lib/symbol-index.js +122 -0
  116. package/lib/vendor/blamejs/examples/wiki/migrations/0001-pages-schema.js +74 -0
  117. package/lib/vendor/blamejs/examples/wiki/package.json +18 -0
  118. package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.png +0 -0
  119. package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.svg +129 -0
  120. package/lib/vendor/blamejs/examples/wiki/public/robots.txt +5 -0
  121. package/lib/vendor/blamejs/examples/wiki/public/vendor/MANIFEST.json +30 -0
  122. package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.css +1 -0
  123. package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.js +15 -0
  124. package/lib/vendor/blamejs/examples/wiki/public/wiki.css +1250 -0
  125. package/lib/vendor/blamejs/examples/wiki/routes/admin.js +366 -0
  126. package/lib/vendor/blamejs/examples/wiki/routes/integration.js +230 -0
  127. package/lib/vendor/blamejs/examples/wiki/routes/pages.js +266 -0
  128. package/lib/vendor/blamejs/examples/wiki/scripts/backfill-module-metadata.js +214 -0
  129. package/lib/vendor/blamejs/examples/wiki/seeders/prod/0001-default-pages.js +35 -0
  130. package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/_index.js +34 -0
  131. package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/api.js +76 -0
  132. package/lib/vendor/blamejs/examples/wiki/server.js +129 -0
  133. package/lib/vendor/blamejs/examples/wiki/site.config.js +197 -0
  134. package/lib/vendor/blamejs/examples/wiki/snippets/README.md +38 -0
  135. package/lib/vendor/blamejs/examples/wiki/snippets/auth/password-hash.example.js +15 -0
  136. package/lib/vendor/blamejs/examples/wiki/src/editor.js +103 -0
  137. package/lib/vendor/blamejs/examples/wiki/src/wiki.js +349 -0
  138. package/lib/vendor/blamejs/examples/wiki/test/AUDIT.md +155 -0
  139. package/lib/vendor/blamejs/examples/wiki/test/codebase-patterns.test.js +594 -0
  140. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +741 -0
  141. package/lib/vendor/blamejs/examples/wiki/test/find-missing-pages.js +254 -0
  142. package/lib/vendor/blamejs/examples/wiki/test/integration.js +391 -0
  143. package/lib/vendor/blamejs/examples/wiki/test/validate-cli-snapshot.js +379 -0
  144. package/lib/vendor/blamejs/examples/wiki/test/validate-env-snapshot.js +346 -0
  145. package/lib/vendor/blamejs/examples/wiki/test/validate-nav-coverage.js +212 -0
  146. package/lib/vendor/blamejs/examples/wiki/test/validate-site-coverage.js +252 -0
  147. package/lib/vendor/blamejs/examples/wiki/test/validate-source-comment-blocks.js +107 -0
  148. package/lib/vendor/blamejs/examples/wiki/views/_layout.html +115 -0
  149. package/lib/vendor/blamejs/examples/wiki/views/admin/api-keys.html +51 -0
  150. package/lib/vendor/blamejs/examples/wiki/views/admin/dashboard.html +22 -0
  151. package/lib/vendor/blamejs/examples/wiki/views/admin/edit.html +17 -0
  152. package/lib/vendor/blamejs/examples/wiki/views/home.html +85 -0
  153. package/lib/vendor/blamejs/examples/wiki/views/login.html +18 -0
  154. package/lib/vendor/blamejs/examples/wiki/views/page.html +5 -0
  155. package/lib/vendor/blamejs/examples/wiki/views/partials/nav.html +13 -0
  156. package/lib/vendor/blamejs/examples/wiki/views/search.html +19 -0
  157. package/lib/vendor/blamejs/examples/wiki/wiki.config.js +15 -0
  158. package/lib/vendor/blamejs/fuzz/README.md +137 -0
  159. package/lib/vendor/blamejs/fuzz/_expected.js +35 -0
  160. package/lib/vendor/blamejs/fuzz/guard-agent-registry.fuzz.js +22 -0
  161. package/lib/vendor/blamejs/fuzz/guard-csv.fuzz.js +16 -0
  162. package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/01-basic.csv +3 -0
  163. package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/02-formula.csv +1 -0
  164. package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/03-hyperlink.csv +1 -0
  165. package/lib/vendor/blamejs/fuzz/guard-dsn.fuzz.js +22 -0
  166. package/lib/vendor/blamejs/fuzz/guard-email.fuzz.js +16 -0
  167. package/lib/vendor/blamejs/fuzz/guard-email_seed_corpus/01-basic.eml +5 -0
  168. package/lib/vendor/blamejs/fuzz/guard-envelope.fuzz.js +24 -0
  169. package/lib/vendor/blamejs/fuzz/guard-event-bus-payload.fuzz.js +24 -0
  170. package/lib/vendor/blamejs/fuzz/guard-event-bus-topic.fuzz.js +20 -0
  171. package/lib/vendor/blamejs/fuzz/guard-html.fuzz.js +16 -0
  172. package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/01-basic.html +1 -0
  173. package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/02-script.html +1 -0
  174. package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/03-event.html +1 -0
  175. package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/04-jsurl.html +1 -0
  176. package/lib/vendor/blamejs/fuzz/guard-idempotency-key.fuzz.js +20 -0
  177. package/lib/vendor/blamejs/fuzz/guard-imap-command.fuzz.js +35 -0
  178. package/lib/vendor/blamejs/fuzz/guard-jmap.fuzz.js +41 -0
  179. package/lib/vendor/blamejs/fuzz/guard-json.fuzz.js +16 -0
  180. package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/01-basic.json +1 -0
  181. package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/02-proto.json +1 -0
  182. package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/03-dupkey.json +1 -0
  183. package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/04-nan.json +1 -0
  184. package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/05-bom.json +1 -0
  185. package/lib/vendor/blamejs/fuzz/guard-list-id.fuzz.js +21 -0
  186. package/lib/vendor/blamejs/fuzz/guard-list-unsubscribe.fuzz.js +25 -0
  187. package/lib/vendor/blamejs/fuzz/guard-mail-compose.fuzz.js +22 -0
  188. package/lib/vendor/blamejs/fuzz/guard-mail-move.fuzz.js +22 -0
  189. package/lib/vendor/blamejs/fuzz/guard-mail-query.fuzz.js +27 -0
  190. package/lib/vendor/blamejs/fuzz/guard-mail-reply.fuzz.js +23 -0
  191. package/lib/vendor/blamejs/fuzz/guard-mail-sieve.fuzz.js +36 -0
  192. package/lib/vendor/blamejs/fuzz/guard-managesieve-command.fuzz.js +26 -0
  193. package/lib/vendor/blamejs/fuzz/guard-markdown.fuzz.js +16 -0
  194. package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/01-basic.md +2 -0
  195. package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/02-jsurl.md +1 -0
  196. package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/03-jsimg.md +1 -0
  197. package/lib/vendor/blamejs/fuzz/guard-message-id.fuzz.js +26 -0
  198. package/lib/vendor/blamejs/fuzz/guard-pop3-command.fuzz.js +23 -0
  199. package/lib/vendor/blamejs/fuzz/guard-posture-chain.fuzz.js +22 -0
  200. package/lib/vendor/blamejs/fuzz/guard-saga-config.fuzz.js +32 -0
  201. package/lib/vendor/blamejs/fuzz/guard-smtp-command.fuzz.js +27 -0
  202. package/lib/vendor/blamejs/fuzz/guard-snapshot-envelope.fuzz.js +22 -0
  203. package/lib/vendor/blamejs/fuzz/guard-stream-args.fuzz.js +22 -0
  204. package/lib/vendor/blamejs/fuzz/guard-svg.fuzz.js +16 -0
  205. package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/01-basic.svg +1 -0
  206. package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/02-script.svg +1 -0
  207. package/lib/vendor/blamejs/fuzz/guard-tenant-id.fuzz.js +20 -0
  208. package/lib/vendor/blamejs/fuzz/guard-trace-context.fuzz.js +30 -0
  209. package/lib/vendor/blamejs/fuzz/guard-xml.fuzz.js +16 -0
  210. package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/01-basic.xml +1 -0
  211. package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/02-xxe.xml +1 -0
  212. package/lib/vendor/blamejs/fuzz/guard-yaml.fuzz.js +16 -0
  213. package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/01-basic.yaml +2 -0
  214. package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/02-anchor.yaml +2 -0
  215. package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/03-norway.yaml +1 -0
  216. package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/04-multidoc.yaml +4 -0
  217. package/lib/vendor/blamejs/fuzz/parsers__safe-ini.fuzz.js +16 -0
  218. package/lib/vendor/blamejs/fuzz/parsers__safe-ini_seed_corpus/01-basic.ini +2 -0
  219. package/lib/vendor/blamejs/fuzz/parsers__safe-toml.fuzz.js +16 -0
  220. package/lib/vendor/blamejs/fuzz/parsers__safe-toml_seed_corpus/01-basic.toml +4 -0
  221. package/lib/vendor/blamejs/fuzz/parsers__safe-xml.fuzz.js +16 -0
  222. package/lib/vendor/blamejs/fuzz/parsers__safe-xml_seed_corpus/01-basic.xml +1 -0
  223. package/lib/vendor/blamejs/fuzz/parsers__safe-yaml.fuzz.js +16 -0
  224. package/lib/vendor/blamejs/fuzz/parsers__safe-yaml_seed_corpus/01-basic.yaml +4 -0
  225. package/lib/vendor/blamejs/fuzz/safe-decompress.fuzz.js +49 -0
  226. package/lib/vendor/blamejs/fuzz/safe-dns.fuzz.js +29 -0
  227. package/lib/vendor/blamejs/fuzz/safe-ical.fuzz.js +16 -0
  228. package/lib/vendor/blamejs/fuzz/safe-icap.fuzz.js +42 -0
  229. package/lib/vendor/blamejs/fuzz/safe-json.fuzz.js +25 -0
  230. package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/01-object.txt +1 -0
  231. package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/02-array.txt +1 -0
  232. package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/03-string.txt +1 -0
  233. package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/04-proto.txt +1 -0
  234. package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/05-deep.txt +1 -0
  235. package/lib/vendor/blamejs/fuzz/safe-jsonpath.fuzz.js +16 -0
  236. package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/01-basic.txt +1 -0
  237. package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/02-filter.txt +1 -0
  238. package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/03-deepscan.txt +1 -0
  239. package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/04-slice.txt +1 -0
  240. package/lib/vendor/blamejs/fuzz/safe-mime.fuzz.js +27 -0
  241. package/lib/vendor/blamejs/fuzz/safe-mount-info.fuzz.js +33 -0
  242. package/lib/vendor/blamejs/fuzz/safe-sieve.fuzz.js +28 -0
  243. package/lib/vendor/blamejs/fuzz/safe-smtp.fuzz.js +64 -0
  244. package/lib/vendor/blamejs/fuzz/safe-url.fuzz.js +16 -0
  245. package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/01-basic.txt +1 -0
  246. package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/02-userinfo.txt +1 -0
  247. package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/03-dangerous.txt +1 -0
  248. package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/04-data.txt +1 -0
  249. package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/05-ipv6.txt +1 -0
  250. package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/06-idn.txt +1 -0
  251. package/lib/vendor/blamejs/fuzz/safe-vcard.fuzz.js +16 -0
  252. package/lib/vendor/blamejs/index.js +678 -0
  253. package/lib/vendor/blamejs/keys/release-pqc-pub.json +7 -0
  254. package/lib/vendor/blamejs/lib/_test/crypto-fixtures.js +67 -0
  255. package/lib/vendor/blamejs/lib/a2a-tasks.js +598 -0
  256. package/lib/vendor/blamejs/lib/a2a.js +407 -0
  257. package/lib/vendor/blamejs/lib/acme.js +1448 -0
  258. package/lib/vendor/blamejs/lib/agent-audit.js +45 -0
  259. package/lib/vendor/blamejs/lib/agent-event-bus.js +382 -0
  260. package/lib/vendor/blamejs/lib/agent-idempotency.js +497 -0
  261. package/lib/vendor/blamejs/lib/agent-orchestrator.js +717 -0
  262. package/lib/vendor/blamejs/lib/agent-posture-chain.js +366 -0
  263. package/lib/vendor/blamejs/lib/agent-saga.js +321 -0
  264. package/lib/vendor/blamejs/lib/agent-snapshot.js +676 -0
  265. package/lib/vendor/blamejs/lib/agent-stream.js +269 -0
  266. package/lib/vendor/blamejs/lib/agent-tenant.js +632 -0
  267. package/lib/vendor/blamejs/lib/agent-trace.js +281 -0
  268. package/lib/vendor/blamejs/lib/ai-adverse-decision.js +184 -0
  269. package/lib/vendor/blamejs/lib/ai-content-detect.js +268 -0
  270. package/lib/vendor/blamejs/lib/ai-input.js +201 -0
  271. package/lib/vendor/blamejs/lib/ai-model-manifest.js +363 -0
  272. package/lib/vendor/blamejs/lib/ai-pref.js +340 -0
  273. package/lib/vendor/blamejs/lib/api-key.js +721 -0
  274. package/lib/vendor/blamejs/lib/api-snapshot.js +458 -0
  275. package/lib/vendor/blamejs/lib/app-shutdown.js +557 -0
  276. package/lib/vendor/blamejs/lib/app.js +365 -0
  277. package/lib/vendor/blamejs/lib/archive.js +547 -0
  278. package/lib/vendor/blamejs/lib/arg-parser.js +697 -0
  279. package/lib/vendor/blamejs/lib/argon2-builtin.js +173 -0
  280. package/lib/vendor/blamejs/lib/asn1-der.js +424 -0
  281. package/lib/vendor/blamejs/lib/asyncapi-bindings.js +160 -0
  282. package/lib/vendor/blamejs/lib/asyncapi-traits.js +143 -0
  283. package/lib/vendor/blamejs/lib/asyncapi.js +575 -0
  284. package/lib/vendor/blamejs/lib/atomic-file.js +1023 -0
  285. package/lib/vendor/blamejs/lib/audit-chain.js +266 -0
  286. package/lib/vendor/blamejs/lib/audit-daily-review.js +389 -0
  287. package/lib/vendor/blamejs/lib/audit-sign.js +751 -0
  288. package/lib/vendor/blamejs/lib/audit-tools.js +1113 -0
  289. package/lib/vendor/blamejs/lib/audit.js +1671 -0
  290. package/lib/vendor/blamejs/lib/auth/aal.js +169 -0
  291. package/lib/vendor/blamejs/lib/auth/access-lock.js +220 -0
  292. package/lib/vendor/blamejs/lib/auth/acr-vocabulary.js +265 -0
  293. package/lib/vendor/blamejs/lib/auth/ato-kill-switch.js +112 -0
  294. package/lib/vendor/blamejs/lib/auth/auth-time-tracker.js +111 -0
  295. package/lib/vendor/blamejs/lib/auth/bot-challenge.js +573 -0
  296. package/lib/vendor/blamejs/lib/auth/ciba.js +637 -0
  297. package/lib/vendor/blamejs/lib/auth/dpop.js +516 -0
  298. package/lib/vendor/blamejs/lib/auth/elevation-grant.js +306 -0
  299. package/lib/vendor/blamejs/lib/auth/fal.js +229 -0
  300. package/lib/vendor/blamejs/lib/auth/fido-mds3.js +681 -0
  301. package/lib/vendor/blamejs/lib/auth/jwt-external.js +519 -0
  302. package/lib/vendor/blamejs/lib/auth/jwt.js +430 -0
  303. package/lib/vendor/blamejs/lib/auth/lockout.js +449 -0
  304. package/lib/vendor/blamejs/lib/auth/oauth.js +2141 -0
  305. package/lib/vendor/blamejs/lib/auth/oid4vci.js +657 -0
  306. package/lib/vendor/blamejs/lib/auth/oid4vp.js +531 -0
  307. package/lib/vendor/blamejs/lib/auth/openid-federation.js +600 -0
  308. package/lib/vendor/blamejs/lib/auth/passkey.js +676 -0
  309. package/lib/vendor/blamejs/lib/auth/password.js +693 -0
  310. package/lib/vendor/blamejs/lib/auth/saml.js +2109 -0
  311. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-disclosure.js +95 -0
  312. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-holder.js +225 -0
  313. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-issuer.js +197 -0
  314. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +728 -0
  315. package/lib/vendor/blamejs/lib/auth/status-list.js +272 -0
  316. package/lib/vendor/blamejs/lib/auth/step-up-policy.js +335 -0
  317. package/lib/vendor/blamejs/lib/auth/step-up.js +454 -0
  318. package/lib/vendor/blamejs/lib/auth-bot-challenge.js +505 -0
  319. package/lib/vendor/blamejs/lib/auth-header.js +148 -0
  320. package/lib/vendor/blamejs/lib/backup/bundle.js +265 -0
  321. package/lib/vendor/blamejs/lib/backup/crypto.js +176 -0
  322. package/lib/vendor/blamejs/lib/backup/index.js +1001 -0
  323. package/lib/vendor/blamejs/lib/backup/manifest.js +443 -0
  324. package/lib/vendor/blamejs/lib/boot-gates.js +174 -0
  325. package/lib/vendor/blamejs/lib/breach-deadline.js +272 -0
  326. package/lib/vendor/blamejs/lib/break-glass.js +1753 -0
  327. package/lib/vendor/blamejs/lib/budr.js +205 -0
  328. package/lib/vendor/blamejs/lib/bundler.js +461 -0
  329. package/lib/vendor/blamejs/lib/cache-redis.js +256 -0
  330. package/lib/vendor/blamejs/lib/cache-status.js +288 -0
  331. package/lib/vendor/blamejs/lib/cache.js +1331 -0
  332. package/lib/vendor/blamejs/lib/calendar.js +1240 -0
  333. package/lib/vendor/blamejs/lib/canonical-json.js +143 -0
  334. package/lib/vendor/blamejs/lib/cdn-cache-control.js +473 -0
  335. package/lib/vendor/blamejs/lib/cert.js +763 -0
  336. package/lib/vendor/blamejs/lib/chain-writer.js +259 -0
  337. package/lib/vendor/blamejs/lib/circuit-breaker.js +101 -0
  338. package/lib/vendor/blamejs/lib/cli-helpers.js +237 -0
  339. package/lib/vendor/blamejs/lib/cli.js +2328 -0
  340. package/lib/vendor/blamejs/lib/client-hints.js +318 -0
  341. package/lib/vendor/blamejs/lib/cloud-events.js +277 -0
  342. package/lib/vendor/blamejs/lib/cluster-provider-db.js +317 -0
  343. package/lib/vendor/blamejs/lib/cluster-storage.js +351 -0
  344. package/lib/vendor/blamejs/lib/cluster.js +1017 -0
  345. package/lib/vendor/blamejs/lib/cms-codec.js +826 -0
  346. package/lib/vendor/blamejs/lib/codepoint-class.js +262 -0
  347. package/lib/vendor/blamejs/lib/compliance-ai-act-logging.js +190 -0
  348. package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +205 -0
  349. package/lib/vendor/blamejs/lib/compliance-ai-act-risk.js +189 -0
  350. package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +200 -0
  351. package/lib/vendor/blamejs/lib/compliance-ai-act.js +821 -0
  352. package/lib/vendor/blamejs/lib/compliance-eaa.js +204 -0
  353. package/lib/vendor/blamejs/lib/compliance-sanctions-aliases.js +167 -0
  354. package/lib/vendor/blamejs/lib/compliance-sanctions-fetcher.js +206 -0
  355. package/lib/vendor/blamejs/lib/compliance-sanctions-fuzzy.js +297 -0
  356. package/lib/vendor/blamejs/lib/compliance-sanctions.js +569 -0
  357. package/lib/vendor/blamejs/lib/compliance.js +1558 -0
  358. package/lib/vendor/blamejs/lib/config-drift.js +426 -0
  359. package/lib/vendor/blamejs/lib/config.js +446 -0
  360. package/lib/vendor/blamejs/lib/consent.js +369 -0
  361. package/lib/vendor/blamejs/lib/constants.js +209 -0
  362. package/lib/vendor/blamejs/lib/content-credentials.js +704 -0
  363. package/lib/vendor/blamejs/lib/cookies.js +560 -0
  364. package/lib/vendor/blamejs/lib/cra-report.js +299 -0
  365. package/lib/vendor/blamejs/lib/credential-hash.js +394 -0
  366. package/lib/vendor/blamejs/lib/crypto-field.js +1017 -0
  367. package/lib/vendor/blamejs/lib/crypto-hpke-pq.js +187 -0
  368. package/lib/vendor/blamejs/lib/crypto-hpke.js +256 -0
  369. package/lib/vendor/blamejs/lib/crypto.js +1908 -0
  370. package/lib/vendor/blamejs/lib/csp.js +271 -0
  371. package/lib/vendor/blamejs/lib/csv.js +418 -0
  372. package/lib/vendor/blamejs/lib/daemon.js +481 -0
  373. package/lib/vendor/blamejs/lib/dark-patterns.js +488 -0
  374. package/lib/vendor/blamejs/lib/data-act.js +328 -0
  375. package/lib/vendor/blamejs/lib/db-collection.js +587 -0
  376. package/lib/vendor/blamejs/lib/db-declare-row-policy.js +267 -0
  377. package/lib/vendor/blamejs/lib/db-declare-view.js +420 -0
  378. package/lib/vendor/blamejs/lib/db-file-lifecycle.js +333 -0
  379. package/lib/vendor/blamejs/lib/db-query.js +802 -0
  380. package/lib/vendor/blamejs/lib/db-role-context.js +50 -0
  381. package/lib/vendor/blamejs/lib/db-schema.js +322 -0
  382. package/lib/vendor/blamejs/lib/db.js +3111 -0
  383. package/lib/vendor/blamejs/lib/dbsc.js +299 -0
  384. package/lib/vendor/blamejs/lib/ddl-change-control.js +523 -0
  385. package/lib/vendor/blamejs/lib/deprecate.js +377 -0
  386. package/lib/vendor/blamejs/lib/dev.js +405 -0
  387. package/lib/vendor/blamejs/lib/dora.js +402 -0
  388. package/lib/vendor/blamejs/lib/dr-runbook.js +368 -0
  389. package/lib/vendor/blamejs/lib/dsr.js +1188 -0
  390. package/lib/vendor/blamejs/lib/dual-control.js +526 -0
  391. package/lib/vendor/blamejs/lib/early-hints.js +212 -0
  392. package/lib/vendor/blamejs/lib/error-page.js +420 -0
  393. package/lib/vendor/blamejs/lib/events.js +214 -0
  394. package/lib/vendor/blamejs/lib/external-db-migrate.js +659 -0
  395. package/lib/vendor/blamejs/lib/external-db.js +1877 -0
  396. package/lib/vendor/blamejs/lib/fapi2.js +394 -0
  397. package/lib/vendor/blamejs/lib/fda-21cfr11.js +395 -0
  398. package/lib/vendor/blamejs/lib/fdx.js +370 -0
  399. package/lib/vendor/blamejs/lib/fedcm.js +264 -0
  400. package/lib/vendor/blamejs/lib/file-type.js +360 -0
  401. package/lib/vendor/blamejs/lib/file-upload.js +1256 -0
  402. package/lib/vendor/blamejs/lib/flag-cache.js +136 -0
  403. package/lib/vendor/blamejs/lib/flag-evaluation-context.js +135 -0
  404. package/lib/vendor/blamejs/lib/flag-providers.js +279 -0
  405. package/lib/vendor/blamejs/lib/flag-targeting.js +210 -0
  406. package/lib/vendor/blamejs/lib/flag.js +346 -0
  407. package/lib/vendor/blamejs/lib/forms.js +525 -0
  408. package/lib/vendor/blamejs/lib/framework-error.js +724 -0
  409. package/lib/vendor/blamejs/lib/framework-schema.js +845 -0
  410. package/lib/vendor/blamejs/lib/framework-sha1-hibp.js +34 -0
  411. package/lib/vendor/blamejs/lib/fsm.js +469 -0
  412. package/lib/vendor/blamejs/lib/gate-contract.js +1661 -0
  413. package/lib/vendor/blamejs/lib/gdpr-ropa.js +261 -0
  414. package/lib/vendor/blamejs/lib/graphql-federation.js +234 -0
  415. package/lib/vendor/blamejs/lib/guard-agent-registry.js +179 -0
  416. package/lib/vendor/blamejs/lib/guard-all.js +555 -0
  417. package/lib/vendor/blamejs/lib/guard-archive.js +901 -0
  418. package/lib/vendor/blamejs/lib/guard-auth.js +451 -0
  419. package/lib/vendor/blamejs/lib/guard-cidr.js +676 -0
  420. package/lib/vendor/blamejs/lib/guard-csv.js +1176 -0
  421. package/lib/vendor/blamejs/lib/guard-domain.js +814 -0
  422. package/lib/vendor/blamejs/lib/guard-dsn.js +382 -0
  423. package/lib/vendor/blamejs/lib/guard-email.js +951 -0
  424. package/lib/vendor/blamejs/lib/guard-envelope.js +294 -0
  425. package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +217 -0
  426. package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +150 -0
  427. package/lib/vendor/blamejs/lib/guard-filename.js +956 -0
  428. package/lib/vendor/blamejs/lib/guard-graphql.js +731 -0
  429. package/lib/vendor/blamejs/lib/guard-html-wcag-aria.js +164 -0
  430. package/lib/vendor/blamejs/lib/guard-html-wcag-forms.js +144 -0
  431. package/lib/vendor/blamejs/lib/guard-html-wcag-tables.js +154 -0
  432. package/lib/vendor/blamejs/lib/guard-html-wcag-tagwalk.js +44 -0
  433. package/lib/vendor/blamejs/lib/guard-html-wcag.js +470 -0
  434. package/lib/vendor/blamejs/lib/guard-html.js +1209 -0
  435. package/lib/vendor/blamejs/lib/guard-idempotency-key.js +151 -0
  436. package/lib/vendor/blamejs/lib/guard-image.js +584 -0
  437. package/lib/vendor/blamejs/lib/guard-imap-command.js +337 -0
  438. package/lib/vendor/blamejs/lib/guard-jmap.js +321 -0
  439. package/lib/vendor/blamejs/lib/guard-json.js +935 -0
  440. package/lib/vendor/blamejs/lib/guard-jsonpath.js +512 -0
  441. package/lib/vendor/blamejs/lib/guard-jwt.js +772 -0
  442. package/lib/vendor/blamejs/lib/guard-list-id.js +318 -0
  443. package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +412 -0
  444. package/lib/vendor/blamejs/lib/guard-mail-compose.js +282 -0
  445. package/lib/vendor/blamejs/lib/guard-mail-move.js +202 -0
  446. package/lib/vendor/blamejs/lib/guard-mail-query.js +310 -0
  447. package/lib/vendor/blamejs/lib/guard-mail-reply.js +172 -0
  448. package/lib/vendor/blamejs/lib/guard-mail-sieve.js +207 -0
  449. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +566 -0
  450. package/lib/vendor/blamejs/lib/guard-markdown.js +768 -0
  451. package/lib/vendor/blamejs/lib/guard-message-id.js +267 -0
  452. package/lib/vendor/blamejs/lib/guard-mime.js +609 -0
  453. package/lib/vendor/blamejs/lib/guard-oauth.js +650 -0
  454. package/lib/vendor/blamejs/lib/guard-pdf.js +569 -0
  455. package/lib/vendor/blamejs/lib/guard-pop3-command.js +317 -0
  456. package/lib/vendor/blamejs/lib/guard-posture-chain.js +201 -0
  457. package/lib/vendor/blamejs/lib/guard-regex.js +632 -0
  458. package/lib/vendor/blamejs/lib/guard-saga-config.js +157 -0
  459. package/lib/vendor/blamejs/lib/guard-shell.js +522 -0
  460. package/lib/vendor/blamejs/lib/guard-smtp-command.js +594 -0
  461. package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +168 -0
  462. package/lib/vendor/blamejs/lib/guard-stream-args.js +166 -0
  463. package/lib/vendor/blamejs/lib/guard-svg.js +1163 -0
  464. package/lib/vendor/blamejs/lib/guard-template.js +490 -0
  465. package/lib/vendor/blamejs/lib/guard-tenant-id.js +138 -0
  466. package/lib/vendor/blamejs/lib/guard-time.js +586 -0
  467. package/lib/vendor/blamejs/lib/guard-trace-context.js +172 -0
  468. package/lib/vendor/blamejs/lib/guard-uuid.js +548 -0
  469. package/lib/vendor/blamejs/lib/guard-xml.js +666 -0
  470. package/lib/vendor/blamejs/lib/guard-yaml.js +726 -0
  471. package/lib/vendor/blamejs/lib/hal.js +125 -0
  472. package/lib/vendor/blamejs/lib/handlers.js +350 -0
  473. package/lib/vendor/blamejs/lib/honeytoken.js +168 -0
  474. package/lib/vendor/blamejs/lib/html-balance.js +347 -0
  475. package/lib/vendor/blamejs/lib/http-client-cache.js +923 -0
  476. package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +519 -0
  477. package/lib/vendor/blamejs/lib/http-client.js +2152 -0
  478. package/lib/vendor/blamejs/lib/http-message-signature.js +589 -0
  479. package/lib/vendor/blamejs/lib/http2-teardown.js +34 -0
  480. package/lib/vendor/blamejs/lib/i18n-messageformat.js +398 -0
  481. package/lib/vendor/blamejs/lib/i18n.js +931 -0
  482. package/lib/vendor/blamejs/lib/iab-mspa.js +257 -0
  483. package/lib/vendor/blamejs/lib/iab-tcf.js +461 -0
  484. package/lib/vendor/blamejs/lib/importmap-integrity.js +90 -0
  485. package/lib/vendor/blamejs/lib/inbox.js +435 -0
  486. package/lib/vendor/blamejs/lib/incident-report.js +314 -0
  487. package/lib/vendor/blamejs/lib/ip-utils.js +102 -0
  488. package/lib/vendor/blamejs/lib/jobs.js +185 -0
  489. package/lib/vendor/blamejs/lib/jose-jwe-experimental.js +228 -0
  490. package/lib/vendor/blamejs/lib/jsonapi.js +230 -0
  491. package/lib/vendor/blamejs/lib/keychain.js +865 -0
  492. package/lib/vendor/blamejs/lib/lazy-require.js +48 -0
  493. package/lib/vendor/blamejs/lib/legal-hold.js +374 -0
  494. package/lib/vendor/blamejs/lib/local-db-thin.js +321 -0
  495. package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +369 -0
  496. package/lib/vendor/blamejs/lib/log-stream-local.js +146 -0
  497. package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +410 -0
  498. package/lib/vendor/blamejs/lib/log-stream-otlp.js +286 -0
  499. package/lib/vendor/blamejs/lib/log-stream-syslog.js +310 -0
  500. package/lib/vendor/blamejs/lib/log-stream-webhook.js +199 -0
  501. package/lib/vendor/blamejs/lib/log-stream.js +584 -0
  502. package/lib/vendor/blamejs/lib/log.js +625 -0
  503. package/lib/vendor/blamejs/lib/lro.js +200 -0
  504. package/lib/vendor/blamejs/lib/mail-agent.js +786 -0
  505. package/lib/vendor/blamejs/lib/mail-arc-sign.js +417 -0
  506. package/lib/vendor/blamejs/lib/mail-arf.js +343 -0
  507. package/lib/vendor/blamejs/lib/mail-auth.js +2144 -0
  508. package/lib/vendor/blamejs/lib/mail-bimi.js +1047 -0
  509. package/lib/vendor/blamejs/lib/mail-bounce.js +955 -0
  510. package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +1286 -0
  511. package/lib/vendor/blamejs/lib/mail-crypto-smime.js +789 -0
  512. package/lib/vendor/blamejs/lib/mail-crypto.js +108 -0
  513. package/lib/vendor/blamejs/lib/mail-dav.js +1224 -0
  514. package/lib/vendor/blamejs/lib/mail-deploy.js +1119 -0
  515. package/lib/vendor/blamejs/lib/mail-dkim.js +1250 -0
  516. package/lib/vendor/blamejs/lib/mail-greylist.js +448 -0
  517. package/lib/vendor/blamejs/lib/mail-helo.js +473 -0
  518. package/lib/vendor/blamejs/lib/mail-journal.js +435 -0
  519. package/lib/vendor/blamejs/lib/mail-mdn.js +424 -0
  520. package/lib/vendor/blamejs/lib/mail-rbl.js +392 -0
  521. package/lib/vendor/blamejs/lib/mail-require-tls.js +198 -0
  522. package/lib/vendor/blamejs/lib/mail-scan.js +502 -0
  523. package/lib/vendor/blamejs/lib/mail-send-deliver.js +629 -0
  524. package/lib/vendor/blamejs/lib/mail-server-imap.js +1858 -0
  525. package/lib/vendor/blamejs/lib/mail-server-jmap.js +1565 -0
  526. package/lib/vendor/blamejs/lib/mail-server-managesieve.js +908 -0
  527. package/lib/vendor/blamejs/lib/mail-server-mx.js +969 -0
  528. package/lib/vendor/blamejs/lib/mail-server-pop3.js +915 -0
  529. package/lib/vendor/blamejs/lib/mail-server-rate-limit.js +315 -0
  530. package/lib/vendor/blamejs/lib/mail-server-registry.js +378 -0
  531. package/lib/vendor/blamejs/lib/mail-server-submission.js +1396 -0
  532. package/lib/vendor/blamejs/lib/mail-server-tls.js +445 -0
  533. package/lib/vendor/blamejs/lib/mail-sieve.js +557 -0
  534. package/lib/vendor/blamejs/lib/mail-spam-score.js +284 -0
  535. package/lib/vendor/blamejs/lib/mail-srs.js +248 -0
  536. package/lib/vendor/blamejs/lib/mail-store-fts.js +394 -0
  537. package/lib/vendor/blamejs/lib/mail-store.js +929 -0
  538. package/lib/vendor/blamejs/lib/mail-unsubscribe.js +400 -0
  539. package/lib/vendor/blamejs/lib/mail.js +1971 -0
  540. package/lib/vendor/blamejs/lib/mcp-tool-registry.js +473 -0
  541. package/lib/vendor/blamejs/lib/mcp.js +950 -0
  542. package/lib/vendor/blamejs/lib/metrics.js +1503 -0
  543. package/lib/vendor/blamejs/lib/middleware/age-gate.js +177 -0
  544. package/lib/vendor/blamejs/lib/middleware/ai-act-disclosure.js +203 -0
  545. package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +981 -0
  546. package/lib/vendor/blamejs/lib/middleware/assetlinks.js +137 -0
  547. package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +171 -0
  548. package/lib/vendor/blamejs/lib/middleware/attach-user.js +220 -0
  549. package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +293 -0
  550. package/lib/vendor/blamejs/lib/middleware/body-parser.js +1519 -0
  551. package/lib/vendor/blamejs/lib/middleware/bot-disclose.js +183 -0
  552. package/lib/vendor/blamejs/lib/middleware/bot-guard.js +217 -0
  553. package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +122 -0
  554. package/lib/vendor/blamejs/lib/middleware/compose-pipeline.js +355 -0
  555. package/lib/vendor/blamejs/lib/middleware/compression.js +489 -0
  556. package/lib/vendor/blamejs/lib/middleware/cookies.js +130 -0
  557. package/lib/vendor/blamejs/lib/middleware/cors.js +386 -0
  558. package/lib/vendor/blamejs/lib/middleware/csp-nonce.js +388 -0
  559. package/lib/vendor/blamejs/lib/middleware/csp-report.js +167 -0
  560. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +499 -0
  561. package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +243 -0
  562. package/lib/vendor/blamejs/lib/middleware/db-role-for.js +304 -0
  563. package/lib/vendor/blamejs/lib/middleware/dpop.js +402 -0
  564. package/lib/vendor/blamejs/lib/middleware/error-handler.js +69 -0
  565. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +168 -0
  566. package/lib/vendor/blamejs/lib/middleware/flag-context.js +110 -0
  567. package/lib/vendor/blamejs/lib/middleware/gpc.js +153 -0
  568. package/lib/vendor/blamejs/lib/middleware/headers.js +242 -0
  569. package/lib/vendor/blamejs/lib/middleware/health.js +438 -0
  570. package/lib/vendor/blamejs/lib/middleware/host-allowlist.js +189 -0
  571. package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +964 -0
  572. package/lib/vendor/blamejs/lib/middleware/index.js +183 -0
  573. package/lib/vendor/blamejs/lib/middleware/nel.js +214 -0
  574. package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +237 -0
  575. package/lib/vendor/blamejs/lib/middleware/no-cache.js +106 -0
  576. package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +177 -0
  577. package/lib/vendor/blamejs/lib/middleware/protected-resource-metadata.js +277 -0
  578. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +556 -0
  579. package/lib/vendor/blamejs/lib/middleware/request-id.js +79 -0
  580. package/lib/vendor/blamejs/lib/middleware/request-log.js +205 -0
  581. package/lib/vendor/blamejs/lib/middleware/require-aal.js +138 -0
  582. package/lib/vendor/blamejs/lib/middleware/require-auth.js +144 -0
  583. package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +290 -0
  584. package/lib/vendor/blamejs/lib/middleware/require-content-type.js +113 -0
  585. package/lib/vendor/blamejs/lib/middleware/require-methods.js +97 -0
  586. package/lib/vendor/blamejs/lib/middleware/require-mtls.js +212 -0
  587. package/lib/vendor/blamejs/lib/middleware/require-step-up.js +226 -0
  588. package/lib/vendor/blamejs/lib/middleware/scim-server.js +375 -0
  589. package/lib/vendor/blamejs/lib/middleware/security-headers.js +285 -0
  590. package/lib/vendor/blamejs/lib/middleware/security-txt.js +170 -0
  591. package/lib/vendor/blamejs/lib/middleware/span-http-server.js +280 -0
  592. package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +323 -0
  593. package/lib/vendor/blamejs/lib/middleware/sse.js +200 -0
  594. package/lib/vendor/blamejs/lib/middleware/trace-log-correlation.js +167 -0
  595. package/lib/vendor/blamejs/lib/middleware/trace-propagate.js +148 -0
  596. package/lib/vendor/blamejs/lib/middleware/tus-upload.js +749 -0
  597. package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +164 -0
  598. package/lib/vendor/blamejs/lib/migration-files.js +37 -0
  599. package/lib/vendor/blamejs/lib/migrations.js +385 -0
  600. package/lib/vendor/blamejs/lib/mime-parse.js +198 -0
  601. package/lib/vendor/blamejs/lib/money.js +699 -0
  602. package/lib/vendor/blamejs/lib/mtls-ca.js +572 -0
  603. package/lib/vendor/blamejs/lib/mtls-engine-default.js +501 -0
  604. package/lib/vendor/blamejs/lib/network-byte-quota.js +308 -0
  605. package/lib/vendor/blamejs/lib/network-dns-resolver.js +533 -0
  606. package/lib/vendor/blamejs/lib/network-dns.js +1930 -0
  607. package/lib/vendor/blamejs/lib/network-heartbeat.js +425 -0
  608. package/lib/vendor/blamejs/lib/network-nts.js +574 -0
  609. package/lib/vendor/blamejs/lib/network-proxy.js +265 -0
  610. package/lib/vendor/blamejs/lib/network-smtp-policy.js +836 -0
  611. package/lib/vendor/blamejs/lib/network-tls.js +3126 -0
  612. package/lib/vendor/blamejs/lib/network.js +346 -0
  613. package/lib/vendor/blamejs/lib/nis2-report.js +181 -0
  614. package/lib/vendor/blamejs/lib/nist-crosswalk.js +293 -0
  615. package/lib/vendor/blamejs/lib/nonce-store.js +177 -0
  616. package/lib/vendor/blamejs/lib/notify.js +683 -0
  617. package/lib/vendor/blamejs/lib/ntp-check.js +458 -0
  618. package/lib/vendor/blamejs/lib/numeric-bounds.js +111 -0
  619. package/lib/vendor/blamejs/lib/numeric-checks.js +40 -0
  620. package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +349 -0
  621. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +488 -0
  622. package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +351 -0
  623. package/lib/vendor/blamejs/lib/object-store/gcs.js +515 -0
  624. package/lib/vendor/blamejs/lib/object-store/http-put.js +153 -0
  625. package/lib/vendor/blamejs/lib/object-store/http-request.js +38 -0
  626. package/lib/vendor/blamejs/lib/object-store/index.js +197 -0
  627. package/lib/vendor/blamejs/lib/object-store/local.js +163 -0
  628. package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +1133 -0
  629. package/lib/vendor/blamejs/lib/object-store/sigv4.js +957 -0
  630. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +420 -0
  631. package/lib/vendor/blamejs/lib/observability-tracer.js +395 -0
  632. package/lib/vendor/blamejs/lib/observability.js +720 -0
  633. package/lib/vendor/blamejs/lib/openapi-paths-builder.js +248 -0
  634. package/lib/vendor/blamejs/lib/openapi-schema-walk.js +192 -0
  635. package/lib/vendor/blamejs/lib/openapi-security.js +169 -0
  636. package/lib/vendor/blamejs/lib/openapi-yaml.js +154 -0
  637. package/lib/vendor/blamejs/lib/openapi.js +489 -0
  638. package/lib/vendor/blamejs/lib/otel-export.js +278 -0
  639. package/lib/vendor/blamejs/lib/outbox.js +547 -0
  640. package/lib/vendor/blamejs/lib/pagination.js +542 -0
  641. package/lib/vendor/blamejs/lib/parsers/index.js +91 -0
  642. package/lib/vendor/blamejs/lib/parsers/safe-env.js +642 -0
  643. package/lib/vendor/blamejs/lib/parsers/safe-ini.js +293 -0
  644. package/lib/vendor/blamejs/lib/parsers/safe-toml.js +784 -0
  645. package/lib/vendor/blamejs/lib/parsers/safe-xml.js +390 -0
  646. package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +1015 -0
  647. package/lib/vendor/blamejs/lib/permissions.js +793 -0
  648. package/lib/vendor/blamejs/lib/pick.js +105 -0
  649. package/lib/vendor/blamejs/lib/pqc-agent.js +351 -0
  650. package/lib/vendor/blamejs/lib/pqc-gate.js +279 -0
  651. package/lib/vendor/blamejs/lib/pqc-software.js +271 -0
  652. package/lib/vendor/blamejs/lib/problem-details.js +482 -0
  653. package/lib/vendor/blamejs/lib/process-spawn.js +196 -0
  654. package/lib/vendor/blamejs/lib/promise-pool.js +162 -0
  655. package/lib/vendor/blamejs/lib/protobuf-encoder.js +190 -0
  656. package/lib/vendor/blamejs/lib/protocol-dispatcher.js +161 -0
  657. package/lib/vendor/blamejs/lib/public-suffix.js +403 -0
  658. package/lib/vendor/blamejs/lib/pubsub-cluster.js +154 -0
  659. package/lib/vendor/blamejs/lib/pubsub-redis.js +167 -0
  660. package/lib/vendor/blamejs/lib/pubsub.js +463 -0
  661. package/lib/vendor/blamejs/lib/queue-local.js +476 -0
  662. package/lib/vendor/blamejs/lib/queue-redis.js +745 -0
  663. package/lib/vendor/blamejs/lib/queue-sqs.js +319 -0
  664. package/lib/vendor/blamejs/lib/queue.js +1016 -0
  665. package/lib/vendor/blamejs/lib/redact.js +1007 -0
  666. package/lib/vendor/blamejs/lib/redis-client.js +520 -0
  667. package/lib/vendor/blamejs/lib/render.js +285 -0
  668. package/lib/vendor/blamejs/lib/request-helpers.js +767 -0
  669. package/lib/vendor/blamejs/lib/resource-access-lock.js +116 -0
  670. package/lib/vendor/blamejs/lib/restore-bundle.js +340 -0
  671. package/lib/vendor/blamejs/lib/restore-rollback.js +365 -0
  672. package/lib/vendor/blamejs/lib/restore.js +409 -0
  673. package/lib/vendor/blamejs/lib/retention.js +640 -0
  674. package/lib/vendor/blamejs/lib/retry.js +523 -0
  675. package/lib/vendor/blamejs/lib/router.js +1289 -0
  676. package/lib/vendor/blamejs/lib/safe-async.js +1184 -0
  677. package/lib/vendor/blamejs/lib/safe-buffer.js +562 -0
  678. package/lib/vendor/blamejs/lib/safe-decompress.js +297 -0
  679. package/lib/vendor/blamejs/lib/safe-dns.js +665 -0
  680. package/lib/vendor/blamejs/lib/safe-ical.js +634 -0
  681. package/lib/vendor/blamejs/lib/safe-icap.js +502 -0
  682. package/lib/vendor/blamejs/lib/safe-json.js +946 -0
  683. package/lib/vendor/blamejs/lib/safe-jsonpath.js +285 -0
  684. package/lib/vendor/blamejs/lib/safe-mime.js +831 -0
  685. package/lib/vendor/blamejs/lib/safe-mount-info.js +306 -0
  686. package/lib/vendor/blamejs/lib/safe-path.js +254 -0
  687. package/lib/vendor/blamejs/lib/safe-redirect.js +106 -0
  688. package/lib/vendor/blamejs/lib/safe-schema.js +1810 -0
  689. package/lib/vendor/blamejs/lib/safe-sieve.js +684 -0
  690. package/lib/vendor/blamejs/lib/safe-smtp.js +185 -0
  691. package/lib/vendor/blamejs/lib/safe-sql.js +363 -0
  692. package/lib/vendor/blamejs/lib/safe-url.js +428 -0
  693. package/lib/vendor/blamejs/lib/safe-vcard.js +473 -0
  694. package/lib/vendor/blamejs/lib/sandbox-worker.js +135 -0
  695. package/lib/vendor/blamejs/lib/sandbox.js +358 -0
  696. package/lib/vendor/blamejs/lib/scheduler.js +827 -0
  697. package/lib/vendor/blamejs/lib/sd-notify.js +269 -0
  698. package/lib/vendor/blamejs/lib/sec-cyber.js +214 -0
  699. package/lib/vendor/blamejs/lib/security-assert.js +395 -0
  700. package/lib/vendor/blamejs/lib/seeders.js +620 -0
  701. package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +309 -0
  702. package/lib/vendor/blamejs/lib/self-update.js +804 -0
  703. package/lib/vendor/blamejs/lib/server-timing.js +174 -0
  704. package/lib/vendor/blamejs/lib/session-device-binding.js +431 -0
  705. package/lib/vendor/blamejs/lib/session-stores.js +138 -0
  706. package/lib/vendor/blamejs/lib/session.js +1162 -0
  707. package/lib/vendor/blamejs/lib/slug.js +381 -0
  708. package/lib/vendor/blamejs/lib/sse.js +349 -0
  709. package/lib/vendor/blamejs/lib/ssrf-guard.js +792 -0
  710. package/lib/vendor/blamejs/lib/standard-webhooks.js +183 -0
  711. package/lib/vendor/blamejs/lib/static.js +1249 -0
  712. package/lib/vendor/blamejs/lib/storage.js +1272 -0
  713. package/lib/vendor/blamejs/lib/stream-throttle.js +235 -0
  714. package/lib/vendor/blamejs/lib/structured-fields.js +244 -0
  715. package/lib/vendor/blamejs/lib/subject.js +667 -0
  716. package/lib/vendor/blamejs/lib/tcpa-10dlc.js +175 -0
  717. package/lib/vendor/blamejs/lib/template.js +931 -0
  718. package/lib/vendor/blamejs/lib/tenant-quota.js +545 -0
  719. package/lib/vendor/blamejs/lib/test-harness.js +275 -0
  720. package/lib/vendor/blamejs/lib/testing.js +1185 -0
  721. package/lib/vendor/blamejs/lib/time.js +578 -0
  722. package/lib/vendor/blamejs/lib/tls-exporter.js +239 -0
  723. package/lib/vendor/blamejs/lib/totp.js +318 -0
  724. package/lib/vendor/blamejs/lib/tracing.js +546 -0
  725. package/lib/vendor/blamejs/lib/uuid.js +207 -0
  726. package/lib/vendor/blamejs/lib/validate-opts.js +381 -0
  727. package/lib/vendor/blamejs/lib/vault/index.js +638 -0
  728. package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +311 -0
  729. package/lib/vendor/blamejs/lib/vault/passphrase-source.js +198 -0
  730. package/lib/vendor/blamejs/lib/vault/rotate.js +803 -0
  731. package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +471 -0
  732. package/lib/vendor/blamejs/lib/vault/wrap.js +296 -0
  733. package/lib/vendor/blamejs/lib/vault-aad.js +259 -0
  734. package/lib/vendor/blamejs/lib/vendor/.vendor-data-pubkey +4 -0
  735. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +161 -0
  736. package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.data.js +68 -0
  737. package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.pem +33 -0
  738. package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.data.js +1325 -0
  739. package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.txt +10002 -0
  740. package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +9 -0
  741. package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +18 -0
  742. package/lib/vendor/blamejs/lib/vendor/pki.cjs +181 -0
  743. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +16382 -0
  744. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5881 -0
  745. package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +328 -0
  746. package/lib/vendor/blamejs/lib/vendor/vendor-data-pubkey.js +16 -0
  747. package/lib/vendor/blamejs/lib/vendor-data.js +520 -0
  748. package/lib/vendor/blamejs/lib/vex.js +630 -0
  749. package/lib/vendor/blamejs/lib/watcher.js +608 -0
  750. package/lib/vendor/blamejs/lib/web-push-vapid.js +322 -0
  751. package/lib/vendor/blamejs/lib/webhook.js +977 -0
  752. package/lib/vendor/blamejs/lib/websocket-channels.js +327 -0
  753. package/lib/vendor/blamejs/lib/websocket.js +1561 -0
  754. package/lib/vendor/blamejs/lib/wiki-concepts.js +338 -0
  755. package/lib/vendor/blamejs/lib/worker-pool.js +464 -0
  756. package/lib/vendor/blamejs/lib/ws-client.js +978 -0
  757. package/lib/vendor/blamejs/lib/xml-c14n.js +506 -0
  758. package/lib/vendor/blamejs/memory/specs/node-26-map-getorinsert-migration.md +164 -0
  759. package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +19 -0
  760. package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/README.md +88 -0
  761. package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/build.sh +26 -0
  762. package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/project.yaml +28 -0
  763. package/lib/vendor/blamejs/package.json +81 -0
  764. package/lib/vendor/blamejs/release-notes/v0.0.x.json +310 -0
  765. package/lib/vendor/blamejs/release-notes/v0.1.x.json +1798 -0
  766. package/lib/vendor/blamejs/release-notes/v0.10.x.json +1288 -0
  767. package/lib/vendor/blamejs/release-notes/v0.11.x.json +2551 -0
  768. package/lib/vendor/blamejs/release-notes/v0.12.0.json +64 -0
  769. package/lib/vendor/blamejs/release-notes/v0.12.1.json +32 -0
  770. package/lib/vendor/blamejs/release-notes/v0.12.2.json +45 -0
  771. package/lib/vendor/blamejs/release-notes/v0.2.x.json +706 -0
  772. package/lib/vendor/blamejs/release-notes/v0.3.x.json +786 -0
  773. package/lib/vendor/blamejs/release-notes/v0.4.x.json +588 -0
  774. package/lib/vendor/blamejs/release-notes/v0.5.x.json +390 -0
  775. package/lib/vendor/blamejs/release-notes/v0.6.x.json +1947 -0
  776. package/lib/vendor/blamejs/release-notes/v0.7.x.json +3811 -0
  777. package/lib/vendor/blamejs/release-notes/v0.8.x.json +3318 -0
  778. package/lib/vendor/blamejs/release-notes/v0.9.x.json +2257 -0
  779. package/lib/vendor/blamejs/scripts/build-vendored-sbom.js +325 -0
  780. package/lib/vendor/blamejs/scripts/check-api-snapshot.js +62 -0
  781. package/lib/vendor/blamejs/scripts/check-changelog-extract.js +108 -0
  782. package/lib/vendor/blamejs/scripts/check-pack-against-gitignore.js +83 -0
  783. package/lib/vendor/blamejs/scripts/check-services.js +483 -0
  784. package/lib/vendor/blamejs/scripts/check-vendor-currency.js +349 -0
  785. package/lib/vendor/blamejs/scripts/consolidate-release-notes.js +216 -0
  786. package/lib/vendor/blamejs/scripts/gen-migrating.js +275 -0
  787. package/lib/vendor/blamejs/scripts/generate-changelog-entry.js +577 -0
  788. package/lib/vendor/blamejs/scripts/generate-release-signing-key.js +79 -0
  789. package/lib/vendor/blamejs/scripts/publish-dep-confusion-placeholder.sh +101 -0
  790. package/lib/vendor/blamejs/scripts/refresh-api-snapshot.js +31 -0
  791. package/lib/vendor/blamejs/scripts/refresh-vendor-manifest.js +132 -0
  792. package/lib/vendor/blamejs/scripts/release.js +652 -0
  793. package/lib/vendor/blamejs/scripts/sha3-digest.js +62 -0
  794. package/lib/vendor/blamejs/scripts/sign-release-artifact.js +92 -0
  795. package/lib/vendor/blamejs/scripts/test-integration.js +181 -0
  796. package/lib/vendor/blamejs/scripts/test-wiki-integration.js +126 -0
  797. package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +77 -0
  798. package/lib/vendor/blamejs/scripts/vendor-data-gen.js +186 -0
  799. package/lib/vendor/blamejs/scripts/vendor-data-keygen.js +101 -0
  800. package/lib/vendor/blamejs/scripts/vendor-update.sh +278 -0
  801. package/lib/vendor/blamejs/test/00-primitives.js +19075 -0
  802. package/lib/vendor/blamejs/test/10-state.js +622 -0
  803. package/lib/vendor/blamejs/test/20-db.js +561 -0
  804. package/lib/vendor/blamejs/test/30-chain.js +2110 -0
  805. package/lib/vendor/blamejs/test/40-consumers.js +2453 -0
  806. package/lib/vendor/blamejs/test/50-integration.js +486 -0
  807. package/lib/vendor/blamejs/test/_helpers.js +10 -0
  808. package/lib/vendor/blamejs/test/_smoke-worker.js +69 -0
  809. package/lib/vendor/blamejs/test/fixtures/exploit-corpus/corpus.json +368 -0
  810. package/lib/vendor/blamejs/test/fixtures/http-client-stream-payload.txt +2 -0
  811. package/lib/vendor/blamejs/test/fixtures/worker-pool/echo.js +52 -0
  812. package/lib/vendor/blamejs/test/helpers/_codebase-shingle-worker.js +24 -0
  813. package/lib/vendor/blamejs/test/helpers/_codebase-shingle.js +203 -0
  814. package/lib/vendor/blamejs/test/helpers/_shape-match.js +513 -0
  815. package/lib/vendor/blamejs/test/helpers/check.js +36 -0
  816. package/lib/vendor/blamejs/test/helpers/cluster.js +70 -0
  817. package/lib/vendor/blamejs/test/helpers/db.js +143 -0
  818. package/lib/vendor/blamejs/test/helpers/drivers.js +207 -0
  819. package/lib/vendor/blamejs/test/helpers/fs-watch.js +101 -0
  820. package/lib/vendor/blamejs/test/helpers/http.js +14 -0
  821. package/lib/vendor/blamejs/test/helpers/index.js +93 -0
  822. package/lib/vendor/blamejs/test/helpers/json-round-trip.js +120 -0
  823. package/lib/vendor/blamejs/test/helpers/mocks.js +20 -0
  824. package/lib/vendor/blamejs/test/helpers/otel.js +13 -0
  825. package/lib/vendor/blamejs/test/helpers/services.js +380 -0
  826. package/lib/vendor/blamejs/test/helpers/wait.js +206 -0
  827. package/lib/vendor/blamejs/test/integration/cache.test.js +235 -0
  828. package/lib/vendor/blamejs/test/integration/cluster-provider-mysql.test.js +174 -0
  829. package/lib/vendor/blamejs/test/integration/federation-auth.test.js +611 -0
  830. package/lib/vendor/blamejs/test/integration/http-client.test.js +129 -0
  831. package/lib/vendor/blamejs/test/integration/log-stream.test.js +219 -0
  832. package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +181 -0
  833. package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +152 -0
  834. package/lib/vendor/blamejs/test/integration/mail-smtp.test.js +161 -0
  835. package/lib/vendor/blamejs/test/integration/mtls-ca.test.js +289 -0
  836. package/lib/vendor/blamejs/test/integration/network-dns.test.js +123 -0
  837. package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +101 -0
  838. package/lib/vendor/blamejs/test/integration/ntp-check.test.js +89 -0
  839. package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +403 -0
  840. package/lib/vendor/blamejs/test/integration/pqc-pkcs8-forward-compat.test.js +271 -0
  841. package/lib/vendor/blamejs/test/integration/pubsub.test.js +137 -0
  842. package/lib/vendor/blamejs/test/integration/queue-redis.test.js +352 -0
  843. package/lib/vendor/blamejs/test/integration/redis-client-tls.test.js +96 -0
  844. package/lib/vendor/blamejs/test/integration/ssrf-guard.test.js +98 -0
  845. package/lib/vendor/blamejs/test/integration/websocket-permessage-deflate.test.js +261 -0
  846. package/lib/vendor/blamejs/test/integration/ws-client-roundtrip.test.js +230 -0
  847. package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +211 -0
  848. package/lib/vendor/blamejs/test/layer-0-primitives/a2a.test.js +59 -0
  849. package/lib/vendor/blamejs/test/layer-0-primitives/access-lock.test.js +136 -0
  850. package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +219 -0
  851. package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +69 -0
  852. package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +266 -0
  853. package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +262 -0
  854. package/lib/vendor/blamejs/test/layer-0-primitives/agent-orchestrator.test.js +390 -0
  855. package/lib/vendor/blamejs/test/layer-0-primitives/agent-posture-chain.test.js +174 -0
  856. package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +279 -0
  857. package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +322 -0
  858. package/lib/vendor/blamejs/test/layer-0-primitives/agent-stream.test.js +227 -0
  859. package/lib/vendor/blamejs/test/layer-0-primitives/agent-tenant.test.js +302 -0
  860. package/lib/vendor/blamejs/test/layer-0-primitives/agent-trace.test.js +150 -0
  861. package/lib/vendor/blamejs/test/layer-0-primitives/ai-adverse-decision.test.js +44 -0
  862. package/lib/vendor/blamejs/test/layer-0-primitives/ai-content-detect.test.js +150 -0
  863. package/lib/vendor/blamejs/test/layer-0-primitives/ai-input.test.js +50 -0
  864. package/lib/vendor/blamejs/test/layer-0-primitives/ai-model-manifest.test.js +96 -0
  865. package/lib/vendor/blamejs/test/layer-0-primitives/ai-pref.test.js +76 -0
  866. package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +1080 -0
  867. package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +311 -0
  868. package/lib/vendor/blamejs/test/layer-0-primitives/archive-zip-stream.test.js +291 -0
  869. package/lib/vendor/blamejs/test/layer-0-primitives/archive.test.js +140 -0
  870. package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +267 -0
  871. package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +108 -0
  872. package/lib/vendor/blamejs/test/layer-0-primitives/asyncapi.test.js +929 -0
  873. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-conflict-path.test.js +80 -0
  874. package/lib/vendor/blamejs/test/layer-0-primitives/audit-cve-defensive.test.js +176 -0
  875. package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +132 -0
  876. package/lib/vendor/blamejs/test/layer-0-primitives/audit-export-cadf.test.js +97 -0
  877. package/lib/vendor/blamejs/test/layer-0-primitives/audit-framework-namespaces.test.js +141 -0
  878. package/lib/vendor/blamejs/test/layer-0-primitives/audit-segregation.test.js +115 -0
  879. package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-ml-dsa-65.test.js +163 -0
  880. package/lib/vendor/blamejs/test/layer-0-primitives/audit-use-store.test.js +246 -0
  881. package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge-verifier.test.js +485 -0
  882. package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +331 -0
  883. package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +352 -0
  884. package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +572 -0
  885. package/lib/vendor/blamejs/test/layer-0-primitives/auth-password-audit.test.js +61 -0
  886. package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-bucket-ops.test.js +258 -0
  887. package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +105 -0
  888. package/lib/vendor/blamejs/test/layer-0-primitives/backup-worker.test.js +34 -0
  889. package/lib/vendor/blamejs/test/layer-0-primitives/bearer-auth.test.js +107 -0
  890. package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-chunked-malformed.test.js +131 -0
  891. package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-smuggling.test.js +118 -0
  892. package/lib/vendor/blamejs/test/layer-0-primitives/boot-gates.test.js +85 -0
  893. package/lib/vendor/blamejs/test/layer-0-primitives/breach-deadline.test.js +38 -0
  894. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +861 -0
  895. package/lib/vendor/blamejs/test/layer-0-primitives/budr.test.js +55 -0
  896. package/lib/vendor/blamejs/test/layer-0-primitives/bundler-engine.test.js +209 -0
  897. package/lib/vendor/blamejs/test/layer-0-primitives/cache-status.test.js +129 -0
  898. package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +871 -0
  899. package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +891 -0
  900. package/lib/vendor/blamejs/test/layer-0-primitives/canonical-json-jcs.test.js +43 -0
  901. package/lib/vendor/blamejs/test/layer-0-primitives/cdn-cache-control.test.js +243 -0
  902. package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +550 -0
  903. package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +107 -0
  904. package/lib/vendor/blamejs/test/layer-0-primitives/cli-api-key.test.js +147 -0
  905. package/lib/vendor/blamejs/test/layer-0-primitives/cli-audit-verify-chain.test.js +104 -0
  906. package/lib/vendor/blamejs/test/layer-0-primitives/cli-backup.test.js +135 -0
  907. package/lib/vendor/blamejs/test/layer-0-primitives/cli-config-drift.test.js +67 -0
  908. package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +75 -0
  909. package/lib/vendor/blamejs/test/layer-0-primitives/cli-file-type.test.js +98 -0
  910. package/lib/vendor/blamejs/test/layer-0-primitives/cli-helpers.test.js +145 -0
  911. package/lib/vendor/blamejs/test/layer-0-primitives/cli-mtls.test.js +133 -0
  912. package/lib/vendor/blamejs/test/layer-0-primitives/cli-password.test.js +97 -0
  913. package/lib/vendor/blamejs/test/layer-0-primitives/cli-restore.test.js +160 -0
  914. package/lib/vendor/blamejs/test/layer-0-primitives/cli-retention.test.js +84 -0
  915. package/lib/vendor/blamejs/test/layer-0-primitives/cli-security.test.js +69 -0
  916. package/lib/vendor/blamejs/test/layer-0-primitives/cli-vault.test.js +142 -0
  917. package/lib/vendor/blamejs/test/layer-0-primitives/client-hints.test.js +133 -0
  918. package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +237 -0
  919. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +9600 -0
  920. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +575 -0
  921. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-cascade.test.js +89 -0
  922. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-eaa.test.js +36 -0
  923. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +712 -0
  924. package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +278 -0
  925. package/lib/vendor/blamejs/test/layer-0-primitives/config-drift.test.js +97 -0
  926. package/lib/vendor/blamejs/test/layer-0-primitives/config.test.js +424 -0
  927. package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +94 -0
  928. package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +357 -0
  929. package/lib/vendor/blamejs/test/layer-0-primitives/cra-report.test.js +31 -0
  930. package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +226 -0
  931. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-base64url.test.js +86 -0
  932. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-envelope.test.js +85 -0
  933. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +193 -0
  934. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +98 -0
  935. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke-pq.test.js +132 -0
  936. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke.test.js +155 -0
  937. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-mlkem768-x25519.test.js +129 -0
  938. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-namespace-hash.test.js +0 -0
  939. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-random-int.test.js +72 -0
  940. package/lib/vendor/blamejs/test/layer-0-primitives/csp-builder.test.js +96 -0
  941. package/lib/vendor/blamejs/test/layer-0-primitives/csp-nonce.test.js +401 -0
  942. package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +34 -0
  943. package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +180 -0
  944. package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +210 -0
  945. package/lib/vendor/blamejs/test/layer-0-primitives/daily-byte-quota.test.js +153 -0
  946. package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +66 -0
  947. package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +74 -0
  948. package/lib/vendor/blamejs/test/layer-0-primitives/db-collection-extensions.test.js +226 -0
  949. package/lib/vendor/blamejs/test/layer-0-primitives/db-collection.test.js +136 -0
  950. package/lib/vendor/blamejs/test/layer-0-primitives/db-init-extensions.test.js +165 -0
  951. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +150 -0
  952. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-extensions.test.js +191 -0
  953. package/lib/vendor/blamejs/test/layer-0-primitives/db-role-for.test.js +228 -0
  954. package/lib/vendor/blamejs/test/layer-0-primitives/db-vacuum.test.js +55 -0
  955. package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +89 -0
  956. package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +184 -0
  957. package/lib/vendor/blamejs/test/layer-0-primitives/declare-row-policy.test.js +203 -0
  958. package/lib/vendor/blamejs/test/layer-0-primitives/declare-view.test.js +303 -0
  959. package/lib/vendor/blamejs/test/layer-0-primitives/dns-dnssec-algorithm.test.js +163 -0
  960. package/lib/vendor/blamejs/test/layer-0-primitives/dns-null-mx.test.js +39 -0
  961. package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +165 -0
  962. package/lib/vendor/blamejs/test/layer-0-primitives/dr-runbook.test.js +59 -0
  963. package/lib/vendor/blamejs/test/layer-0-primitives/dsr-state-rules.test.js +55 -0
  964. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +786 -0
  965. package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +105 -0
  966. package/lib/vendor/blamejs/test/layer-0-primitives/early-hints.test.js +147 -0
  967. package/lib/vendor/blamejs/test/layer-0-primitives/events.test.js +105 -0
  968. package/lib/vendor/blamejs/test/layer-0-primitives/exploit-replay.test.js +243 -0
  969. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +181 -0
  970. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +190 -0
  971. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-routing.test.js +531 -0
  972. package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +118 -0
  973. package/lib/vendor/blamejs/test/layer-0-primitives/fapi2.test.js +89 -0
  974. package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +156 -0
  975. package/lib/vendor/blamejs/test/layer-0-primitives/fdx.test.js +79 -0
  976. package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +216 -0
  977. package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +434 -0
  978. package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +432 -0
  979. package/lib/vendor/blamejs/test/layer-0-primitives/file-type.test.js +81 -0
  980. package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +887 -0
  981. package/lib/vendor/blamejs/test/layer-0-primitives/forensic-snapshot.test.js +51 -0
  982. package/lib/vendor/blamejs/test/layer-0-primitives/fsm.test.js +375 -0
  983. package/lib/vendor/blamejs/test/layer-0-primitives/gcs-bucket-ops.test.js +321 -0
  984. package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +41 -0
  985. package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +32 -0
  986. package/lib/vendor/blamejs/test/layer-0-primitives/guard-agent-registry.test.js +87 -0
  987. package/lib/vendor/blamejs/test/layer-0-primitives/guard-all.test.js +328 -0
  988. package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +339 -0
  989. package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +694 -0
  990. package/lib/vendor/blamejs/test/layer-0-primitives/guard-dsn.test.js +296 -0
  991. package/lib/vendor/blamejs/test/layer-0-primitives/guard-email.test.js +234 -0
  992. package/lib/vendor/blamejs/test/layer-0-primitives/guard-envelope.test.js +192 -0
  993. package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-payload.test.js +89 -0
  994. package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-topic.test.js +71 -0
  995. package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +386 -0
  996. package/lib/vendor/blamejs/test/layer-0-primitives/guard-html-wcag.test.js +859 -0
  997. package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +357 -0
  998. package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +92 -0
  999. package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
  1000. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +174 -0
  1001. package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +317 -0
  1002. package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-id.test.js +199 -0
  1003. package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-unsubscribe.test.js +214 -0
  1004. package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-compose.test.js +111 -0
  1005. package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-move.test.js +110 -0
  1006. package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-query.test.js +112 -0
  1007. package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-reply.test.js +86 -0
  1008. package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-sieve.test.js +92 -0
  1009. package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +301 -0
  1010. package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +265 -0
  1011. package/lib/vendor/blamejs/test/layer-0-primitives/guard-message-id.test.js +0 -0
  1012. package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +161 -0
  1013. package/lib/vendor/blamejs/test/layer-0-primitives/guard-posture-chain.test.js +100 -0
  1014. package/lib/vendor/blamejs/test/layer-0-primitives/guard-saga-config.test.js +79 -0
  1015. package/lib/vendor/blamejs/test/layer-0-primitives/guard-smtp-command.test.js +269 -0
  1016. package/lib/vendor/blamejs/test/layer-0-primitives/guard-snapshot-envelope.test.js +89 -0
  1017. package/lib/vendor/blamejs/test/layer-0-primitives/guard-stream-args.test.js +78 -0
  1018. package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +288 -0
  1019. package/lib/vendor/blamejs/test/layer-0-primitives/guard-tenant-id.test.js +69 -0
  1020. package/lib/vendor/blamejs/test/layer-0-primitives/guard-trace-context.test.js +102 -0
  1021. package/lib/vendor/blamejs/test/layer-0-primitives/guard-xml.test.js +202 -0
  1022. package/lib/vendor/blamejs/test/layer-0-primitives/guard-yaml.test.js +203 -0
  1023. package/lib/vendor/blamejs/test/layer-0-primitives/hal.test.js +51 -0
  1024. package/lib/vendor/blamejs/test/layer-0-primitives/honeytoken.test.js +50 -0
  1025. package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +37 -0
  1026. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +692 -0
  1027. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +280 -0
  1028. package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +225 -0
  1029. package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +203 -0
  1030. package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +991 -0
  1031. package/lib/vendor/blamejs/test/layer-0-primitives/iab-mspa.test.js +63 -0
  1032. package/lib/vendor/blamejs/test/layer-0-primitives/iab-tcf.test.js +73 -0
  1033. package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +612 -0
  1034. package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +56 -0
  1035. package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +166 -0
  1036. package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +29 -0
  1037. package/lib/vendor/blamejs/test/layer-0-primitives/jose-jwe-experimental.test.js +121 -0
  1038. package/lib/vendor/blamejs/test/layer-0-primitives/json-api.test.js +58 -0
  1039. package/lib/vendor/blamejs/test/layer-0-primitives/json-round-trip-helper.test.js +110 -0
  1040. package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +159 -0
  1041. package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
  1042. package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +118 -0
  1043. package/lib/vendor/blamejs/test/layer-0-primitives/local-db-thin.test.js +150 -0
  1044. package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-cloudwatch.test.js +489 -0
  1045. package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp-grpc.test.js +207 -0
  1046. package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp.test.js +283 -0
  1047. package/lib/vendor/blamejs/test/layer-0-primitives/lro.test.js +65 -0
  1048. package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +417 -0
  1049. package/lib/vendor/blamejs/test/layer-0-primitives/mail-arf.test.js +208 -0
  1050. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +910 -0
  1051. package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +502 -0
  1052. package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +680 -0
  1053. package/lib/vendor/blamejs/test/layer-0-primitives/mail-canspam.test.js +128 -0
  1054. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +149 -0
  1055. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +323 -0
  1056. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +297 -0
  1057. package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +514 -0
  1058. package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy-tlsrpt.test.js +369 -0
  1059. package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +199 -0
  1060. package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +627 -0
  1061. package/lib/vendor/blamejs/test/layer-0-primitives/mail-feedback-id.test.js +56 -0
  1062. package/lib/vendor/blamejs/test/layer-0-primitives/mail-greylist.test.js +217 -0
  1063. package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +283 -0
  1064. package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +217 -0
  1065. package/lib/vendor/blamejs/test/layer-0-primitives/mail-mdn.test.js +334 -0
  1066. package/lib/vendor/blamejs/test/layer-0-primitives/mail-rbl.test.js +271 -0
  1067. package/lib/vendor/blamejs/test/layer-0-primitives/mail-require-tls.test.js +128 -0
  1068. package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +215 -0
  1069. package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +336 -0
  1070. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +732 -0
  1071. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +840 -0
  1072. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +130 -0
  1073. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +285 -0
  1074. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +74 -0
  1075. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +112 -0
  1076. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +229 -0
  1077. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +394 -0
  1078. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-tls.test.js +147 -0
  1079. package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +151 -0
  1080. package/lib/vendor/blamejs/test/layer-0-primitives/mail-spam-score.test.js +204 -0
  1081. package/lib/vendor/blamejs/test/layer-0-primitives/mail-srs.test.js +152 -0
  1082. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +279 -0
  1083. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +323 -0
  1084. package/lib/vendor/blamejs/test/layer-0-primitives/mail-unsubscribe.test.js +165 -0
  1085. package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +439 -0
  1086. package/lib/vendor/blamejs/test/layer-0-primitives/mcp-tool-registry.test.js +202 -0
  1087. package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +155 -0
  1088. package/lib/vendor/blamejs/test/layer-0-primitives/metrics-shadow-registry.test.js +112 -0
  1089. package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +224 -0
  1090. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-compose-pipeline.test.js +278 -0
  1091. package/lib/vendor/blamejs/test/layer-0-primitives/money.test.js +376 -0
  1092. package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-paths.test.js +89 -0
  1093. package/lib/vendor/blamejs/test/layer-0-primitives/nel.test.js +200 -0
  1094. package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +106 -0
  1095. package/lib/vendor/blamejs/test/layer-0-primitives/network-byte-quota.test.js +133 -0
  1096. package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +372 -0
  1097. package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +635 -0
  1098. package/lib/vendor/blamejs/test/layer-0-primitives/network-heartbeat-passive.test.js +128 -0
  1099. package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-build-options.test.js +130 -0
  1100. package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-ct-inclusion.test.js +179 -0
  1101. package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +447 -0
  1102. package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +369 -0
  1103. package/lib/vendor/blamejs/test/layer-0-primitives/nis2-report.test.js +21 -0
  1104. package/lib/vendor/blamejs/test/layer-0-primitives/nist-crosswalk.test.js +42 -0
  1105. package/lib/vendor/blamejs/test/layer-0-primitives/no-cache.test.js +98 -0
  1106. package/lib/vendor/blamejs/test/layer-0-primitives/notify.test.js +707 -0
  1107. package/lib/vendor/blamejs/test/layer-0-primitives/numeric-bounds.test.js +142 -0
  1108. package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +72 -0
  1109. package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +597 -0
  1110. package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +190 -0
  1111. package/lib/vendor/blamejs/test/layer-0-primitives/openapi.test.js +877 -0
  1112. package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +257 -0
  1113. package/lib/vendor/blamejs/test/layer-0-primitives/pagination.test.js +522 -0
  1114. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +216 -0
  1115. package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +324 -0
  1116. package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +546 -0
  1117. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +153 -0
  1118. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-software.test.js +94 -0
  1119. package/lib/vendor/blamejs/test/layer-0-primitives/problem-details.test.js +195 -0
  1120. package/lib/vendor/blamejs/test/layer-0-primitives/process-spawn.test.js +62 -0
  1121. package/lib/vendor/blamejs/test/layer-0-primitives/promise-pool.test.js +93 -0
  1122. package/lib/vendor/blamejs/test/layer-0-primitives/protected-resource-metadata.test.js +68 -0
  1123. package/lib/vendor/blamejs/test/layer-0-primitives/protobuf-encoder.test.js +138 -0
  1124. package/lib/vendor/blamejs/test/layer-0-primitives/protocol-dispatcher.test.js +174 -0
  1125. package/lib/vendor/blamejs/test/layer-0-primitives/public-suffix.test.js +197 -0
  1126. package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +232 -0
  1127. package/lib/vendor/blamejs/test/layer-0-primitives/queue-dlq-extend-lease.test.js +178 -0
  1128. package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +322 -0
  1129. package/lib/vendor/blamejs/test/layer-0-primitives/queue-priority-rate-progress.test.js +266 -0
  1130. package/lib/vendor/blamejs/test/layer-0-primitives/queue-sqs.test.js +300 -0
  1131. package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-cluster.test.js +338 -0
  1132. package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-registry.test.js +75 -0
  1133. package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +246 -0
  1134. package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +130 -0
  1135. package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +335 -0
  1136. package/lib/vendor/blamejs/test/layer-0-primitives/request-log.test.js +170 -0
  1137. package/lib/vendor/blamejs/test/layer-0-primitives/require-auth-cache-control.test.js +93 -0
  1138. package/lib/vendor/blamejs/test/layer-0-primitives/require-mtls.test.js +34 -0
  1139. package/lib/vendor/blamejs/test/layer-0-primitives/resource-access-lock.test.js +52 -0
  1140. package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +67 -0
  1141. package/lib/vendor/blamejs/test/layer-0-primitives/retry.test.js +535 -0
  1142. package/lib/vendor/blamejs/test/layer-0-primitives/router-cross-origin-redirect.test.js +0 -0
  1143. package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +128 -0
  1144. package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-loops.test.js +163 -0
  1145. package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-parallel.test.js +170 -0
  1146. package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +248 -0
  1147. package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +451 -0
  1148. package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +289 -0
  1149. package/lib/vendor/blamejs/test/layer-0-primitives/safe-icap.test.js +206 -0
  1150. package/lib/vendor/blamejs/test/layer-0-primitives/safe-jsonpath.test.js +104 -0
  1151. package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +339 -0
  1152. package/lib/vendor/blamejs/test/layer-0-primitives/safe-mount-info.test.js +180 -0
  1153. package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +78 -0
  1154. package/lib/vendor/blamejs/test/layer-0-primitives/safe-sieve.test.js +123 -0
  1155. package/lib/vendor/blamejs/test/layer-0-primitives/safe-smtp.test.js +95 -0
  1156. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-idn-homograph.test.js +77 -0
  1157. package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +257 -0
  1158. package/lib/vendor/blamejs/test/layer-0-primitives/saml-slo.test.js +249 -0
  1159. package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +228 -0
  1160. package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-exactly-once.test.js +238 -0
  1161. package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +92 -0
  1162. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +700 -0
  1163. package/lib/vendor/blamejs/test/layer-0-primitives/sd-notify.test.js +67 -0
  1164. package/lib/vendor/blamejs/test/layer-0-primitives/sec-cyber.test.js +85 -0
  1165. package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +107 -0
  1166. package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +175 -0
  1167. package/lib/vendor/blamejs/test/layer-0-primitives/seeders.test.js +816 -0
  1168. package/lib/vendor/blamejs/test/layer-0-primitives/self-update-standalone-verifier.test.js +168 -0
  1169. package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +302 -0
  1170. package/lib/vendor/blamejs/test/layer-0-primitives/server-timing.test.js +93 -0
  1171. package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +247 -0
  1172. package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +295 -0
  1173. package/lib/vendor/blamejs/test/layer-0-primitives/shape-match.test.js +142 -0
  1174. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +952 -0
  1175. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-multipart-sse.test.js +441 -0
  1176. package/lib/vendor/blamejs/test/layer-0-primitives/slug.test.js +330 -0
  1177. package/lib/vendor/blamejs/test/layer-0-primitives/smtp-policy.test.js +233 -0
  1178. package/lib/vendor/blamejs/test/layer-0-primitives/source-comment-blocks.test.js +105 -0
  1179. package/lib/vendor/blamejs/test/layer-0-primitives/speculation-rules.test.js +319 -0
  1180. package/lib/vendor/blamejs/test/layer-0-primitives/sse.test.js +148 -0
  1181. package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +283 -0
  1182. package/lib/vendor/blamejs/test/layer-0-primitives/standard-webhooks.test.js +67 -0
  1183. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +266 -0
  1184. package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +487 -0
  1185. package/lib/vendor/blamejs/test/layer-0-primitives/storage-chunk-scratch.test.js +0 -0
  1186. package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +773 -0
  1187. package/lib/vendor/blamejs/test/layer-0-primitives/stream-throttle.test.js +173 -0
  1188. package/lib/vendor/blamejs/test/layer-0-primitives/structured-fields.test.js +180 -0
  1189. package/lib/vendor/blamejs/test/layer-0-primitives/tcpa-10dlc.test.js +66 -0
  1190. package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +89 -0
  1191. package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +571 -0
  1192. package/lib/vendor/blamejs/test/layer-0-primitives/test-harness.test.js +190 -0
  1193. package/lib/vendor/blamejs/test/layer-0-primitives/testing-request.test.js +119 -0
  1194. package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +522 -0
  1195. package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +151 -0
  1196. package/lib/vendor/blamejs/test/layer-0-primitives/tls-exporter.test.js +168 -0
  1197. package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-ct.test.js +275 -0
  1198. package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-verify.test.js +105 -0
  1199. package/lib/vendor/blamejs/test/layer-0-primitives/tls-pinset-drift.test.js +35 -0
  1200. package/lib/vendor/blamejs/test/layer-0-primitives/tls-preferred-groups.test.js +81 -0
  1201. package/lib/vendor/blamejs/test/layer-0-primitives/tracing.test.js +280 -0
  1202. package/lib/vendor/blamejs/test/layer-0-primitives/uuid.test.js +93 -0
  1203. package/lib/vendor/blamejs/test/layer-0-primitives/vault-aad.test.js +277 -0
  1204. package/lib/vendor/blamejs/test/layer-0-primitives/vault-seal-pem-file.test.js +252 -0
  1205. package/lib/vendor/blamejs/test/layer-0-primitives/vendor-data.test.js +149 -0
  1206. package/lib/vendor/blamejs/test/layer-0-primitives/vendor-manifest.test.js +92 -0
  1207. package/lib/vendor/blamejs/test/layer-0-primitives/vex.test.js +661 -0
  1208. package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +308 -0
  1209. package/lib/vendor/blamejs/test/layer-0-primitives/web-push-vapid.test.js +144 -0
  1210. package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +674 -0
  1211. package/lib/vendor/blamejs/test/layer-0-primitives/websocket-channels.test.js +360 -0
  1212. package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool.test.js +302 -0
  1213. package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +349 -0
  1214. package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +717 -0
  1215. package/lib/vendor/blamejs/test/layer-5-integration/bundler-output.test.js +444 -0
  1216. package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +597 -0
  1217. package/lib/vendor/blamejs/test/layer-5-integration/security-chaos.test.js +308 -0
  1218. package/lib/vendor/blamejs/test/smoke.js +431 -0
  1219. package/lib/webhooks.js +305 -0
  1220. package/package.json +43 -0
@@ -0,0 +1,2551 @@
1
+ {
2
+ "$schema": "../scripts/release-notes-consolidated-schema.json",
3
+ "minor": "0.11",
4
+ "releases": [
5
+ {
6
+ "version": "0.11.45",
7
+ "date": "2026-05-22",
8
+ "headline": "Wiki compose pins track framework version",
9
+ "summary": "The dev and prod compose pins for `ghcr.io/blamejs/blamejs-wiki` move from the legacy `0.3.x` wiki-only versioning (`0.3.24` prod, `0.3.8` dev) to the framework version (`0.11.45`). The wiki container's build tag has matched the framework version since the v0.11.44 release-container workflow fix; the compose pins now follow suit so a fresh clone + `docker compose up` works against the most recent published image without manual edits.",
10
+ "sections": [
11
+ {
12
+ "heading": "Changed",
13
+ "items": [
14
+ {
15
+ "title": "`examples/wiki/docker-compose.prod.yml` pin: `0.3.24` → `0.11.45`",
16
+ "body": "The prod compose now pulls `ghcr.io/blamejs/blamejs-wiki:0.11.45`. Operators deploying to a host running an older pin keep working unchanged — their `.env` / image override takes precedence. To upgrade, the operator runs `docker compose -f docker-compose.yml -f docker-compose.prod.yml pull && docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d` on the host."
17
+ },
18
+ {
19
+ "title": "`examples/wiki/docker-compose.yml` pin: `0.3.8` → `0.11.45`",
20
+ "body": "The dev compose (local-build path) also tracks the framework version. Aligns the locally-built dev image tag with the published image tag so the two paths don't drift over time."
21
+ }
22
+ ]
23
+ }
24
+ ],
25
+ "references": []
26
+ },
27
+ {
28
+ "version": "0.11.44",
29
+ "date": "2026-05-22",
30
+ "headline": "Fix wiki container build — release-container smoke step still hit port 8080",
31
+ "summary": "The v0.11.40 wiki port swap (8080 → 3008) missed `.github/workflows/release-container.yml`'s post-publish smoke step. The smoke ran `docker run -p 8080:8080` + curled `localhost:8080/healthz`, but the v0.11.40+ wiki container listens on 3008 — so the smoke failed and the wiki container was NOT pushed to GHCR for v0.11.40, v0.11.42, or v0.11.43. The npm package was unaffected (publish completes on a different job). Operators pulling `ghcr.io/blamejs/blamejs-wiki:latest` were getting the v0.11.39 build until this fix. This release fixes the workflow + adds a codebase-patterns detector that gates the Dockerfile's `WIKI_PORT` against the workflow's port map + curl host so the same silent-deploy failure can't recur.",
32
+ "sections": [
33
+ {
34
+ "heading": "Fixed",
35
+ "items": [
36
+ {
37
+ "title": "release-container.yml smoke step uses port 3008",
38
+ "body": "Three locations updated: comment header references port 3008; `-p 3008:3008` host port mapping; `curl http://localhost:3008/healthz` health check. Tagging v0.11.44 triggers a fresh container build + push that lands the v0.11.40 / v0.11.42 / v0.11.43 changes (port 3008 default, JSCalendar Group, BYSETPOS, multi-rule recurrence union, JMAP EmailSubmission/set, wiki nav alphabetical) in GHCR for the first time."
39
+ }
40
+ ]
41
+ },
42
+ {
43
+ "heading": "Detectors",
44
+ "items": [
45
+ {
46
+ "title": "Wiki-port cross-artifact agreement gate",
47
+ "body": "`testWikiPortAgreesAcrossArtifacts` reads `WIKI_PORT=<n>` from `examples/wiki/Dockerfile` and asserts every `docker run -p X:X` mapping + every `curl http://localhost:X/healthz` reference in `.github/workflows/release-container.yml` matches. Smoke-tested by introducing a deliberate mismatch — detector fires with file:line + the conflicting values. Prevents the v0.11.40 silent-deploy failure class from recurring on any future port change."
48
+ }
49
+ ]
50
+ }
51
+ ],
52
+ "references": []
53
+ },
54
+ {
55
+ "version": "0.11.43",
56
+ "date": "2026-05-22",
57
+ "headline": "Wiki nav: alphabetical sidebar + dedup drifted category labels",
58
+ "summary": "The wiki sidebar now lists categories alphabetically (case-insensitive) instead of in editorial order. Welcome remains pinned first (landing page); Other and Reference remain pinned last (catch-all groups). Three category-label dups are also consolidated at the source comment-blocks: `Agent Protocols` (one entry — `a2a-tasks`) joins `Agent` with the rest of the agent substrate; `Networking` (two entries — `stream-throttle`, `web-push-vapid`) joins `Network`; `Audit & Compliance` (one entry — `nist-crosswalk`) joins `Compliance`. New `@nav` categories now land in their alphabetical slot automatically without a site.config edit.",
59
+ "sections": [
60
+ {
61
+ "heading": "Changed",
62
+ "items": [
63
+ {
64
+ "title": "Wiki sidebar group order alphabetical",
65
+ "body": "Replaces the curated `GROUP_ORDER` editorial list in `examples/wiki/site.config.js` with an alphabetical sort. Pinning preserved: `Welcome` always first; `Other` and `Reference` always last. Adding a new `@nav` category to any `lib/*.js` `@module` block now slots into its alphabetical position automatically — no separate site.config update required."
66
+ },
67
+ {
68
+ "title": "Category consolidation: Agent Protocols → Agent",
69
+ "body": "`a2a-tasks` was the sole entry under `Agent Protocols`. Moved to `Agent` to live alongside the orchestrator / saga / idempotency / event-bus / posture-chain / stream / trace / tenant / snapshot / fsm primitives — the W3C A2A task surface is part of the same agent-substrate concern."
70
+ },
71
+ {
72
+ "title": "Category consolidation: Networking → Network",
73
+ "body": "`stream-throttle` and `web-push-vapid` were tagged `Networking` while every other network-layer primitive used `Network`. Drift cleanup — both now live under `Network`."
74
+ },
75
+ {
76
+ "title": "Category consolidation: Audit & Compliance → Compliance",
77
+ "body": "`nist-crosswalk` was the sole entry under `Audit & Compliance`. Moved to `Compliance` alongside the other regulatory primitives."
78
+ }
79
+ ]
80
+ },
81
+ {
82
+ "heading": "Detectors",
83
+ "items": [
84
+ {
85
+ "title": "Nav-category allowlist gate",
86
+ "body": "`testNavCategoryAllowlist` in the codebase-patterns runner walks every `lib/*.js` `@module` block and refuses any `@nav` value not in the canonical category list. Adding a new sidebar category is a deliberate edit — it lands in `NAV_ALLOWLIST` + the operator-facing `FIRST_GROUPS` / `LAST_GROUPS` pin list in `examples/wiki/site.config.js` at the same time. Prevents the Networking-vs-Network and Agent-vs-Agent-Protocols dup classes from re-emerging silently."
87
+ }
88
+ ]
89
+ }
90
+ ],
91
+ "references": []
92
+ },
93
+ {
94
+ "version": "0.11.42",
95
+ "date": "2026-05-22",
96
+ "headline": "`b.calendar` JSCalendar Group objects (RFC 8984 §1.4.4)",
97
+ "summary": "Closes the v0.11.31 deferral on JSCalendar Group. `b.calendar.validate` now recognises `@type: \"Group\"` as a container envelope for multiple Event / Task / Note entries that share a logical name + categories. `b.calendar.toIcal` emits a single VCALENDAR wrap containing every entry's component in declared order (VEVENT for Event, VTODO for Task, VJOURNAL for Note). Group is JSCalendar-only — iCalendar has no envelope-level metadata for Group.name / Group.categories, so a Group's metadata is dropped on the iCal-side of a round-trip; operators preserving Group state across systems use the JSON-native surface.",
98
+ "sections": [
99
+ {
100
+ "heading": "Added",
101
+ "items": [
102
+ {
103
+ "title": "`@type: \"Group\"` envelope (RFC 8984 §1.4.4)",
104
+ "body": "A Group carries `uid`, `updated`, `entries` (required non-empty array), and optional `name`, `description`, `categories` (String-keyed Boolean set), and `source` (URI string). Entries are validated recursively — each entry must be a valid Event / Task / Note per its own type rules. Groups nesting Groups is refused (RFC 8984 does not define a nesting semantic)."
105
+ },
106
+ {
107
+ "title": "`b.calendar.toIcal` emits single VCALENDAR for Group",
108
+ "body": "When `@type === \"Group\"`, toIcal emits one BEGIN:VCALENDAR / END:VCALENDAR envelope containing every entry's component in declared order. The Group's own uid / updated / name / description / categories are NOT round-tripped to iCalendar (RFC 5545 has no envelope-level metadata for these); operators preserving Group state across a round-trip use the JSON-native JSCalendar surface."
109
+ },
110
+ {
111
+ "title": "Refusal vocabulary",
112
+ "body": "New structured `CalendarError` codes: `calendar/bad-entries` (entries missing, empty, non-array, or any entry malformed / Group-typed), `calendar/bad-group` (entry-specific field like start / duration / due / progress / recurrenceRules set on the Group envelope), `calendar/bad-categories` (non-object categories or non-`true` value), `calendar/bad-source` (non-string source)."
113
+ },
114
+ {
115
+ "title": "`JSCAL_TYPES.Group` export",
116
+ "body": "`b.calendar.JSCAL_TYPES.Group === \"Group\"` exposes the discriminator string for operator-side type-dispatch."
117
+ }
118
+ ]
119
+ }
120
+ ],
121
+ "references": [
122
+ {
123
+ "label": "RFC 8984 §1.4.4 (JSCalendar Group)",
124
+ "url": "https://www.rfc-editor.org/rfc/rfc8984.html#section-1.4.4"
125
+ },
126
+ {
127
+ "label": "RFC 5545 §3.4 (VCALENDAR envelope)",
128
+ "url": "https://www.rfc-editor.org/rfc/rfc5545.html#section-3.4"
129
+ }
130
+ ]
131
+ },
132
+ {
133
+ "version": "0.11.41",
134
+ "date": "2026-05-21",
135
+ "headline": "`b.calendar.expandRecurrence` picks up BYSETPOS (RFC 5545 §3.3.10)",
136
+ "summary": "Closes the v0.11.31 deferral on BYSETPOS — the recurrence filter that picks the Nth candidate from a BY*-filtered set within a FREQ interval. Common operator patterns now work directly: `FREQ=MONTHLY;BYDAY=FR;BYSETPOS=-1` for last Friday of each month, `FREQ=MONTHLY;BYDAY=TU;BYSETPOS=2` for second Tuesday, `FREQ=YEARLY;BYMONTH=10;BYDAY=SU;BYSETPOS=1` for first Sunday of October (the DST-end announcement pattern). Supported for `FREQ=MONTHLY` / `YEARLY` / `WEEKLY` at day-granularity (time-of-day inherited from `start`). `FREQ=DAILY` + BYSETPOS is refused with `calendar/bad-recurrence` since the semantics aren't meaningful at sub-day frequency.",
137
+ "sections": [
138
+ {
139
+ "heading": "Added",
140
+ "items": [
141
+ {
142
+ "title": "`bySetPos` filter on RecurrenceRule",
143
+ "body": "`recurrenceRules[i].bySetPos: [1, -1, 2]` picks the listed positions from the BY*-filtered candidate set within each FREQ interval. Positive values are 1-indexed from the start of the period; negative values count from the end. Multiple positions emit per period (e.g. `[1, -1]` emits both the first and last matching day of each month). Out-of-range positions silently drop per RFC 5545's tolerant grammar."
144
+ },
145
+ {
146
+ "title": "MONTHLY / YEARLY / WEEKLY support",
147
+ "body": "BYSETPOS expands within month / year / WKST-aligned-week boundaries. For each period, all day-level candidates that pass the existing BY* filters (byDay / byMonth / byMonthDay / byWeekNo / byYearDay) are enumerated, sorted ascending, then indexed by `bySetPos`. The expand loop's step budget (`MAX_EXPAND_INSTANCES * 366`) is shared across periods so the BYSETPOS path can't outrun the v0.11.31 DoS bound."
148
+ },
149
+ {
150
+ "title": "DAILY frequency refused",
151
+ "body": "`FREQ=DAILY` + BYSETPOS throws `calendar/bad-recurrence` at expand time. The combination has no meaningful per-period set semantics (a day has no sub-day BY* set to pick from in the v1 day-granularity model). Operators with a sub-day BYSETPOS use case use `FREQ=HOURLY` semantics directly without BYSETPOS, or open an issue with the use case."
152
+ }
153
+ ]
154
+ },
155
+ {
156
+ "heading": "Security",
157
+ "items": [
158
+ {
159
+ "title": "Step budget shared with non-BYSETPOS path",
160
+ "body": "BYSETPOS enumeration decrements the same `MAX_EXPAND_INSTANCES * 366` step budget the non-BYSETPOS path uses, and the per-period day-loop has a hard 400-iteration safety cap (covers max-366-days in a leap year + slack). Adversarial events combining many rules + sparse BY* filters + BYSETPOS can't outpace the original single-rule DoS bound."
161
+ }
162
+ ]
163
+ }
164
+ ],
165
+ "references": [
166
+ {
167
+ "label": "RFC 5545 §3.3.10 (RRULE — BYSETPOS)",
168
+ "url": "https://www.rfc-editor.org/rfc/rfc5545.html#section-3.3.10"
169
+ },
170
+ {
171
+ "label": "RFC 8984 §4.3.2 (JSCalendar RecurrenceRule)",
172
+ "url": "https://www.rfc-editor.org/rfc/rfc8984.html#section-4.3.2"
173
+ }
174
+ ]
175
+ },
176
+ {
177
+ "version": "0.11.40",
178
+ "date": "2026-05-21",
179
+ "headline": "Wiki Docker default port moves from 8080 → 3008",
180
+ "summary": "The `examples/wiki` Docker stack now defaults to port 3008 throughout — `WIKI_PORT`, the Dockerfile `EXPOSE` directive, the in-container HEALTHCHECK URL, the dev `docker-compose.yml` host mapping, the Caddy reverse-proxy upstream, and the `server.js` + `lib/build-app.js` code defaults. Production deployments (`docker-compose.prod.yml`) are operator-invisible since Caddy fronts the container on 80/443 — the upstream port is never exposed to the host. Dev users hitting the wiki container directly now use `http://localhost:3008`. Operators who set `WIKI_PORT` explicitly in their `.env` keep working unchanged.",
181
+ "sections": [
182
+ {
183
+ "heading": "Changed",
184
+ "items": [
185
+ {
186
+ "title": "Wiki default port 8080 → 3008",
187
+ "body": "Default `WIKI_PORT` moves from 8080 (HTTP-alt, frequently collides with Tomcat / Jenkins / Confluence / Spring-Boot defaults) to 3008 (IANA-unassigned, no service-convention collision). The new default applies consistently across: `examples/wiki/Dockerfile` (`ENV WIKI_PORT`, `EXPOSE`, HEALTHCHECK URL), `examples/wiki/server.js`, `examples/wiki/lib/build-app.js`, `examples/wiki/docker-compose.yml` (host mapping `3008:3008`), `examples/wiki/docker-compose.prod.yml` (internal-network expose), `examples/wiki/Caddyfile` (upstream resolver), `examples/wiki/README.md`, `examples/wiki/DEPLOY.md`."
188
+ },
189
+ {
190
+ "title": "Operator action",
191
+ "body": "Dev users with `localhost:8080` bookmarks, curl scripts, host-firewall allowlists, or IDE port forwarders pointed at the wiki need to switch to `localhost:3008`. Operators who set `WIKI_PORT=8080` explicitly in their `.env` keep working unchanged. Production deployments behind Caddy see no change — the upstream port is internal-network only."
192
+ }
193
+ ]
194
+ }
195
+ ],
196
+ "references": [
197
+ {
198
+ "label": "IANA Service Name and Port Number Registry",
199
+ "url": "https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml"
200
+ }
201
+ ]
202
+ },
203
+ {
204
+ "version": "0.11.39",
205
+ "date": "2026-05-21",
206
+ "headline": "`b.calendar.expandRecurrence` honors multiple `recurrenceRules` (RFC 8984 §4.3.2)",
207
+ "summary": "Closes the v0.11.31 deferral on the multi-rule path. `expandRecurrence` now walks every entry in `event.recurrenceRules`, expands each rule independently against the same `start` anchor, and UNIONs the resulting instances (dedup + sorted ascending). Per-rule `count` caps apply per-rule per RFC 8984 §4.3.2; the global `max` / `MAX_EXPAND_INSTANCES` cap applies to the unioned set. The step budget (`MAX_EXPAND_INSTANCES * 366`) is shared across all rules in the same expand call so an N-rule fan-out can't amplify the worst-case loop past the single-rule bound.",
208
+ "sections": [
209
+ {
210
+ "heading": "Added",
211
+ "items": [
212
+ {
213
+ "title": "Multi-rule expansion + UNION",
214
+ "body": "`event.recurrenceRules` of length > 1 now expands every rule, not just the first. Each rule's stepping (frequency / interval / count / until / BY* filters) operates independently; the resulting ISO 8601 UTC instant strings dedupe via object-keyed set semantics, then sort ascending. The single-rule case is structurally unchanged — same step loop, same per-rule cap behaviour."
215
+ },
216
+ {
217
+ "title": "Per-rule `count` applies per-rule (RFC 8984 §4.3.2)",
218
+ "body": "Two rules with `count: 3` and `count: 2` produce up to 5 instances in the unioned output (minus any timestamps that dedupe across rules), not 5 instances total across the rules. Matches RFC 8984 §4.3.2 phrasing: each Recurrence Rule's count is applied per rule."
219
+ },
220
+ {
221
+ "title": "Global step budget shared across rules",
222
+ "body": "The `MAX_EXPAND_INSTANCES * 366` step budget (introduced in v0.11.31) now tracks across rules in the same expand call. A 4-rule event with sparse BY* filters can't accumulate 4× the single-rule worst-case work; the budget is depleted across the full rule set."
223
+ }
224
+ ]
225
+ },
226
+ {
227
+ "heading": "Security",
228
+ "items": [
229
+ {
230
+ "title": "DoS amplification across N rules bounded by the same step budget",
231
+ "body": "The step budget is shared (not per-rule), so adversarial multi-rule events can't bypass the v0.11.31 BY*-filter cap by stacking N rules with sparse filters. Sparse-filter rules still complete within the original 10-year `MAX_EXPAND_SPAN_MS` window cap."
232
+ }
233
+ ]
234
+ },
235
+ {
236
+ "heading": "Detectors",
237
+ "items": [
238
+ {
239
+ "title": "No new detector — covered by existing recurrence-test surface",
240
+ "body": "Multi-rule UNION + dedup + global step budget are exercised by three new tests in `test/layer-0-primitives/calendar.test.js` (multi-rule union, same-rule dedup, global max applied to union). No detector needed — the bug class is testable end-to-end and the test file IS the canonical regression surface."
241
+ }
242
+ ]
243
+ }
244
+ ],
245
+ "references": [
246
+ {
247
+ "label": "RFC 8984 §4.3.2 (JSCalendar RecurrenceRule — multi-rule expansion)",
248
+ "url": "https://www.rfc-editor.org/rfc/rfc8984.html#section-4.3.2"
249
+ },
250
+ {
251
+ "label": "RFC 5545 §3.8.5.3 (iCalendar RRULE — semantics under composition)",
252
+ "url": "https://www.rfc-editor.org/rfc/rfc5545.html#section-3.8.5.3"
253
+ }
254
+ ]
255
+ },
256
+ {
257
+ "version": "0.11.38",
258
+ "date": "2026-05-21",
259
+ "headline": "`b.mail.server.jmap.emailSubmissionSetHandler` — reference JMAP EmailSubmission/set composing `b.mail.send.deliver`",
260
+ "summary": "Reference implementation of JMAP `EmailSubmission/set` (RFC 8621 §7.5) that composes `b.mail.send.deliver` (v0.11.24). Operators wire it as a method handler on `b.mail.server.jmap.create({ methods: ... })`. The handler walks `args.create`, validates each EmailSubmission's shape against the RFC 8621 §7.5 vocabulary (`identityId` / `emailId` / `envelope.mailFrom.email` / `envelope.rcptTo[]`), looks up the referenced Email blob via an operator-supplied `lookupEmail(emailId, accountId, actor)`, hands the RFC 822 body to `deliver(envelope)`, and maps the result into JMAP `deliveryStatus` (`recipient → { smtpReply, delivered, displayed }` per RFC 8621 §7.4). Closes the v0.11.24 deferral on a reference JMAP→deliver bridge.",
261
+ "sections": [
262
+ {
263
+ "heading": "Added",
264
+ "items": [
265
+ {
266
+ "title": "`b.mail.server.jmap.emailSubmissionSetHandler(opts)` factory",
267
+ "body": "Returns an async `(actor, args, ctx) → result` function suitable for the `opts.methods` map on `b.mail.server.jmap.create`. Required opts: `deliver` (a `b.mail.send.deliver` instance), `lookupEmail` (async `(emailId, accountId, actor) → Buffer|null`), `identities` (sync `(accountId) → Array<{ id, email }>`). Optional: `onCreated(subId, submission, accountId)` for persistence, `onDestroyed(subId, accountId)`, `onCancel(subId, accountId) → boolean` for undo support, `maxRecipients` (default 1000)."
268
+ },
269
+ {
270
+ "title": "Full RFC 8621 §7.5 error vocabulary",
271
+ "body": "Refusals map to the spec's typed `notCreated` shape with JMAP-namespaced types: `identityNotFound` (unknown identityId), `emailNotFound` (lookupEmail returned null), `forbiddenMailFrom` (envelope.mailFrom doesn't match identity.email), `invalidRecipients` (malformed envelope.rcptTo[i].email), `noRecipients` (empty rcptTo), `tooManyRecipients` (exceeds maxRecipients), `invalidProperties` (missing required keys). Per RFC 8621 §3.6.2, only `undoStatus` is honored in `args.update`; non-canceled values + non-undoStatus keys return `invalidProperties`."
272
+ },
273
+ {
274
+ "title": "Delivery-result → JMAP deliveryStatus mapping",
275
+ "body": "`b.mail.send.deliver`'s `{ delivered, deferred, failed }` outcomes translate into the per-recipient JMAP deliveryStatus shape: `delivered` → `{ smtpReply, delivered: \"yes\" }`; `deferred` → `{ smtpReply, delivered: \"queued\" }`; `failed` → `{ smtpReply, delivered: \"no\" }`. `displayed` is `unknown` until a downstream MDN (RFC 9007) reports otherwise — operators with MDN ingest update via `EmailSubmission/set`'s update branch."
276
+ },
277
+ {
278
+ "title": "Undo via `onCancel` hook",
279
+ "body": "`args.update[subId] = { undoStatus: \"canceled\" }` invokes `opts.onCancel(subId, accountId) → boolean`. Operators backing the JMAP server with a deferred-send queue (e.g. `b.outbox`) return true when the cancel succeeded; the handler refuses with `cannotUnsend` when `onCancel` isn't configured (operator hasn't wired a queue-based send model)."
280
+ },
281
+ {
282
+ "title": "Audit event on every set",
283
+ "body": "Emits `mail.jmap.emailsubmission.set` with `{ accountId, created, notCreated, updated, notUpdated, destroyed, notDestroyed }` counts. The metadata never carries recipient email addresses or RFC 822 body bytes — those stay in the operator's deliver primitive's per-host audit stream."
284
+ }
285
+ ]
286
+ },
287
+ {
288
+ "heading": "Security",
289
+ "items": [
290
+ {
291
+ "title": "Identity binding refuses spoofed `envelope.mailFrom`",
292
+ "body": "RFC 8621 §7.5.1.2 — every create gates `envelope.mailFrom.email` against the identity record's `email` field. The reference handler refuses with `forbiddenMailFrom` when they differ. Operators wiring a delegation model populate the identity's `mayDelegate` / per-domain authorized-senders list and supply the corresponding `identities(accountId)` lookup; the reference handler treats the lookup output as the trust source."
293
+ },
294
+ {
295
+ "title": "Recipient count cap matches `b.mail.send.deliver`",
296
+ "body": "Default `maxRecipients = 1000` aligns with `b.mail.send.deliver`'s recipient-fan-out cap; operators reduce it for tighter postures (e.g. 50 for transactional-mail accounts) without weakening the deliver primitive's own gate."
297
+ }
298
+ ]
299
+ },
300
+ {
301
+ "heading": "Detectors",
302
+ "items": [
303
+ {
304
+ "title": "Two new KNOWN_CLUSTERS family-subset entries",
305
+ "body": "The new handler's opts-walk shingle structurally resembles `lib/importmap-integrity.js:build` (SRI module-map walk) and `lib/middleware/security-headers.js:create` (header-map walk). Added explanatory family-subset entries documenting why each primitive's per-entry body validates a distinct spec vocabulary (W3C Importmap-Integrity vs RFC 8621 §7.5 EmailSubmission vs HTTP header-value sanitisation) so the dup detector can't surface the false-positive again."
306
+ }
307
+ ]
308
+ }
309
+ ],
310
+ "references": [
311
+ {
312
+ "label": "RFC 8621 §7 (JMAP EmailSubmission)",
313
+ "url": "https://www.rfc-editor.org/rfc/rfc8621.html#section-7"
314
+ },
315
+ {
316
+ "label": "RFC 8621 §7.5 (EmailSubmission/set)",
317
+ "url": "https://www.rfc-editor.org/rfc/rfc8621.html#section-7.5"
318
+ },
319
+ {
320
+ "label": "RFC 8621 §7.4 (deliveryStatus shape)",
321
+ "url": "https://www.rfc-editor.org/rfc/rfc8621.html#section-7.4"
322
+ },
323
+ {
324
+ "label": "RFC 8620 §3.6.1 (JMAP error vocabulary)",
325
+ "url": "https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6.1"
326
+ }
327
+ ]
328
+ },
329
+ {
330
+ "version": "0.11.37",
331
+ "date": "2026-05-21",
332
+ "headline": "`b.calendar` VJOURNAL ↔ JSCalendar Note (RFC 5545 §3.6.3)",
333
+ "summary": "Closes the v0.11.31 deferral on VJOURNAL. `b.calendar.fromIcal` now recognises VJOURNAL components and maps them to JSCalendar-shaped Note objects (`@type: \"Note\"`). `b.calendar.toIcal` emits a VJOURNAL envelope when the input `@type` is `Note`. `b.calendar.validate` adds Note-specific shape rules: optional `start` LocalDateTime, no `duration` / `due` / `progress` / `percentComplete` / `progressUpdated` (those are Event / Task-only properties), optional `status` from the RFC 5545 §3.8.1.11 VJOURNAL vocabulary (`draft` | `final` | `cancelled`). VJOURNAL is the only iCalendar component that may carry multiple DESCRIPTION properties — Note preserves operator-visible boundaries by joining them with a blank-line separator. A VCALENDAR carrying VEVENT + VTODO + VJOURNAL children now returns a mixed `Event` + `Task` + `Note` array.",
334
+ "sections": [
335
+ {
336
+ "heading": "Added",
337
+ "items": [
338
+ {
339
+ "title": "`b.calendar.fromIcal` maps VJOURNAL → JSCalendar Note",
340
+ "body": "VJOURNAL components in the VCALENDAR map to `@type: \"Note\"` objects with `uid` / `updated` / `title` (SUMMARY) / `description` (DESCRIPTION) / `start` (DTSTART → LocalDateTime, optional) / `timeZone` / `status` (lower-cased STATUS) / `locations` / `recurrenceRules`. UTC DTSTART maps to `timeZone: \"Etc/UTC\"` the same way DTSTART does for Event."
341
+ },
342
+ {
343
+ "title": "`b.calendar.toIcal` emits VJOURNAL when `@type === \"Note\"`",
344
+ "body": "The envelope is `BEGIN:VJOURNAL` / `END:VJOURNAL` instead of `BEGIN:VEVENT` or `BEGIN:VTODO`. Note's `status` round-trips uppercased per RFC 5545 §3.8.1.11. Note does NOT emit DURATION / DUE / PERCENT-COMPLETE / COMPLETED on the wire (those properties are forbidden on VJOURNAL per RFC 5545 §3.6.3 grammar)."
345
+ },
346
+ {
347
+ "title": "`b.calendar.validate` learns Note-specific shape rules (RFC 5545 §3.6.3)",
348
+ "body": "Note's `start` must be a LocalDateTime when present. Setting `duration`, `due`, `progress`, `percentComplete`, or `progressUpdated` on a Note is refused (those are Event / Task-only properties). `status` must be in the `JSCAL_NOTE_STATUS` catalogue (`draft` | `final` | `cancelled` — different from the Task progress vocabulary). Structured `CalendarError` codes: `calendar/bad-note-status` plus reuse of `calendar/bad-due`, `calendar/bad-progress`, `calendar/bad-duration`, `calendar/bad-percent`, `calendar/bad-progress-updated` for the forbidden-property refusals."
349
+ },
350
+ {
351
+ "title": "Multiple DESCRIPTION properties preserved",
352
+ "body": "VJOURNAL is the only iCalendar component permitted to carry multiple DESCRIPTION properties (one per discrete journal entry). `fromIcal` joins them into a single Note.description with `\\n\\n` between entries, preserving the operator-visible boundary. Single-DESCRIPTION VJOURNALs map to the literal string with no join."
353
+ },
354
+ {
355
+ "title": "Mixed-component VCALENDAR returns array of Event + Task + Note shapes",
356
+ "body": "A VCALENDAR carrying VEVENT + VTODO + VJOURNAL children now returns an Array — events first, tasks second, journals third, in their declared order. The single-component shortcut (returning the bare object) still applies when only one component is present."
357
+ },
358
+ {
359
+ "title": "`JSCAL_TYPES.Note` and `JSCAL_NOTE_STATUS` exports",
360
+ "body": "`b.calendar.JSCAL_TYPES.Note === \"Note\"` (the discriminator string). `b.calendar.JSCAL_NOTE_STATUS` exposes the `draft` / `final` / `cancelled` vocabulary as a frozen object for operator-side enum lookups."
361
+ }
362
+ ]
363
+ },
364
+ {
365
+ "heading": "Changed",
366
+ "items": [
367
+ {
368
+ "title": "JSCalendar `Note` is a blamejs-recognised extension",
369
+ "body": "RFC 8984 §1.2 only enumerates `Event`, `Task`, and `Group` as discriminator values. `Note` is not formally an RFC 8984 type — blamejs adopts it as a recognised extension shape for VJOURNAL round-trip interop. Operators interoperating with strict RFC 8984 consumers should map Note → Group + a vendor-namespaced property before exchange."
370
+ }
371
+ ]
372
+ }
373
+ ],
374
+ "references": [
375
+ {
376
+ "label": "RFC 5545 §3.6.3 (iCalendar VJOURNAL component)",
377
+ "url": "https://www.rfc-editor.org/rfc/rfc5545.html#section-3.6.3"
378
+ },
379
+ {
380
+ "label": "RFC 5545 §3.8.1.11 (STATUS — DRAFT/FINAL/CANCELLED on VJOURNAL)",
381
+ "url": "https://www.rfc-editor.org/rfc/rfc5545.html#section-3.8.1.11"
382
+ },
383
+ {
384
+ "label": "RFC 8984 §1.2 (JSCalendar @type discriminator)",
385
+ "url": "https://www.rfc-editor.org/rfc/rfc8984.html#section-1.2"
386
+ }
387
+ ]
388
+ },
389
+ {
390
+ "version": "0.11.36",
391
+ "date": "2026-05-21",
392
+ "headline": "`b.calendar.expandRecurrence` picks up BYWEEKNO / BYYEARDAY / BYHOUR / BYMINUTE / BYSECOND filters (RFC 5545 §3.3.10)",
393
+ "summary": "Closes the v0.11.31 deferral on the time-of-day and year-relative BY* filters. `expandRecurrence` now honours `byWeekNo` (ISO 8601 1..53 with negative-from-end semantics), `byYearDay` (1..366 with negative-from-end), `byHour` (0..23), `byMinute` (0..59), and `bySecond` (0..60 — covers POSIX leap-second representation). The expansion still operates as a per-step filter (stepping at the rule's FREQ, then dropping candidates that fail the BY* predicates) — BYSETPOS remains deferred-with-condition because it requires expanding ALL candidates within a FREQ interval and picking the Nth, which is a structural restructure of the expand loop. Operators with `last weekday of month`-style needs continue to see the same defer note from v0.11.31.",
394
+ "sections": [
395
+ {
396
+ "heading": "Added",
397
+ "items": [
398
+ {
399
+ "title": "`byWeekNo` filter — ISO 8601 week numbers",
400
+ "body": "`recurrenceRules[i].byWeekNo: [1, 53, -1]` filters candidates to the named ISO 8601 weeks. Negative values count from the end of the year (`-1` = last ISO week, which may be week 52 or week 53 depending on the year). The ISO week-of-year calculation matches the canonical algorithm — week 1 is the week containing the first Thursday of the year."
401
+ },
402
+ {
403
+ "title": "`byYearDay` filter — day-of-year (1..366 / -1..-366)",
404
+ "body": "`recurrenceRules[i].byYearDay: [1, 366, -1]` filters by ordinal day-of-year. Negative values count from the end of the year, accounting for leap-year length (366 vs 365). With a `frequency: \"daily\"` rule + `byYearDay: [1]` the expander emits Jan 1 of each year."
405
+ },
406
+ {
407
+ "title": "`byHour` / `byMinute` / `bySecond` time-of-day filters",
408
+ "body": "`byHour: [9, 17]` filters candidates by UTC hour-of-day (0..23). `byMinute: [0, 30]` (0..59). `bySecond: [0, 30, 60]` (0..60 — the leap-second representation in POSIX time). Most useful with sub-day frequencies — `frequency: \"hourly\"` + `byHour: [9, 17]` emits twice-daily at 9am and 5pm."
409
+ },
410
+ {
411
+ "title": "Negative BY* semantics per RFC 5545 §3.3.10",
412
+ "body": "`byWeekNo: [-1]` matches the last ISO week of the year (computed via the Dec-28 anchor — Dec 28 always falls in the last ISO week). `byYearDay: [-1]` matches the last day of the year (uses the Gregorian leap-year rule to determine whether 365 or 366 is the correct ordinal). Both negative-value paths covered by tests."
413
+ }
414
+ ]
415
+ },
416
+ {
417
+ "heading": "Security",
418
+ "items": [
419
+ {
420
+ "title": "Same step-budget cap from v0.11.31 applies",
421
+ "body": "Sparse BY* filters (e.g. `byYearDay: [1]` with `frequency: \"daily\"` — only 1 in 365 candidates matches) still loop within the `MAX_EXPAND_INSTANCES * 366` step budget; the 10-year `MAX_EXPAND_SPAN_MS` window cap also applies. Operators can't construct a BY*-filter that loops past the bounded budget."
422
+ },
423
+ {
424
+ "title": "Integer-range validation on every BY* value",
425
+ "body": "`byWeekNo` rejects values outside ±53. `byYearDay` rejects outside ±366. `byHour` 0..23. `byMinute` 0..59. `bySecond` 0..60. Adversarial-shape values silently drop from the set (no error — the rule continues with the surviving values per RFC 5545's tolerant grammar)."
426
+ }
427
+ ]
428
+ }
429
+ ],
430
+ "references": [
431
+ {
432
+ "label": "RFC 5545 §3.3.10 (RRULE — BYWEEKNO / BYYEARDAY / BYHOUR / BYMINUTE / BYSECOND)",
433
+ "url": "https://www.rfc-editor.org/rfc/rfc5545.html#section-3.3.10"
434
+ },
435
+ {
436
+ "label": "RFC 8984 §4.3.2 (JSCalendar RecurrenceRule)",
437
+ "url": "https://www.rfc-editor.org/rfc/rfc8984.html#section-4.3.2"
438
+ },
439
+ {
440
+ "label": "ISO 8601 (Week numbering)",
441
+ "url": "https://www.iso.org/iso-8601-date-and-time-format.html"
442
+ }
443
+ ]
444
+ },
445
+ {
446
+ "version": "0.11.35",
447
+ "date": "2026-05-21",
448
+ "headline": "`b.calendar` VTODO ↔ JSCalendar Task (RFC 8984 §6)",
449
+ "summary": "Closes the v0.11.31 deferral. `b.calendar.fromIcal` now recognises VTODO components and maps them to JSCalendar Task objects per RFC 8984 §6. `b.calendar.toIcal` emits a VTODO envelope (with DUE / STATUS / PERCENT-COMPLETE / COMPLETED iCalendar properties) when the input `@type` is `Task`. `b.calendar.validate` picks up Task-specific shape rules: `due` as LocalDateTime, `estimatedDuration` as PnYnMnDTnHnMnS, `progress` from the RFC 8984 §6.4.3 vocabulary (`needs-action` | `in-process` | `completed` | `cancelled` | `failed`), `percentComplete` ∈ [0..100], `progressUpdated` as UTCDateTime. A VCALENDAR carrying both VEVENT and VTODO components now returns an array of mixed `Event` + `Task` shapes.",
450
+ "sections": [
451
+ {
452
+ "heading": "Added",
453
+ "items": [
454
+ {
455
+ "title": "`b.calendar.fromIcal` maps VTODO → JSCalendar Task",
456
+ "body": "VTODO components in the VCALENDAR are mapped to `@type: \"Task\"` objects with the same `uid` / `updated` / `title` / `description` / `start` / `timeZone` / `locations` / `recurrenceRules` slots Event uses, plus the Task-specific fields: `due` (DUE → LocalDateTime), `estimatedDuration` (DURATION), `progress` (STATUS lowercased), `percentComplete` (PERCENT-COMPLETE 0..100), `progressUpdated` (COMPLETED → UTCDateTime). UTC DUE values map to `timeZone: \"Etc/UTC\"` the same way DTSTART does."
457
+ },
458
+ {
459
+ "title": "`b.calendar.toIcal` emits VTODO when `@type === \"Task\"`",
460
+ "body": "The envelope is `BEGIN:VTODO` / `END:VTODO` instead of `BEGIN:VEVENT`. Task-specific properties round-trip: `DUE` (with `;TZID=` parameter or `Z` suffix or floating local time per the same RFC 8984 §1.4.4 timeZone mapping as DTSTART), `STATUS` (uppercased), `PERCENT-COMPLETE`, `COMPLETED` (UTC). `estimatedDuration` maps to `DURATION` (RFC 5545 doesn't distinguish Event vs Task duration on the wire)."
461
+ },
462
+ {
463
+ "title": "`b.calendar.validate` learns Task-specific shape rules (RFC 8984 §6.4)",
464
+ "body": "`due` must be a LocalDateTime, `estimatedDuration` must match the same PnYnMnDTnHnMnS Duration grammar Event uses, `progress` must be in the `JSCAL_TASK_PROGRESS` catalogue (also exposed as a new top-level export), `percentComplete` must be a finite number in 0..100, `progressUpdated` must be a UTCDateTime. Structured `CalendarError` codes: `calendar/bad-due`, `calendar/bad-progress`, `calendar/bad-percent`, `calendar/bad-progress-updated`."
465
+ },
466
+ {
467
+ "title": "Mixed-component VCALENDAR returns an array of Event + Task shapes",
468
+ "body": "When a VCALENDAR contains both VEVENT and VTODO children, `fromIcal` returns an Array — events first, tasks second, in their declared order. The single-component shortcut (returning the bare object) still applies when only one component is present."
469
+ },
470
+ {
471
+ "title": "`calendar/no-vevent` error code renamed to `calendar/no-component`",
472
+ "body": "The refusal that fires when a VCALENDAR has no parseable child component now uses the more accurate `calendar/no-component` code (since VTODO is also valid). Operators that grep on the prior `calendar/no-vevent` code need to update the match; the human-facing error message also updates."
473
+ }
474
+ ]
475
+ }
476
+ ],
477
+ "references": [
478
+ {
479
+ "label": "RFC 8984 §6 (JSCalendar Task)",
480
+ "url": "https://www.rfc-editor.org/rfc/rfc8984.html#section-6"
481
+ },
482
+ {
483
+ "label": "RFC 5545 §3.6.2 (iCalendar VTODO component)",
484
+ "url": "https://www.rfc-editor.org/rfc/rfc5545.html#section-3.6.2"
485
+ },
486
+ {
487
+ "label": "RFC 5545 §3.8.2.3 (DUE date-time)",
488
+ "url": "https://www.rfc-editor.org/rfc/rfc5545.html#section-3.8.2.3"
489
+ },
490
+ {
491
+ "label": "RFC 5545 §3.8.1.11 (STATUS — needs-action/in-process/completed/cancelled)",
492
+ "url": "https://www.rfc-editor.org/rfc/rfc5545.html#section-3.8.1.11"
493
+ }
494
+ ]
495
+ },
496
+ {
497
+ "version": "0.11.34",
498
+ "date": "2026-05-21",
499
+ "headline": "JMAP WebSocket transport (RFC 8887) on `b.mail.server.jmap`",
500
+ "summary": "Closes the v0.11.29 deferral. The JMAP listener now exposes `webSocketHandler(req, socket, head)` — a turnkey RFC 8887 transport built on the framework's `b.websocket.handleUpgrade`. Clients connect with the `jmap` subprotocol; bidirectional JSON frames carry `{ \"@type\": \"Request\" }` / `{ \"@type\": \"WebSocketPushEnable\" }` / `{ \"@type\": \"WebSocketPushDisable\" }` from the client, and `{ \"@type\": \"Response\" }` / `{ \"@type\": \"StateChange\" }` / `{ \"@type\": \"RequestError\" }` from the server. `Request` frames flow through the same `dispatch(actor, request)` path the HTTP `apiHandler` uses; `WebSocketPushEnable` hooks the operator's `mailStore.subscribePush(actor, types, emitFn)` and converts backend StateChange events into outbound WebSocket frames.\n\nThe session resource picks up `webSocketUrl` so clients discover the endpoint via the same JMAP session-discovery flow they use for `apiUrl` / `eventSourceUrl` / `uploadUrl` / `downloadUrl`. permessage-deflate is OFF by default — same CRIME-class compression-oracle threat model as the v0.11.28 IMAP COMPRESS=DEFLATE intentional skip — operators opt in via `opts.webSocketPermessageDeflate`.",
501
+ "sections": [
502
+ {
503
+ "heading": "Added",
504
+ "items": [
505
+ {
506
+ "title": "`webSocketHandler(req, socket, head)` on the listener handle",
507
+ "body": "Mount on the operator's HTTP server's `'upgrade'` event. Auth is delegated to the surrounding HTTP middleware (the handler expects `req.user` / `req.actor` to be populated by upstream auth); unauthenticated requests write `HTTP/1.1 401 Unauthorized` to the raw socket per the WebSocket spec's pre-handshake error shape. RFC 8887 §3.1 requires the `jmap` subprotocol; the handler refuses the upgrade with close-code 1002 if `Sec-WebSocket-Protocol: jmap` is missing."
508
+ },
509
+ {
510
+ "title": "Bidirectional JSON-framed transport",
511
+ "body": "Client → server `@type`: `Request` (same body as HTTP POST — `{ using, methodCalls, createdIds? }`), `WebSocketPushEnable` (`{ dataTypes?, pushState? }`), `WebSocketPushDisable`. Server → client `@type`: `Response` (`{ requestId, methodResponses, sessionState, createdIds }`), `StateChange` (`{ changed, pushed? }`), `RequestError` (`{ requestId, type, description }`). Unknown `@type` frames trigger a `RequestError` rather than tearing down the channel."
512
+ },
513
+ {
514
+ "title": "Push integration shares the operator hook with EventSource",
515
+ "body": "`WebSocketPushEnable` forwards `(actor, dataTypes, emitFn)` to `mailStore.subscribePush` — the same backend hook the EventSource handler (v0.11.29) consumes. Operators wire push once and both transports surface the same `StateChange` events. Per-connection `WebSocketPushDisable` calls the unsubscribe function the backend returned from `subscribePush`. Connection close cleans up implicitly."
516
+ },
517
+ {
518
+ "title": "Session resource carries `webSocketUrl`",
519
+ "body": "The session JSON now includes `webSocketUrl: opts.webSocketUrl || \"/jmap/ws\"` per RFC 8887 §3. Operators discoverable-by-default — clients using a stock JMAP library find the WS endpoint via the session resource the same way they find `apiUrl` today."
520
+ }
521
+ ]
522
+ },
523
+ {
524
+ "heading": "Security",
525
+ "items": [
526
+ {
527
+ "title": "Binary frames refused",
528
+ "body": "JMAP is JSON-only over WebSocket. Binary frames trigger a `RequestError` with `type: \"notJSON\"`; the connection stays open so the next text frame can be valid. Prevents a misbehaving client from sneaking opaque bytes past the JSON parser."
529
+ },
530
+ {
531
+ "title": "JSON parse routed through `b.safeJson.parse`",
532
+ "body": "WebSocket text frames are parsed through `b.safeJson.parse` with the per-connection `maxBytes` cap (default 10 MiB; operator-tunable via `opts.webSocketMaxMessageBytes`). Catches CVE-2020-7660-class prototype-pollution payloads + adversarial-depth JSON before they reach the JMAP method dispatcher."
533
+ },
534
+ {
535
+ "title": "permessage-deflate OFF by default (CRIME-class threat)",
536
+ "body": "RFC 7692 permessage-deflate enables compression-oracle attacks (CVE-2012-4929 CRIME class) when the operator pipes JSON containing both attacker-controlled and confidential data through the same connection. Default is OFF; operators with explicit threat-model justification opt in via `opts.webSocketPermessageDeflate = true`. Mirrors the v0.11.28 IMAP COMPRESS=DEFLATE intentional refusal."
537
+ },
538
+ {
539
+ "title": "Subprotocol negotiation refuses non-`jmap` clients",
540
+ "body": "If the client's `Sec-WebSocket-Protocol` header doesn't include `jmap`, the listener refuses the upgrade with close-code 1002 (protocol error). RFC 8887 §3.1 — the JMAP-WS connection is identified by the subprotocol; refusing here prevents the connection from being misinterpreted as a generic WebSocket channel by middleware downstream."
541
+ }
542
+ ]
543
+ }
544
+ ],
545
+ "references": [
546
+ {
547
+ "label": "RFC 8887 (JMAP over WebSocket)",
548
+ "url": "https://www.rfc-editor.org/rfc/rfc8887.html"
549
+ },
550
+ {
551
+ "label": "RFC 8620 (JMAP Core)",
552
+ "url": "https://www.rfc-editor.org/rfc/rfc8620.html"
553
+ },
554
+ {
555
+ "label": "RFC 6455 (The WebSocket Protocol)",
556
+ "url": "https://www.rfc-editor.org/rfc/rfc6455.html"
557
+ },
558
+ {
559
+ "label": "RFC 7692 (Compression Extensions for WebSocket — NOT enabled)",
560
+ "url": "https://www.rfc-editor.org/rfc/rfc7692.html"
561
+ },
562
+ {
563
+ "label": "CVE-2012-4929 (CRIME — compression-oracle attack on TLS)",
564
+ "url": "https://nvd.nist.gov/vuln/detail/CVE-2012-4929"
565
+ }
566
+ ]
567
+ },
568
+ {
569
+ "version": "0.11.33",
570
+ "date": "2026-05-21",
571
+ "headline": "IMAP QRESYNC (RFC 7162 §3.2) — VANISHED responses + SELECT delta on `b.mail.server.imap`",
572
+ "summary": "Closes the v0.11.27 deferral. The IMAP listener now advertises QRESYNC in CAPABILITY, accepts `ENABLE QRESYNC` (which implicitly engages CONDSTORE per §3.2.5), and parses the `(QRESYNC (<uidvalidity> <modseq> [<knownUids>] [<knownSequenceMatchData>]))` parameter list on SELECT / EXAMINE. When the client's UIDVALIDITY matches the backend's, the listener emits a single `* VANISHED (EARLIER) <uid-set>` listing UIDs the server expunged since the client's snapshot — operators implement the actual delta computation via `mailStore.selectFolder(actor, mailbox, { qresync }) → { ..., vanishedEarlier }`. Stale-UIDVALIDITY clients fall through to a full re-SELECT.",
573
+ "sections": [
574
+ {
575
+ "heading": "Added",
576
+ "items": [
577
+ {
578
+ "title": "CAPABILITY advertises `QRESYNC`",
579
+ "body": "Sits next to the existing `CONDSTORE` advertisement. Both extensions are server-advertised; clients ENABLE before relying on the responses. RFC 7162 §3.2.5 — QRESYNC implies CONDSTORE."
580
+ },
581
+ {
582
+ "title": "`ENABLE QRESYNC` engages both flags",
583
+ "body": "The handler flips `state.enabledQResync = true` AND `state.enabledCondStore = true` and emits `* ENABLED QRESYNC` + `OK ENABLE completed`. Already-engaged QRESYNC re-issues skip the advertisement line (only newly-engaged extensions appear)."
584
+ },
585
+ {
586
+ "title": "`SELECT mailbox (QRESYNC (<uidvalidity> <modseq> [<knownUids>] [<knownSeq>]))`",
587
+ "body": "The QRESYNC parameter list is stripped from the SELECT args before the mailbox-name validator runs and forwarded to `mailStore.selectFolder` as `opts.qresync = { uidvalidity, modseq, knownUids, knownSeq }`. Backends compute the delta and return `vanishedEarlier` (sequence-set string) in the existing select-info object. Non-finite uidvalidity / modseq values refuse with `BAD SELECT QRESYNC params must be (<uidvalidity> <modseq> ...) numerics` before the backend call."
588
+ },
589
+ {
590
+ "title": "Implicit CONDSTORE+QRESYNC engagement on parameterised SELECT",
591
+ "body": "Per RFC 7162 §3.2.4, a client that issues `SELECT INBOX (QRESYNC (...))` without a prior `ENABLE QRESYNC` flips both flags implicitly for the session. Subsequent FETCH responses include `MODSEQ (<n>)` just as they would after explicit ENABLE."
592
+ },
593
+ {
594
+ "title": "`* VANISHED (EARLIER) <uid-set>` emission",
595
+ "body": "The listener emits a single VANISHED untagged response between `HIGHESTMODSEQ` and the tagged OK when (a) the client supplied a QRESYNC parameter, (b) the client's UIDVALIDITY matches the backend's, AND (c) the backend supplied a non-empty `vanishedEarlier`. Mismatched UIDVALIDITY suppresses the VANISHED line so the client correctly falls through to a full re-sync (RFC 7162 §3.2.5)."
596
+ }
597
+ ]
598
+ },
599
+ {
600
+ "heading": "Security",
601
+ "items": [
602
+ {
603
+ "title": "QRESYNC parameter parse is bounded",
604
+ "body": "The regex anchors on `\\(\\s*QRESYNC\\s*\\(\\s*([^)]+)\\)` — `[^)]*` not `.*` — so a malformed parameter list cannot consume the entire SELECT args. Both `uidvalidity` and `modseq` parse strictly as `\\d+` via `parseInt(...)` + `isFinite` check; non-numeric values refuse before the backend dispatch."
605
+ },
606
+ {
607
+ "title": "VANISHED suppressed on UIDVALIDITY mismatch",
608
+ "body": "RFC 7162 §3.2.5 — when the client's `uidvalidity` does NOT match the server's current value, the mailbox has been reset (e.g., recreated under the same name) and the client's UID cache is invalid. The listener intentionally drops the VANISHED line so the client forces a fresh full-sync instead of acting on a delta that references a different message set."
609
+ }
610
+ ]
611
+ }
612
+ ],
613
+ "references": [
614
+ {
615
+ "label": "RFC 7162 §3.2 (QRESYNC — Quick Mailbox Resynchronization)",
616
+ "url": "https://www.rfc-editor.org/rfc/rfc7162.html"
617
+ },
618
+ {
619
+ "label": "RFC 9051 (IMAP4rev2)",
620
+ "url": "https://www.rfc-editor.org/rfc/rfc9051.html"
621
+ },
622
+ {
623
+ "label": "RFC 5161 (IMAP ENABLE Extension)",
624
+ "url": "https://www.rfc-editor.org/rfc/rfc5161.html"
625
+ }
626
+ ]
627
+ },
628
+ {
629
+ "version": "0.11.32",
630
+ "date": "2026-05-21",
631
+ "headline": "`b.mail.crypto.pgp.encrypt` / `.decrypt` / `.wkd` promoted to stable — WKD IDN-homograph defense",
632
+ "summary": "The PGP encrypt / decrypt / Web Key Directory (WKD) primitives that shipped under `b.mail.crypto.pgp.experimental.*` at v0.10.16 are promoted to top-level stable. `b.mail.crypto.pgp.encrypt` / `b.mail.crypto.pgp.decrypt` / `b.mail.crypto.pgp.wkd.fetch` / `b.mail.crypto.pgp.wkd.computeUrl` are now the canonical paths; the v0.10.16 `experimental` aliases continue to work so existing operator code keeps importing through the old path until they migrate at their own pace. WKD picks up a hard IDN-homograph refusal at `wkd.computeUrl` — Cyrillic / Greek / full-width / Han confusable domain characters refuse with `mail-crypto/pgp/bad-domain` before any HTTP fetch runs. Operators with internationalised domains MUST Punycode-encode upstream (RFC 3492 `xn--` form).",
633
+ "sections": [
634
+ {
635
+ "heading": "Added",
636
+ "items": [
637
+ {
638
+ "title": "Top-level `b.mail.crypto.pgp.encrypt` / `.decrypt` / `.wkd.{fetch,computeUrl}`",
639
+ "body": "Same function bodies that shipped under `b.mail.crypto.pgp.experimental.*` at v0.10.16 are now exported at the top of the namespace. The framework-private envelope (BJ-PGP-PQ magic + version) is unchanged; the IANA-pending RFC 9580bis ML-KEM PKESK codepoints will land as an alternate-encoding option in a follow-up slice. The `experimental` alias keeps the v0.10.16 import paths working — operator migration is opt-in. `b.mail.crypto.pgp.encrypt === b.mail.crypto.pgp.experimental.encrypt` (same reference) so a feature-detection test like `typeof b.mail.crypto.pgp.encrypt === 'function'` is the new operator-facing pattern."
640
+ }
641
+ ]
642
+ },
643
+ {
644
+ "heading": "Security",
645
+ "items": [
646
+ {
647
+ "title": "WKD IDN-homograph refusal at `wkd.computeUrl`",
648
+ "body": "`wkd.computeUrl(email)` now refuses any domain containing characters outside the LDH+dot ASCII subset (RFC 952 / RFC 1123 §2). The threat model: Cyrillic `а` (U+0430), Greek `ο` (U+03BF), full-width `A` (U+FF21), and Han homographs visually impersonate Latin letters in `paypa1.com` / `gοogle.com` style phishing host strings; a naive `toLowerCase` + concat into the WKD URL would route the key fetch to the attacker's domain. Operators with legitimate internationalised domains MUST Punycode-encode upstream (the `xn--` form is plain ASCII LDH and passes). The framework's `b.httpClient` already refuses non-ASCII hostnames at the SSRF guard layer; this primitive surfaces the same refusal at the WKD entry point so the error surface is consistent."
649
+ },
650
+ {
651
+ "title": "RFC 5321 + RFC 1035 length caps",
652
+ "body": "Email length capped at 320 octets (RFC 5321 §4.5.3.1 maximum). Domain length capped at 253 octets (RFC 1035 §2.3.4). Empty labels (`bad..example.com`), leading-dot, and trailing-dot domains refuse before any URL construction. Adversarial-length inputs cannot reach the tokenizer / hasher path."
653
+ }
654
+ ]
655
+ }
656
+ ],
657
+ "references": [
658
+ {
659
+ "label": "draft-koch-openpgp-webkey-service (Web Key Directory)",
660
+ "url": "https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/"
661
+ },
662
+ {
663
+ "label": "RFC 9580 (OpenPGP)",
664
+ "url": "https://www.rfc-editor.org/rfc/rfc9580.html"
665
+ },
666
+ {
667
+ "label": "RFC 3492 (Punycode — IDNA `xn--` form)",
668
+ "url": "https://www.rfc-editor.org/rfc/rfc3492.html"
669
+ },
670
+ {
671
+ "label": "RFC 5891 (IDNA 2008)",
672
+ "url": "https://www.rfc-editor.org/rfc/rfc5891.html"
673
+ },
674
+ {
675
+ "label": "RFC 5321 §4.5.3.1 (SMTP — local-part + domain octet maximums)",
676
+ "url": "https://www.rfc-editor.org/rfc/rfc5321.html"
677
+ },
678
+ {
679
+ "label": "RFC 1035 §2.3.4 (domain label length)",
680
+ "url": "https://www.rfc-editor.org/rfc/rfc1035.html"
681
+ },
682
+ {
683
+ "label": "Unicode TR 39 (Security Mechanisms — confusable identifiers)",
684
+ "url": "https://www.unicode.org/reports/tr39/"
685
+ }
686
+ ]
687
+ },
688
+ {
689
+ "version": "0.11.31",
690
+ "date": "2026-05-21",
691
+ "headline": "`b.calendar` — JSCalendar (RFC 8984) primitive + JMAP Calendars method catalogue",
692
+ "summary": "JSCalendar is the JSON-native calendar grammar JMAP Calendars rides on. The framework now ships `b.calendar` — a thin layer over the existing `b.safeIcal.parse` (RFC 5545 bounded parser shipped earlier) that exposes validate / fromIcal / toIcal / expandRecurrence. The JMAP method catalogue at `b.mail.serverRegistry` picks up the 15 Calendar / CalendarEvent / CalendarEventNotification / ParticipantIdentity methods per the draft-ietf-jmap-calendars spec, so operators wire them through the existing `b.mail.server.jmap.create({ methods: { ... } })` dispatch path without `allowExperimental: true` escape-hatches.\n\nv1 scope: validate JSCalendar Event / Task shape per RFC 8984 §5/§6, bidirectional VEVENT ↔ JSCalendar conversion, RRULE expansion for FREQ=DAILY/WEEKLY/MONTHLY/YEARLY/HOURLY/MINUTELY/SECONDLY with INTERVAL / COUNT / UNTIL / BYDAY / BYMONTH / BYMONTHDAY. BYSETPOS / BYWEEKNO / BYYEARDAY filters + non-Gregorian calendars (RFC 7529) + JSCalendar Group objects + VTODO/VJOURNAL mapping are deferred-with-condition for follow-up slices.",
693
+ "sections": [
694
+ {
695
+ "heading": "Added",
696
+ "items": [
697
+ {
698
+ "title": "`b.calendar.validate(jsCal)` — JSCalendar Event/Task shape gate (RFC 8984 §5/§6)",
699
+ "body": "Asserts `@type` ∈ { 'Event', 'Task' }, non-empty `uid` (≤ 1024 bytes), `updated` matches UTCDateTime grammar, Event's optional `start` matches LocalDateTime, `duration` is RFC 8601 PnYnMnDTnHnMnS, every `recurrenceRules[i].@type` is 'RecurrenceRule' with `frequency` in the JSCAL_FREQUENCIES catalogue, every `alerts.{id}.action` is 'display' or 'email'. Returns the input on success; throws `CalendarError` with a structured `.code` (`calendar/no-uid`, `calendar/bad-recurrence`, etc.) on refusal so operator-side error handling has a stable surface."
700
+ },
701
+ {
702
+ "title": "`b.calendar.fromIcal(text, opts?)` + `b.calendar.toIcal(jsCal, opts?)`",
703
+ "body": "Bidirectional bridge: iCalendar (RFC 5545) ↔ JSCalendar (RFC 8984). `fromIcal` runs the text through `b.safeIcal.parse` (already CVE-bounded for parser DoS) and maps the first VEVENT into an Event object (UID → uid, DTSTAMP → updated, DTSTART → start, DURATION → duration, SUMMARY → title, DESCRIPTION → description, LOCATION → locations[L1].name, RRULE → recurrenceRules[0]). `toIcal` round-trips the same shape back through a CRLF-folded VCALENDAR envelope per RFC 5545 §3.1 (75-octet content-line cap). Operators wire it on the EmailSubmission send path so calendar invitations from a JSCalendar back-end emit RFC 5545 over MIME, and inbound iCalendar attachments parse straight into the JMAP method's Event return shape."
704
+ },
705
+ {
706
+ "title": "`b.calendar.expandRecurrence(event, { from, to, max })` — bounded RRULE expansion",
707
+ "body": "Emits ISO 8601 UTC instance timestamps for an Event in the operator's `[from, to]` window. Supports FREQ=DAILY/WEEKLY/MONTHLY/YEARLY/HOURLY/MINUTELY/SECONDLY with INTERVAL, COUNT, UNTIL. Bounded by `MAX_EXPAND_INSTANCES` (4096) and `MAX_EXPAND_SPAN_MS` (10 years) — refuses with `calendar/oversize-expansion-span` when the window exceeds 10 years (CVE-2024-39687-class recurrence-bomb defense, mirroring the parser-side caps already in `b.safeIcal`). LocalDateTime starts without a `timeZone` are treated as floating-UTC during expansion so wall-clock semantics survive `Date.parse` host-locale interpretation."
708
+ },
709
+ {
710
+ "title": "JMAP method catalogue: Calendar / CalendarEvent / CalendarEventNotification / ParticipantIdentity",
711
+ "body": "15 new methods added to `JMAP_METHODS` in `lib/mail-server-registry.js` per draft-ietf-jmap-calendars: `Calendar/{get,changes,set,query,queryChanges}`, `CalendarEvent/{get,changes,query,queryChanges,set,copy}`, `CalendarEventNotification/{get,changes,query,queryChanges,set}`, `ParticipantIdentity/{get,changes,set}`. Operators wire concrete handlers through the existing `b.mail.server.jmap.create({ methods: { 'CalendarEvent/get': async function (actor, args) { ... } } })` path — no `allowExperimental: true` escape-hatch is required."
712
+ }
713
+ ]
714
+ },
715
+ {
716
+ "heading": "Security",
717
+ "items": [
718
+ {
719
+ "title": "Recurrence-bomb expansion caps",
720
+ "body": "`MAX_EXPAND_INSTANCES = 4096` + `MAX_EXPAND_SPAN_MS = 10 years` clamp the expansion budget at the framework boundary so an operator who forwards an unbounded JSCalendar from a hostile peer cannot DoS the host. The caps mirror `b.safeIcal`'s RRULE COUNT + BY-entries caps (which already defend the CVE-2024-39687 class). The 10-year span refusal carries `code: 'calendar/oversize-expansion-span'` so operators distinguish bomb defense from legitimate too-wide-window misconfiguration."
721
+ },
722
+ {
723
+ "title": "UID + duration + UTCDateTime parsing is strict",
724
+ "body": "`validate` refuses `uid` over 1024 bytes (anti-DoS), `updated` that doesn't match the RFC 8984 §1.4.3 `\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z` UTCDateTime grammar exactly (no `+00:00` aliases, no fractional-second-only suffixes), and `duration` that isn't `PnYnMnDTnHnMnS`. Hostile JSCalendar payloads from federation peers can't slip non-canonical-shape values into the operator's storage layer through the validator."
725
+ },
726
+ {
727
+ "title": "iCalendar parsing routed through bounded `b.safeIcal.parse`",
728
+ "body": "`fromIcal` does NOT roll its own RFC 5545 parser — it forwards to `b.safeIcal.parse` which is already audit-hardened (CVE-2024-39929 / CVE-2025-30258 mitigation, bounded depth, capped RRULE COUNT + BY-entries). The JSCalendar layer composes the existing security substrate rather than re-litigating it."
729
+ }
730
+ ]
731
+ }
732
+ ],
733
+ "references": [
734
+ {
735
+ "label": "RFC 8984 (JSCalendar — JSON Representation of Calendar Data)",
736
+ "url": "https://www.rfc-editor.org/rfc/rfc8984.html"
737
+ },
738
+ {
739
+ "label": "RFC 5545 (iCalendar — Internet Calendaring and Scheduling Core Object)",
740
+ "url": "https://www.rfc-editor.org/rfc/rfc5545.html"
741
+ },
742
+ {
743
+ "label": "draft-ietf-jmap-calendars (JMAP for Calendars)",
744
+ "url": "https://datatracker.ietf.org/doc/draft-ietf-jmap-calendars/"
745
+ },
746
+ {
747
+ "label": "RFC 8620 (JMAP Core)",
748
+ "url": "https://www.rfc-editor.org/rfc/rfc8620.html"
749
+ },
750
+ {
751
+ "label": "RFC 8601 (Duration grammar PnYnMnDTnHnMnS)",
752
+ "url": "https://www.rfc-editor.org/rfc/rfc3339.html"
753
+ },
754
+ {
755
+ "label": "RFC 7986 (New properties for iCalendar)",
756
+ "url": "https://www.rfc-editor.org/rfc/rfc7986.html"
757
+ },
758
+ {
759
+ "label": "CVE-2024-39687 (iCalendar recurrence-bomb expansion)",
760
+ "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-39687"
761
+ }
762
+ ]
763
+ },
764
+ {
765
+ "version": "0.11.30",
766
+ "date": "2026-05-21",
767
+ "headline": "JMAP blob upload + download handlers on `b.mail.server.jmap` (RFC 8620 §6)",
768
+ "summary": "The JMAP listener now exposes turnkey HTTP handlers for blob upload (`POST /jmap/upload/{accountId}`) and blob download (`GET /jmap/download/{accountId}/{blobId}/{name}`). Upload streams the request body into `b.safeBuffer.boundedChunkCollector` (default cap 50 MiB; operator-tunable via `opts.maxBlobBytes`), then calls the operator backend's `mailStore.uploadBlob(actor, accountId, contentType, bytes) → { blobId, type?, size? }` and returns the JSON descriptor clients pass to subsequent `Email/set` / `Email/import` calls. Download walks the operator backend's `mailStore.downloadBlob(actor, accountId, blobId) → { bytes, type }` and pipes the bytes through with the right `Content-Type` + `Content-Disposition` headers. Both handlers refuse 503 when the corresponding backend hook is missing — never silent OK.\n\nEmailSubmission (RFC 8621 §7) was already in the JMAP method catalogue at `lib/mail-server-registry.js`; operators wire `EmailSubmission/get` / `EmailSubmission/set` through the existing `opts.methods` dispatch and compose `b.mail.send.deliver` (shipped v0.11.24) for the actual outbound send. No additional framework wiring is required.",
769
+ "sections": [
770
+ {
771
+ "heading": "Added",
772
+ "items": [
773
+ {
774
+ "title": "`uploadHandler(req, res)` — POST `/jmap/upload/{accountId}`",
775
+ "body": "Streams the request body through `b.safeBuffer.boundedChunkCollector` so a runaway upload refuses with `413 maxSizeUpload` instead of OOM'ing the process. Default cap 50 MiB; tune via `b.mail.server.jmap.create({ maxBlobBytes })`. The `accountId` path segment is identifier-shape-validated (`/^[A-Za-z0-9_-]{1,64}$/`) — path-traversal shapes are refused at the boundary. Calls `mailStore.uploadBlob(actor, accountId, contentType, bytes) → { blobId, type?, size? }`; the listener echoes `accountId` + `blobId` + `type` + `size` back as a `201 Created` JSON response per RFC 8620 §6.1."
776
+ },
777
+ {
778
+ "title": "`downloadHandler(req, res)` — GET `/jmap/download/{accountId}/{blobId}/{name}`",
779
+ "body": "Parses the path's `accountId` / `blobId` / `name` segments + optional `?accept=<type>` query, calls `mailStore.downloadBlob(actor, accountId, blobId) → { bytes, type } | Buffer | null`, and pipes the bytes through with `Content-Type` (backend-supplied OR query-`accept` OR `application/octet-stream` fallback) + `Content-Length` + `Content-Disposition: attachment; filename=\"<safe-name>\"` (only when the filename matches the safe `[A-Za-z0-9._-]{1,200}` shape — anti header-injection). Missing blob → `404 invalidArguments`."
780
+ },
781
+ {
782
+ "title": "Both handlers exposed on the listener handle",
783
+ "body": "`b.mail.server.jmap.create(opts)` returns `{ apiHandler, sessionHandler, discoveryHandler, eventSourceHandler, uploadHandler, downloadHandler, MailServerJmapError }`. Operators mount each at the path their HTTP router exposes; the `session.uploadUrl` / `session.downloadUrl` already advertise the canonical paths."
784
+ },
785
+ {
786
+ "title": "EmailSubmission methods continue through the existing dispatch path",
787
+ "body": "RFC 8621 §7 — `EmailSubmission/get` / `EmailSubmission/changes` / `EmailSubmission/query` / `EmailSubmission/queryChanges` / `EmailSubmission/set` are already in the JMAP method catalogue at `lib/mail-server-registry.js`. Operators wire them through `b.mail.server.jmap.create({ methods: { 'EmailSubmission/set': async function (actor, args) { ... b.mail.send.deliver(...) ... } } })`. No additional framework wiring is required for v0.11.30; the substrate composition (JMAP method → `b.mail.send.deliver` → SMTP MX → DSN) was already complete after v0.11.24."
788
+ }
789
+ ]
790
+ },
791
+ {
792
+ "heading": "Security",
793
+ "items": [
794
+ {
795
+ "title": "AccountId path-traversal refusal at the boundary",
796
+ "body": "Both handlers validate the `accountId` URL segment against `/^[A-Za-z0-9_-]{1,64}$/`. URL-encoded path-traversal payloads (`%2E%2E%2F`, `..%2F`, `/`) refuse with `400 invalidArguments` before any backend call. Operator-side validation in `mailStore.uploadBlob` / `mailStore.downloadBlob` is a defense-in-depth second layer."
797
+ },
798
+ {
799
+ "title": "Upload size cap via `safeBuffer.boundedChunkCollector` (cap-bounded)",
800
+ "body": "Replaces the prior hand-rolled `Buffer.concat(chunks, received)` shape with the framework's bounded-collector primitive. The collector throws `mail-server-jmap/blob-too-large` on overflow; the handler converts it into a `413 maxSizeUpload` JSON error per RFC 8620 §6.1. A misbehaving client cannot stream a multi-gigabyte payload past the limit — the collector enforces the cap byte-by-byte."
801
+ },
802
+ {
803
+ "title": "`Content-Disposition` filename is identifier-shape only",
804
+ "body": "Download responses only set the `Content-Disposition` header when the URL's `name` segment matches `/^[A-Za-z0-9._-]{1,200}$/`. Filenames with `;` / `\"` / CRLF cannot be smuggled into the header — RFC 6266 §4.3 + CVE-2023-46604-class header-injection defense."
805
+ }
806
+ ]
807
+ }
808
+ ],
809
+ "references": [
810
+ {
811
+ "label": "RFC 8620 (JMAP Core — §6 Blob upload/download)",
812
+ "url": "https://www.rfc-editor.org/rfc/rfc8620.html"
813
+ },
814
+ {
815
+ "label": "RFC 8621 (JMAP Mail — §7 EmailSubmission)",
816
+ "url": "https://www.rfc-editor.org/rfc/rfc8621.html"
817
+ },
818
+ {
819
+ "label": "RFC 6266 (Content-Disposition in HTTP)",
820
+ "url": "https://www.rfc-editor.org/rfc/rfc6266.html"
821
+ },
822
+ {
823
+ "label": "RFC 5987 (Encoding-aware filename* parameter)",
824
+ "url": "https://www.rfc-editor.org/rfc/rfc5987.html"
825
+ }
826
+ ]
827
+ },
828
+ {
829
+ "version": "0.11.29",
830
+ "date": "2026-05-21",
831
+ "headline": "JMAP Push — EventSource SSE handler on `b.mail.server.jmap` (RFC 8620 §7.3)",
832
+ "summary": "The JMAP listener now exposes a real EventSource handler at `/jmap/eventsource`. The session-resource's `eventSourceUrl` becomes a live push channel: clients connect with `?types=*|<csv>&closeafter=no|state&ping=<seconds>`, the listener subscribes via the operator backend's `mailStore.subscribePush(actor, types, emitFn)` hook, and StateChange events arrive as `event: state` SSE frames with the `{ \"@type\": \"StateChange\", changed: {...} }` payload per RFC 8620 §7.4. Keepalive `event: ping` frames default to 30 s (operator-tunable 5-900 s), `closeafter=state` closes after one event for poll-like clients, and the SSE response carries the headers proxies expect (`Cache-Control: no-cache`, `Connection: keep-alive`, `X-Accel-Buffering: no`) so nginx-fronted deployments don't buffer the stream. Without the backend subscribe hook the handler refuses with `503 serverUnavailable`, never silent OK.",
833
+ "sections": [
834
+ {
835
+ "heading": "Added",
836
+ "items": [
837
+ {
838
+ "title": "`eventSourceHandler(req, res)` exposed on the JMAP listener handle",
839
+ "body": "`b.mail.server.jmap.create(opts).eventSourceHandler` mounts on the operator's HTTP router at whatever path the session-resource's `eventSourceUrl` points to (default `/jmap/eventsource`). Authentication is delegated to the surrounding HTTP middleware (the handler expects `req.user` / `req.actor` to be set); unauthenticated requests return `401 forbidden` per RFC 8620 §1.5. Query-string params parse `types=` (CSV or `*` wildcard), `closeafter=` (`no` | `state`), `ping=` (seconds, clamped 5..900) per §7.3."
840
+ },
841
+ {
842
+ "title": "Operator backend hook — `mailStore.subscribePush(actor, types, emitFn)`",
843
+ "body": "Backends opt in by exposing a `subscribePush` method that accepts (1) the authenticated actor, (2) a parsed `types` list (or `null` for the wildcard `*`), and (3) an `emitFn(event)` callback the backend calls when a state change occurs. The expected event shape is `{ kind: 'StateChange', changed: { <accountId>: { <typeName>: <stateString>, ... }, ... }, pushed?: {...} }`; the listener formats it into the SSE `event: state\\ndata: <JSON>\\n\\n` wire shape. The subscribe call may return either `void` or an `unsubscribe()` function the listener invokes on disconnect."
844
+ },
845
+ {
846
+ "title": "SSE wire-correct headers + initial-state hint",
847
+ "body": "Response headers: `Content-Type: text/event-stream; charset=utf-8`, `Cache-Control: no-cache`, `Connection: keep-alive`, `X-Accel-Buffering: no` (nginx-specific — disables response buffering on the stream so per-event frames flush immediately). The initial bytes carry `retry: 5000\\n\\n` (HTML5 SSE reconnect hint — 5 s) + a `: connected\\n\\n` comment so clients can confirm the channel is alive before the first state event."
848
+ }
849
+ ]
850
+ },
851
+ {
852
+ "heading": "Security",
853
+ "items": [
854
+ {
855
+ "title": "Push backend missing returns `503 serverUnavailable` (no silent accept)",
856
+ "body": "If the operator wired the listener without `mailStore.subscribePush`, the handler returns `503 urn:ietf:params:jmap:error:serverUnavailable` with a `description` pointing at the missing hook. RFC 8620 §7.3 expects the server to honour subscriptions or refuse explicitly — silently accepting would let a client believe events will arrive when the server cannot deliver."
857
+ },
858
+ {
859
+ "title": "`closeafter` accepts only `no` | `state` (RFC 8620 §7.3)",
860
+ "body": "Any other value returns `400 invalidArguments` before the subscribe hook fires. Prevents an operator-supplied query string from steering the handler into an undocumented mode."
861
+ },
862
+ {
863
+ "title": "Ping interval clamped 5..900 seconds",
864
+ "body": "`ping=<seconds>` below 5 s or above 900 s clamps to the bounds rather than refusing — a misconfigured client cannot DoS the listener via 1 ms ping floods, and a 24 h ping won't outlast intermediate proxy idle timeouts (typical 60-120 s). The clamped default (30 s) matches the RFC 8620 §7.3 example."
865
+ },
866
+ {
867
+ "title": "Ping timer is `.unref()`'d",
868
+ "body": "Background timers without `.unref()` pin the Node process — graceful shutdown waits indefinitely. The interval is unref'd immediately after creation; per-connection cleanup (`req.on('close')` / `req.on('error')`) clears the timer + invokes the backend's optional `unsubscribe()` + ends the response."
869
+ }
870
+ ]
871
+ }
872
+ ],
873
+ "references": [
874
+ {
875
+ "label": "RFC 8620 (JMAP Core — §7 Push / §7.3 EventSource / §7.4 StateChange)",
876
+ "url": "https://www.rfc-editor.org/rfc/rfc8620.html"
877
+ },
878
+ {
879
+ "label": "RFC 8621 (JMAP Mail)",
880
+ "url": "https://www.rfc-editor.org/rfc/rfc8621.html"
881
+ },
882
+ {
883
+ "label": "RFC 8887 (JMAP WebSocket transport — opt-in, deferred to a later slice)",
884
+ "url": "https://www.rfc-editor.org/rfc/rfc8887.html"
885
+ },
886
+ {
887
+ "label": "HTML5 Server-Sent Events (EventSource)",
888
+ "url": "https://html.spec.whatwg.org/multipage/server-sent-events.html"
889
+ }
890
+ ]
891
+ },
892
+ {
893
+ "version": "0.11.28",
894
+ "date": "2026-05-21",
895
+ "headline": "IMAP opt-in extensions: NOTIFY (RFC 5465), METADATA (RFC 5464), CATENATE (RFC 4469)",
896
+ "summary": "Three IMAP extensions advertised in CAPABILITY and dispatched through the existing per-method registry. NOTIFY accepts a client subscription spec and hands it to the operator's `mailStore.subscribeNotify(actor, spec, emitFn)` hook — actual event emission stays operator-side. METADATA exposes GETMETADATA and SETMETADATA per-mailbox + server-wide annotations through `mailStore.getMetadata` / `setMetadata`. CATENATE extends APPEND to compose a message from existing parts (`TEXT {N}` literals + `URL \"imap://...\"`) via `mailStore.appendCatenate`. Each handler refuses gracefully (`NO ... backend not configured`) when the operator backend doesn't supply the hook. COMPRESS=DEFLATE (RFC 4978) intentionally NOT advertised — CRIME-class compression-oracle threat on the encrypted IMAP stream.",
897
+ "sections": [
898
+ {
899
+ "heading": "Added",
900
+ "items": [
901
+ {
902
+ "title": "CAPABILITY advertises `NOTIFY`, `METADATA`, `METADATA-SERVER`, `CATENATE`",
903
+ "body": "All four added unconditionally so capable clients can exercise the extension regardless of authentication state. Each handler is registered in the protocol verb catalogue (`b.mail.serverRegistry`) + the wire-level guard verb list (`b.guardImapCommand.KNOWN_VERBS`) so the existing dispatch + audit + ratelimit gates apply uniformly."
904
+ },
905
+ {
906
+ "title": "`NOTIFY SET ...` / `NOTIFY NONE` — RFC 5465",
907
+ "body": "The handler parses `NOTIFY SET [STATUS] (<filter-set> (<event>...))*` and `NOTIFY NONE` and stores the filter-set verbatim on `state.notifySpec`. When the operator backend exposes `mailStore.subscribeNotify(actor, spec, emitFn)`, the listener wires an `emitFn` that translates backend events (`{ kind: 'STATUS' | 'LIST' | 'FETCH', payload, seq? }`) into untagged IMAP responses on the same connection — drop-silent if the socket has already closed. Without the backend hook, the wire command refuses with `NO NOTIFY backend not configured` rather than silently accepting subscriptions the server can't fulfil."
908
+ },
909
+ {
910
+ "title": "`GETMETADATA` / `SETMETADATA` — RFC 5464",
911
+ "body": "Both verbs parse the per-mailbox + server-wide annotation forms. GETMETADATA accepts optional `(MAXSIZE N)` / `(DEPTH ...)` options before the mailbox + entry list, walks the entries through `mailStore.getMetadata(actor, mailbox, names, opts) → [{ entry, value }]`, and renders an untagged `* METADATA <mailbox> (<entry> <value>...)` response. SETMETADATA tokenises the entry/value pairs (quoted-strings + NIL for clearing), validates the mailbox name, and forwards to `mailStore.setMetadata(actor, mailbox, entries)`. Without the backend hooks, both return `NO ... backend not configured`."
912
+ },
913
+ {
914
+ "title": "APPEND `CATENATE` modifier — RFC 4469",
915
+ "body": "`APPEND mailbox [flags] [date-time] CATENATE (...)` is recognised before the legacy literal-required APPEND path. The parts list mixes `TEXT {N}` literal-bytes parts (handed in via the literal-aware parser) and `URL \"imap://...\"` reference parts; the listener bundles them into `parts: [{ kind: 'TEXT', bytes } | { kind: 'URL', url }]` and forwards to `mailStore.appendCatenate(mailbox, parts, { actor, flags, internalDate }) → { uid, uidValidity }`. When the backend returns the APPENDUID metadata the response carries `OK [APPENDUID <validity> <uid>] APPEND completed` (RFC 4315). Without the backend hook, refuses with `NO CATENATE backend not configured`."
916
+ }
917
+ ]
918
+ },
919
+ {
920
+ "heading": "Security",
921
+ "items": [
922
+ {
923
+ "title": "COMPRESS=DEFLATE intentionally NOT advertised (CRIME-class)",
924
+ "body": "RFC 4978 IMAP COMPRESS=DEFLATE enables stream compression that interacts badly with TLS — the CRIME attack class (CVE-2012-4929, BREACH, et al.) recovers plaintext via chosen-plaintext compression-ratio analysis. The framework default is OFF; operators with explicit threat models accept the downgrade via `opts.compress = true` (no opt-in path landed in v1, intentionally — defer-with-condition: open when an operator surfaces a deployment that needs it AND can document the chosen-plaintext threat model is mitigated)."
925
+ },
926
+ {
927
+ "title": "Mailbox-name validation reused for both METADATA verbs",
928
+ "body": "Both GETMETADATA and SETMETADATA run `_validateMailboxName` on the parsed mailbox argument (except for the empty-string `\"\"` server-wide-metadata special case per RFC 5464 §3.1). Operators with the existing `allowLegacyMUtf7` opt see the same mailbox-name policy as the rest of the listener; injection-shape mailbox names are refused identically."
929
+ },
930
+ {
931
+ "title": "NOTIFY backend-missing returns NO (not silent accept)",
932
+ "body": "If the operator wired the listener without `mailStore.subscribeNotify`, `NOTIFY SET ...` returns `NO NOTIFY backend not configured` — never a silent `OK`. RFC 5465 §6 specifies NO as the correct refusal shape; silent acceptance would let a client believe events will arrive when the server cannot fulfil the subscription."
933
+ }
934
+ ]
935
+ }
936
+ ],
937
+ "references": [
938
+ {
939
+ "label": "RFC 5465 (IMAP NOTIFY)",
940
+ "url": "https://www.rfc-editor.org/rfc/rfc5465.html"
941
+ },
942
+ {
943
+ "label": "RFC 5464 (IMAP METADATA)",
944
+ "url": "https://www.rfc-editor.org/rfc/rfc5464.html"
945
+ },
946
+ {
947
+ "label": "RFC 4469 (IMAP CATENATE)",
948
+ "url": "https://www.rfc-editor.org/rfc/rfc4469.html"
949
+ },
950
+ {
951
+ "label": "RFC 4315 (IMAP UIDPLUS — APPENDUID response)",
952
+ "url": "https://www.rfc-editor.org/rfc/rfc4315.html"
953
+ },
954
+ {
955
+ "label": "RFC 4978 (IMAP COMPRESS — NOT enabled; CRIME-class threat)",
956
+ "url": "https://www.rfc-editor.org/rfc/rfc4978.html"
957
+ },
958
+ {
959
+ "label": "CVE-2012-4929 (CRIME — compression-oracle attack on TLS)",
960
+ "url": "https://nvd.nist.gov/vuln/detail/CVE-2012-4929"
961
+ }
962
+ ]
963
+ },
964
+ {
965
+ "version": "0.11.27",
966
+ "date": "2026-05-20",
967
+ "headline": "IMAP CONDSTORE (RFC 7162) — modseq-aware FETCH + STORE on `b.mail.server.imap`",
968
+ "summary": "The IMAP listener advertises and honours the CONDSTORE extension. Clients that issue `ENABLE CONDSTORE` get MODSEQ attributes in every untagged FETCH response; FETCH parses the `(CHANGEDSINCE <modseq>)` modifier and forwards it to the operator's backend so the backend can prune unchanged rows server-side; STORE parses the `(UNCHANGEDSINCE <modseq>)` conditional-update modifier and surfaces the backend's MODIFIED conflict set in the tagged OK response (`OK [MODIFIED <set>] STORE completed`). The backend interface picks up four new opts on the existing `fetchRange` / `storeFlags` calls: `changedSince`, `includeVanished`, `includeModseq`, `unchangedSince`. Backends MAY return modseq on each row; the listener injects `MODSEQ (<n>)` into the payload when present and CONDSTORE is enabled. QRESYNC (RFC 7162 §3.2) is deferred — accepted in ENABLE but no vanished-set surface is exposed yet.",
969
+ "sections": [
970
+ {
971
+ "heading": "Added",
972
+ "items": [
973
+ {
974
+ "title": "CAPABILITY advertises `CONDSTORE` unconditionally",
975
+ "body": "Per RFC 7162 §3 servers advertise CONDSTORE; clients ENABLE before relying on MODSEQ in untagged FETCH responses. The advertisement is unconditional (state-independent) so clients that issue CAPABILITY pre-LOGIN see CONDSTORE in the same untagged-response shape they'll see post-LOGIN. The old SELECT-side `HIGHESTMODSEQ` emission keeps working."
976
+ },
977
+ {
978
+ "title": "`ENABLE CONDSTORE` handler flips `state.enabledCondStore`",
979
+ "body": "Replaces the no-op `OK ENABLED` shortcut with a real handler that parses the requested capability set, flips `state.enabledCondStore = true` on CONDSTORE, and replies with `ENABLED CONDSTORE` + `OK ENABLE completed`. Unknown extensions are silently ignored per RFC 5161 §3.1. QRESYNC is recognised but accepted only when a v1+ backend exposes the vanished-set surface."
980
+ },
981
+ {
982
+ "title": "FETCH parses `(CHANGEDSINCE <modseq>)` + injects MODSEQ in responses",
983
+ "body": "When the FETCH args carry a trailing `(CHANGEDSINCE <n>)` modifier (RFC 7162 §3.1.4) the listener strips it from the fetch-att spec and forwards `opts.changedSince` to `mailStore.fetchRange`. The backend can prune unchanged messages server-side. When CONDSTORE is enabled (or the client explicitly requested MODSEQ as a fetch-att), each untagged FETCH response includes `MODSEQ (<n>)` — synthesised from `row.modseq` if the backend supplies it and the payload doesn't already contain it. Also recognises the QRESYNC `VANISHED` modifier (flag forwarded as `opts.includeVanished`); the vanished-set emission is the backend's responsibility for now."
984
+ },
985
+ {
986
+ "title": "STORE parses `(UNCHANGEDSINCE <modseq>)` + emits `[MODIFIED <set>]` on conflict",
987
+ "body": "Per RFC 7162 §3.1.3 the conditional STORE refuses to update messages whose modseq advanced past `unchangedSince` since the client last fetched. The listener parses the modifier between the seq-set and the FLAGS op, forwards `opts.unchangedSince` to `mailStore.storeFlags`, and accepts either the legacy `rows: [...]` shape or a structured `{ rows, modified }` shape. When `modified` is non-empty, the tagged response carries `OK [MODIFIED <set>] STORE completed` so the client knows which messages need re-fetching before retry. Untagged FETCH responses also include `MODSEQ (<n>)` when STORE accepted updates under CONDSTORE."
988
+ }
989
+ ]
990
+ },
991
+ {
992
+ "heading": "Security",
993
+ "items": [
994
+ {
995
+ "title": "Modifier parsing is bounded + non-greedy",
996
+ "body": "The CHANGEDSINCE / UNCHANGEDSINCE matchers use `[^)]*` rather than `.*` so a malformed modifier can't consume the entire fetch-att spec. Both modifiers parse `\\d+` only — non-integer / negative / Infinity values are silently dropped (the modifier becomes a no-op), so a client cannot ride the modifier to inject arbitrary fragments into the backend opts."
997
+ },
998
+ {
999
+ "title": "Modseq attribute is opt-in",
1000
+ "body": "MODSEQ injection into untagged FETCH responses ONLY happens when (a) CONDSTORE is enabled OR (b) the client's fetch-att spec contains the `MODSEQ` keyword. Pre-CONDSTORE clients see exactly the responses they saw before this release. The IMAP wire-format compatibility line is unchanged for the IMAP4rev1 / IMAP4rev2 cohorts that never issue `ENABLE CONDSTORE`."
1001
+ }
1002
+ ]
1003
+ }
1004
+ ],
1005
+ "references": [
1006
+ {
1007
+ "label": "RFC 7162 (IMAP4 CONDSTORE / QRESYNC)",
1008
+ "url": "https://www.rfc-editor.org/rfc/rfc7162.html"
1009
+ },
1010
+ {
1011
+ "label": "RFC 9051 (IMAP4rev2)",
1012
+ "url": "https://www.rfc-editor.org/rfc/rfc9051.html"
1013
+ },
1014
+ {
1015
+ "label": "RFC 5161 (IMAP ENABLE Extension)",
1016
+ "url": "https://www.rfc-editor.org/rfc/rfc5161.html"
1017
+ },
1018
+ {
1019
+ "label": "RFC 4315 (IMAP UIDPLUS Extension)",
1020
+ "url": "https://www.rfc-editor.org/rfc/rfc4315.html"
1021
+ }
1022
+ ]
1023
+ },
1024
+ {
1025
+ "version": "0.11.26",
1026
+ "date": "2026-05-20",
1027
+ "headline": "`b.mail.server.submission` — CHUNKING / BDAT (RFC 3030)",
1028
+ "summary": "The outbound submission listener now advertises and accepts the RFC 3030 BDAT (binary data) command, the chunked-upload alternative to DATA. Operators with large outbound payloads (attachments, MIME multipart bodies, encoded HTML) no longer pay the dot-stuffing cost of DATA; clients can stream chunks of arbitrary size and finalise with a `BDAT 0 LAST` (or `BDAT N LAST` for the final chunk). Mixing DATA + BDAT within one transaction is refused per RFC 3030 §3. Same `agent.handoff` contract — the body bytes arrive at the agent layer in their canonical SMTP form, no dot-stuffing applied (BDAT payloads are opaque).",
1029
+ "sections": [
1030
+ {
1031
+ "heading": "Added",
1032
+ "items": [
1033
+ {
1034
+ "title": "EHLO advertises `CHUNKING` + new `BDAT <chunk-size> [LAST]` command",
1035
+ "body": "The EHLO 250-line list now includes `CHUNKING` (RFC 3030 §2.1). A new `BDAT` command handler accepts `BDAT <chunk-size> [LAST]` after MAIL FROM + RCPT TO; the server reads exactly `<chunk-size>` bytes from the socket — no dot-stuffing, no end-of-data marker — and acknowledges with `250 2.0.0 <octets> octets received`. Multiple BDAT chunks accumulate into the message body; the final chunk carries the `LAST` keyword and triggers the same agent-handoff path as DATA. A `BDAT 0 LAST` finalises an empty trailer when the last chunk's byte count is unknown in advance."
1036
+ },
1037
+ {
1038
+ "title": "Cumulative size cap honoured up-front",
1039
+ "body": "`BDAT <large-size>` is refused with `552 5.3.4 BDAT cumulative size <total> exceeds maxMessageBytes (<cap>)` BEFORE the server begins reading bytes off the wire. A misconfigured client that pipelines `BDAT 999999999 LAST` and 1 GB of body is rejected at the command line, not after the byte stream lands. The collector bound on the receive side enforces the same cap as a backstop."
1040
+ },
1041
+ {
1042
+ "title": "Mid-segment payload drainage",
1043
+ "body": "When `BDAT N LAST\\r\\n<payload>` arrives in one TCP segment (typical for pipelined small messages), the line-loop drains the post-`\\r\\n` bytes from the command buffer straight into the BDAT collector before returning. Any tail bytes after the BDAT chunk completes get re-fed as the next command. Operators get pipelining + chunking with no extra round-trip cost."
1044
+ }
1045
+ ]
1046
+ },
1047
+ {
1048
+ "heading": "Security",
1049
+ "items": [
1050
+ {
1051
+ "title": "BDAT state cleared on every STARTTLS upgrade",
1052
+ "body": "Same threat model as CVE-2021-38371 (Exim) / CVE-2021-33515 (Dovecot): pre-handshake bytes a malicious peer pipelined MUST NOT reach the post-TLS state machine. The STARTTLS handler clears `inBdatChunk` / `bdatRemaining` / `bdatCollector` / `bdatTotalBytes` alongside the existing line-buffer + DATA-collector reset, so a smuggled `BDAT <n>` + body that lands before the TLS upgrade can't bleed into the encrypted transaction."
1053
+ },
1054
+ {
1055
+ "title": "Refusal on BDAT outside transaction",
1056
+ "body": "BDAT before MAIL FROM / with zero RCPT returns `503 5.5.1` and does not enter chunk-collection mode. A misbehaving client that issues BDAT eagerly cannot leak state into the next transaction; the RSET reset path also clears all BDAT-side state."
1057
+ },
1058
+ {
1059
+ "title": "Pipelining race gate mirrors DATA",
1060
+ "body": "If the operator's `recipientPolicy` is async and not all RCPT verdicts have resolved, BDAT returns `451 4.5.0 RCPT TO verdicts pending; reissue BDAT after recipient replies` — same shape as the DATA pipelining-race gate. The transaction never commits with a partially-resolved recipient set."
1061
+ }
1062
+ ]
1063
+ }
1064
+ ],
1065
+ "references": [
1066
+ {
1067
+ "label": "RFC 3030 (SMTP Service Extensions — CHUNKING / BDAT / BINARYMIME)",
1068
+ "url": "https://www.rfc-editor.org/rfc/rfc3030.html"
1069
+ },
1070
+ {
1071
+ "label": "RFC 5321 (SMTP)",
1072
+ "url": "https://www.rfc-editor.org/rfc/rfc5321.html"
1073
+ },
1074
+ {
1075
+ "label": "RFC 6409 (Message Submission for Mail)",
1076
+ "url": "https://www.rfc-editor.org/rfc/rfc6409.html"
1077
+ },
1078
+ {
1079
+ "label": "RFC 8314 (Cleartext considered obsolete — submission ports)",
1080
+ "url": "https://www.rfc-editor.org/rfc/rfc8314.html"
1081
+ },
1082
+ {
1083
+ "label": "CVE-2021-38371 (Exim STARTTLS injection)",
1084
+ "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-38371"
1085
+ },
1086
+ {
1087
+ "label": "CVE-2021-33515 (Dovecot STARTTLS pre-handshake state leak)",
1088
+ "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-33515"
1089
+ }
1090
+ ]
1091
+ },
1092
+ {
1093
+ "version": "0.11.25",
1094
+ "date": "2026-05-20",
1095
+ "headline": "Five new primitives: sealed-token mail FTS + Stripe HMAC-SHA256 webhook verify + `b.money` + `b.fsm` + `b.auth.botChallenge`",
1096
+ "summary": "Five additive primitives in one release. Every consumer building on the framework — mail UIs, billing pipelines, e-commerce backends, multi-step business workflows, public web endpoints — now finds the substrate in-tree instead of reimplementing it. None of these replaces a default; every primitive is opt-in at the call site. Mail full-text search hashes tokens with the per-deployment vault salt before any byte hits the SQLite FTS5 index, so the index is searchable but a database dump leaks nothing readable. The Stripe-shaped HMAC-SHA-256 verifier sits next to the existing PQC and SHA3-512 webhook signers; the `whsec_` prefix stays in the key bytes per Stripe's spec; signatures are constant-time-compared and replay-defended via an operator-supplied nonce store. `b.money` arithmetic is BigInt-only across 40 ISO 4217 currencies with largest-remainder allocation and banker's-rounding FX conversion. `b.fsm` is the in-process sibling of `b.agent.saga` — declarative state + transition definition, guards, async on-enter/on-exit, drop-silent audit on every transition, with a transition-serialisation lock that makes concurrent `.transition()` calls deterministic. `b.auth.botChallenge` composes `b.httpClient` for siteverify against Cloudflare Turnstile (default), hCaptcha, and reCAPTCHA-v3 — the secret bytes never appear in any audit metadata.",
1097
+ "sections": [
1098
+ {
1099
+ "heading": "Added",
1100
+ "items": [
1101
+ {
1102
+ "title": "`b.mailStore.fts` — sealed-token full-text index + `b.mailStore.create().search(folder, filter)`",
1103
+ "body": "Every `appendMessage` now inserts a row into a SQLite FTS5 virtual table whose subject / address / body columns hold space-separated 16-char hex hashes — vault-salted SHA3 over each NFC-normalised token. The salt is the same `b.vault.getDerivedHashSalt()` `b.cryptoField` uses for sealed-column derived hashes, so rotating the vault salt invalidates the FTS index in step with every other sealed-row hash. Query terms go through the same tokenize → hash transform on the search path and issue FTS5 MATCH against the hash-token rows — the index is fully searchable without ever materialising the plaintext on disk. `b.mail.agent.search({ folder, filter })` now accepts `text` / `subject` / `body` / `from` / `to` filter keys; the agent layer hands them to the store. `hardExpunge` deletes the FTS row inside the same transaction as the canonical message row so the two cannot drift. Limits: 8192 tokens / row / field, 2..64 codepoint length band, 8 MiB input cap per field. Exact-token only (no prefix wildcard, no stemmer) — the cost of sealed-at-rest."
1104
+ },
1105
+ {
1106
+ "title": "`b.webhook.verify({ alg: 'hmac-sha256-stripe', secret, header, body, toleranceMs?, nonceStore? })` + `b.webhook.sign(...)`",
1107
+ "body": "Stripe-spec inbound webhook signature verifier (Paddle + Shopify use the same shape). Parses `Stripe-Signature: t=<unix>,v1=<hex>[,v1=<hex>...]`, validates the timestamp is within the tolerance window (default 5 min; refuses below 30s), HMAC-SHA-256s `<t>.<body>` with the `whsec_...` secret bytes verbatim (the prefix IS the key — `b.webhook.verify` never strips it), and walks the v1 entries with `b.crypto.timingSafeEqual` so a rotation grace window doesn't leak which entry matched. `b.webhook.sign` is the round-trip companion for outbound Stripe-shaped emission and round-trip tests. Optional `nonceStore: { has(k), set(k, ttlMs) }` records the accepted signature so a replay within the tolerance window refuses. The header value is hard-capped at 4096 bytes and per-`v1` hex at 256 chars (anti-DoS)."
1108
+ },
1109
+ {
1110
+ "title": "`b.money` — decimal-safe money + 40-currency ISO 4217 catalog",
1111
+ "body": "BigInt minor units throughout — Number is refused at construction. Currency exponents covering 0 (JPY/KRW), 2 (USD/EUR/GBP/…), 3 (KWD/BHD/JOD/OMR/TND), 4 (CLF) are pre-populated. Surface: `b.money.of(amount, code)` / `b.money.fromMinorUnits(bigint, code)` / `b.money.parse('12.50 USD')` / `b.money.zero(code)` plus instance methods `.add` / `.subtract` / `.multiply` / `.allocate(weights[])` / `.negate` / `.abs` / `.equals` / comparison family / `.toMinorUnits()` / `.toString()` / `.toJSON()` / `.format(locale?)`. `b.money.convert(money, toCurrency, fxRateProvider)` rounds half-to-even (banker's rounding) by default. Cross-currency arithmetic throws; `0.10 + 0.20 === 0.30` exactly. Allocation uses largest-remainder so $10 / 3 = [$3.34, $3.33, $3.33] with sum preserved."
1112
+ },
1113
+ {
1114
+ "title": "`b.fsm` — in-process state machine (sibling of `b.agent.saga`)",
1115
+ "body": "`b.fsm.define({ name, initial, states, transitions })` returns a frozen machine factory. `Machine.create({ initialContext })` returns an instance with `.state` / `.context` / `.history` / `.allowed()` / `.can(name)` / `.transition(name, opts)` / `.toJSON()`. Transitions carry an optional guard (predicate; refusal throws `fsm/guard-refused`) and trigger per-state `onEnter` / `onExit` side-effects (sync or returned-Promise — `.transition` awaits). Every transition emits a `fsm.<machineName>.transition` audit event via `b.audit.safeEmit` (drop-silent on hot path), including the actor + metadata the caller passed in. A transition lock serialises concurrent `.transition()` calls — the test suite exercises five parallel transitions and verifies only the legal path runs. State + transition names are identifier-shape only (`safeSql.DEFAULT_IDENTIFIER_RE`) so a name never lands in SQL / HTML un-validated. `Machine.restore(snapshot)` rebuilds an instance from a prior `.toJSON()` snapshot — state + history + context survive a process restart."
1116
+ },
1117
+ {
1118
+ "title": "`b.auth.botChallenge.create({ secret, provider?, ... }) → { verify(token, opts?) }`",
1119
+ "body": "Server-side challenge-response verifier for Cloudflare Turnstile (default), hCaptcha, and reCAPTCHA-v3. Composes `b.httpClient` for the outbound `/siteverify` POST — every request rides the framework's SSRF guard + DNS pinning + retry policy; the operator's secret never appears in a URL or any audit metadata field. Body encoding is `application/x-www-form-urlencoded` per the provider specs. Returns `{ ok, provider, hostname, action, challengeTs, score?, raw }` on success; throws `BotChallengeError` with structured codes (`invalid-token`, `timeout`, `hostname-mismatch`, `action-mismatch`, `provider-error`) on failure, with the upstream `error-codes` array exposed at `err.errorCodes`. Per-factory `allowedHostnames` + `allowedActions` allowlists; per-call `expectedHostname` + `expectedAction` overrides. Tokens are refused at the factory boundary if empty / non-string / > 4096 bytes, before any outbound call."
1120
+ }
1121
+ ]
1122
+ },
1123
+ {
1124
+ "heading": "Security",
1125
+ "items": [
1126
+ {
1127
+ "title": "Mail FTS index leaks zero plaintext (sealed-at-rest invariant extended)",
1128
+ "body": "Pre-v0.11.25, the only operator-facing search path on `b.mail.agent` was a modseq cursor + post-fetch unseal — fast for cursoring but useless for text content discovery. Plaintext-FTS would have broken the sealed-at-rest invariant. The new index hashes every indexed token with the per-deployment vault salt before insert; a `sqlite3 .dump` produces ASCII-hex tokens indistinguishable from random. The same vault salt protects every `b.cryptoField`-sealed column, so adding the FTS index does NOT widen the cryptographic trust boundary."
1129
+ },
1130
+ {
1131
+ "title": "Stripe verifier defends the documented attack surface",
1132
+ "body": "Constant-time HMAC compare (timing-safe across the v1 rotation list — operators don't leak which entry matched). Per-`v1` 256-hex anti-DoS cap. Tolerance-window minimum 30s — refuses operator misconfiguration that would accept hour-old signatures. The `whsec_` prefix preservation is encoded as a `codebase-patterns` detector (`stripe-hmac-sha256-no-strip-whsec-prefix`) so re-introducing the strip-bug is impossible without tripping a release gate."
1133
+ },
1134
+ {
1135
+ "title": "Bot-challenge secret never reaches audit / logs",
1136
+ "body": "`b.auth.botChallenge` audit emits `{ provider, hostname, ok, errorCodes }` — the operator's secret bytes never appear in any metadata key. A `codebase-patterns` detector (`bot-challenge-secret-not-in-audit`) scans `lib/auth/bot-challenge.js` for any `audit.*emit` window that references `secret` so a future refactor cannot regress."
1137
+ }
1138
+ ]
1139
+ },
1140
+ {
1141
+ "heading": "Detectors",
1142
+ "items": [
1143
+ {
1144
+ "title": "Five new `codebase-patterns` detectors encode the shape-specific bug classes",
1145
+ "body": "`stripe-hmac-sha256-no-strip-whsec-prefix` flags `secret.replace(/^whsec_/, '')` / `secret.slice(6)` near an HMAC call (the `whsec_` prefix IS the key). `no-number-money-arithmetic` flags `b.money.of(<Number>, ...)` and Number / Money arithmetic (precision lost; only BigInt / string OK at construction). `fsm-transition-without-await` flags `<fsm>.transition(...)` without `await` or `.then` in `lib/` (the transition is async; sync misuse swallows errors). `bot-challenge-secret-not-in-audit` is file-scoped to `lib/auth/bot-challenge.js` and flags any `audit.*emit` window or `metadata: { ... }` object literal referencing `secret`. Each detector is wired into `codebase-patterns.test.js`'s `run()` so every future commit re-checks the shape."
1146
+ }
1147
+ ]
1148
+ }
1149
+ ],
1150
+ "references": [
1151
+ {
1152
+ "label": "RFC 2104 (HMAC)",
1153
+ "url": "https://www.rfc-editor.org/rfc/rfc2104.html"
1154
+ },
1155
+ {
1156
+ "label": "RFC 6234 (US Secure Hash Algorithms — SHA-256)",
1157
+ "url": "https://www.rfc-editor.org/rfc/rfc6234.html"
1158
+ },
1159
+ {
1160
+ "label": "Stripe webhook signature spec",
1161
+ "url": "https://docs.stripe.com/webhooks/signature"
1162
+ },
1163
+ {
1164
+ "label": "Paddle webhook signature verification",
1165
+ "url": "https://developer.paddle.com/webhooks/signature-verification"
1166
+ },
1167
+ {
1168
+ "label": "Shopify webhooks — HMAC verification",
1169
+ "url": "https://shopify.dev/docs/apps/webhooks/configuration/https"
1170
+ },
1171
+ {
1172
+ "label": "ISO 4217 (Currency codes + minor unit catalog)",
1173
+ "url": "https://www.iso.org/iso-4217-currency-codes.html"
1174
+ },
1175
+ {
1176
+ "label": "IEEE 754 (the float-precision problem `b.money` avoids)",
1177
+ "url": "https://standards.ieee.org/standard/754-2019.html"
1178
+ },
1179
+ {
1180
+ "label": "Cloudflare Turnstile — server-side validation",
1181
+ "url": "https://developers.cloudflare.com/turnstile/get-started/server-side-validation/"
1182
+ },
1183
+ {
1184
+ "label": "hCaptcha — verify the user response server-side",
1185
+ "url": "https://docs.hcaptcha.com/"
1186
+ },
1187
+ {
1188
+ "label": "reCAPTCHA-v3 — server-side verification",
1189
+ "url": "https://developers.google.com/recaptcha/docs/v3"
1190
+ },
1191
+ {
1192
+ "label": "OWASP ASVS v5 §11.5 — bot defense controls",
1193
+ "url": "https://owasp.org/www-project-application-security-verification-standard/"
1194
+ },
1195
+ {
1196
+ "label": "RFC 9051 (IMAP4rev2 — SEARCH semantics)",
1197
+ "url": "https://www.rfc-editor.org/rfc/rfc9051.html"
1198
+ },
1199
+ {
1200
+ "label": "RFC 8621 (JMAP Mail — Email/query)",
1201
+ "url": "https://www.rfc-editor.org/rfc/rfc8621.html"
1202
+ },
1203
+ {
1204
+ "label": "SQLite FTS5",
1205
+ "url": "https://www.sqlite.org/fts5.html"
1206
+ },
1207
+ {
1208
+ "label": "UML State Machine (OMG UML 2.5.1 §14)",
1209
+ "url": "https://www.omg.org/spec/UML/2.5.1"
1210
+ }
1211
+ ]
1212
+ },
1213
+ {
1214
+ "version": "0.11.24",
1215
+ "date": "2026-05-20",
1216
+ "headline": "`b.mail.send.deliver` — turnkey outbound SMTP composer (MX → MTA-STS → DANE → REQUIRETLS → DSN)",
1217
+ "summary": "One factory wires together the full outbound mail chain. The operator hands the primitive an envelope (`{ from, to, rfc822 }`) and gets back per-recipient outcomes: `delivered`, `deferred` (with retry budget), or `failed` (with the corresponding RFC 3464 DSN already composed and the configured DSN sink invoked). Per-recipient handling is independent — one recipient permanently failing does not interfere with another's delivery or retry. MX records are resolved live (operator may inject a resolver for testing), MTA-STS policy (RFC 8461) is fetched + matched against the resolved MX before TLS, DANE TLSA records (RFC 7672) are consulted when present, REQUIRETLS (RFC 8689) is honored, and the per-host SMTP transport is the framework's `b.mail.smtpTransport`. SMTP outcomes are classified deterministically: hard 5xx + null-MX → permanent (with DSN); soft 4xx + DNS / connect / TLS errors → transient (with backoff budget). Recipient cap (1000 per call) and per-host / per-lookup timeout caps are baked in.",
1218
+ "sections": [
1219
+ {
1220
+ "heading": "Added",
1221
+ "items": [
1222
+ {
1223
+ "title": "`b.mail.send.deliver.create(opts) → async deliver(envelope)` — composed outbound delivery",
1224
+ "body": "Factory returns a `deliver(envelope)` async function. `envelope = { from, to[], rfc822 }`. Returns `{ delivered: [{...}], deferred: [{...}], failed: [{...}] }`. Composes `b.network.smtp.policy.mtaSts.fetch` + `.matchMx`, `b.network.smtp.policy.dane.tlsa` + `.verifyChain`, `b.mail.smtpTransport.create`, and `b.audit.safeEmit`. Required opts: `hostname` (EHLO + MAIL FROM identity). Optional: `resolver` (object exposing `resolveMx` / `resolve` — defaults to node:dns/promises); `policy.mtaSts.enabled` (default true); `policy.dane.enabled` (default true); `retry.maxAttempts` (default 5); `retry.backoffMs` (default exponential 60s/5m/30m/2h/12h); `dsn.from` + `dsn.onPermanentFailure(messageBuffer, ctx)` (required only when DSN delivery is desired); `timeouts.mxLookupMs` (default 5s); `timeouts.perHostMs` (default 60s); `transportFactory` (test-injection override)."
1225
+ },
1226
+ {
1227
+ "title": "Per-recipient outcome classifier (`_classifySmtpOutcome`)",
1228
+ "body": "Maps SMTP reply codes + Node socket / TLS errors onto the `permanent` / `transient` axis. Permanent: SMTP 5xx (any subclass), null-MX RFC 7505 sentinel `.`, no-MX-records, RFC 5321 §3.6.2 unrouteable. Transient: SMTP 4xx (any subclass), TCP connection refused / reset / timeout, TLS handshake failure (when MTA-STS / DANE is not enforce-mode), DNS NXDOMAIN at lookup time. Once `retry.maxAttempts` is exhausted, transient is escalated to permanent with reason `retry exhausted (after N attempts)`."
1229
+ },
1230
+ {
1231
+ "title": "RFC 3464 DSN composer (`_buildDsnMessage`)",
1232
+ "body": "Builds a `multipart/report; report-type=delivery-status` message with three parts: human-readable explanation, `message/delivery-status` block (Reporting-MTA, Original-Recipient, Final-Recipient, Action: failed, Status: 5.x.x), and a `message/rfc822-headers` block carrying the failed message's headers. Boundary token is generated via `b.crypto.generateToken(12)` so it can't collide with attacker-chosen header / body bytes. The composed DSN is passed to the operator-supplied `dsn.onPermanentFailure(message, ctx)` sink — the primitive does not itself send the DSN, keeping the operator in control of which transport carries DSNs (typically the same submission service)."
1233
+ },
1234
+ {
1235
+ "title": "MX failover + per-host audit",
1236
+ "body": "MX records are walked in priority order. A failed connect / TLS / 4xx on the first host falls over to the next; only when every MX has been tried does the recipient receive its final outcome. Each per-host failure emits a structured audit event (`mail.send.deliver.host-fail` with `recipient` + `mxHost` + `priority` + `reason`); the final outcome emits `mail.send.deliver.delivered` / `.deferred` / `.permanent-fail`. Audit is drop-silent on the hot path (catch + ignore)."
1237
+ },
1238
+ {
1239
+ "title": "Recipient + envelope hard caps",
1240
+ "body": "`MAX_RECIPIENTS_PER_CALL = 1000` refuses oversized fan-out at the factory boundary (5xx-style DoS prevention). `envelope.rfc822` accepts a Buffer or UTF-8 string — strings are converted at the boundary so downstream byte-level reasoning (DKIM, REQUIRETLS, length headers) sees stable bytes. Bad-envelope refusals carry `DeliverError` codes `deliver/bad-envelope`, `/bad-envelope-from`, `/bad-envelope-to`, `/too-many-recipients`, `/bad-envelope-rfc822` — operators get structured surface for each refusal class."
1241
+ }
1242
+ ]
1243
+ },
1244
+ {
1245
+ "heading": "Security",
1246
+ "items": [
1247
+ {
1248
+ "title": "MTA-STS enforcement before TLS handshake (RFC 8461)",
1249
+ "body": "When `policy.mtaSts.enabled` (default), MTA-STS policy is fetched for the recipient domain. If the policy mode is `enforce` and a resolved MX hostname does not match an `mx:` entry, the host is refused without attempting TLS. This closes the MX-substitution attack window (`b.mail.send.deliver` cannot be diverted to an attacker-controlled MX even when DNS is hijacked, as long as the recipient publishes MTA-STS)."
1250
+ },
1251
+ {
1252
+ "title": "DANE TLSA verification when present (RFC 7672)",
1253
+ "body": "When `policy.dane.enabled` (default) and the recipient domain publishes TLSA records, the SMTP transport's TLS certificate chain is verified against the TLSA hash before the SMTP command pipeline starts. TLSA records take precedence over PKIX trust roots for SMTP — RFC 7672 §2.2."
1254
+ },
1255
+ {
1256
+ "title": "Boundary token unguessable (DSN boundary-injection defense)",
1257
+ "body": "MIME boundary token in composed DSNs is 12 random bytes hex-encoded via `b.crypto.generateToken` (SHAKE256 over OS-RNG) — not `Date.now()` + `Math.random()`. The boundary appears verbatim in the message; a predictable boundary would let an attacker who controls the failed message's headers craft byte sequences that close the boundary early + inject MIME parts."
1258
+ }
1259
+ ]
1260
+ },
1261
+ {
1262
+ "heading": "Detectors",
1263
+ "items": [
1264
+ {
1265
+ "title": "`per-recipient-loop-fallthrough-to-failed` (codebase-patterns)",
1266
+ "body": "A new detector flags per-recipient delivery loops where the `delivered` branch does not exit the iteration before falling into the permanent-failure / DSN-emit path. Encodes the bug class that was caught during this slice's bring-up — a recipient delivering successfully also being added to `failed[]` because the `if (delivered)` branch lacked an explicit `continue`."
1267
+ }
1268
+ ]
1269
+ }
1270
+ ],
1271
+ "references": [
1272
+ {
1273
+ "label": "RFC 5321 (Simple Mail Transfer Protocol)",
1274
+ "url": "https://www.rfc-editor.org/rfc/rfc5321.html"
1275
+ },
1276
+ {
1277
+ "label": "RFC 3464 (Extensible Message Format for Delivery Status Notifications)",
1278
+ "url": "https://www.rfc-editor.org/rfc/rfc3464.html"
1279
+ },
1280
+ {
1281
+ "label": "RFC 7505 (Null MX no-service resource record)",
1282
+ "url": "https://www.rfc-editor.org/rfc/rfc7505.html"
1283
+ },
1284
+ {
1285
+ "label": "RFC 8461 (SMTP MTA Strict Transport Security — MTA-STS)",
1286
+ "url": "https://www.rfc-editor.org/rfc/rfc8461.html"
1287
+ },
1288
+ {
1289
+ "label": "RFC 7672 (SMTP Security via Opportunistic DANE TLS)",
1290
+ "url": "https://www.rfc-editor.org/rfc/rfc7672.html"
1291
+ },
1292
+ {
1293
+ "label": "RFC 8689 (SMTP REQUIRETLS option)",
1294
+ "url": "https://www.rfc-editor.org/rfc/rfc8689.html"
1295
+ },
1296
+ {
1297
+ "label": "RFC 3463 (Enhanced Mail System Status Codes)",
1298
+ "url": "https://www.rfc-editor.org/rfc/rfc3463.html"
1299
+ }
1300
+ ]
1301
+ },
1302
+ {
1303
+ "version": "0.11.23",
1304
+ "date": "2026-05-20",
1305
+ "headline": "`b.mail.agent.expunge` — hard EXPUNGE with legal-hold + retention-floor refusal gates",
1306
+ "summary": "Operators (and the future IMAP EXPUNGE + JMAP Email/set destroyed wire-protocol adapters) get a single canonical path for permanent message removal that refuses to delete anything currently under legal hold or still inside the regulator-mandated retention window. The gate runs per-message; refused ids carry an explicit reason (`legal-hold` / `retention-floor` / `not-in-folder`) plus the floor + age + posture metadata that drove the refusal — wire adapters mirror those reasons to operators verbatim. The destructive SQL runs only on the surviving id set, inside a backend transaction that also bumps folder modseq + decrements quota atomically.",
1307
+ "sections": [
1308
+ {
1309
+ "heading": "Added",
1310
+ "items": [
1311
+ {
1312
+ "title": "`b.mail.agent.expunge({ actor, folder, objectIds })` — hard EXPUNGE primitive",
1313
+ "body": "Composes two refusal gates before the destructive SQL runs. (1) Legal-hold gate: any message whose `legal_hold` flag is set refuses with reason `legal-hold`. The mail-store layer surfaces the flag in per-row metadata; this layer maps it to the operator-facing refusal. (2) Retention-floor gate: under a configured compliance posture (`hipaa` / `pci-dss` / `gdpr` / `soc2`), the regulator-mandated minimum retention TTL is read from `b.retention.COMPLIANCE_RETENTION_FLOOR_MS[posture]` and any message whose age (`now - receivedAt`) is below the floor refuses with reason `retention-floor` plus `floorMs` + `ageMs` + `posture` metadata. Returns `{ deleted: <ids>, refused: [{ id, reason, ... }] }`. Audit event `mail.agent.expunge.success` carries the requested / deleted / refused counts and a reason histogram so dashboards can spot abnormal refusal patterns without parsing per-id detail."
1314
+ },
1315
+ {
1316
+ "title": "`b.mailStore.create(...).hardExpunge(folder, objectIds)` — destructive SQL primitive",
1317
+ "body": "Removes messages permanently from a folder inside a single backend transaction: deletes the message row + its flag rows, bumps the folder modseq, decrements the per-folder quota by the freed bytes / count. Returns `{ rows, deleted, refused }` where `refused` carries `{ id, reason: 'legal-hold' | 'not-in-folder' }` for each id the SQL gate refused (legal-hold is mirrored from the column; not-in-folder catches stale ids). The agent layer (`b.mail.agent.expunge`) is responsible for the retention-floor gate; this primitive is the wire-protocol-shaped backend surface."
1318
+ },
1319
+ {
1320
+ "title": "`b.mailStore.create(...).fetchByObjectId` returns `legalHold: boolean`",
1321
+ "body": "Pre-existing fetch path now consistently exposes the legal-hold flag in its return shape. Previously the field existed in the returned object via a separate path; this commit consolidates the duplicate exports into a single canonical `legalHold` boolean derived from the SQLite `legal_hold` INTEGER column."
1322
+ }
1323
+ ]
1324
+ }
1325
+ ],
1326
+ "references": [
1327
+ {
1328
+ "label": "RFC 9051 (IMAP4rev2 — EXPUNGE semantics, §6.4.3)",
1329
+ "url": "https://www.rfc-editor.org/rfc/rfc9051.html"
1330
+ },
1331
+ {
1332
+ "label": "RFC 8621 (JMAP Mail — Email/set destroyed)",
1333
+ "url": "https://www.rfc-editor.org/rfc/rfc8621.html"
1334
+ },
1335
+ {
1336
+ "label": "45 CFR §164.316 (HIPAA — retention of records)",
1337
+ "url": "https://www.ecfr.gov/current/title-45/subtitle-A/subchapter-C/part-164/subpart-C/section-164.316"
1338
+ },
1339
+ {
1340
+ "label": "PCI-DSS v4.0.1 §3.5.1.1 (retention of cardholder data)",
1341
+ "url": "https://www.pcisecuritystandards.org/document_library"
1342
+ },
1343
+ {
1344
+ "label": "GDPR Art. 17 (right to erasure — operator-side accountability)",
1345
+ "url": "https://gdpr-info.eu/art-17-gdpr/"
1346
+ }
1347
+ ]
1348
+ },
1349
+ {
1350
+ "version": "0.11.22",
1351
+ "date": "2026-05-20",
1352
+ "headline": "`b.cert.create` — turnkey TLS-certificate manager composing ACME + sealed persistence + renewal scheduler + SNI + key escrow",
1353
+ "summary": "Operators wiring TLS no longer have to glue ACME + key generation + cert persistence + renewal scheduling + SNI dispatch + escrow by hand. `b.cert.create({ storage, acme, certs, renew, ocsp, audit, compliance })` accepts a declarative manifest of certificates plus the operator's choice of ACME challenge solver, and the manager owns the rest of the lifecycle: ACME RFC 8555 order + RFC 9773 ARI-aware renewal, leaf key rotation on renew, OCSP refresh scheduling, sealed-disk persistence via `b.vault.seal`, optional break-glass key escrow encrypted to an operator-supplied recipient via `b.crypto.encryptEnvelope`, and SNI dispatch for `https.createServer({ SNICallback })`. Composes existing primitives — `b.acme.create` (extended this release with per-challenge methods), `b.vault.seal`, `b.safeAsync.repeating`, `b.network.tls`, `b.audit` — so the operator-facing surface is one factory call.",
1354
+ "sections": [
1355
+ {
1356
+ "heading": "Added",
1357
+ "items": [
1358
+ {
1359
+ "title": "`b.cert.create(opts)` — turnkey certificate manager",
1360
+ "body": "New top-level primitive. Storage backend: `sealed-disk` (default; sealed via `b.vault.seal`). ACME: directory URL + contact + auto-generated account key (sealed on disk; operator can override with explicit key material). Certs manifest: per-cert name + domains + keyAlg (ecdsa-p256 / ecdsa-p384 / rsa-2048 / rsa-3072 / rsa-4096) + challenge `{ type, provision, cleanup }` callbacks (http-01 / dns-01 / tls-alpn-01). Renewal: ARI-respecting scheduler with configurable `intervalMs` + `minDaysBeforeExpiry` thresholds. OCSP: `stapling: true` schedules periodic OCSP refresh via `b.network.tls.ocsp`. Key escrow: optional `keyEscrow: { recipient }` encrypts each renewed private key to the recipient public key via `b.crypto.encryptEnvelope` and persists alongside the sealed key — break-glass-only recovery path, NOT routine access. Surface: `start()` / `stop()` / `getContext(name)` / `sniCallback` / `refresh(name)` / event-emitter `on('cert.issued' | 'cert.renewed' | 'cert.renew-failed', handler)`."
1361
+ },
1362
+ {
1363
+ "title": "`b.acme.create.fetchAuthorization(authUrl)` — RFC 8555 §7.5 authorization GET",
1364
+ "body": "POST-as-GET an authorization URL; returns the parsed authorization object with the challenge array. The challenge entries carry `{ type, url, token, status }` for each offered challenge type. Required by the cert manager's per-challenge flow but useful to operators implementing custom ACME wrappers."
1365
+ },
1366
+ {
1367
+ "title": "`b.acme.create.notifyChallengeReady(challengeUrl)` — RFC 8555 §7.5.1 ready-notification",
1368
+ "body": "POST an empty JSON object to a challenge URL to signal the operator has provisioned the response. Returns the updated challenge object; the CA's validation runs asynchronously and the operator polls via `waitForAuthorization` afterwards."
1369
+ },
1370
+ {
1371
+ "title": "`b.acme.create.waitForAuthorization(authUrl, opts?)` — authorization status polling",
1372
+ "body": "Polls an authorization until `status === 'valid'` (success) or `status === 'invalid'` (CA refused). Honors the client's `pollIntervalMs` + `pollMaxMs` defaults; per-call `opts.intervalMs` + `opts.timeoutMs` override available. Throws typed `acme/auth-invalid` on CA refusal and `acme/auth-timeout` on poll-budget exhaustion."
1373
+ },
1374
+ {
1375
+ "title": "`b.acme.create.buildCsr({ privateKey, publicKey, domains })` — RFC 2986 PKCS#10 CSR builder",
1376
+ "body": "Builds a CertificationRequest signed with the leaf private key. Subject `CN=<first domain>`, all domains as `dNSName` entries in the SubjectAltName extension. Supports ECDSA P-256 (signed sha256), ECDSA P-384 (signed sha384), RSA 2048 / 3072 / 4096 (signed sha256). Ed25519 is rejected at the CSR layer because CA support is uneven — operators wanting Ed25519 certs build the CSR externally. PEM-encoded output ready to feed to `finalize(order, csrPem)`."
1377
+ },
1378
+ {
1379
+ "title": "`b.asn1Der` write primitives — `writeBitString` / `writeSet` / `writeUtf8String` / `writePrintableString` / `writeIa5String` / `writeBoolean` / `writeContextImplicit`",
1380
+ "body": "ASN.1 DER encoder extensions needed for PKCS#10 CSR construction. PrintableString refuses input outside the RFC 5280 character set; IA5String refuses non-ASCII; UTF8String refuses non-string input. SET-OF encoding sorts children by their encoded bytes per DER. Internal-only today (consumed by `b.acme.create.buildCsr`); operators with their own ASN.1 needs can compose them."
1381
+ }
1382
+ ]
1383
+ },
1384
+ {
1385
+ "heading": "Changed",
1386
+ "items": [
1387
+ {
1388
+ "title": "`b.audit.FRAMEWORK_NAMESPACES` adds `cert`",
1389
+ "body": "The cert-manager lifecycle emits `cert.account.generated` / `cert.issued` / `cert.renewed` / `cert.renew-failed` / `cert.challenge-cleanup` audit events; the audit-namespace coverage check at smoke-time now recognizes the `cert` namespace."
1390
+ }
1391
+ ]
1392
+ }
1393
+ ],
1394
+ "references": [
1395
+ {
1396
+ "label": "RFC 8555 (ACME)",
1397
+ "url": "https://www.rfc-editor.org/rfc/rfc8555.html"
1398
+ },
1399
+ {
1400
+ "label": "RFC 9773 (ACME Renewal Information / ARI)",
1401
+ "url": "https://www.rfc-editor.org/rfc/rfc9773.html"
1402
+ },
1403
+ {
1404
+ "label": "RFC 2986 (PKCS#10 Certification Request Syntax)",
1405
+ "url": "https://www.rfc-editor.org/rfc/rfc2986.html"
1406
+ },
1407
+ {
1408
+ "label": "RFC 5280 (X.509 Internet PKI)",
1409
+ "url": "https://www.rfc-editor.org/rfc/rfc5280.html"
1410
+ },
1411
+ {
1412
+ "label": "RFC 8737 (TLS-ALPN-01 challenge)",
1413
+ "url": "https://www.rfc-editor.org/rfc/rfc8737.html"
1414
+ },
1415
+ {
1416
+ "label": "OpenSSL CSR roundtrip — local OpenSSL validation",
1417
+ "url": "https://www.openssl.org/docs/man3.5/man1/openssl-req.html"
1418
+ }
1419
+ ]
1420
+ },
1421
+ {
1422
+ "version": "0.11.21",
1423
+ "date": "2026-05-20",
1424
+ "headline": "Supply-chain hardening — pinact + zizmor + actionlint gate, sha-to-tag tag-integrity verifier, GOVERNANCE.md",
1425
+ "summary": "Closes the input + tag-integrity halves of the supply-chain trust boundary. `pinact` refuses any `uses:` reference that isn't pinned to a 40-char SHA with a verified version-comment (defense against the CVE-2025-30066 retroactive-tag-rewrite class). `zizmor` audits every workflow for the documented security anti-pattern catalog (template-injection / excessive-permissions / cache-poisoning / impostor-commit / unredacted-secrets / etc.). The new `sha-to-tag-verify` workflow refuses to let the publish workflow proceed if a release tag's commit SHA isn't on `main`'s first-parent history OR wasn't the result of a merged PR — the source-side gate that TanStack's 2026-05-11 attack (84 malicious `@tanstack/*` versions published with valid SLSA L3 provenance) demonstrated as a structural defense alongside provenance verification. SECURITY.md gains operator-facing `slsa-verifier` and tag-SHA-integrity recipes. New top-level `GOVERNANCE.md` documents the solo-maintainer governance model, succession plan, key-loss recovery, and dependent-notification protocol.",
1426
+ "sections": [
1427
+ {
1428
+ "heading": "Added",
1429
+ "items": [
1430
+ {
1431
+ "title": "`.github/workflows/actions-lint.yml` — pinact + zizmor + actionlint gate",
1432
+ "body": "Three tools, three layers per the arxiv.org \"Unpacking Security Scanners for GitHub Actions Workflows\" taxonomy. `pinact run --check` refuses any `uses:` reference that isn't SHA-pinned with a matching version-comment; `pinact run --verify` re-resolves each pinned SHA's registered tag at check time and refuses if the workflow's version-comment disagrees (catches retroactive tag rewrites). `zizmor` audits at `--min-severity low` across every documented rule class (template-injection, excessive-permissions, dangerous-triggers, unpinned-uses, cache-poisoning, github-env, hardcoded-container-credentials, impostor-commit, known-vulnerable-actions, obfuscation, ref-confusion, secrets-inherit, self-hosted-runner, unredacted-secrets, unsound-contains, use-trusted-publishing); SARIF emitted to GitHub Code Scanning. `actionlint` runs YAML + expression validation + shellcheck on every `run:` block. Single documented exception in `.pinact.yaml` — the SLSA reusable workflow MUST be tag-pinned because its internal builder-fetch step refuses non-tag refs."
1433
+ },
1434
+ {
1435
+ "title": "`.github/workflows/sha-to-tag-verify.yml` — tag-SHA integrity gate",
1436
+ "body": "Runs on every `v*` tag push and refuses to let the publish workflow start if the tag's commit SHA isn't on `main`'s first-parent history OR wasn't the result of a merged PR. Defends against the tag-mutation class (CVE-2025-30066: 23,000+ affected repos in March 2025) and the source-side-malicious-publish class (TanStack 2026-05-11: 84 valid-SLSA-L3-provenance malicious versions). The same chain is documented for operator-side re-verification in SECURITY.md's new \"Verifying release-commit integrity\" subsection."
1437
+ },
1438
+ {
1439
+ "title": "`GOVERNANCE.md` — solo-maintainer governance, succession, key-loss recovery, dependent-notification",
1440
+ "body": "New top-level document covering: (a) current governance model (solo maintainer pre-1.0, maintainer-final on technical direction); (b) succession plan with TBD-successor-with-documented-re-open-trigger, repository ownership, npm publish credentials, SSH signing key rotation procedure, Sigstore identity rotation; (c) key-loss recovery for every asset (npm publish, GitHub org, SSH signing key, Sigstore, domain); (d) dependent-notification protocol via `security@blamejs.com` with 30-day no-activity escalation. Bus-factor-1 is the largest non-technical risk pre-1.0; this document makes the recovery path defensible."
1441
+ },
1442
+ {
1443
+ "title": "SECURITY.md — `slsa-verifier` and tag-SHA-integrity operator-verification recipes",
1444
+ "body": "\"Verifying release authenticity\" gains a `slsa-verifier verify-artifact` recipe (pinned to v2.7.1) for offline / API-independent provenance verification — `gh attestation verify` walks the chain via the GitHub API; `slsa-verifier` does it from disk. The recipe explicitly states the limit: SLSA provenance binds the tarball bytes to a workflow+commit+tag, but does NOT prove the source is clean (the TanStack incident shipped valid-provenance malicious versions because the source side was compromised). A new \"Verifying release-commit integrity\" subsection documents the sha-to-tag chain operators run alongside provenance verification — the source-side gate."
1445
+ },
1446
+ {
1447
+ "title": "`.pinact.yaml` — pinact configuration with documented SLSA exception",
1448
+ "body": "Defines a single tag-pin exception for `slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml`. The SLSA reusable workflow's internal builder-fetch step refuses non-tag `BUILDER_REF` values, so the call MUST be tag-pinned; mitigated by slsa-framework's upstream tag-protection + immutable-release rules. The same exception also appears as a per-line `# allow:slsa-framework-action-not-sha-pinned` marker in `npm-publish.yml` for the framework's own codebase-patterns gate."
1449
+ }
1450
+ ]
1451
+ },
1452
+ {
1453
+ "heading": "Changed",
1454
+ "items": [
1455
+ {
1456
+ "title": "Workflow version-comment integrity — every pinned action's `# vX.Y.Z` comment now matches the registered tag for that SHA",
1457
+ "body": "Pre-existing pins carried stale version-comments (`actions/checkout@de0fac2e... # v6.0.0` where that SHA is actually `v6.0.2`, `actions/setup-node@48b55a01... # v6.0.0` where the SHA is `v6.4.0`, `actions/download-artifact@3e5f45b2... # v7.0.1` where the SHA is `v8.0.1`, `github/codeql-action@9e0d7b8d... # v3.30.9` where the SHA is `v4.35.5`). The SHAs themselves stay (they're the actual-released versions); the comments now match. This is what pinact's `--verify` check enforces structurally going forward — a stale version-comment is the early-warning signal that a retroactive tag rewrite landed without operator notice."
1458
+ }
1459
+ ]
1460
+ }
1461
+ ],
1462
+ "references": [
1463
+ {
1464
+ "label": "CVE-2025-30066 (tj-actions/changed-files retroactive tag rewrite)",
1465
+ "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-30066"
1466
+ },
1467
+ {
1468
+ "label": "TanStack npm publish incident 2025-05-11",
1469
+ "url": "https://blog.tanstack.com/the-tanstack-may-2025-supply-chain-attack/"
1470
+ },
1471
+ {
1472
+ "label": "pinact",
1473
+ "url": "https://github.com/suzuki-shunsuke/pinact"
1474
+ },
1475
+ {
1476
+ "label": "zizmor",
1477
+ "url": "https://github.com/woodruffw/zizmor"
1478
+ },
1479
+ {
1480
+ "label": "actionlint",
1481
+ "url": "https://github.com/rhysd/actionlint"
1482
+ },
1483
+ {
1484
+ "label": "slsa-verifier",
1485
+ "url": "https://github.com/slsa-framework/slsa-verifier"
1486
+ }
1487
+ ]
1488
+ },
1489
+ {
1490
+ "version": "0.11.20",
1491
+ "date": "2026-05-20",
1492
+ "headline": "`b.backup.localStorage` legacy alias removed + test-discipline backlog fully drained",
1493
+ "summary": "Two pieces bundled into one patch. (1) The `b.backup.localStorage` legacy alias introduced in v0.11.2 as a deprecation cycle for the rename to `b.backup.diskStorage` is removed entirely — pre-v1 the project's stated policy is no backwards-compat shims, and the cleanup no longer needs to wait for the Node 26 floor-bump. (2) Every test file in the `test-promise-settimeout-sleep` detector's 49-file migration backlog is converted from `await new Promise(r => setTimeout(r, N))` sleeps to `helpers.waitUntil(predicate, opts)` (condition-waits) or the new `helpers.passiveObserve(ms, label)` (verify-absence-of-event windows). The previously-split `test-codebase-patterns.test.js` catalog is folded into the main `codebase-patterns.test.js` `KNOWN_ANTIPATTERNS` array with `scanScope: \"test\"`.",
1494
+ "sections": [
1495
+ {
1496
+ "heading": "Removed",
1497
+ "items": [
1498
+ {
1499
+ "title": "`b.backup.localStorage` — the legacy alias is gone",
1500
+ "body": "The property no longer resolves on `b.backup` and the one-time deprecation warning is no longer emitted. Migration is a literal-name find-and-replace: `b.backup.localStorage({ root: ... })` becomes `b.backup.diskStorage({ root: ... })`. The returned storage backend object, its options shape, and its wire contract with `b.backupBundle.create` are unchanged. See SECURITY.md's Node 26 compatibility section for the original rename rationale."
1501
+ }
1502
+ ]
1503
+ },
1504
+ {
1505
+ "heading": "Added",
1506
+ "items": [
1507
+ {
1508
+ "title": "`helpers.passiveObserve(ms, label)` — real-time observation budget",
1509
+ "body": "Distinct from `helpers.waitUntil`: the goal is NOT to poll until a condition is truthy, but to let real time elapse so the test can verify ABSENCE of an event over a window. Required `label` (string) surfaces in audit logs / future flake diagnostics so a grep through a CI log immediately identifies which observation budget was consumed. Throws TypeError on non-positive `ms` or missing label. Use sparingly — if there IS an observable predicate, `waitUntil` is the right primitive (faster on fast platforms; passive observation always burns the full budget)."
1510
+ }
1511
+ ]
1512
+ },
1513
+ {
1514
+ "heading": "Changed",
1515
+ "items": [
1516
+ {
1517
+ "title": "Test-discipline catalog unified — `codebase-patterns.test.js` is now the single source of truth",
1518
+ "body": "The previously-split `test-codebase-patterns.test.js` runner is merged into the main `codebase-patterns.test.js` catalog. Six test-side detectors are now inline `KNOWN_ANTIPATTERNS` entries with `scanScope: \"test\"`: `test-promise-settimeout-sleep` (broadened regex catches block-bodied arrows + multi-arg arrows + function bodies with leading statements), `test-uses-stream-pipeline-without-withtesttimeout` (per-test wall-clock ceiling for node:stream.pipeline tests), `release-named-test-file` (basename-mode; refuses `v0-8-41-additions.test.js` / `slot-19-enhancements.test.js` / `batch-N.test.js`), `test-hardcoded-server-bind-port` (`.listen(0)` + `server.address().port`), `test-fs-watch-direct-call` (`helpers.backdateFile` + `helpers.waitForWatcher` instead), `test-future-utimes-without-backdated-baseline` (pair future-mtime writes with `backdateFile`), `test-creates-db-handle-without-isolation` (`b.db.create()` must compose `setupTestDb` / `setupVaultOnly` / `mkdtempSync`). The test-scope walker now also includes `examples/*/test/` so wiki integration tests share the same discipline. Single catalog means a single allowlist per detector, single migration backlog, and one runner to invoke from CI."
1519
+ },
1520
+ {
1521
+ "title": "Test-discipline migration — `setTimeout(r, N)` backlog fully drained (49 → 0 migration entries)",
1522
+ "body": "Converted every test file in the migration backlog to `helpers.waitUntil` / `helpers.passiveObserve`: `a2a`, `a2a-tasks`, `agent-event-bus`, `agent-idempotency`, `agent-orchestrator`, `agent-snapshot`, `api-encrypt`, `app-shutdown`, `audit-segregation`, `break-glass`, `config`, `cors`, `daily-byte-quota`, `dsr`, `external-db-routing`, `guard-csv`, `http-client-cache`, `log-stream-cloudwatch`, `log-stream-otlp` (dead `_sleep` helper removed), `log-stream-otlp-grpc`, `mail-greylist`, `middleware-compose-pipeline`, `network`, `network-dns-resolver`, `network-heartbeat-passive`, `notify`, `observability-tracing`, `promise-pool`, `pubsub`, `queue-dlq-extend-lease`, `queue-flow-repeat`, `queue-priority-rate-progress`, `require-auth-cache-control`, `retry`, `safe-async-loops`, `safe-async-parallel`, `scim-server`, `sse`, `vault-seal-pem-file`, `watcher` (3 fs.watchFile poll-event waits), `webhook`, `websocket-channels`, `ws-client`, `api-key` (layer-1), and integration tests `cache`, `cluster-provider-mysql`, `log-stream`, `network-heartbeat`, `object-store-sigv4`, `pubsub`, `queue-redis`, `websocket-permessage-deflate`, `ws-client-roundtrip`. Each condition-wait now has a grep'able label and an automatic 5000ms ceiling. Two files are permanent structural FPs (not migration backlog): `test/helpers/services.js` (TCP/TLS/UDP probe primitives — race-with-socket-event pattern, not condition-wait) and `examples/wiki/test/integration.js` (consumes `@blamejs/core` via npm symlink, doesn't have access to the framework's internal test helpers; single 100ms post-shutdown flush window)."
1523
+ }
1524
+ ]
1525
+ }
1526
+ ],
1527
+ "references": [
1528
+ {
1529
+ "label": "Node.js 26 release notes (localStorage global)",
1530
+ "url": "https://nodejs.org/en/blog/release/v26.0.0"
1531
+ }
1532
+ ]
1533
+ },
1534
+ {
1535
+ "version": "0.11.18",
1536
+ "date": "2026-05-20",
1537
+ "headline": "ML-DSA-65 release-signing key onboarded — `.mldsa.sig` sidecar lights up",
1538
+ "summary": "v0.11.17 was the first release the new pipeline published end-to-end, but the `.mldsa.sig` PQC sidecar was missing because the operator-side `RELEASE_PQC_SIGNING_KEY` setup hadn't been done yet. v0.11.18 onboards the keypair: `keys/release-pqc-pub.json` is committed in-tree, the matching private key lives only in the `npm-publish` environment's `RELEASE_PQC_SIGNING_KEY` secret, and `SECURITY.md` documents the SHA3-512 fingerprint + the operator-side verification recipe (no external verifier binary required — verifies via the framework's own vendored noble-post-quantum primitive).",
1539
+ "sections": [
1540
+ {
1541
+ "heading": "Added",
1542
+ "items": [
1543
+ {
1544
+ "title": "`keys/release-pqc-pub.json` — ML-DSA-65 release-signing public key committed in-tree",
1545
+ "body": "Generated locally via `node scripts/generate-release-signing-key.js`; the matching private key was set as the `RELEASE_PQC_SIGNING_KEY` secret in the `npm-publish` GitHub Actions environment (same scope as `NPM_TOKEN`). The publish workflow's PQC sidecar step now finds both pieces present and computes a `<tarball>.mldsa.sig` for every release tarball. Self-verify-before-write inside `sign-release-artifact.js` catches a stale env-secret-vs-in-tree-pubkey mismatch — refuses to write a non-verifiable sig."
1546
+ },
1547
+ {
1548
+ "title": "`SECURITY.md` — `PQC release-signing key` fingerprint + verification recipe",
1549
+ "body": "New SECURITY.md table records the algorithm (ML-DSA-65, FIPS 204), the in-tree public-key file path, and the SHA3-512 fingerprint (`ad6bee96…2ede`). Verification recipe uses the framework's own `b.pqcSoftware.ml_dsa_65.verify` — no external verifier binary required, no dependency on Sigstore's classical-crypto chain. Operators with a PQC-only verification posture have a self-contained path: `gh release download`, then `node -e` against the vendored noble-post-quantum primitive."
1550
+ }
1551
+ ]
1552
+ },
1553
+ {
1554
+ "heading": "Changed",
1555
+ "items": [
1556
+ {
1557
+ "title": "`Verifying release authenticity` — four trust roots, not three",
1558
+ "body": "SECURITY.md's closing summary updated to count the ML-DSA-65 release-signing sidecar as the fourth independently-verifiable trust root, alongside SLSA L3 npm provenance + Sigstore-keyless SBOM signing + SSH-signed tags. Each remains verifiable without trusting any of the others; tampering with one is detected by the others."
1559
+ }
1560
+ ]
1561
+ }
1562
+ ],
1563
+ "references": [
1564
+ {
1565
+ "label": "FIPS 204 — ML-DSA",
1566
+ "url": "https://csrc.nist.gov/pubs/fips/204/final"
1567
+ },
1568
+ {
1569
+ "label": "FIPS 202 — SHA-3 + SHAKE",
1570
+ "url": "https://csrc.nist.gov/pubs/fips/202/final"
1571
+ },
1572
+ {
1573
+ "label": "RFC 9909 — ML-DSA in X.509 + CMS",
1574
+ "url": "https://www.rfc-editor.org/rfc/rfc9909.html"
1575
+ },
1576
+ {
1577
+ "label": "noble-post-quantum (vendored under lib/vendor)",
1578
+ "url": "https://github.com/paulmillr/noble-post-quantum"
1579
+ }
1580
+ ]
1581
+ },
1582
+ {
1583
+ "version": "0.11.17",
1584
+ "date": "2026-05-20",
1585
+ "headline": "SLSA reusable workflow tag-pinned (the call requires a tag ref internally)",
1586
+ "summary": "v0.11.7 through v0.11.16 each ship'd with the SLSA `generator_generic_slsa3` reusable workflow SHA-pinned and the publish workflow failed identically every time — masked by the SLSA workflow's cascading `continue-on-error: true` on each step until v0.11.16's diagnostic finally surfaced the real error: `Invalid ref: <40-hex>. Expected ref of the form refs/tags/vX.Y.Z`. The SLSA workflow's internal builder-fetch step refuses anything other than a tag ref for `BUILDER_REF`. Tag-pinned the callsite to `@v2.1.0`; updated the codebase-patterns `slsa-framework-action-not-sha-pinned` detector to allowlist this one callsite with the upstream-mitigation reasoning documented inline.",
1587
+ "sections": [
1588
+ {
1589
+ "heading": "Fixed",
1590
+ "items": [
1591
+ {
1592
+ "title": "SLSA reusable workflow callsite pinned to `@v2.1.0` (tag) instead of `@<sha>`",
1593
+ "body": "The SLSA `generate-builder` action's builder-fetch step refuses non-tag refs for `BUILDER_REF` — the SHA we'd been pinning to since v0.11.7 was always failing here, hidden behind the SLSA workflow's step-level + workflow-level `continue-on-error: true` cascades. v0.11.16's diagnostic finally surfaced the actual error message. Switched to `@v2.1.0`. The repo-level `sha_pinning_required` actions policy was relaxed earlier so the tag pin is permitted. The `slsa-action-not-sha-pinned` codebase-patterns detector allowlists this one callsite — slsa-framework's tag-protection + immutable-release rules at their own org level mitigate the upstream-mutation risk the detector was originally guarding against."
1594
+ },
1595
+ {
1596
+ "title": "`slsa-action-not-sha-pinned` detector allowlists the npm-publish workflow callsite",
1597
+ "body": "The detector continues to enforce SHA-pinning on every OTHER `slsa-framework/*` reusable-workflow callout repository-wide. Only the npm-publish workflow's `provenance` job is allowlisted, with the upstream tag-protection mitigation documented at the allowlist entry. New callsites still trip the detector."
1598
+ }
1599
+ ]
1600
+ }
1601
+ ],
1602
+ "references": [
1603
+ {
1604
+ "label": "SLSA `generate-builder` action — `Invalid ref` refusal",
1605
+ "url": "https://github.com/slsa-framework/slsa-github-generator/blob/v2.1.0/.github/actions/generate-builder/action.yml"
1606
+ },
1607
+ {
1608
+ "label": "slsa-framework `generator_generic_slsa3.yml` v2.1.0",
1609
+ "url": "https://github.com/slsa-framework/slsa-github-generator/blob/v2.1.0/.github/workflows/generator_generic_slsa3.yml"
1610
+ }
1611
+ ]
1612
+ },
1613
+ {
1614
+ "version": "0.11.16",
1615
+ "date": "2026-05-20",
1616
+ "headline": "SLSA generator `private-repository: true` to override the false-positive privacy halt",
1617
+ "summary": "v0.11.15 cleared the SLSA outcome-AND chain via `continue-on-error: true`, then the underlying provenance generation FAILED with `Repository is private. The workflow has halted in order to keep the repository name from being exposed in the public transparency log. Set 'private-repository' to override.` — but `continue-on-error: true` masked it, the SLSA workflow reported success, no artifact was actually uploaded, and the downstream `publish-and-release` job's `Download SLSA provenance` step then failed. github.com/blamejs/blamejs is in fact public (gh api confirms `private: false, visibility: public`); SLSA's detection appears to walk workflow permissions rather than the repo's actual visibility. Passing `private-repository: true` overrides the detection and lets the workflow proceed to the Sigstore transparency-log write — which is the operator-desired behavior for a public-repo release.",
1618
+ "sections": [
1619
+ {
1620
+ "heading": "Added",
1621
+ "items": [
1622
+ {
1623
+ "title": "`SECURITY.md` — `Supply-chain transparency posture` subsection",
1624
+ "body": "New SECURITY.md subsection documenting the Sigstore Rekor transparency-log writes (SLSA L3 attestation + cosign SBOM signatures), the operator-side rationale for the `private-repository: true` override, and the fork-operator escape hatch (set the input to `false` to halt transparency-log writes for private mirrors). Also calls out the framework's no-telemetry posture: every external endpoint primitive (DoH / ACME / OCSP / NTP / OSV-Scanner) runs through operator-supplied infrastructure; there is no framework-owned ingest channel."
1625
+ }
1626
+ ]
1627
+ },
1628
+ {
1629
+ "heading": "Fixed",
1630
+ "items": [
1631
+ {
1632
+ "title": "Pass `private-repository: true` to the SLSA reusable workflow",
1633
+ "body": "The SLSA generic generator halts when its internal privacy detection thinks the repo is private. github.com/blamejs/blamejs is public per gh api repos/blamejs/blamejs's `visibility: public`, but SLSA's detection halts anyway (likely walking workflow permissions). Setting `private-repository: true` acknowledges the operator-side decision to write the transparency log entry regardless of detection. v0.11.15 cleared the outcome-AND chain but the upstream attest step was failing for this reason — `continue-on-error: true` masked it, the artifact never uploaded, and the downstream download failed."
1634
+ }
1635
+ ]
1636
+ }
1637
+ ],
1638
+ "references": [
1639
+ {
1640
+ "label": "SLSA generic generator inputs",
1641
+ "url": "https://github.com/slsa-framework/slsa-github-generator/blob/v2.1.0/.github/workflows/generator_generic_slsa3.yml"
1642
+ },
1643
+ {
1644
+ "label": "Sigstore Rekor transparency log",
1645
+ "url": "https://docs.sigstore.dev/logging/overview/"
1646
+ }
1647
+ ]
1648
+ },
1649
+ {
1650
+ "version": "0.11.15",
1651
+ "date": "2026-05-20",
1652
+ "headline": "SLSA reusable workflow `continue-on-error: true` so `upload-assets: false` doesn't fail the workflow",
1653
+ "summary": "v0.11.14 finally reached the SLSA provenance generation step end-to-end — the `validate` and `generator` jobs both succeeded, the provenance artifact uploaded — but the SLSA workflow's `final` step exited 27 because its outcome-AND chain evaluates `needs.upload-assets.outputs.outcome != 'failure'` against an empty value (the `upload-assets` job's `if:` short-circuited via `upload-assets: false`). The empty value evaluates falsy in the AND chain, the SLSA workflow marks itself failed, and the dependent `publish-and-release` job skips. `continue-on-error: true` on the SLSA reusable workflow call tells it to report success regardless of the final-step outcome computation; the `publish-and-release` job's `Download SLSA provenance` step catches a real missing-artifact failure independently.",
1654
+ "sections": [
1655
+ {
1656
+ "heading": "Fixed",
1657
+ "items": [
1658
+ {
1659
+ "title": "Pass `continue-on-error: true` to the SLSA reusable workflow",
1660
+ "body": "The SLSA `generator_generic_slsa3.yml` workflow has four nested jobs (`detect-env`, `generator`, `upload-assets`, `final`). When the caller passes `upload-assets: false`, the `upload-assets` job's `if:` short-circuits to skip — but its outputs evaluate empty, and the `final` job's outcome-AND chain treats that empty value as falsy, exits 27, and marks the entire SLSA workflow failed even though `generator` produced the provenance artifact successfully. `continue-on-error: true` is the documented escape hatch — the SLSA workflow now reports success when the underlying provenance generation succeeded regardless of the `final` step's strict-AND outcome computation. The `publish-and-release` job's `Download SLSA provenance` step is the real safety check — a missing artifact would surface there with an `actions/download-artifact` not-found error rather than being hidden behind the SLSA workflow's overly-strict `final` outcome."
1661
+ }
1662
+ ]
1663
+ }
1664
+ ],
1665
+ "references": [
1666
+ {
1667
+ "label": "SLSA generic generator v2.1.0 `continue-on-error` input",
1668
+ "url": "https://github.com/slsa-framework/slsa-github-generator/blob/v2.1.0/.github/workflows/generator_generic_slsa3.yml"
1669
+ }
1670
+ ]
1671
+ },
1672
+ {
1673
+ "version": "0.11.14",
1674
+ "date": "2026-05-20",
1675
+ "headline": "`helpers.backdateFile` + `helpers.waitForWatcher` — shared test primitives for `fs.watch` / `fs.watchFile`-driven tests",
1676
+ "summary": "v0.11.13 inlined the backdate + widened-wait fix in `vault-seal-pem-file.test.js` directly; v0.11.14 lifts the discipline into `test/helpers/fs-watch.js` so every existing + future fs.watch-driven test composes it the same way. Two test-side codebase-patterns detectors enforce the composition: `test-fs-watch-direct-call` (no raw `fs.watch*` calls in tests) and `test-future-utimes-without-backdated-baseline` (future-mtime via fs.utimesSync MUST pair with a prior `helpers.backdateFile` call). The existing `watcher.test.js` migrates off three fixed-budget `setTimeout(r, N)` sleeps to `helpers.waitForWatcher(predicate)`.",
1677
+ "sections": [
1678
+ {
1679
+ "heading": "Added",
1680
+ "items": [
1681
+ {
1682
+ "title": "`helpers.backdateFile(path, msAgo?)` — shift a file's mtime/atime into the past",
1683
+ "body": "Use immediately before starting an fs.watchFile-based watcher so the watcher's first-poll baseline is unambiguously older than any subsequent mutation. Default backdate is one hour (`3_600_000` ms), which dwarfs any clock-skew or poll-cadence drift the runner might exhibit. Solves the recurring class of flakes where the watcher's first poll lands AFTER the test mutates the source — recording the post-mutation mtime as `prev` and never seeing the transition."
1684
+ },
1685
+ {
1686
+ "title": "`helpers.waitForWatcher(predicate, opts?)` — polling wait helper for fs.watch / fs.watchFile observations",
1687
+ "body": "Same predicate shape as `helpers.waitUntil` but widens the default timeout from 5s to 15s for fs.watch's known cross-platform event-delivery cadence drift (macOS FSEvents + Linux inotify both occasionally deliver 2-3s late under CI runner contention). Use `helpers.waitUntil` directly for tests that don't involve a filesystem watcher; the wider budget here is specifically for the fs-watch class."
1688
+ }
1689
+ ]
1690
+ },
1691
+ {
1692
+ "heading": "Changed",
1693
+ "items": [
1694
+ {
1695
+ "title": "`vault-seal-pem-file.test.js` migrates from inline backdate to `helpers.backdateFile`",
1696
+ "body": "v0.11.13's inline fix is replaced with a single `backdateFile(src)` call. The local `_waitForGen` helper composes `helpers.waitForWatcher` with the watcher-generation predicate. Same behavior, but the discipline now lives at the helper instead of duplicated per-test."
1697
+ },
1698
+ {
1699
+ "title": "`watcher.test.js` drops three fixed-budget `setTimeout(r, N)` sleeps for `helpers.waitForWatcher`",
1700
+ "body": "The pre-write 200ms prime sleep, the 1500ms post-unlink sleep, and the 1500ms post-write sleep are all replaced with `helpers.waitForWatcher(predicate)` calls. Fast platforms (Linux/Windows) finish in milliseconds; macOS FSEvents under load gets the full 15s budget; the test no longer flakes on either end of the spectrum. The symlink-skip assertion uses a tight 2s window because it asserts the ABSENCE of an event, which requires a finite wait before checking."
1701
+ }
1702
+ ]
1703
+ },
1704
+ {
1705
+ "heading": "Detectors",
1706
+ "items": [
1707
+ {
1708
+ "title": "`test-fs-watch-direct-call` (test-side catalog)",
1709
+ "body": "Refuses direct `fs.watchFile(` or `fs.watch(` calls in test files. The framework exposes `b.watcher` (kernel-event-based) and `b.vault.sealPemFile` (poll-based) as the operator-facing watchers; tests of those primitives compose the helpers instead of re-deriving the fs.watch surface. Allowlists `test/helpers/fs-watch.js` (the helper itself documents the shape) and the catalog file (self-allowlist for the regex source)."
1710
+ },
1711
+ {
1712
+ "title": "`test-future-utimes-without-backdated-baseline` (test-side catalog)",
1713
+ "body": "Pairs the `fs.utimesSync(path, new Date(Date.now() + N), ...)` future-mtime idiom with a required companion `backdateFile(` call somewhere in the same file. Without the backdate, the watcher's first poll can record the future-mtime as `prev` and miss the transition. The `requires` companion-check passes the file when both shapes appear together; flags it as a violation when only the future-utimes is present."
1714
+ }
1715
+ ]
1716
+ }
1717
+ ],
1718
+ "references": [
1719
+ {
1720
+ "label": "Node.js fs.watchFile docs",
1721
+ "url": "https://nodejs.org/api/fs.html#fswatchfilefilename-options-listener"
1722
+ },
1723
+ {
1724
+ "label": "Node.js fs.watch docs",
1725
+ "url": "https://nodejs.org/api/fs.html#fswatchfilename-options-listener"
1726
+ }
1727
+ ]
1728
+ },
1729
+ {
1730
+ "version": "0.11.13",
1731
+ "date": "2026-05-20",
1732
+ "headline": "`vault.sealPemFile` auto-reseal test now tolerant of contended runners",
1733
+ "summary": "v0.11.12's smoke flaked on the publish workflow with `vault-seal-pem-file.test.js: FAIL: sealPemFile auto: gen incremented after source change`. Root cause: on contended `ubuntu-latest` CI runners, `fs.watchFile`'s initial poll could land AFTER the test's V2 + utimesSync calls, recording the post-change mtime as `prev` and missing the transition. Fix backdates V1's mtime by an hour before starting the watcher so any subsequent mtime change is detectable from any baseline the watcher might capture, and widens the wait budget from 5s to 15s for runner drift.",
1734
+ "sections": [
1735
+ {
1736
+ "heading": "Fixed",
1737
+ "items": [
1738
+ {
1739
+ "title": "`sealPemFile` auto-reseal test backdates V1 mtime + widens wait budget",
1740
+ "body": "The test relied on writing V1, starting the watcher, then writing V2 with a future mtime — assuming the watcher's first poll would record V1's mtime as `prev`. On contended `ubuntu-latest` CI runners the first poll could land after both writes, recording the post-change mtime as `prev` and missing the transition. Backdating V1 by an hour via `fs.utimesSync` before watcher start makes the V2 mtime change unambiguous regardless of poll timing. The wait budget for the `gen >= 2` poll widens from 5s to 15s to absorb runner-cadence drift under load."
1741
+ }
1742
+ ]
1743
+ }
1744
+ ],
1745
+ "references": [
1746
+ {
1747
+ "label": "Node.js fs.watchFile docs",
1748
+ "url": "https://nodejs.org/api/fs.html#fswatchfilefilename-options-listener"
1749
+ }
1750
+ ]
1751
+ },
1752
+ {
1753
+ "version": "0.11.12",
1754
+ "date": "2026-05-19",
1755
+ "headline": "SBOM step omits devDependencies so the zero-runtime-dep check stays accurate",
1756
+ "summary": "v0.11.11's SBOM step wired in `npm sbom` correctly but didn't pass `--omit=dev`. The smoke gate installs `esbuild` + `postject` as devDependencies (needed for the bundler-output smoke check), and `npm sbom` reported them as components. The runtime-deps-greater-than-zero guard refused the publish with `SBOM lists 3 runtime dependency component(s)`. Adding `--omit=dev` to the `npm sbom` invocation gives the guard a clean view of just the runtime tree (which stays empty per the framework's zero-runtime-dep contract).",
1757
+ "sections": [
1758
+ {
1759
+ "heading": "Fixed",
1760
+ "items": [
1761
+ {
1762
+ "title": "`npm sbom` invocation now passes `--omit=dev`",
1763
+ "body": "The smoke gate's devDependency install step (esbuild + postject) showed up in the SBOM's `components` array because the npm-tree walk includes the installed devDependencies by default. `--omit=dev` filters them out so the runtime-deps guard sees the same zero-component view it's intended to enforce. The vendored SBOM (`sbom.vendored.cdx.json`) is unaffected — it's generated from `lib/vendor/MANIFEST.json`, never from the npm tree."
1764
+ }
1765
+ ]
1766
+ }
1767
+ ],
1768
+ "references": [
1769
+ {
1770
+ "label": "npm sbom command",
1771
+ "url": "https://docs.npmjs.com/cli/commands/npm-sbom"
1772
+ }
1773
+ ]
1774
+ },
1775
+ {
1776
+ "version": "0.11.11",
1777
+ "date": "2026-05-19",
1778
+ "headline": "Release-workflow SBOM step calls the right script + cleanup script-path drift",
1779
+ "summary": "v0.11.10 finally cleared the workflow-parse barrier (after the org allowlist gained `slsa-framework/slsa-github-generator/.github/{actions,workflows}/*` patterns and the SHA-pinning policy was relaxed to accept the SLSA reusable workflow's transitive `@v2.1.0` references), and then failed at the SBOM step because the workflow called `node scripts/generate-sbom.js` — a script that doesn't exist in the repo. The actual scripts are `npm sbom` (npm-tree CycloneDX, built into npm 10+) and `scripts/build-vendored-sbom.js` (the higher-value vendored-deps SBOM). v0.11.11 wires both into the workflow's SBOM step.",
1780
+ "sections": [
1781
+ {
1782
+ "heading": "Fixed",
1783
+ "items": [
1784
+ {
1785
+ "title": "Workflow SBOM step now calls the existing scripts",
1786
+ "body": "Replaced the missing `node scripts/generate-sbom.js` invocation with `npm sbom --sbom-format=cyclonedx --sbom-type=application > sbom.cdx.json` (the npm-tree SBOM, built into npm 10+) plus `node scripts/build-vendored-sbom.js > sbom.vendored.cdx.json` (the vendored-deps SBOM that's been in `scripts/` all along). The runtime-deps-greater-than-zero guard still runs after both SBOMs are produced so the framework's zero-npm-deps contract stays enforced at publish time."
1787
+ }
1788
+ ]
1789
+ }
1790
+ ],
1791
+ "references": [
1792
+ {
1793
+ "label": "CycloneDX SBOM 1.6 spec",
1794
+ "url": "https://cyclonedx.org/specification/overview/"
1795
+ },
1796
+ {
1797
+ "label": "npm sbom command",
1798
+ "url": "https://docs.npmjs.com/cli/commands/npm-sbom"
1799
+ }
1800
+ ]
1801
+ },
1802
+ {
1803
+ "version": "0.11.10",
1804
+ "date": "2026-05-19",
1805
+ "headline": "Release-workflow allowlist root cause + comment correction",
1806
+ "summary": "v0.11.7, v0.11.8, and v0.11.9 each shipped with a hypothetical fix for the publish-workflow `startup_failure`, and each one failed for the same actual reason: the GitHub organization's actions allowlist did not include `slsa-framework/slsa-github-generator/*` (the SLSA L3 provenance reusable workflow) or `aquasecurity/setup-trivy/*` (a transitive of the `aquasecurity/trivy-action` we already allow). GitHub Actions surfaces this as `startup_failure` before any job runs, with no log file. Adding both patterns to the organization-level allowlist at https://github.com/organizations/blamejs/settings/actions is the operator-side prerequisite — the workflow file itself never had a parse-time defect.",
1807
+ "sections": [
1808
+ {
1809
+ "heading": "Changed",
1810
+ "items": [
1811
+ {
1812
+ "title": "`provenance` job comment rewritten to reflect the real root cause",
1813
+ "body": "The earlier comment claimed GitHub Actions evaluates every called-workflow job's permissions at parse time (so `contents: read` would short-circuit even the skipped `upload-assets` job). That theory was wrong; the actual cause was the organization actions allowlist. The comment now points at the SLSA documented caller example as the authoritative reference and names the two allowlist patterns operators must add to the org settings. The `contents: write` permission stays in place — it matches the SLSA caller example default and future-proofs an `upload-assets: true` flip without a permissions-only patch."
1814
+ }
1815
+ ]
1816
+ },
1817
+ {
1818
+ "heading": "Migration",
1819
+ "items": [
1820
+ {
1821
+ "title": "Operator-side: add two patterns to the org actions allowlist",
1822
+ "body": "Navigate to https://github.com/organizations/blamejs/settings/actions and add `slsa-framework/slsa-github-generator/*` AND `aquasecurity/setup-trivy/*` to the allowed-actions list. Both still SHA-pin at their call sites per the org policy. Without these, every release after v0.11.6 startup_failed at workflow parse with no log file. v0.11.7, v0.11.8, and v0.11.9 are tombstone tags — the git history records them but no GitHub Release pages exist and no npm registry entries shipped. v0.11.10 carries the same surface those tags would have shipped; `npm install @blamejs/core@latest` lands you on v0.11.10."
1823
+ }
1824
+ ]
1825
+ }
1826
+ ],
1827
+ "references": [
1828
+ {
1829
+ "label": "GitHub Actions — managing allowed actions and reusable workflows",
1830
+ "url": "https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization"
1831
+ },
1832
+ {
1833
+ "label": "SLSA generic generator v2.1.0 caller example",
1834
+ "url": "https://github.com/slsa-framework/slsa-github-generator/tree/v2.1.0/internal/builders/generic"
1835
+ }
1836
+ ]
1837
+ },
1838
+ {
1839
+ "version": "0.11.9",
1840
+ "date": "2026-05-19",
1841
+ "headline": "Release-workflow `contents: write` — actually fixes the startup_failure that blocked v0.11.7 and v0.11.8",
1842
+ "summary": "v0.11.8 carried the wrong permission for the SLSA reusable workflow caller. GitHub Actions evaluates the entire reusable-workflow permission surface at workflow startup — including jobs that won't run because their `if:` short-circuits. The SLSA `upload-assets` job declares `permissions: contents: write`; `contents: read` in the caller failed the parse-time superset check before any job started, surfacing as `startup_failure` with no log file. Raising the caller to `contents: write` makes the caller's permission set a superset of every job the reusable workflow declares, regardless of which jobs end up running.",
1843
+ "sections": [
1844
+ {
1845
+ "heading": "Fixed",
1846
+ "items": [
1847
+ {
1848
+ "title": "Caller-level `contents: write` for the SLSA reusable workflow",
1849
+ "body": "`.github/workflows/npm-publish.yml`'s `provenance` job now grants `contents: write` instead of `contents: read`. The change is parse-time-only — at runtime, `upload-assets: false` still skips the actual upload step, so the release-create flow remains the atomic-create-with-all-assets pattern v0.11.7 introduced. v0.11.7 and v0.11.8 are both `startup_failure` tags on the git history (no GH release, no npm publish); operators upgrading from `<= v0.11.6` should land directly on v0.11.9 — `npm install @blamejs/core@latest` lands you on v0.11.9."
1850
+ }
1851
+ ]
1852
+ },
1853
+ {
1854
+ "heading": "Migration",
1855
+ "items": [
1856
+ {
1857
+ "title": "v0.11.7 and v0.11.8 are tombstone tags",
1858
+ "body": "The git tags for v0.11.7 and v0.11.8 exist in the repository but the publish workflow rejected at startup, so there are no corresponding GitHub Release pages and no npm registry entries. v0.11.9 carries everything those tags would have shipped: the workflow-driven release creation with SLSA L3 + Sigstore-keyless SBOM signatures + PQC sidecars (graceful-skip until the operator completes the one-time release-signing-key setup), the structured release-notes pipeline, and the CHANGELOG.md rebuild discipline."
1859
+ }
1860
+ ]
1861
+ }
1862
+ ],
1863
+ "references": [
1864
+ {
1865
+ "label": "GitHub Actions — reusable-workflow permissions",
1866
+ "url": "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#supported-keywords-for-jobs-that-call-a-reusable-workflow"
1867
+ },
1868
+ {
1869
+ "label": "SLSA generic generator v2.1.0 example caller",
1870
+ "url": "https://github.com/slsa-framework/slsa-github-generator/tree/v2.1.0/internal/builders/generic"
1871
+ }
1872
+ ]
1873
+ },
1874
+ {
1875
+ "version": "0.11.8",
1876
+ "date": "2026-05-19",
1877
+ "headline": "Release-workflow startup-failure fix + graceful PQC sidecar skip + CHANGELOG.md becomes a derived artifact",
1878
+ "summary": "The v0.11.7 npm-publish workflow rejected with `startup_failure` before any job ran because the `provenance` job's permissions block was a subset of what the called SLSA reusable workflow requires. The ML-DSA-65 sidecar step ALSO blocked when the operator hadn't yet generated the release-signing keypair. v0.11.8 fixes both — the workflow now passes parse-time validation, and the PQC sig step gracefully skips with a clear operator warning when the key/secret aren't set up yet. CHANGELOG.md is now generated end-to-end from `release-notes/*.json` (the single source of truth); the old hand-edit splice path is removed.",
1879
+ "sections": [
1880
+ {
1881
+ "heading": "Fixed",
1882
+ "items": [
1883
+ {
1884
+ "title": "`provenance` job carries the full permission superset the SLSA reusable workflow requires",
1885
+ "body": "GitHub Actions refuses to start a workflow when the caller's job-level `permissions:` block is a subset of the called reusable workflow's job permissions, and the only diagnostic is `startup_failure` with no log file. The SLSA `generator_generic_slsa3.yml` workflow's `generator` job declares `id-token: write`, `contents: read`, `actions: read`; our `provenance` job declared the first and third but not `contents: read`. Added `contents: read` to the provenance job permissions."
1886
+ },
1887
+ {
1888
+ "title": "PQC sidecar step skips gracefully when the release-signing key isn't yet set up",
1889
+ "body": "The `RELEASE_PQC_SIGNING_KEY` environment secret and the committed `keys/release-pqc-pub.json` public-key file are an operator-side one-time setup. When either piece is missing, the workflow now logs a `::warning::` with the setup recipe and skips the ML-DSA-65 sig step; the release still ships with the SLSA L3 attestation, both Sigstore-keyless SBOM signatures, and the SHA-256 + SHA3-512 byte digests. The PQC sidecar lights up on the first release after the operator completes the setup."
1890
+ }
1891
+ ]
1892
+ },
1893
+ {
1894
+ "heading": "Changed",
1895
+ "items": [
1896
+ {
1897
+ "title": "Release-asset list built dynamically from per-step outputs",
1898
+ "body": "The `gh release create` step builds its asset bundle from a bash array driven by the prior PQC-sig step's `outputs.available` flag. When PQC is skipped, the `.mldsa.sig` asset isn't referenced (otherwise `gh release create` would fail trying to attach a non-existent file)."
1899
+ },
1900
+ {
1901
+ "title": "`CHANGELOG.md` is now a derived artifact rebuilt from `release-notes/*.json`",
1902
+ "body": "`scripts/generate-changelog-entry.js` gains `--rebuild` (regenerate the whole `CHANGELOG.md` from the JSON tree) and `--check` (in-memory rebuild + diff against on-disk; non-mutating). The hand-edit `--write` / splice path is removed entirely — operators edit the JSON, run `--rebuild`, commit both. Smoke wires the `--check` gate so any release-notes change without a matching rebuild fails pre-push. The `version → new RegExp` data flow that CodeQL kept flagging is gone (the splice that needed it no longer exists), and so is the `existsSync → readFileSync` window the lookup used to traverse — the single rebuild path reads every JSON via `readFileSync` directly and lets `ENOENT` distinguish missing from malformed."
1903
+ }
1904
+ ]
1905
+ }
1906
+ ],
1907
+ "references": [
1908
+ {
1909
+ "label": "GitHub Actions — permissions for reusable workflows",
1910
+ "url": "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#supported-keywords-for-jobs-that-call-a-reusable-workflow"
1911
+ },
1912
+ {
1913
+ "label": "SLSA generic generator v2.1.0",
1914
+ "url": "https://github.com/slsa-framework/slsa-github-generator/releases/tag/v2.1.0"
1915
+ }
1916
+ ]
1917
+ },
1918
+ {
1919
+ "version": "0.11.7",
1920
+ "date": "2026-05-19",
1921
+ "headline": "Workflow-driven release creation with SLSA L3 attestation + PQC sidecar signatures",
1922
+ "summary": "The release pipeline now creates the GitHub release atomically with the full asset bundle attached at creation time — operators no longer run a manual `gh release create` step. Every release ships with SLSA L3 provenance (verifiable via `slsa-verifier`), Sigstore-keyless SBOM signatures, AND framework-native PQC sidecars: a SHA3-512 byte digest and an ML-DSA-65 signature over the tarball, verifiable with the framework's own vendored noble-post-quantum primitives.",
1923
+ "sections": [
1924
+ {
1925
+ "heading": "Added",
1926
+ "items": [
1927
+ {
1928
+ "title": "SLSA L3 provenance attached to every GitHub release",
1929
+ "body": "The `npm-publish.yml` workflow now invokes `slsa-framework/slsa-github-generator` (pinned to a commit SHA) to produce a signed `*.intoto.jsonl` attestation describing the tarball + SBOM hashes, the workflow that built them, and the OIDC identity that signed them. The attestation is attached as a GitHub Release asset under the name `blamejs-X.Y.Z.intoto.jsonl`. Operators verify via `slsa-verifier verify-artifact <tarball> --provenance-path <jsonl> --source-uri github.com/blamejs/blamejs --source-tag vX.Y.Z`."
1930
+ },
1931
+ {
1932
+ "title": "Byte-digest sidecars (SHA-256 + SHA3-512)",
1933
+ "body": "Every release tarball ships with two checksum sidecars attached to the GitHub Release: `<tarball>.sha256` (the conventional `sha256sum`-compatible format operators expect — verify via `sha256sum -c`) and `<tarball>.sha3-512` (the framework's PQC-first hash, matching the audit chain and CMS-signing posture — verify via `openssl dgst -sha3-512`). Both digests cover the exact bytes the tarball ships."
1934
+ },
1935
+ {
1936
+ "title": "PQC signature sidecar (ML-DSA-65)",
1937
+ "body": "Every release tarball gets a `<tarball>.mldsa.sig` sidecar — an ML-DSA-65 signature over the tarball bytes, computed with the framework's vendored noble-post-quantum primitive. The signing key is stored as an environment secret in the `npm-publish` GitHub Actions environment; the matching public key is committed at `keys/release-pqc-pub.json` with a SHA3-512 fingerprint published in `SECURITY.md` for out-of-band verification. Operators with a PQC-only verification posture verify via the framework's own `b.pqcSoftware.ml_dsa_65.verify(sig, msg, pubKey)` — no dependency on Sigstore's classical-crypto chain. Three new helper scripts (`scripts/generate-release-signing-key.js` for one-time setup, `scripts/sign-release-artifact.js` for the workflow signer with self-verify-before-write defense, `scripts/sha3-digest.js` for the byte-digest sidecars) compose the existing `b.pqcSoftware` primitive."
1938
+ },
1939
+ {
1940
+ "title": "Structured release-notes source",
1941
+ "body": "Release notes are now generated from a structured JSON file at `release-notes/v<version>.json`. The generator (`scripts/generate-changelog-entry.js`) validates each field against an operator-facing-vocabulary check before emitting the `CHANGELOG.md` entry — internal-process narrative is refused at validation time so the discipline holds by construction. Operators edit the JSON, run the generator, commit both files."
1942
+ }
1943
+ ]
1944
+ },
1945
+ {
1946
+ "heading": "Changed",
1947
+ "items": [
1948
+ {
1949
+ "title": "Manual `gh release create` removed from the release workflow",
1950
+ "body": "Operators previously ran `gh release create vX.Y.Z --notes \"...\"` after pushing the tag, then the workflow tried to attach assets via `gh release upload`. With the immutable-releases setting on, the upload would fail with HTTP 422 (the release locks at creation time, before the workflow finishes), so the framework's last 5 releases shipped with zero assets. The workflow now creates the release atomically with all assets attached at creation; the immutable-release setting stays on for the post-publish-swap protection it provides. Operators just push the tag and wait for the workflow."
1951
+ },
1952
+ {
1953
+ "title": "Release notes extracted from CHANGELOG.md by the workflow",
1954
+ "body": "The release-create step extracts the current version's CHANGELOG section using an awk script and passes it via `--notes-file`. Single source of truth — no separate heredoc duplicating the CHANGELOG content. The local static gate `scripts/check-changelog-extract.js` exercises the same extract pre-push so format drift fails fast."
1955
+ }
1956
+ ]
1957
+ },
1958
+ {
1959
+ "heading": "Fixed",
1960
+ "items": [
1961
+ {
1962
+ "title": "Code-comment references to an internal configuration file removed",
1963
+ "body": "Source comments across `lib/`, `scripts/`, `.github/workflows/`, the codebase-patterns catalog, and historical `CHANGELOG.md` entries used to point at an internal maintainer-side configuration file. Operators reading those files saw pointers at an artifact they don't have. Every such reference is rewritten to carry the discipline inline (the comment describes what the rule says, not where to read it). A new codebase-patterns detector prevents re-introduction; its match pattern is constructed at runtime so the literal token strings don't appear in the catalog's prose either."
1964
+ }
1965
+ ]
1966
+ },
1967
+ {
1968
+ "heading": "Detectors",
1969
+ "items": [
1970
+ {
1971
+ "title": "slsa-framework-action-not-sha-pinned",
1972
+ "body": "Catches any `slsa-framework/*` reusable workflow callout whose `@<ref>` is not exactly 40 hex characters. Tag-pinned references to the SLSA builder are mutable — the upstream maintainer can re-publish the tag — which silently rotates the builder root of trust. The new `scanScope: \"workflows\"` extends the codebase-patterns scanner to walk `.github/workflows/`."
1973
+ },
1974
+ {
1975
+ "title": "internal-rulebook-vocabulary-in-source",
1976
+ "body": "Catches the four leak shapes (the framework's internal config-file name plus three rule-shorthand variants) across `lib/`, `scripts/`, `test/`, and `.github/workflows/`. The catalog file allowlists itself since the runtime regex construction inevitably involves the patterns being detected."
1977
+ }
1978
+ ]
1979
+ }
1980
+ ],
1981
+ "references": [
1982
+ {
1983
+ "label": "SLSA v1.0 spec",
1984
+ "url": "https://slsa.dev/spec/v1.0/"
1985
+ },
1986
+ {
1987
+ "label": "slsa-github-generator v2.1.0 release",
1988
+ "url": "https://github.com/slsa-framework/slsa-github-generator/releases/tag/v2.1.0"
1989
+ },
1990
+ {
1991
+ "label": "FIPS 204 ML-DSA",
1992
+ "url": "https://csrc.nist.gov/pubs/fips/204/final"
1993
+ },
1994
+ {
1995
+ "label": "RFC 9909 ML-DSA in X.509 / CMS",
1996
+ "url": "https://www.rfc-editor.org/rfc/rfc9909.html"
1997
+ },
1998
+ {
1999
+ "label": "NIST FIPS 202 SHA-3 / SHAKE",
2000
+ "url": "https://csrc.nist.gov/pubs/fips/202/final"
2001
+ },
2002
+ {
2003
+ "label": "OpenSSF Scorecard Signed-Releases check",
2004
+ "url": "https://github.com/ossf/scorecard/blob/main/docs/checks.md#signed-releases"
2005
+ }
2006
+ ]
2007
+ },
2008
+ {
2009
+ "version": "0.11.6",
2010
+ "date": "2026-05-19",
2011
+ "headline": "`b.safeMountInfo` — canonical `/proc/self/mountinfo` parser",
2012
+ "summary": "New operator-facing primitive at `lib/safe-mount-info.js` that centralizes the field-4 parse discipline bind-mount detection requires. Existing inline parsers in `lib/watcher.js` are migrated; a new codebase-patterns detector blocks future direct reads of `/proc/self/mountinfo` outside the primitive.",
2013
+ "sections": [
2014
+ {
2015
+ "heading": "Added",
2016
+ "items": [
2017
+ {
2018
+ "title": "`b.safeMountInfo.parse` / `read` / `bestMatch` / `isBindMount`",
2019
+ "body": "The new primitive exposes `parse(text)` for line-oriented decoding, `read(opts)` for sandbox-safe filesystem access, `bestMatch(entries, path)` for longest-prefix selection, and `isBindMount(entry)` for the canonical bind-mount predicate. `isBindMount` consults field 4 (root within source FS) only — NOT the options string, which the kernel never emits as `bind` per Linux Documentation/filesystems/proc.rst §3.5. Ad-hoc parsers that scan options for the word `bind` miss every real bind-mount."
2020
+ },
2021
+ {
2022
+ "title": "Typed refusal codes with audit emission",
2023
+ "body": "Refusals surface as `safe-mount-info/read-failed` (non-Linux / restricted sandbox; returns `opts.fallback`, default `null`), `safe-mount-info/parse-failed` (single malformed line — silent-skip by default, `opts.strict: true` upgrades to throw), `safe-mount-info/too-many-lines` (line cap, default 4096), and `safe-mount-info/bad-input` (non-string / non-positive-integer arg). Every refusal emits `system.safe_mount_info.refused` drop-silent — hot-path observability sinks must not crash or stall the request that emitted them."
2024
+ },
2025
+ {
2026
+ "title": "Fuzz harness at `fuzz/safe-mount-info.fuzz.js`",
2027
+ "body": "Probes `parse` with adversarial bytes: malformed lines, oversize input, Unicode garbage, truncated headers. Catches any uncaught error class outside the documented refusal surface."
2028
+ }
2029
+ ]
2030
+ },
2031
+ {
2032
+ "heading": "Changed",
2033
+ "items": [
2034
+ {
2035
+ "title": "`lib/watcher.js` filesystem auto-probe routes through `b.safeMountInfo`",
2036
+ "body": "The watcher's bind-mount detection now calls `b.safeMountInfo.read() + bestMatch() + isBindMount()` instead of parsing `/proc/self/mountinfo` inline. The single canonical parser means future container-escape detection, sealed-store path validation, and sandbox auto-probe call sites inherit the discipline automatically."
2037
+ }
2038
+ ]
2039
+ },
2040
+ {
2041
+ "heading": "Detectors",
2042
+ "items": [
2043
+ {
2044
+ "title": "`mountinfo-not-via-safemountinfo`",
2045
+ "body": "Flags direct `fs.readFileSync(\"/proc/self/mountinfo\", ...)` in `lib/` as a migration target. Only `lib/safe-mount-info.js` itself reaches the kernel surface; every other call site routes through the primitive."
2046
+ }
2047
+ ]
2048
+ }
2049
+ ],
2050
+ "references": [
2051
+ {
2052
+ "label": "Linux Documentation/filesystems/proc.rst §3.5",
2053
+ "url": "https://www.kernel.org/doc/Documentation/filesystems/proc.txt"
2054
+ },
2055
+ {
2056
+ "label": "CVE-2024-21626 runc leaky-vessels",
2057
+ "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-21626"
2058
+ },
2059
+ {
2060
+ "label": "CVE-2022-0185 fsconfig",
2061
+ "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-0185"
2062
+ }
2063
+ ]
2064
+ },
2065
+ {
2066
+ "version": "0.11.5",
2067
+ "date": "2026-05-19",
2068
+ "headline": "`b.safeDecompress(buf, opts)` — bomb-resistant decompression primitive",
2069
+ "summary": "New operator-facing primitive at `lib/safe-decompress.js` that centralizes bounded-output and bounded-ratio decompression defenses across the framework. `lib/websocket.js` permessage-deflate now routes through the primitive.",
2070
+ "sections": [
2071
+ {
2072
+ "heading": "Added",
2073
+ "items": [
2074
+ {
2075
+ "title": "`b.safeDecompress(buf, opts)` with explicit algorithm allowlist",
2076
+ "body": "Accepts `gzip` / `deflate` / `deflate-raw` (RFC 1951) / `brotli` under an explicit allowlist — unknown algorithms refuse with `safe-decompress/unsupported-algorithm`. Refuses bomb-class input via zlib's own `maxOutputLength` BEFORE allocation; AFTER decompression checks `output.length / input.length` against `maxRatio` (default 50:1) and overwrites + drops the buffer if the ratio is exceeded so operator-facing paths never see the bomb bytes. Pre-decompression input cap (`maxCompressedBytes`, default 4 MiB) defends against very-large compressed payloads whose zlib parse alone is expensive."
2077
+ },
2078
+ {
2079
+ "title": "Typed refusal codes with audit emission",
2080
+ "body": "Refusals surface as `safe-decompress/output-too-large`, `ratio-exceeded`, `decompress-failed`, `empty-input`, `oversized-input`, `unsupported-algorithm`, `bad-arg`, and `bad-input`. Operators wire `opts.audit` to receive the `system.safe_decompress.refused` event with `{ code, algorithm, ctx, reason }` metadata; emission is drop-silent — hot-path observability sinks must not crash or stall the request that emitted them."
2081
+ },
2082
+ {
2083
+ "title": "Fuzz harness at `fuzz/safe-decompress.fuzz.js`",
2084
+ "body": "Probes the four-algorithm allowlist with adversarial bytes (bomb / malformed / truncated / bogus dictionary) to catch any uncaught error class outside the documented refusal surface."
2085
+ }
2086
+ ]
2087
+ },
2088
+ {
2089
+ "heading": "Changed",
2090
+ "items": [
2091
+ {
2092
+ "title": "`lib/websocket.js` `_inflateMessage` routes through `b.safeDecompress`",
2093
+ "body": "WebSocket per-message-deflate now calls `b.safeDecompress({ algorithm: \"deflate-raw\", maxRatio: 0, ... })`. WS already binds upstream via `maxMessageBytes` so the ratio cap is opt-out at this call site; future per-message-deflate sites adopt the same shape."
2094
+ }
2095
+ ]
2096
+ }
2097
+ ],
2098
+ "references": [
2099
+ {
2100
+ "label": "RFC 1950 zlib",
2101
+ "url": "https://www.rfc-editor.org/rfc/rfc1950"
2102
+ },
2103
+ {
2104
+ "label": "RFC 1951 deflate",
2105
+ "url": "https://www.rfc-editor.org/rfc/rfc1951"
2106
+ },
2107
+ {
2108
+ "label": "RFC 1952 gzip",
2109
+ "url": "https://www.rfc-editor.org/rfc/rfc1952"
2110
+ },
2111
+ {
2112
+ "label": "RFC 7932 brotli",
2113
+ "url": "https://www.rfc-editor.org/rfc/rfc7932"
2114
+ },
2115
+ {
2116
+ "label": "CVE-2025-0725",
2117
+ "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-0725"
2118
+ },
2119
+ {
2120
+ "label": "RFC 8460 §5.2 TLS-RPT decompression community guidance",
2121
+ "url": "https://www.rfc-editor.org/rfc/rfc8460#section-5.2"
2122
+ }
2123
+ ]
2124
+ },
2125
+ {
2126
+ "version": "0.11.4",
2127
+ "date": "2026-05-19",
2128
+ "headline": "`b.audit.useStore({ record })` shadow store + WebSocket permessage-deflate bomb fix + new detectors",
2129
+ "summary": "Operators can now register a shadow audit store that receives a copy of every chain append after the framework's tamper-evident commit. The WebSocket inflate path gains a `maxOutputLength` cap. Five new codebase-patterns detectors and a token-aware shape-matcher land for future regex-bypass-resistant detectors.",
2130
+ "sections": [
2131
+ {
2132
+ "heading": "Added",
2133
+ "items": [
2134
+ {
2135
+ "title": "`b.audit.useStore({ record })` shadow store registration",
2136
+ "body": "Registers an operator-supplied shadow store that receives a copy of every audit chain append AFTER the framework's tamper-evident chain commits. The operator's `record(row)` async function receives the fully-formed row — `{ _id, recordedAt, monotonicCounter, prevHash, rowHash, action, outcome, actorUserId, ..., metadata }` — so external destinations (AWS QLDB / Azure Confidential Ledger / Google Cloud Audit Logs / in-house WORM appliances / SIEM forwarders) see identical hashes for cross-store reconciliation. Shadow failures are drop-silent — hot-path observability sinks must not crash or stall the request that emitted them — the framework chain is authoritative and already committed; an unreachable shadow surfaces via `b.observability` as the `audit.shadow_failed` event but never crashes the request path. Composes with HIPAA §164.312(b) / PCI-DSS Req 10.5.3 (separation-of-duties retention) / SOX §404 / SEC 17a-4 WORM postures. Pass `null` or `{ record: null }` to unregister."
2137
+ },
2138
+ {
2139
+ "title": "Shape-matcher substrate at `test/helpers/_shape-match.js`",
2140
+ "body": "Test-only token-aware traversal (never ships — `test/` is absent from package.json `files:` allowlist) that tracks paren / brace / bracket depth + string / template-literal / regex / comment state. Exposes `findCalls(source, calleeRegex)` / `findEnclosingTry(source, pos)` / `aliasesOf(source, chainRegex)`. Future releases convert the highest-bypass-risk regex-only detectors to AST-aware variants using this substrate, closing the class of regex-bypass via variable renaming / parens / line splits that surface-pattern detectors miss."
2141
+ }
2142
+ ]
2143
+ },
2144
+ {
2145
+ "heading": "Fixed",
2146
+ "items": [
2147
+ {
2148
+ "title": "WebSocket permessage-deflate decompression-bomb amplification",
2149
+ "body": "`lib/websocket.js` `_inflateMessage` previously called `zlib.inflateRawSync` without `maxOutputLength` — a malicious peer could ship a small compressed frame that exploded into gigabytes BEFORE the framework's post-decompression `maxMessageBytes` check ran. The inflate now passes `maxOutputLength: this.maxMessageBytes` so zlib refuses mid-decompress; same amplification class the `gunzip-without-output-size-cap` detector defends elsewhere."
2150
+ }
2151
+ ]
2152
+ },
2153
+ {
2154
+ "heading": "Detectors",
2155
+ "items": [
2156
+ {
2157
+ "title": "`test-promise-settimeout-sleep`",
2158
+ "body": "Scans the `test/` tree — the first detector under the new test-scope walker — for the `await new Promise(r => setTimeout(r, N))` shape forbidden in tests. The framework's `helpers.waitUntil(predicate, opts?)` is the canonical replacement: polls the actual condition every 25ms up to a 5000ms cap, exiting early when truthy. Fast platforms finish in milliseconds; contended platforms get the full budget. The migration backlog is pre-allowlisted as a release-gate countdown."
2159
+ },
2160
+ {
2161
+ "title": "`inflate-unzip-without-output-size-cap`",
2162
+ "body": "Extends the v0.10.15 gunzip-cap detector to `zlib.inflateSync` / `inflateRawSync` / `unzipSync` / `createInflate` family. RFC 1951 deflate is the same bomb class."
2163
+ },
2164
+ {
2165
+ "title": "`map-get-falsy-then-set-pre-node-26`",
2166
+ "body": "Companion to `map-has-then-set-pre-node-26` — catches the `!M.get(k)` / `M.get(k) === undefined|null` semantically-identical variants that Node 26's `Map.prototype.getOrInsertComputed` replaces."
2167
+ },
2168
+ {
2169
+ "title": "`fs-existssync-then-read-toctou`",
2170
+ "body": "CodeQL `js/file-system-race` class — `fs.existsSync(p) + fs.readFile(p)` against the same path is symlink-swap-vulnerable. The canonical defense is `lib/atomic-file.js`'s open-by-fd-first pattern."
2171
+ },
2172
+ {
2173
+ "title": "`buffer-from-string-on-auth-path`",
2174
+ "body": "Flags `Buffer.from(String(x))` in `lib/` — auth-bearing sites become `b.safeBytes` migration targets in the next release."
2175
+ }
2176
+ ]
2177
+ }
2178
+ ],
2179
+ "references": [
2180
+ {
2181
+ "label": "RFC 7692 §7.2.2 WebSocket permessage-deflate",
2182
+ "url": "https://www.rfc-editor.org/rfc/rfc7692#section-7.2.2"
2183
+ },
2184
+ {
2185
+ "label": "HIPAA §164.312(b) Audit Controls",
2186
+ "url": "https://www.law.cornell.edu/cfr/text/45/164.312"
2187
+ },
2188
+ {
2189
+ "label": "PCI-DSS v4.0 Req 10",
2190
+ "url": "https://www.pcisecuritystandards.org/"
2191
+ },
2192
+ {
2193
+ "label": "SEC 17a-4 WORM",
2194
+ "url": "https://www.sec.gov/files/rules/final/34-44238.pdf"
2195
+ },
2196
+ {
2197
+ "label": "SOX §404",
2198
+ "url": "https://www.sec.gov/about/laws/soa2002.pdf"
2199
+ },
2200
+ {
2201
+ "label": "CVE-2025-0725",
2202
+ "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-0725"
2203
+ },
2204
+ {
2205
+ "label": "CodeQL js/file-system-race",
2206
+ "url": "https://codeql.github.com/codeql-query-help/javascript/js-file-system-race/"
2207
+ }
2208
+ ]
2209
+ },
2210
+ {
2211
+ "version": "0.11.3",
2212
+ "date": "2026-05-19",
2213
+ "headline": "SPF `a` and `mx` mechanism dispatch + smaller deferral-condition cleanups",
2214
+ "summary": "`b.mail.spf.verify` now evaluates the `a` and `mx` mechanisms per RFC 7208 §5.3 + §5.4, including the dual-cidr-length syntax. Senders publishing `v=spf1 mx -all` or `v=spf1 a -all` previously permerrored against this framework even though those are the second-most-common SPF mechanisms in fielded policies.",
2215
+ "sections": [
2216
+ {
2217
+ "heading": "Added",
2218
+ "items": [
2219
+ {
2220
+ "title": "SPF `a` and `mx` mechanism evaluation",
2221
+ "body": "`b.mail.spf.verify` now evaluates the `a` and `mx` mechanisms per RFC 7208 §5.3 + §5.4, including the dual-cidr-length syntax (`a:foo.example/24//64`, `mx//64`). Verification resolves the operator-supplied A / AAAA / MX records (via the existing `dnsLookup` callback contract — which is now honored for every record type, not only TXT) and matches the connecting IP under the parsed cidr. MX expansion is capped at the RFC §4.6.4 limit of 10 hosts (over-limit = permerror); each MX-host A/AAAA expansion counts toward the 10-lookup global ceiling and the 2-lookup void-lookup sub-limit."
2222
+ }
2223
+ ]
2224
+ },
2225
+ {
2226
+ "heading": "Changed",
2227
+ "items": [
2228
+ {
2229
+ "title": "Empty digit segments in dual-cidr-length grammar refuse with permerror",
2230
+ "body": "`a/`, `a//`, `mx/`, `mx//`, `a/24//` and similar shapes now permerror with an explanatory message. RFC §5.3/§5.4 grammar requires `1*DIGIT` after each slash; accepting empty would over-authorize senders publishing `v=spf1 a/ -all` (would match every IP in the /32 of every A record)."
2231
+ },
2232
+ {
2233
+ "title": "`exists` and `ptr` mechanisms permerror with explanatory message",
2234
+ "body": "`exists` (RFC §5.7) needs macro-string expansion (RFC §7) to be usable in fielded policies; `ptr` (RFC §5.5) is strongly discouraged by the RFC and rarely seen. Each now permerrors with an explanatory message naming the RFC section and a practical operator-side mitigation."
2235
+ },
2236
+ {
2237
+ "title": "S/MIME documentation corrected to reflect v0.10.16 shipped state",
2238
+ "body": "`b.mail.crypto.smime` `@card` and the v1-only-emits-metadata comment in `lib/mail-crypto-smime.js` are corrected to reflect that sign + verify shipped in v0.10.16 on the `b.cms` substrate. EFAIL-class encrypt/decrypt remains the only deferred slice."
2239
+ },
2240
+ {
2241
+ "title": "Deferral conditions documented for ACME revocation and IMAP BAD UID responses",
2242
+ "body": "`b.acme.create.revokeCert({ useCertKey: true })` and the `BAD UID <subverb>` IMAP listener response now carry explicit re-open conditions plus named operator escape hatches alongside the deferral."
2243
+ }
2244
+ ]
2245
+ },
2246
+ {
2247
+ "heading": "Detectors",
2248
+ "items": [
2249
+ {
2250
+ "title": "`slice1-optional-parseint-silent-default`",
2251
+ "body": "Any `.slice(1)` followed by an `if (X.length > 0)` guard around `parseInt(X, 10)` MUST sit in a file that also carries an explicit empty-segment refusal phrasing. Future cidr-length / prefix-length / port-range parsers inherit the discipline automatically."
2252
+ }
2253
+ ]
2254
+ }
2255
+ ],
2256
+ "references": [
2257
+ {
2258
+ "label": "RFC 7208 §5.3 a mechanism",
2259
+ "url": "https://www.rfc-editor.org/rfc/rfc7208#section-5.3"
2260
+ },
2261
+ {
2262
+ "label": "RFC 7208 §5.4 mx mechanism",
2263
+ "url": "https://www.rfc-editor.org/rfc/rfc7208#section-5.4"
2264
+ },
2265
+ {
2266
+ "label": "RFC 7208 §4.6.4 DNS-lookup limits",
2267
+ "url": "https://www.rfc-editor.org/rfc/rfc7208#section-4.6.4"
2268
+ },
2269
+ {
2270
+ "label": "RFC 8551 S/MIME 4.0",
2271
+ "url": "https://www.rfc-editor.org/rfc/rfc8551.html"
2272
+ },
2273
+ {
2274
+ "label": "RFC 9051 IMAP4rev2 §6.4.9 UID",
2275
+ "url": "https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9"
2276
+ }
2277
+ ]
2278
+ },
2279
+ {
2280
+ "version": "0.11.2",
2281
+ "date": "2026-05-19",
2282
+ "headline": "Node 26 floor-bump preparation",
2283
+ "summary": "Today's `engines.node` floor is `>=24.14.1` and the framework runs cleanly on Node 26 (which satisfies the floor). This release ships the prep scaffolding so the future floor-bump (when Node 26 promotes to Active LTS and `>=26.x` becomes the floor) is mechanical.",
2284
+ "sections": [
2285
+ {
2286
+ "heading": "Added",
2287
+ "items": [
2288
+ {
2289
+ "title": "`b.backup.diskStorage(opts)` — canonical name for the local-filesystem backup backend",
2290
+ "body": "`b.backup.localStorage(opts)` continues to work and emits a one-time deprecation warning via `b.deprecate.alias`, with removal scheduled for the next major. The rename avoids the Node 26 platform-level `localStorage` global naming collision; the deprecation path follows the framework's stable upgrade policy (one minor with deprecation warnings before removal)."
2291
+ },
2292
+ {
2293
+ "title": "Node 26 forward-compatibility integration test",
2294
+ "body": "`test/integration/pqc-pkcs8-forward-compat.test.js` captures the ML-KEM-1024 / ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f / Ed25519 PKCS8 export-byte shape on the current Node, asserts the sign+verify / encap+decap roundtrip via a re-imported KeyObject, and embeds a Node-26-shape fixture that re-imports every run. The forward-compat contract is testable today; the reverse-direction (Node-26-exported → Node-24-imported) test follows the floor-bump."
2295
+ },
2296
+ {
2297
+ "title": "SECURITY.md and README gain Node 26 compatibility documentation",
2298
+ "body": "SECURITY.md gains a Node 26 compatibility section documenting the `localStorage` global naming collision (bare references in operator handler code now resolve to a Node global rather than throwing `ReferenceError`) and the ML-KEM / ML-DSA seed-only PKCS8 export shape (Node-24-sealed material re-imports cleanly on Node 26; new material from Node 26 is seed-only — parallel Node 24 readers of the same sealed disk need a one-time migration when the writer moves). README Requirements line gains the matching Node 26 note."
2299
+ }
2300
+ ]
2301
+ },
2302
+ {
2303
+ "heading": "Detectors",
2304
+ "items": [
2305
+ {
2306
+ "title": "`map-get-or-insert-pre-node-26`",
2307
+ "body": "Flags the `if (!m.has(k)) m.set(k, factory()); m.get(k)` shape that Node 26's `Map.prototype.getOrInsertComputed(key, factory)` replaces in a single call. The detector lands as an allowlist marker — every existing call site in `lib/` is allowlisted with the spec file as the migration target; new code post-this-patch trips the gate. When the floor bumps the allowlist is walked and the detector flips to enforce."
2308
+ }
2309
+ ]
2310
+ },
2311
+ {
2312
+ "heading": "Deprecated",
2313
+ "items": [
2314
+ {
2315
+ "title": "`b.backup.localStorage(opts)` aliased to `b.backup.diskStorage(opts)`",
2316
+ "body": "First-call deprecation warning via `b.deprecate.alias`; removal scheduled for the next major. The rename avoids the Node 26 `localStorage` global naming collision."
2317
+ }
2318
+ ]
2319
+ }
2320
+ ],
2321
+ "references": [
2322
+ {
2323
+ "label": "Node.js v26 release notes",
2324
+ "url": "https://nodejs.org/en/blog/release/v26.0.0"
2325
+ },
2326
+ {
2327
+ "label": "TC39 Map.getOrInsertComputed",
2328
+ "url": "https://github.com/tc39/proposal-upsert"
2329
+ },
2330
+ {
2331
+ "label": "RFC 8032 §5.1 Ed25519 context parameter",
2332
+ "url": "https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1"
2333
+ }
2334
+ ]
2335
+ },
2336
+ {
2337
+ "version": "0.11.1",
2338
+ "date": "2026-05-19",
2339
+ "headline": "Integration suite hardening + live coverage for the v0.11.0 surface",
2340
+ "summary": "`b.httpClient.request` proxy + `allowInternal` interaction is corrected; `b.mail.crypto.smime.verify` exposes a `chainVerified` boolean; new integration tests cover S/MIME signing, SAML SLO, and RFC 7592 Dynamic Client Registration Management against the live Keycloak harness.",
2341
+ "sections": [
2342
+ {
2343
+ "heading": "Changed",
2344
+ "items": [
2345
+ {
2346
+ "title": "`b.httpClient.request` skips local SSRF DNS lookup when proxy + `allowInternal: true`",
2347
+ "body": "The proxy resolves the destination hostname in its own network context, so requiring local resolution refused legitimate intranet / docker-service-name targets routed through the proxy. The SSRF gate still runs when `allowInternal` is false or array-form — the proxy's freedom to reach internal IPs is not a blanket license; the explicit opt-in is still required."
2348
+ },
2349
+ {
2350
+ "title": "`b.mtlsCa` integration tests compose with `caKeySealedMode: \"disabled\"`",
2351
+ "body": "Fixture purpose only. Production deployments continue to wire `opts.vault` for sealed-at-rest CA-key storage."
2352
+ },
2353
+ {
2354
+ "title": "`b.mail.crypto.smime.verify` returns a `chainVerified` boolean",
2355
+ "body": "The return shape gains a `chainVerified: boolean` field reflecting whether `opts.trustAnchorCertsPem` was supplied and the leaf-to-root chain walk completed."
2356
+ }
2357
+ ]
2358
+ },
2359
+ {
2360
+ "heading": "Added",
2361
+ "items": [
2362
+ {
2363
+ "title": "Live S/MIME integration test against a real X.509 chain",
2364
+ "body": "New `test/integration/mail-crypto-smime.test.js` round-trips S/MIME sign + verify with a real X.509 chain issued by `b.mtlsCa` (CA → leaf cert → ML-DSA-65 signer), exercises tamper / wrong-key / untrusted-anchor refusal paths, and validates the `chainVerified` return field."
2365
+ },
2366
+ {
2367
+ "title": "SAML SLO + RFC 7592 DCR Management coverage in federation-auth integration",
2368
+ "body": "`test/integration/federation-auth.test.js` extends to cover SAML SLO (`buildLogoutRequest` against Keycloak's `/protocol/saml` SLO endpoint with the wire-format-parse assertion) and RFC 7592 Dynamic Client Registration Management (`registerClient` / `readClient` / `updateClient` / `deleteClient` against Keycloak's DCR endpoint)."
2369
+ }
2370
+ ]
2371
+ }
2372
+ ]
2373
+ },
2374
+ {
2375
+ "version": "0.11.0",
2376
+ "date": "2026-05-19",
2377
+ "headline": "Mail-crypto sign/verify + SAML Single Logout + browser identity + CSP3 + hypermedia + sectoral postures",
2378
+ "summary": "S/MIME sign + verify lands on a new in-tree CMS substrate with PQC signers; SAML 2.0 Single Logout (Redirect / POST / SOAP) ships with EncryptedAssertion and Holder-of-Key; browser-identity primitives (FedCM, DBSC, VAPID, Import Maps SRI) join the framework; a CSP Level 3 builder enforces no-`unsafe-*` defaults; OAuth Dynamic Client Registration Management (RFC 7592) and OpenID Connect Native SSO 1.0 close out the identity surface; 17 sectoral / cybersecurity / AI-governance compliance postures join the catalog.",
2379
+ "sections": [
2380
+ {
2381
+ "heading": "Added",
2382
+ "items": [
2383
+ {
2384
+ "title": "`b.cms.parseSignedData(buf)` — RFC 5652 SignedData walker",
2385
+ "body": "Parses an inbound RFC 5652 §5.1 SignedData ContentInfo and returns `{ digestAlgs, encapContent, certificates, signerInfos }` as structured arrays so downstream verifiers check signatures without re-implementing the SignedData walker."
2386
+ },
2387
+ {
2388
+ "title": "`b.mail.crypto.smime.sign(opts)` + `.verify(opts)` live on the `b.cms` substrate",
2389
+ "body": "`sign` composes `b.cms.encodeSignedData` and wraps the payload in an RFC 8551 `multipart/signed; protocol=\"application/pkcs7-signature\"; micalg=sha3-{256,512}` envelope. `verify` parses the CMS SignedData payload, walks signed-attributes to extract the `messageDigest` attribute, recomputes the message digest, refuses tamper with `mail-crypto/smime/message-digest-mismatch`, and PQC-verifies the signature against the operator-supplied signer public key. Supports ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f signers; SHA-2 family refused at `cms/bad-digest`."
2390
+ },
2391
+ {
2392
+ "title": "S/MIME trust-anchor chain walk + revocation hook",
2393
+ "body": "`b.mail.crypto.smime.verify({ trustAnchorCertsPem })` walks the SignerInfo cert chain leaf-to-root via `node:crypto.X509Certificate` with `notBefore` / `notAfter` checks and refuses `mail-crypto/smime/untrusted-chain` / `cert-expired` / `cert-not-yet-valid`. Revocation freshness is operator-wired via `b.network.tls.ocsp` when required."
2394
+ },
2395
+ {
2396
+ "title": "`b.mail.crypto.pgp.experimental` PQC PGP encrypt/decrypt + WKD URL",
2397
+ "body": "`encrypt(opts)` + `decrypt(opts)` ship under the `experimental` namespace because the RFC 9580bis PKESK ML-KEM codepoints are not IANA-registered yet. Envelope is ML-KEM-1024 KEM + ChaCha20-Poly1305 AEAD with per-recipient KEK derived via SHAKE256 bound to the literal label `pgp/experimental/chacha20-poly1305`. Multi-recipient envelopes; tamper / wrong-key refusal as typed errors. `wkd.computeUrl(email)` computes the draft-koch-openpgp-webkey-service URL (SHAKE256-hash localpart + zbase32 encoding) and returns `{ direct, advanced }` URLs — operators supply their own HTTPS fetcher."
2398
+ },
2399
+ {
2400
+ "title": "`b.auth.saml.sp` Single Logout — Redirect, POST, and SOAP bindings",
2401
+ "body": "`buildLogoutRequest({...})` / `parseLogoutRequest(b64, opts)` / `buildLogoutResponse({...})` implement SAML 2.0 Single Logout on the HTTP-Redirect binding per SAML Bindings §3.4.4.1 with PQC-signed canonical query (ML-DSA-65 / ML-DSA-87 / Ed25519). `buildLogoutRequestPost` / `parseLogoutRequestPost` cover the HTTP-POST binding, and `buildLogoutRequestSoap` / `parseLogoutResponseSoap` cover the SOAP synchronous back-channel — both with embedded XMLDSig-Enveloped signatures. SP metadata now emits `<md:SingleLogoutService>` bindings when `singleLogoutServiceUrl` is set; tamper / wrong-key / missing-signature each refuse as typed errors."
2402
+ },
2403
+ {
2404
+ "title": "SAML SignatureMethod surface spanning XMLDSig 1.1 + RFC 9231",
2405
+ "body": "Accepts `rsa-sha256` / `rsa-sha384` / `rsa-sha512` and `ecdsa-sha256` / `ecdsa-sha384` / `ecdsa-sha512` (W3C XMLDSig Core 1.1) plus `ed25519` (RFC 9231) so the framework interops with deployed IdPs out of the box. Classical keys are PEM strings or `node:crypto` KeyObject instances; PQC keys are `Uint8Array` from `b.pqcSoftware.ml_dsa_*.keygen()`. ML-DSA-65 / ML-DSA-87 are accepted under framework-private URIs (`urn:blamejs:experimental:saml-sig-alg:ml-dsa-65` / `:ml-dsa-87`) — no IETF / W3C XMLDSig registration exists for ML-DSA yet. Verification refuses inclusive-c14n, algorithm-confusion (SignatureMethod URI must match the operator-declared `idpVerifyAlg`), signature-wrapping (Reference URI must match root ID via timing-safe digest compare), and SHA-1 digest methods (CVE-2017-7525-class)."
2406
+ },
2407
+ {
2408
+ "title": "SAML 2.0 §2.5 EncryptedAssertion decryption",
2409
+ "body": "Decrypts AES-128-GCM / AES-256-GCM (W3C XMLEnc 1.1 §5.2.4) content with RSA-OAEP-MGF1P / xmlenc11 RSA-OAEP key transport (SHA-256/384/512 only; SHA-1 OAEP refused as CVE-2023-49141-class). AES-CBC content encryption is refused under both `xmlenc#aes128-cbc` and `xmlenc#aes256-cbc` (CVE-2011-1473 padding-oracle class) — operators integrating with IdPs that default to CBC (older ADFS / Azure AD / Okta / Keycloak / OneLogin) switch the IdP's content-encryption setting to AES-128-GCM or AES-256-GCM. Framework-experimental URIs `urn:blamejs:experimental:xmlenc:ml-kem-1024` (key transport) and `urn:blamejs:experimental:xmlenc:xchacha20-poly1305` (content) are accepted alongside the W3C URIs."
2410
+ },
2411
+ {
2412
+ "title": "SAML Holder-of-Key SubjectConfirmation",
2413
+ "body": "`b.auth.saml.sp.verifyResponse({ holderOfKey: { presentedCertPem } })` honors `urn:oasis:names:tc:SAML:2.0:cm:holder-of-key`: SHA3-512 fingerprint of the embedded KeyInfo/X509Data certificate is compared against the operator-supplied presented mTLS / possession-proof cert via `timingSafeEqual`. HoK and Bearer confirmations coexist."
2414
+ },
2415
+ {
2416
+ "title": "OAuth Dynamic Client Registration Management (RFC 7592)",
2417
+ "body": "`b.auth.oauth.readClient(uri, token)` / `updateClient(uri, token, metadata)` / `deleteClient(uri, token)` bind to the `registration_access_token` returned by `registerClient` and implement the GET / PUT / DELETE endpoints per RFC 7592. `updateClient` enforces the same `redirect_uris`-array refusal as `registerClient`."
2418
+ },
2419
+ {
2420
+ "title": "OpenID Connect Native SSO 1.0 token exchange",
2421
+ "body": "`b.auth.oauth.nativeSsoExchange({ deviceSecret, idToken, audience })` convenience-wraps `exchangeToken` for OpenID Connect Native SSO 1.0 §6 and adds `urn:openid:params:token-type:device-secret` to the RFC 8693 §3 token-type allowlist."
2422
+ },
2423
+ {
2424
+ "title": "`b.webPush` — VAPID JWT keypair generation + auth header",
2425
+ "body": "`generateVapidKeypair()` + `buildVapidAuthHeader(opts)` sign the RFC 8292 VAPID JWT inline (ECDSA-P256 per the spec). The framework's PQC-default JWT signer refuses ES256 by design, so `b.webPush` owns the signing rather than relaxing the broader policy."
2426
+ },
2427
+ {
2428
+ "title": "`b.fedcm` — W3C FedCM 2024 IdP-side response builders",
2429
+ "body": "`wellKnown({ provider_urls })` / `config({ accounts_endpoint, ... })` / `accountsResponse({ accounts })` / `idAssertionResponse({ token })` emit the JSON shapes the browser FedCM API expects from an IdP."
2430
+ },
2431
+ {
2432
+ "title": "`b.dbsc` — IETF Device-Bound Session Credentials",
2433
+ "body": "`challenge({ secretKey })` mints an HMAC-SHA3-512-signed challenge token that requires no server-side storage. `verifyBindingAssertion(jwt, opts)` refuses HS256 / `none` as algorithm-confusion, validates ES256 / RS256 against the embedded JWK, and returns the RFC 7638 JWK thumbprint so operators pin the binding key to a session."
2434
+ },
2435
+ {
2436
+ "title": "`b.importmapIntegrity.build({ modules })` — WICG Import Maps + SRI",
2437
+ "body": "Emits an integrity map (SHA-384 by default) so browsers refuse module bytes that don't match the operator-declared hashes."
2438
+ },
2439
+ {
2440
+ "title": "`b.csp.build(directives, opts?)` + `b.csp.nonce(byteLen?)` + `b.csp.hash(scriptBody, alg?)`",
2441
+ "body": "CSP Level 3 builder surface that refuses `'unsafe-*'` / catch-all `*` / `https:` / `data:` in non-image directives without an explicit acknowledgement opt. Auto-appends `require-trusted-types-for 'script'` plus the operator-supplied `trusted-types` policy list when any `script-*` directive is set. Emits ≥128-bit nonces by default and computes `sha256` / `sha384` / `sha512` hash sources for inline scripts. `b.middleware.securityHeaders` `coep` opt now documents the W3C CR 2024-12 `credentialless` value alongside `require-corp`."
2442
+ },
2443
+ {
2444
+ "title": "OpenMetrics 1.0 exposition format",
2445
+ "body": "`b.metrics` registry `exposition({ format: \"openmetrics\" })` emits the counter `_total` suffix, `# UNIT` lines, exemplar trace IDs on histograms, and the `# EOF` terminator per the openmetrics.io 1.0 wire format."
2446
+ },
2447
+ {
2448
+ "title": "`b.standardWebhooks.sign` + `verify`",
2449
+ "body": "Implements the standardwebhooks.com consortium spec (Stripe / Svix / Okta wire format): HMAC-SHA256, multi-version signature header, 5-minute default skew tolerance."
2450
+ },
2451
+ {
2452
+ "title": "`b.lro` — AIP-151 Long-Running Operations",
2453
+ "body": "`create({ store }) → { submit, status, list, cancel }` with operator-supplied storage and AbortSignal-aware cancellation."
2454
+ },
2455
+ {
2456
+ "title": "`b.jsonApi.dataResponse(data, opts)` + `.errorResponse(errors)`",
2457
+ "body": "Wraps domain payloads in the JSON:API v1.1 top-level shape and refuses missing Resource Object `type`."
2458
+ },
2459
+ {
2460
+ "title": "`b.hal.resource(payload, { links, embedded, templates })`",
2461
+ "body": "Builds HAL responses (draft-kelly-json-hal) with an RFC 8288 link-object normaliser."
2462
+ },
2463
+ {
2464
+ "title": "Sectoral / cybersecurity / AI-governance compliance postures (17 new regimes)",
2465
+ "body": "`b.compliance` posture catalog gains `42-cfr-part-2`, `hti-1`, `uscdi-v4`, `irs-1075`, `nist-800-172-r3`, `tlp-2.0`, `soci-au`, `nis2`, `cra`, `ffiec-cat-2`, `cri-profile-v2.0`, `m-22-09`, `m-22-18`, `nist-800-53-r5-privacy`, `nist-ai-600-1-genai`, `nist-csf-2.0`, `sb-53`, and `nyc-ll144-2024`. Each cascade pins the regime's normative floor (`backupEncryptionRequired` / `auditChainSignedRequired` / `tlsMinVersion` / `requireVacuumAfterErase`)."
2466
+ }
2467
+ ]
2468
+ },
2469
+ {
2470
+ "heading": "Detectors",
2471
+ "items": [
2472
+ {
2473
+ "title": "`number-coerce-or-zero-on-json-source`",
2474
+ "body": "Refuses `Number(<var>['<kebab-cased-key>']) || 0` shapes — that coercion silently accepts `Infinity`, `NaN`, negative values, and arbitrary strings on operator-untrusted JSON-source input. Codifies the discipline that the previous TLS-RPT one-off established."
2475
+ }
2476
+ ]
2477
+ }
2478
+ ],
2479
+ "references": [
2480
+ {
2481
+ "label": "RFC 5652 Cryptographic Message Syntax",
2482
+ "url": "https://www.rfc-editor.org/rfc/rfc5652.html"
2483
+ },
2484
+ {
2485
+ "label": "RFC 8551 S/MIME 4.0",
2486
+ "url": "https://www.rfc-editor.org/rfc/rfc8551.html"
2487
+ },
2488
+ {
2489
+ "label": "RFC 9580 OpenPGP",
2490
+ "url": "https://www.rfc-editor.org/rfc/rfc9580.html"
2491
+ },
2492
+ {
2493
+ "label": "draft-koch-openpgp-webkey-service",
2494
+ "url": "https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/"
2495
+ },
2496
+ {
2497
+ "label": "SAML 2.0 Bindings (Redirect / POST / SOAP)",
2498
+ "url": "https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf"
2499
+ },
2500
+ {
2501
+ "label": "SAML 2.0 Core §2.5 EncryptedAssertion",
2502
+ "url": "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf"
2503
+ },
2504
+ {
2505
+ "label": "W3C XML-Encryption 1.1",
2506
+ "url": "https://www.w3.org/TR/xmlenc-core1/"
2507
+ },
2508
+ {
2509
+ "label": "RFC 7592 Dynamic Client Registration Management",
2510
+ "url": "https://www.rfc-editor.org/rfc/rfc7592.html"
2511
+ },
2512
+ {
2513
+ "label": "OpenID Connect Native SSO 1.0",
2514
+ "url": "https://openid.net/specs/openid-connect-native-sso-1_0.html"
2515
+ },
2516
+ {
2517
+ "label": "RFC 8292 VAPID for Web Push",
2518
+ "url": "https://www.rfc-editor.org/rfc/rfc8292.html"
2519
+ },
2520
+ {
2521
+ "label": "W3C CSP Level 3",
2522
+ "url": "https://www.w3.org/TR/CSP3/"
2523
+ },
2524
+ {
2525
+ "label": "W3C Trusted Types",
2526
+ "url": "https://www.w3.org/TR/trusted-types/"
2527
+ },
2528
+ {
2529
+ "label": "OpenMetrics 1.0",
2530
+ "url": "https://openmetrics.io/"
2531
+ },
2532
+ {
2533
+ "label": "Standard Webhooks",
2534
+ "url": "https://www.standardwebhooks.com/"
2535
+ },
2536
+ {
2537
+ "label": "Google AIP-151 Long-Running Operations",
2538
+ "url": "https://google.aip.dev/151"
2539
+ },
2540
+ {
2541
+ "label": "JSON:API v1.1",
2542
+ "url": "https://jsonapi.org/format/1.1/"
2543
+ },
2544
+ {
2545
+ "label": "draft-kelly-json-hal",
2546
+ "url": "https://datatracker.ietf.org/doc/draft-kelly-json-hal/"
2547
+ }
2548
+ ]
2549
+ }
2550
+ ]
2551
+ }