@blamejs/blamejs-shop 0.4.117 → 0.4.119

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 (30) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/affiliates.js +14 -1
  3. package/lib/asset-manifest.json +1 -1
  4. package/lib/vendor/MANIFEST.json +29 -25
  5. package/lib/vendor/blamejs/CHANGELOG.md +4 -0
  6. package/lib/vendor/blamejs/api-snapshot.json +2 -2
  7. package/lib/vendor/blamejs/lib/app-shutdown.js +2 -2
  8. package/lib/vendor/blamejs/lib/arg-parser.js +3 -3
  9. package/lib/vendor/blamejs/lib/auth/fido-mds3.js +1 -1
  10. package/lib/vendor/blamejs/lib/cert.js +1 -1
  11. package/lib/vendor/blamejs/lib/compliance.js +1 -1
  12. package/lib/vendor/blamejs/lib/db-file-lifecycle.js +1 -1
  13. package/lib/vendor/blamejs/lib/guard-list-id.js +1 -1
  14. package/lib/vendor/blamejs/lib/log.js +1 -1
  15. package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +1 -1
  16. package/lib/vendor/blamejs/lib/mail-send-deliver.js +1 -1
  17. package/lib/vendor/blamejs/lib/middleware/span-http-server.js +2 -2
  18. package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
  19. package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
  20. package/lib/vendor/blamejs/lib/pqc-gate.js +1 -1
  21. package/lib/vendor/blamejs/lib/static.js +3 -3
  22. package/lib/vendor/blamejs/lib/vault-aad.js +1 -1
  23. package/lib/vendor/blamejs/lib/vendor-data.js +1 -1
  24. package/lib/vendor/blamejs/lib/watcher.js +1 -1
  25. package/lib/vendor/blamejs/lib/ws-client.js +2 -2
  26. package/lib/vendor/blamejs/package.json +1 -1
  27. package/lib/vendor/blamejs/release-notes/v0.15.31.json +18 -0
  28. package/lib/vendor/blamejs/release-notes/v0.15.32.json +18 -0
  29. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +73 -25
  30. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.4.x
10
10
 
11
+ - v0.4.119 (2026-06-26) — **Vendored framework refreshed to 0.15.32.** Updates the vendored blamejs framework to 0.15.32, taking in the 0.15.31 and 0.15.32 upstream releases. Both are internal test-suite tooling only: they extend the framework's guard suite with a re-verification gate that retires renamed suppression-marker tokens (so a re-verified suppression class can't inherit a stale approval) and update the matching in-source marker comments. No runtime code, public API, or wire format changed upstream, so there is no runtime or behavior change for this storefront and no operator action is required. **Changed:** *Vendored framework refreshed to 0.15.32* — Refreshes the vendored blamejs framework to 0.15.32 (taking in 0.15.31 and 0.15.32), upstream releases scoped to test-suite tooling — a re-verification gate for renamed suppression markers and the matching marker-comment updates. The shipped framework runtime, public API, and wire format are unchanged, so no operator action is required.
12
+
13
+ - v0.4.118 (2026-06-26) — **Changing an affiliate's commission kind now revalidates the commission value against the new kind's cap.** An affiliate's commission value is validated against its kind's ceiling when it is set: a percentage commission is capped at 100% (10000 basis points), while a flat per-order amount may be far larger. Changing only the commission kind — without resupplying the value — skipped that revalidation. Switching a flat per-order amount to a percentage therefore left the large flat value in place as a percentage rate: a $500-per-order flat amount became a 5,000,000% rate that computes a runaway commission on the next recorded order and feeds it straight into the pending-payout total. Updating the commission kind now revalidates the effective commission value (the resupplied one, or the stored one) against the new kind, so a change that would push the value over the new kind's cap is rejected with the same error as setting it directly, instead of being silently persisted. **Fixed:** *An affiliate commission-kind change revalidates the stored value against the new kind's cap* — Updating an affiliate re-checked the commission value against the (possibly new) kind's cap only when the value itself was part of the same update. A kind-only change left the stored value untouched and unvalidated, so switching a flat per-order amount to a percentage kept a value that is legal as a flat amount (up to the large flat ceiling) but thousands of times over the 100%-basis-point percentage cap — producing a grossly inflated commission on the next order and an inflated pending payout. The update path now revalidates the effective commission value against the effective kind whenever either the kind or the value changes, rejecting an over-cap combination rather than persisting it.
14
+
11
15
  - v0.4.117 (2026-06-26) — **Weighted-average COGS reports reconstruct each sale's cost exactly, with no penny drift.** The weighted-average cost-of-goods-sold attribution stored a per-unit cost rounded to the nearest minor unit on each attribution row. The COGS reports (per-order and per-period) recover each line's cost as quantity times that per-unit price — there is no stored line total — so the residual attribution row, whose quantity does not always divide its cost evenly, reconstructed a total that differed from the cost the sale itself recorded, by up to one minor unit per multi-unit residual row. The drift could be a gain or a loss and accumulated across every weighted-average sale into the per-order profit figure and the period COGS report. Each attribution now splits a debit whose cost does not divide evenly into a floor-priced remainder and a ceil-priced sliver, so quantity times unit price summed over the rows reproduces the sale's cost exactly. FIFO and LIFO costing, and weighted-average debits that divide evenly, are unchanged. **Fixed:** *Weighted-average COGS attribution no longer drifts from the sale's recorded cost* — A weighted-average sale records the cost it consumes, then writes per-layer attribution rows the COGS reports sum back up. The attribution stored a single integer per-unit price, rounded — and because the reports reconstruct each row as quantity times that price (there is no stored line-total column), the residual-bearing row reconstructed quantity-times-rounded-unit, which differs from the attributed cost whenever the quantity does not divide it evenly (for example three units carrying a cost of 23 stored 8 per unit and reconstructed 24). A debit whose cost does not divide evenly across its units is now split into a floor-priced remainder and a ceil-priced sliver — (quantity minus r) units at the floor price and r units at the floor-plus-one price, where r is the leftover — so the rows reconstruct the attributed cost exactly. The per-order and per-period COGS reports now equal the cost each sale recorded; layer reversal restores the same quantity and value.
12
16
 
13
17
  - v0.4.116 (2026-06-26) — **Sales-tax filings net partial refunds and derive owed tax from the jurisdiction's default rate.** Two correctness fixes to the sales-tax filing computation. A partial refund leaves its order in a paid / fulfilling / shipped / delivered status (there is no partially-refunded state), so the order still counts toward the filing for the period it sold in — but the filing counted the order's FULL tax and subtotal, ignoring the refunded portion, which over-stated collected tax and the amount remitted to the authority. A partial refund recorded inside the filing window now reduces that order's contribution to net taxable sales and net collected tax, apportioned across tax and subtotal by the order's own grand-total structure. Separately, the owed re-derivation attributed every order to the first rate row covering its date regardless of category, so a reduced category rate (e.g. food) defined alongside the jurisdiction's default rate in the same window could be applied to all revenue — and which rate won depended on the database's row order, making the owed figure wrong and non-deterministic. The owed re-derivation now models the jurisdiction's default (category-less) rate only; the collected figure still reflects the actual per-order tax each order was charged at its own rate. **Fixed:** *Partial refunds are netted out of a sales-tax filing instead of counting full tax* — A partial refund is recorded as a same-status order transition that does not change the order's status, subtotal, or tax columns, so the order remained in the filing with its pre-refund tax and subtotal counted in full — over-stating collected tax and over-remitting. computeFiling now sums the partial refunds recorded inside the filing window per order and nets each order's taxable subtotal and collected tax by the refunded share, apportioned across tax and subtotal by the order's grand total (half-even, integer minor units). Refunds are attributed to the period they occur in; an order with no in-window refund is unaffected, and recomputing a filing is idempotent. · *Owed tax is re-derived from the jurisdiction's default rate, deterministically* — The owed re-derivation took the first tax-rate row covering an order's date with no regard for category. Because a category-specific rate (such as a reduced food rate) can legally coexist with the jurisdiction's default rate in the same window, whichever row the database returned first claimed all revenue — yielding a wrong owed figure that flipped with the database's row order. The re-derivation now selects only the jurisdiction's default (category-less) rate, so the owed figure is deterministic and modelled on the jurisdiction's primary rate. The collected figure is unchanged: it remains the sum of the actual tax each order was charged at its own category-aware rate at order time.
package/lib/affiliates.js CHANGED
@@ -607,7 +607,20 @@ function create(opts) {
607
607
  if (patch.payout_method != null) _set("payout_method", _payoutMethod(patch.payout_method));
608
608
  if (patch.payout_address != null) _set("payout_address", _payoutAddress(patch.payout_address));
609
609
  if (patch.commission_kind != null) _set("commission_kind", effectiveKind);
610
- if (patch.commission_value != null) _set("commission_value", _commissionValue(patch.commission_value, effectiveKind));
610
+ if (patch.commission_value != null) {
611
+ // Value supplied — validate against the effective kind and write it.
612
+ _set("commission_value", _commissionValue(patch.commission_value, effectiveKind));
613
+ } else if (patch.commission_kind != null) {
614
+ // Kind-only patch: re-validate the STORED value against the new kind
615
+ // so an over-cap combination is rejected (a flat amount, legal up to
616
+ // the 1e11 flat cap, switched to percent_bps where the cap is 10000 =
617
+ // 100%, throws here instead of silently persisting a runaway
618
+ // multiplier). Do NOT add commission_value to the SET list: writing
619
+ // the read-before value would clobber a concurrent value-only update,
620
+ // and when the (re)validation passes the stored value is already
621
+ // correct for the new kind, so it is left untouched.
622
+ _commissionValue(Number(current.commission_value), effectiveKind);
623
+ }
611
624
  if (patch.attribution_window_days != null) _set("attribution_window_days", _attributionWindow(patch.attribution_window_days));
612
625
 
613
626
  var ts = _now();
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.117",
2
+ "version": "0.4.119",
3
3
  "assets": {
4
4
  "css/admin.css": {
5
5
  "integrity": "sha384-imfe0otYErcB8rr2h6KLSGTtStirysptpXETSPY4zLv3bZoIT75Lo1dOvkOav+xL",
@@ -3,8 +3,8 @@
3
3
  "_about": "blamejs.shop vendors a single framework — blamejs — which itself bundles every server-side crypto/identity dependency. The transitive packages blamejs ships are surfaced in its own MANIFEST.json at lib/vendor/blamejs/lib/vendor/MANIFEST.json — Trivy / Grype rely on that nested data for CVE attribution.",
4
4
  "packages": {
5
5
  "blamejs": {
6
- "version": "0.15.30",
7
- "tag": "v0.15.30",
6
+ "version": "0.15.32",
7
+ "tag": "v0.15.32",
8
8
  "license": "Apache-2.0",
9
9
  "author": "blamejs contributors",
10
10
  "source": "https://github.com/blamejs/blamejs",
@@ -853,6 +853,8 @@
853
853
  "release-notes/v0.15.29.json": "lib/vendor/blamejs/release-notes/v0.15.29.json",
854
854
  "release-notes/v0.15.3.json": "lib/vendor/blamejs/release-notes/v0.15.3.json",
855
855
  "release-notes/v0.15.30.json": "lib/vendor/blamejs/release-notes/v0.15.30.json",
856
+ "release-notes/v0.15.31.json": "lib/vendor/blamejs/release-notes/v0.15.31.json",
857
+ "release-notes/v0.15.32.json": "lib/vendor/blamejs/release-notes/v0.15.32.json",
856
858
  "release-notes/v0.15.4.json": "lib/vendor/blamejs/release-notes/v0.15.4.json",
857
859
  "release-notes/v0.15.5.json": "lib/vendor/blamejs/release-notes/v0.15.5.json",
858
860
  "release-notes/v0.15.6.json": "lib/vendor/blamejs/release-notes/v0.15.6.json",
@@ -1561,7 +1563,7 @@
1561
1563
  ".npmrc": "sha256:66f104e7d07c496d2d0409988225e8c0e4ceb8d247dbcac3be75b2128d20ce66",
1562
1564
  ".pinact.yaml": "sha256:0213ffda55961dc49b64c0a5dfa3c0567419633b1499d57eaf7c8d842d7da6c7",
1563
1565
  "ARCHITECTURE.md": "sha256:9b1c8d2b1b7a41838eb348b0a008e4b4369718fd72bfe2974b37155f7536d35b",
1564
- "CHANGELOG.md": "sha256:321082dca053d6850267b324eef441e8e5e8b0127e513338c94a926565719eb7",
1566
+ "CHANGELOG.md": "sha256:e0c814dcd62903cdd67f2a50fa6667692956a4695459b9178ac239257be847ff",
1565
1567
  "CODE_OF_CONDUCT.md": "sha256:148a281960fff7c2fe6554dab66da572c72245ddeb00b0d14811558397bff386",
1566
1568
  "CONTRIBUTING.md": "sha256:bb4dbdbc8598da31dbce653a8ed322e08ff46560173f2eb67a4d684653948332",
1567
1569
  "GOVERNANCE.md": "sha256:906df6afb1f552b27b9acb50f7f96c47b917a2f1021cd4e987dbf4ee0e0a821b",
@@ -1571,7 +1573,7 @@
1571
1573
  "NOTICE": "sha256:f487fa47a11aca0f89e2615cdd3c713e9842abf7a30d8d328eeeae1c864aa774",
1572
1574
  "README.md": "sha256:3ddcc197b003da0b02db8bdd1aef1e943c94f7eab613c633d6a45bb11d0a80e9",
1573
1575
  "SECURITY.md": "sha256:77d8c2bcc04b425a08ef30e51204cebddbd48954b028a259c7a8412dbfeab40a",
1574
- "api-snapshot.json": "sha256:4eaa496230e6d8b721d189857ab24f9d8a806f5289058ef139a057fe02f011d9",
1576
+ "api-snapshot.json": "sha256:27f70007fec3539fc97ecef20f681e774a0dec98d359be47de412e3f19d5c1cf",
1575
1577
  "assets/BlameJS_Logo.png": "sha256:3c65699753c771b48ef9ac7f45bb40815ec19a23afcdd0cd30ef4601bbbe293e",
1576
1578
  "assets/BlameJS_Logo.svg": "sha256:dda44f3fb1343d5de9db6b1fcdb75fc649c57e7a99a8e8239fcf852e3841e1a8",
1577
1579
  "bench/README.md": "sha256:74202f2507fd840bfc1ac6c681975d9273cf36cca6e0f72655f138337304033c",
@@ -1800,7 +1802,7 @@
1800
1802
  "lib/ai-quota.js": "sha256:e7ff0fe6c5dae2925e2320a1df7be046052ce9f34597c662e72eb9c226034c56",
1801
1803
  "lib/api-key.js": "sha256:6ee664481b53ac1bc99a8a4bac67d887b11089e889048d559a1bcfda112302ae",
1802
1804
  "lib/api-snapshot.js": "sha256:bade91aa53b6a08c4c1a33c4331140d9cab92be505e637d89ebd2ca0d40656ec",
1803
- "lib/app-shutdown.js": "sha256:dae7e0f4d17d1560c3a45643d7ee7f1dbc94f86e7ea13e61649f226a2f1baf35",
1805
+ "lib/app-shutdown.js": "sha256:b1c9efb97109c55d894ba530f61bda8a2b43c7cbfec21c32a7085b98a91e8eca",
1804
1806
  "lib/app.js": "sha256:a3633c332d36dc6ffa6bd9aa4ff25361aaf9a78a2b1e70ca7dd8f379787191e6",
1805
1807
  "lib/archive-adapters.js": "sha256:05d9205294fd642eb9bcac6ffe5f518ca3baf5428cb72713bc220f42748f3f2d",
1806
1808
  "lib/archive-entry-policy.js": "sha256:0dff89f8a2bdedc971fd3db2fcee9925c646b5c3d20f86936606e2f54d436f71",
@@ -1810,7 +1812,7 @@
1810
1812
  "lib/archive-tar.js": "sha256:2723c58ca3dd715a15082cba958e2a4fb335356de3f3405faf23f72026f79c4d",
1811
1813
  "lib/archive-wrap.js": "sha256:57e075cdf386b0eea63bdbfeb856ca82931d3b6d410dfde2c6dfc595c2dbb56a",
1812
1814
  "lib/archive.js": "sha256:2a84b85215c32a2ee13bd60da26457923dc75a11aa79fb90c8979f8d556d2610",
1813
- "lib/arg-parser.js": "sha256:3e5cc51bfe22152cefbae0c646d2c90a7e62cc76930d75468a82a8ec6a3e4b67",
1815
+ "lib/arg-parser.js": "sha256:7f3b4dfad9869a7146582d162e50b6d39a7c0137b741e1bb98a45f5b06ffa50a",
1814
1816
  "lib/argon2-builtin.js": "sha256:9cf301aa88c321051804bdb957cb50b53fd8c800e0f403c0d21b61011e03f314",
1815
1817
  "lib/asn1-der.js": "sha256:bf085ff06bc12f08207ee641e63373ea2bf20e3dd52b8f6f026fd67700cf905c",
1816
1818
  "lib/asyncapi-bindings.js": "sha256:2f6f3d7ea836d0483e40f08b8202d78bae54bdc3d76b5faf57d918e699ebeac8",
@@ -1835,7 +1837,7 @@
1835
1837
  "lib/auth/dpop.js": "sha256:13d2098e2042dbab61baa93ec371b45357e691576232f08f6e99ea67fa64e200",
1836
1838
  "lib/auth/elevation-grant.js": "sha256:cf1c498359073a29d0192f390fe767731025d7cd65c5272298ae06ee10e307e7",
1837
1839
  "lib/auth/fal.js": "sha256:aabf6d8095dd41dcda8a2efdb48e00e95bffe70c78991c93fbef827816918692",
1838
- "lib/auth/fido-mds3.js": "sha256:c511f91a19a3d30e3f48af1e9949e5d1f7a9f4cbc39b68faa15f068a54f29a91",
1840
+ "lib/auth/fido-mds3.js": "sha256:dcb4962707596d2070826a181dc8088ed69cc7c0968037d2f69bf7f77e4d77b7",
1839
1841
  "lib/auth/jar.js": "sha256:f333f25a87b8c60f5f19c51d68aefba8ae8ed304e0f54a957560475359e11f7f",
1840
1842
  "lib/auth/jwt-external.js": "sha256:4b0073de3b72e3215534613781f16892736b4433f7c9b4fd9f5968a106bb777d",
1841
1843
  "lib/auth/jwt.js": "sha256:e018192d8578c4b1d549fdb14a93852d153f75ecf0443301d6227b6fd72d7d31",
@@ -1872,7 +1874,7 @@
1872
1874
  "lib/canonical-json.js": "sha256:3546e2a4b7692c4f8c77602e8804a8aaebff29b2183329588578c1d6f0df3eee",
1873
1875
  "lib/cbor.js": "sha256:9b6a6799b10646e50705f9e0839fbac2f3b8df6e41943170c15e308215936feb",
1874
1876
  "lib/cdn-cache-control.js": "sha256:adc1e5bdc59492de9e2f688dbee279a0ab09166adba609c4b5355028c4d3e0d1",
1875
- "lib/cert.js": "sha256:f93bfc6e4b87da8ffcf9c1e2f1ba5fb0942122c36b2a0766b06c215d0f59b45c",
1877
+ "lib/cert.js": "sha256:df21cf9f041877e593259637133db8acfe0eec1f43846003330ca7e6263afff7",
1876
1878
  "lib/chain-writer.js": "sha256:e067e2ecf1088a0fb334f512b95025685bff9a3fc648b3522d20f33ad0e613a2",
1877
1879
  "lib/circuit-breaker.js": "sha256:54244401ef17e588341176cece113b39f42c55ac3cfefe8f46b5172835b26f8c",
1878
1880
  "lib/cli-helpers.js": "sha256:ab292718a0076b66c32fc4a19a8150c25030cb7cff4bef9363612c29cb66f119",
@@ -1894,7 +1896,7 @@
1894
1896
  "lib/compliance-sanctions-fetcher.js": "sha256:1808b4852ced7894d1ba2998526366b52ef8a27489b80b9a915283cff0a11307",
1895
1897
  "lib/compliance-sanctions-fuzzy.js": "sha256:ec6f76fc40a245ff40a36f59a4d81ac8f20c16c2dbf9462e5c78bb58cbe36ba4",
1896
1898
  "lib/compliance-sanctions.js": "sha256:abbbe986a780af36209bde9aade1371acb4e7008dee0979e4fec1ca367ea65cd",
1897
- "lib/compliance.js": "sha256:4d9219a60f659131358129cf017677b60e1cb3585b330e369846f15cc09bc0c6",
1899
+ "lib/compliance.js": "sha256:0e9c7b80da7090519e62defceaff57f432bf0974b0bda708acec014631b5214a",
1898
1900
  "lib/config-drift.js": "sha256:45d68d319fd4a8e28a60260d317b734ac5ee558273c04243f2b95eed50220385",
1899
1901
  "lib/config.js": "sha256:07e20539293e9e365690addc902bc623e213c5ece972dc5b72199b375a17e66d",
1900
1902
  "lib/consent.js": "sha256:7a101c997ad040a2845648670b866425935c3cda96a48dc678723ca2cd20f76d",
@@ -1921,7 +1923,7 @@
1921
1923
  "lib/db-collection.js": "sha256:cb7aa1f7cf6b7f6bd86829cc54fc27999e97422370283b258e54e4c05c6f0388",
1922
1924
  "lib/db-declare-row-policy.js": "sha256:aea911d30d8d4108f2f83fd7d91c5f5a3c5be3f1ecec18be4674d7f1f8b39666",
1923
1925
  "lib/db-declare-view.js": "sha256:82371eb03f635877f6541850edb617905722c3bcf60f592f28b2d57d44a50bff",
1924
- "lib/db-file-lifecycle.js": "sha256:610538b8a6e7ad140b92f257c58fc0c2fdc97d3ef8c002d43987b97fbfd971c0",
1926
+ "lib/db-file-lifecycle.js": "sha256:c2dfbe775869da8828f7898b9f97b6977ba39eecdf1ce0827c200cb28850577c",
1925
1927
  "lib/db-query.js": "sha256:1f8ebfb7481b58cacb689fe87e92cb5574bd42f2665da5f4d27febb1010e45f2",
1926
1928
  "lib/db-role-context.js": "sha256:fa97dd17a8de1278a76a01d7979c3c847295c5efe1f14ab90701ca0e98f9bd3b",
1927
1929
  "lib/db-schema.js": "sha256:5aaef5f61f16dc0913225cb3f134e9ef6d05bf917d15ea35360c8cfbb81de1cd",
@@ -1989,7 +1991,7 @@
1989
1991
  "lib/guard-json.js": "sha256:8727e9bda5cf1944b785a4e7f10b3df51aadc9d53209ce0da23b12e6e2edf8ff",
1990
1992
  "lib/guard-jsonpath.js": "sha256:b06c6250d9d824f8dd1db695657afd4b15f2db22e87f26067da1f3caeabdd4f3",
1991
1993
  "lib/guard-jwt.js": "sha256:5348c509f57822d29ece205826da25959f09dcc5f2491b8c48a950fe01649724",
1992
- "lib/guard-list-id.js": "sha256:c4f201c50d99fe3ab2cd75db9fee4f1bd32b2f1129720d18746a59a00215f503",
1994
+ "lib/guard-list-id.js": "sha256:02bba26038c5229ac0064b4bdcb8efa4dcec6aa29f637ee98a19bc5f481fb226",
1993
1995
  "lib/guard-list-unsubscribe.js": "sha256:4c839812b872bbf98444242875c3305105d1442075b2b2a21bf4aabf720d7772",
1994
1996
  "lib/guard-mail-compose.js": "sha256:5abe96c9df302209727d88c64ff83b4c9ad8aa7a2e4f7592c605d32601f46c14",
1995
1997
  "lib/guard-mail-move.js": "sha256:3b78b77c18398006c475b1f805ba5ada7f91b8193519ab8f855554601fa0102e",
@@ -2059,7 +2061,7 @@
2059
2061
  "lib/log-stream-syslog.js": "sha256:da9648a760c35a609a673419ead08b3f21daf72749ef6365f0c40256304e9bfc",
2060
2062
  "lib/log-stream-webhook.js": "sha256:aaec51da99ec52b62c217ff589558bdb73f7f1439a2d1f750e4ee1826be99a19",
2061
2063
  "lib/log-stream.js": "sha256:9ffda79044835670fba447876b617b1d5cef0592abf08b52167e2ae7b6bcdba7",
2062
- "lib/log.js": "sha256:c95d8850a1c7064be20a1aab448078375643410f5cc9a6bb22cd2598811f9bc6",
2064
+ "lib/log.js": "sha256:8268967e7f45bf59b131ba7e651b48a15656850f5585efe98001af4406c7c495",
2063
2065
  "lib/lro.js": "sha256:da9baf47f27c422c32d51495b2896c887ec3ac283875712efcd7528fd396868b",
2064
2066
  "lib/mail-agent.js": "sha256:4ca93408c03873796d285d002067ffa54fa70859ad5437215d6001d90c0f76ea",
2065
2067
  "lib/mail-arc-sign.js": "sha256:872f31e16321f8629e3af3cac31d3deb460804748b25a7fbb427164a2ad88f84",
@@ -2067,7 +2069,7 @@
2067
2069
  "lib/mail-auth.js": "sha256:da409e8f2b3bae1308f1d008b6df27f468075a83ceaeae8ee7cda0ab9c4a1027",
2068
2070
  "lib/mail-bimi.js": "sha256:b22afc5e8ad04503fee7e41f3b34c05c9f0759579436bfbe5f7706180c466155",
2069
2071
  "lib/mail-bounce.js": "sha256:461e885dd2ea0664e9a4af39ae5c36f3a5d9c7dc914dad4c40641b705952fd04",
2070
- "lib/mail-crypto-pgp.js": "sha256:045f1f3a2eea809e5f22fa00d7e8fe99f688b1356341cf7819ad2fe30729a4b6",
2072
+ "lib/mail-crypto-pgp.js": "sha256:5339f6e0b03d76add7cba04c0435c8281fbea5ce2dcba23d585f4fdfd86e73b1",
2071
2073
  "lib/mail-crypto-smime.js": "sha256:066d0456f385c470000dd05dcf978592c369298675dd8a25263592c8f15c7934",
2072
2074
  "lib/mail-crypto.js": "sha256:19d5f2a9fe6376d2ed6b0153a944dd1d05d8b893172bad173e2df9733c0dafd3",
2073
2075
  "lib/mail-dav.js": "sha256:3bfe6d8c0fa1c0a5d56d78a07778de932828eeebd6e091d2f988ebeaabcf451a",
@@ -2080,7 +2082,7 @@
2080
2082
  "lib/mail-rbl.js": "sha256:3888306741149c2acee9cd84b4f0c2a044d9299cf4b4fe6475c2b1159af6eaa7",
2081
2083
  "lib/mail-require-tls.js": "sha256:1480690ff0f31fa3ab587c17c03fb396649b24cf684aedc6bd0b51e3092529c9",
2082
2084
  "lib/mail-scan.js": "sha256:ce42e49a1f8995bb0563d8282362da1effd1890c40ce0eda83370e923acf2b21",
2083
- "lib/mail-send-deliver.js": "sha256:f3f05259abfdb70f6cb68992e94874cd5b6bca891b9e12588056003d34aab28c",
2085
+ "lib/mail-send-deliver.js": "sha256:8cd0f7272d376cd495858dd8f4ba165555c8595d87273d16aba3370bc846da9d",
2084
2086
  "lib/mail-server-imap.js": "sha256:6ede51283422fac53070483beb33bcf42329fe87824a5154c697c70662b9813f",
2085
2087
  "lib/mail-server-jmap.js": "sha256:2aa4064807f4039b04bbc69aaf1431a95136db50474802591c29c4e1cdb39ae7",
2086
2088
  "lib/mail-server-managesieve.js": "sha256:1e8ab81d79a3aacc364bc1b4bda1567584872674770b58f828eafd2867afa9de",
@@ -2153,7 +2155,7 @@
2153
2155
  "lib/middleware/scim-server.js": "sha256:2f38d77c7e949df9e219e4b351a6eb047dc85a7337aca932ac4b598e953c471d",
2154
2156
  "lib/middleware/security-headers.js": "sha256:87506e572454033b33955d58d9f0cbfdc14edc63c1cbe7e01f0dc4f07753c64d",
2155
2157
  "lib/middleware/security-txt.js": "sha256:3140d6537aa09a7052d83088840358949dd36393697fa2215f8f915d31aaa112",
2156
- "lib/middleware/span-http-server.js": "sha256:6ab5561e779192fedf4d25e708240a38457164096912e11b528b87b10dc8a2b7",
2158
+ "lib/middleware/span-http-server.js": "sha256:8f22bf4feb5f05a201da20b89425f0e904ed6368c91b2fcd1be9a0f5cb067696",
2157
2159
  "lib/middleware/speculation-rules.js": "sha256:ca102beebd529ec42244d9d709d3c4cb9dd27669bc324bb707ea508294250b63",
2158
2160
  "lib/middleware/sse.js": "sha256:d0094cb33fced9bc748edd1f6d55fba6029ff3bff6da735d72ad5d10806f02f7",
2159
2161
  "lib/middleware/trace-log-correlation.js": "sha256:f6a36bfcb666cee5434867b1841092793491491bb9da7bc92626dd93cfa7b0d0",
@@ -2169,13 +2171,13 @@
2169
2171
  "lib/mtls-engine-default.js": "sha256:bb2e47de5b8a6fa03f433c4c8ecc02fc7e7841f9eff97a36e7a4379880c81cd7",
2170
2172
  "lib/network-byte-quota.js": "sha256:79eafbf602a10ced990d89cfd26bea3992e42b177407b8e512bd4790999d17e3",
2171
2173
  "lib/network-dane.js": "sha256:1f60a47618bd99a487d6c2faad423546896f753e33b516bdbc354138438d06f8",
2172
- "lib/network-dns-resolver.js": "sha256:3fd08605a077a8de1c15b3f436d2c2910af77e0078d27ce9d62fdce2e5949b7e",
2174
+ "lib/network-dns-resolver.js": "sha256:057b796e94875e8c87a7875bd1e2a9d8ee2e5456cb4f1f1aa9b801f6a1cbe163",
2173
2175
  "lib/network-dns.js": "sha256:ff986202f7800719efe32d6f49900432783eb40a21185f4e8acdb7da45ba4139",
2174
2176
  "lib/network-dnssec.js": "sha256:2b0758d09d5af89b49c69892fabaebabba125c3f9f162996c12b9abbbbae4bca",
2175
2177
  "lib/network-heartbeat.js": "sha256:f14ec7fa4667cbb04477d91ae59d6253b6c9ccc43071bc558e737cc672f43975",
2176
2178
  "lib/network-nts.js": "sha256:90ce47584ad58c171e8029eb1d5df8ba4ba130c4038d1fe260109685c34c8f75",
2177
2179
  "lib/network-proxy.js": "sha256:9342c534863f905000d2432b67c26dd7a5ea02be25aaf9ebe05976079e61d4c9",
2178
- "lib/network-smtp-policy.js": "sha256:a7b6c91983b312a16c436d3e15259f8f6a84579e2e55f64106285b22795e1510",
2180
+ "lib/network-smtp-policy.js": "sha256:95927f23bb7cafaf716dee869bb89d1a3d84bc307167365d08c12e9db9c50370",
2179
2181
  "lib/network-tls.js": "sha256:554b64c77389e1c961da8d061c63c806be994960466fb26f2b2268dbec0a369b",
2180
2182
  "lib/network-tsig.js": "sha256:41dc85a6e816cce1c58c36891dfc6bafc612b68978936107a09a5a553563e0d7",
2181
2183
  "lib/network.js": "sha256:40cb01627bf45741cac22ef79f5985f69323e47732d54cf3ce28c06882830c25",
@@ -2217,7 +2219,7 @@
2217
2219
  "lib/pick.js": "sha256:944653b8fe5e4f5d32bc24ace79e4855f11d7f6fb2e0b766c2063f743d038340",
2218
2220
  "lib/pipl-cn.js": "sha256:6f72e6860ae7dfa8271bc54c104c29b8188845075347aae3fd9485e1807df67c",
2219
2221
  "lib/pqc-agent.js": "sha256:47319cf87a43d93ded8e8b4e3e52d156c8d57b0053fe6ca53ed2b8b8d8259c40",
2220
- "lib/pqc-gate.js": "sha256:18a8c7bc9f01a5ce2a87f22474e4601ae0ad99d4264f0f7c5c85e5ca0c2c68c2",
2222
+ "lib/pqc-gate.js": "sha256:1892ed9ee4140a7ceefca3d68408038620c1402b819784b90fffed0b02a88c13",
2221
2223
  "lib/pqc-software.js": "sha256:0945fa073cbf3683b6e524a868155eb966285d7409432cef6de85f7b1a9290e7",
2222
2224
  "lib/privacy-pass.js": "sha256:18114f2f5d853ff3a129245f3a4cdec187f2efc8abab814ee595fd811c56e80f",
2223
2225
  "lib/privacy.js": "sha256:7d391638832c19320e5f8218266645e39df327288c2483ecb0d8353475251976",
@@ -2284,7 +2286,7 @@
2284
2286
  "lib/sse.js": "sha256:5e4fca52ddb92ab1c13ad7434c6b13d5c4ef0f7fc3845b003f8c3603c1629dbf",
2285
2287
  "lib/ssrf-guard.js": "sha256:65d3d1bf6841064cdf9b9e7ffb5a0a3ac9358e462943f5de09087640353dfac4",
2286
2288
  "lib/standard-webhooks.js": "sha256:e604534d48202a41f2c9f6954a990731db80d0693794d3a80f371f843490ff57",
2287
- "lib/static.js": "sha256:6ece120d7220d4122781ccf14dfc73e32641dfb831e6e32589484fbdb9249710",
2289
+ "lib/static.js": "sha256:09b63860428beef3117d7b8e59954857eb27a612064f4f083a597c0428865341",
2288
2290
  "lib/storage.js": "sha256:0f6d4acbcd6709e0117ae2a7d9f8cea250ea4f28da8d1acdc2bbd3dea46b2668",
2289
2291
  "lib/stream-throttle.js": "sha256:abb1743b4b28c93a5c930a170b4fcc11ba45c5902193913512eab58675a09b2a",
2290
2292
  "lib/structured-fields.js": "sha256:2d1d1fbe6e94ef84df3647ef4a0945622eb5418b183347e53eb10b5a7833d7ea",
@@ -2302,7 +2304,7 @@
2302
2304
  "lib/uri-template.js": "sha256:9b7252fce4a8245ee1ad51b3ce4dadd42d08080ddded961dafad2330a1a6cd8d",
2303
2305
  "lib/uuid.js": "sha256:5b4c2b1880a66a52adb529afce679888a033f17dd284c190419f7e496803d182",
2304
2306
  "lib/validate-opts.js": "sha256:259ab2788dc408608bd34ef9153d7453b3bfdf1065856bd7c55fe735d8b64e59",
2305
- "lib/vault-aad.js": "sha256:22d3bc6cfa1b5e66d6e6ab3188edaf0a527b7222070e25582853cc002cb35e13",
2307
+ "lib/vault-aad.js": "sha256:05b73dbba2f1582f847faababd19c5b25ecb917f11f6a59529f49c6c13b58431",
2306
2308
  "lib/vault/index.js": "sha256:e18a9bd284fc3a680d4cb3f2cebb63e8b22f2216b99ab7d3bdc1d4823d713564",
2307
2309
  "lib/vault/passphrase-ops.js": "sha256:8a2fe8aa4af4d5302832815c41cfdafa5bef03b5221044a415981b6530daa154",
2308
2310
  "lib/vault/passphrase-source.js": "sha256:fe2ddd5816078434feffb9fcb42142517fd3f2e8512f9bce655b0c1ce76f1846",
@@ -2310,7 +2312,7 @@
2310
2312
  "lib/vault/seal-pem-file.js": "sha256:0eaaa19af92cea39a5849591c71a7c3c0be81c4c463f133dcfa365f6efe3480a",
2311
2313
  "lib/vault/wrap.js": "sha256:5d8d748c6eb603c018a9f5f5581f4d3270da34ea69dee6d6ef0e5199c727b800",
2312
2314
  "lib/vc.js": "sha256:554cf29ea712b231256e1eaa75f6b1f1d39c392aac86b33e818d52e956ea83ae",
2313
- "lib/vendor-data.js": "sha256:ddb77b33d89a2c5345deca98eacb45695d4c8a70014bc5df22c7393f52e11416",
2315
+ "lib/vendor-data.js": "sha256:137b493f0eae8a94ac6722134624cc2041b38aa84717db9fbb6ac43078b16eee",
2314
2316
  "lib/vendor/.vendor-data-pubkey": "sha256:73a935b8c72f55d821c22ddb8188453bab324f80d229d033a0a1549819a1f096",
2315
2317
  "lib/vendor/MANIFEST.json": "sha256:c2b9e40fb993db0f92c49154a5ac078e01324ccd02a19985e38186da06dd7338",
2316
2318
  "lib/vendor/bimi-trust-anchors.data.js": "sha256:aa7a4d33b65a68422a2a2c1670177689f66fdcaa08bd2514d78798b827bd1608",
@@ -2326,7 +2328,7 @@
2326
2328
  "lib/vendor/simplewebauthn-server.cjs": "sha256:49411d893f5e9b0e2fcaa564b4ec7921f73a9a06229b5e53d49c1453ea1a365c",
2327
2329
  "lib/vendor/vendor-data-pubkey.js": "sha256:a12afa34cd7472e2eaebad2fcd44714102d3edd0601e45769404124a513926d0",
2328
2330
  "lib/vex.js": "sha256:bed6880980e7f166fd30bbddab57d7fa06085aaae0dc13ed82a17531b76878b7",
2329
- "lib/watcher.js": "sha256:8618da919affabbe4c4d33915647b3ee4b27e6ea091638f032d4bfead797baf8",
2331
+ "lib/watcher.js": "sha256:339213851f7293e7d2f67eadd230ad132cd9dd8bc7ee0d2be3af101bd06a6074",
2330
2332
  "lib/web-push-vapid.js": "sha256:54d7d4beb764681bc97dc338490fc2afc3996a032e080401830206814b8c97f0",
2331
2333
  "lib/webhook-dispatcher.js": "sha256:3a8517f6dd3bbc37c1fa2e5e23e81ce60b8488aec75ab4598ec57df743d186db",
2332
2334
  "lib/webhook.js": "sha256:0db5e3aed7b1f10eeeb15b5fd94424f2cb44e443fbfdc6029800ac89b2f8dad1",
@@ -2335,14 +2337,14 @@
2335
2337
  "lib/wiki-concepts.js": "sha256:54789790b950d926cbd0657dd51b32435c4988012b520819615df93673680de5",
2336
2338
  "lib/worker-pool.js": "sha256:fc35b3c9f83a4c72be0981400f5294b566472bfe187df0500775e37dd71afa4c",
2337
2339
  "lib/worm.js": "sha256:f17e24f8ca39295f396f40bd0c4ad1c946f5e41ff646ef1b33e8d50dc8b9435e",
2338
- "lib/ws-client.js": "sha256:66d615ad9d57099afd281e3b4f32666fc2981207b084e9eb46444d67b2a8fe19",
2340
+ "lib/ws-client.js": "sha256:4e66a26e94438c4d6bea31b69ada0f6fb14a8b1f8914b81607db493ae483e375",
2339
2341
  "lib/x509-chain.js": "sha256:fdc0ecd264d9de0e039f348e819675fdf3838d61097a97a0e3e119def338722a",
2340
2342
  "lib/xml-c14n.js": "sha256:01a27d20df99ebd6eaefbff0aad933a2231049f65a54ae994236694d4146ce5d",
2341
2343
  "oss-fuzz/projects/blamejs/Dockerfile": "sha256:6c48ec4d81f4b1836a6b43701a2497cf737002413dd0da6a10892be1498b0766",
2342
2344
  "oss-fuzz/projects/blamejs/README.md": "sha256:ae13b7bb79ed8d69b1b3276e5562807a0349fb6e6b7d11cf1f683aad1eafdb4b",
2343
2345
  "oss-fuzz/projects/blamejs/build.sh": "sha256:0ced1cf21782c97be7f8d74faf5e27a308b60b2f858836fb5ca3b8c4e939a8f7",
2344
2346
  "oss-fuzz/projects/blamejs/project.yaml": "sha256:59f2cb83aa622325a175b77416fe155be15b70a9c798bd1a78bba05763b1b03d",
2345
- "package.json": "sha256:c5301b20b46e0cf52bf9e7d936bb532a4655e1674fed66ea624a775121ba6ade",
2347
+ "package.json": "sha256:82e87499ed49c5957bc6c22da3dddd25c8ccb7d5bea1bb38cee2e7d8f6973e60",
2346
2348
  "release-notes/v0.0.x.json": "sha256:7a49819f30068ee119000cad7010194882bb8bfaa12acbdab4dfc066efb7982f",
2347
2349
  "release-notes/v0.1.x.json": "sha256:6742a8c17f947c5cb76f69dead7eea86b942d80621d914b774ba5488e09937e5",
2348
2350
  "release-notes/v0.10.x.json": "sha256:fe498045daf88337bd3d987e5964aa42c99a50e1685b6f09e51f698b8687726f",
@@ -2375,6 +2377,8 @@
2375
2377
  "release-notes/v0.15.29.json": "sha256:9e912ac2c32bb84bf1d89b8cc5f04d73a78d74a2ac1be24e42e6462f5135b4f5",
2376
2378
  "release-notes/v0.15.3.json": "sha256:19a0074c445545468ca3cc411b21ec8bdb27be2669ae1950347cc244f6aa348c",
2377
2379
  "release-notes/v0.15.30.json": "sha256:d40ce2c5ffb7ae5a2b501ea0f949bd29510d8544a5f588f136a37ce4981d50ad",
2380
+ "release-notes/v0.15.31.json": "sha256:f1d259dcee41503588b9c54060e56e436eb5fa7db502872a895c84ebb96627a9",
2381
+ "release-notes/v0.15.32.json": "sha256:af99a8a782dcc30aff3a135c27179b064d63181fc4c302c31da632c1615b68ce",
2378
2382
  "release-notes/v0.15.4.json": "sha256:6ac7fa0ef1728c27e71b2050d1b07a810f9b4b1440ccddbf28ad56e2f54d8585",
2379
2383
  "release-notes/v0.15.5.json": "sha256:cca1d0edd5d6fc41b512d19d98be224b990dcab41478622c11962f0fcb1bb09a",
2380
2384
  "release-notes/v0.15.6.json": "sha256:0e3b9e5e43b70b61dd258c3003d1b8729cd3c26c62a34dedcca81bbec5d31077",
@@ -2621,7 +2625,7 @@
2621
2625
  "test/layer-0-primitives/cluster-storage.test.js": "sha256:238b3b3db0eba3e6312a863710533178f566347b90e161e564481aa826707647",
2622
2626
  "test/layer-0-primitives/cluster-vault-rotation.test.js": "sha256:3514e9e71d6c39e805248f58ad2f41528d091e196c0f3766a032675677161b2d",
2623
2627
  "test/layer-0-primitives/cms-codec.test.js": "sha256:7e46078ed82be5b69d22c48f22dba37ea5015371c2a8cf5f94fb1a792fb7bb78",
2624
- "test/layer-0-primitives/codebase-patterns.test.js": "sha256:7f58d0fc1a47d2d085b6d88c282a672f6a94315a427594cfd12dc5e077a3796a",
2628
+ "test/layer-0-primitives/codebase-patterns.test.js": "sha256:6520fb495c58a1979fd8e47af2eb8a2daefc7d4eed93877b19c3477b288736ac",
2625
2629
  "test/layer-0-primitives/codepoint-class.test.js": "sha256:19d1b69efa7e0e9f7ef2392ca264167767a5ec5890ee339b2cfd8a7818035d27",
2626
2630
  "test/layer-0-primitives/compliance-ai-act.test.js": "sha256:5ee4ad05d12233cb3c5457ef10a727833710bbc1ce1318838f9f9ef5d2cb8d4b",
2627
2631
  "test/layer-0-primitives/compliance-cascade.test.js": "sha256:ee02cf14541a837a9d7977c6ea6bf7f9210bed293925d93c976e31f270aebec4",
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.15.x
10
10
 
11
+ - v0.15.32 (2026-06-26) — **Internal test-suite hardening only — the published library's runtime behavior and public API are unchanged (source-comment marker text aside).** Three more suppression classes in the codebase-patterns guard suite were re-verified from scratch and renamed to descriptive tokens, with their old names recorded as retired so the prior approval cannot be silently resurrected and their in-source marker comments updated to match. No runtime code, public API, or wire format changed. **Detectors:** *Three more suppression-marker classes re-verified and their tokens retired* — Continuing the re-verification pass: each marked site for the process-exit, hand-rolled buffer-collect, and raw-outbound-http guard classes was re-read and confirmed (operator-opt-in exits; bounded protocol-framing / TLV assembly; framework-routed outbound calls), then the class was renamed to a descriptive token and its old name added to the retired-token set. This is test-suite tooling; no shipped framework behavior changed.
12
+
13
+ - v0.15.31 (2026-06-26) — **Internal test-suite hardening only — the published library's runtime behavior and public API are unchanged (source-comment marker text aside).** The codebase-patterns guard suite gains a gate that retires a renamed suppression-marker token: once a suppression class is re-verified and renamed, its old token cannot be silently re-registered, so a later suppression has to be re-examined under the new name instead of inheriting a stale approval. Eight suppression classes were re-verified and renamed to descriptive tokens this release, and their in-source marker comments were updated to match. No runtime code, public API, or wire format changed. **Detectors:** *Re-verification gate retires a renamed suppression-marker token* — A new guard refuses to re-register a retired suppression-marker token, and the orphan check points a stale marker at its current name. When a suppression class is re-verified, it is renamed to a descriptive token and the old name is recorded as retired, so the old approval cannot be quietly resurrected — a future suppression must be re-examined and re-stamped under the new name. This is test-suite tooling; no shipped framework behavior changed.
14
+
11
15
  - v0.15.30 (2026-06-26) — **Importing the webhook module for inbound signature verification no longer pulls the Node HTTP client (or its `node:http` / `node:https` / `node:http2` chain), so `b.webhook.verify` runs in a Worker / edge runtime when the module is imported directly.** lib/webhook and its delivery store eagerly required the outbound HTTP client at module load — directly, and transitively through the persistence-backed dispatcher — so importing the webhook module threw in a Cloudflare Workers / edge runtime that has no node:http / node:net / node:tls, even though b.webhook.verify needs only HMAC-SHA-256 and a timing-safe compare. The HTTP client and the SQL-backed dispatcher are now resolved lazily, only on the outbound send / dispatch paths; importing the module for inbound verification loads no Node networking module. Verified by asserting no networking builtin appears in process.moduleLoadList on the verify path. The framework's aggregate entry point still initializes the networking stack eagerly, so the edge verify path is the webhook module imported directly rather than the aggregate b namespace. **Fixed:** *Inbound webhook verification loads without the Node networking stack (Worker / edge runtime)* — The webhook module and its dispatcher delivery store required the outbound HTTP client (node:http / node:https / node:http2) at module load, so importing the module threw in a runtime without those builtins — even though b.webhook.verify is HMAC-SHA-256 plus a timing-safe compare and the dispatcher (SQL-backed persistence) is only needed for outbound delivery. The HTTP client and the dispatcher are now resolved lazily, on the outbound send / dispatch paths only; the inbound verify path loads no Node networking module. An edge handler that imports the webhook module directly can pre-verify an inbound Stripe webhook signature with the primitive's hardening (Stripe-Signature size cap, v1-hex anti-DoS cap, minimum-tolerance floor, optional atomic replay store) instead of hand-rolling the t=/v1= parse, tolerance window, and timing-safe compare. The framework's aggregate entry still loads the networking stack eagerly; import the webhook module directly for the edge verify path.
12
16
 
13
17
  - v0.15.29 (2026-06-25) — **JSON parsing of operator and untrusted input is routed through `b.safeJson` everywhere, so a `"__proto__"` member is stripped and the parse is size-bounded — `b.openapi.parse`, `b.asyncapi.parse`, and `b.sandbox.run` were using a raw `JSON.parse`.** b.openapi.parse and b.asyncapi.parse parsed an operator-supplied document string with a raw JSON.parse, which re-creates a "__proto__" member as an own key and imposes no size bound; b.sandbox.run parsed the untrusted sandboxed code's serialized result the same way (proto member kept, depth unbounded). All three now route through b.safeJson, which strips the prototype-pollution keys and applies the depth / key / size caps. A new b.safeJson.parseStringOrObject primitive captures the recurring "accept a document as a JSON string OR a pre-built object" shape (a string is parsed safely, an object passes through) that openapi and asyncapi had each hand-rolled, with each consumer's typed error class and a generous document size cap passed as data. Valid input parses identically to before; a malicious or oversized document is now defused rather than accepted. **Added:** *b.safeJson.parseStringOrObject(input, opts?)* — Accepts either a JSON string — parsed through b.safeJson.parse, so the prototype-pollution-key strip and depth / key / size caps apply — or an already-decoded plain object (returned unchanged). For the recurring "operator hands me a document as a JSON string or a pre-built object" surface (b.openapi / b.asyncapi). The consumer's typed error class, error codes, label, and size cap are passed as options, so a raw JSON.parse on operator input cannot be hand-rolled per consumer. **Security:** *Operator and untrusted JSON parses strip __proto__ and bound size (no raw JSON.parse)* — A raw JSON.parse keeps a "__proto__" object member as an own key (a prototype-pollution gadget for any downstream merge) and parses unbounded input. b.openapi.parse and b.asyncapi.parse used it on operator document strings, and b.sandbox.run used it on the untrusted sandboxed code's JSON-serialized result. All now route through b.safeJson — the prototype-pollution keys are stripped and the depth / key / size caps apply (a 16 MiB document cap for openapi/asyncapi, the existing result-bytes cap for the sandbox). Internal JSON parses (the sealed DSR store payloads, the VEX canonical re-format) route through the same primitive too, so the guarantee cannot be bypassed one call site at a time. Valid input is unaffected; a "__proto__"-laden or oversized payload is defused.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 1,
3
- "frameworkVersion": "0.15.30",
4
- "createdAt": "2026-06-26T10:28:32.149Z",
3
+ "frameworkVersion": "0.15.32",
4
+ "createdAt": "2026-06-26T19:50:16.827Z",
5
5
  "exports": {
6
6
  "a2a": {
7
7
  "type": "object",
@@ -247,7 +247,7 @@ function create(opts) {
247
247
  var exitCode = (process.exitCode !== undefined && process.exitCode !== 0)
248
248
  ? process.exitCode : (allOk ? 0 : 1);
249
249
  setImmediate(function () {
250
- // allow:process-exit — operator opted into exitAfterPhases,
250
+ // allow:process-exit-operator-optin — operator opted into exitAfterPhases,
251
251
  // delegating process lifecycle to the orchestrator
252
252
  process.exit(exitCode);
253
253
  });
@@ -280,7 +280,7 @@ function create(opts) {
280
280
  // Bounded forced exit after the grace deadline, armed ONLY inside the
281
281
  // signal handler (operator opted into installSignalHandlers,
282
282
  // delegating process lifecycle to the orchestrator).
283
- // allow:process-exit — operator-delegated lifecycle, watchdog only
283
+ // allow:process-exit-operator-optin — operator-delegated lifecycle, watchdog only
284
284
  process.exit(process.exitCode || 1);
285
285
  }, graceMs + forceExitMarginMs);
286
286
  if (typeof watchdog.unref === "function") watchdog.unref();
@@ -550,7 +550,7 @@ function create(opts) {
550
550
  // `<prog> help [<cmd>]`
551
551
  var topic = (split.rest && split.rest.length > 0) ? split.rest[0] : null;
552
552
  var msg = topic && commandsByName[topic] ? help(topic) : help();
553
- if (exitOnHelp) { stdout.write(msg + "\n"); process.exit(0); } // allow:process-exit — explicit { exit: true } from a bin/ shim
553
+ if (exitOnHelp) { stdout.write(msg + "\n"); process.exit(0); } // allow:process-exit-operator-optin — explicit { exit: true } from a bin/ shim
554
554
  return { command: null, flags: {}, positionals: [], helpRequested: true, helpText: msg };
555
555
  }
556
556
 
@@ -582,7 +582,7 @@ function create(opts) {
582
582
 
583
583
  if (cmdParsed.helpRequested || preParsed.helpRequested) {
584
584
  var cmsg = _renderCommandHelp(parser, cmdEntry);
585
- if (exitOnHelp) { stdout.write(cmsg + "\n"); process.exit(0); } // allow:process-exit — explicit { exit: true } from a bin/ shim
585
+ if (exitOnHelp) { stdout.write(cmsg + "\n"); process.exit(0); } // allow:process-exit-operator-optin — explicit { exit: true } from a bin/ shim
586
586
  return { command: cmdEntry.name, flags: {}, positionals: [], helpRequested: true, helpText: cmsg };
587
587
  }
588
588
 
@@ -606,7 +606,7 @@ function create(opts) {
606
606
  // as a flag-only parser. Honor `--` and aggregate everything else.
607
607
  if (preParsed.helpRequested) {
608
608
  var hmsg = _renderHelp(parser);
609
- if (exitOnHelp) { stdout.write(hmsg + "\n"); process.exit(0); } // allow:process-exit — explicit { exit: true } from a bin/ shim
609
+ if (exitOnHelp) { stdout.write(hmsg + "\n"); process.exit(0); } // allow:process-exit-operator-optin — explicit { exit: true } from a bin/ shim
610
610
  return { command: null, flags: {}, positionals: [], helpRequested: true, helpText: hmsg };
611
611
  }
612
612
  _applyDefaultsAndRequired(parser.flags.list, preParsed.flags, "top-level");
@@ -420,7 +420,7 @@ function _verifyAndParseBlob(token) {
420
420
  * typeof blob.entries.length === "number";
421
421
  * // → true
422
422
  */
423
- async function fetch(opts) { // allow:raw-outbound-http — function name is fetch, internal call routes through b.httpClient
423
+ async function fetch(opts) { // allow:raw-outbound-http-framework-internal — function name is fetch, internal call routes through b.httpClient
424
424
  opts = opts || {};
425
425
  validateOpts(opts, ["url", "caCertificate", "force", "timeoutMs"], "auth.fido_mds3.fetch");
426
426
 
@@ -721,7 +721,7 @@ function create(opts) {
721
721
  var chain = _splitPemChain(ctx.cert);
722
722
  if (chain.length < 2) return; // no issuer in the served chain
723
723
  try {
724
- // allow:raw-outbound-http — b.network.tls.ocsp.fetch composes b.httpClient internally (SSRF guard + pinned DNS); not a raw outbound call
724
+ // allow:raw-outbound-http-framework-internal — b.network.tls.ocsp.fetch composes b.httpClient internally (SSRF guard + pinned DNS); not a raw outbound call
725
725
  var rv = await networkTls().ocsp.fetch({ leafPem: chain[0], issuerPem: chain[1] });
726
726
  ctx.ocspResponse = rv.ocspDer;
727
727
  _emitAudit("cert.ocsp.refreshed", "success", { name: name });
@@ -443,7 +443,7 @@ function set(posture) {
443
443
  // guaranteed). Pure signal — no behavior change.
444
444
  var REGULATED = ["hipaa", "pci-dss", "sox", "gdpr", "soc2", "fda-21cfr11"];
445
445
  if (REGULATED.indexOf(posture) !== -1) {
446
- var tz = process.env.TZ; // allow:raw-process-env — bootstrap signal, no operator-supplied default needed
446
+ var tz = process.env.TZ; // allow:raw-process-env-bootstrap — bootstrap signal, no operator-supplied default needed
447
447
  if (typeof tz === "string" && tz !== "UTC" && tz !== "Etc/UTC") {
448
448
  _emitAudit("compliance.posture.tz_warning",
449
449
  { posture: posture, tz: tz, recommendation: "Set TZ=UTC under regulated postures so audit timestamps align with regulator expectations." },
@@ -288,7 +288,7 @@ function fileLifecycle(opts) {
288
288
  "fileLifecycle.startFlushTimer: timer already running — call stop() first");
289
289
  }
290
290
  var interval = sopts.intervalMs || flushIntervalMs;
291
- encTimer = setInterval(function () { // allow:timer-no-unref — .unref() called immediately below; timer doesn't pin the event loop
291
+ encTimer = setInterval(function () { // allow:timer-no-unref-unrefed-below — .unref() called immediately below; timer doesn't pin the event loop
292
292
  try { flushNow(db); }
293
293
  catch (e) {
294
294
  _emitAudit("flush_failed", "failure", {
@@ -242,7 +242,7 @@ function validate(headerValue, opts) {
242
242
  // so a `localhost.` label-suffix is already refused at the segment-
243
243
  // shape level (an empty trailing segment fails the dot-atom-text
244
244
  // grammar). No trailing-dot bypass surface here.
245
- var isLocalScopeTld = lastLabel === "localhost" || lastLabel === "local" || lastLabel === "lan"; // allow:hostname-compare-trailing-dot — see comment above; List-Id parts already split on `.` so trailing-dot label is empty and refused upstream
245
+ var isLocalScopeTld = lastLabel === "localhost" || lastLabel === "local" || lastLabel === "lan"; // allow:hostname-compare-trailing-dot-pre-split-refused — see comment above; List-Id parts already split on `.` so trailing-dot label is empty and refused upstream
246
246
  if (caps.requireFqdn) {
247
247
  if (parts.length < 3 && !isLocalScopeTld) { // FQDN requires ≥ 3 labels for non-local-scope namespace
248
248
  return _refuse("list-id has < 3 labels for non-local-scope namespace (FQDN required under '" +
@@ -589,7 +589,7 @@ function _escapeC0Controls(s) {
589
589
  // runs before safeEnv on the boot path; safeEnv requires log, so log
590
590
  // can't go through safeEnv to read its own level.
591
591
  function _bootMinLevel() {
592
- // allow:raw-process-env — see header comment above
592
+ // allow:raw-process-env-bootstrap — see header comment above
593
593
  var raw = process.env.BLAMEJS_BOOT_LOG_LEVEL || process.env.LOG_LEVEL || "info";
594
594
  return Object.prototype.hasOwnProperty.call(LEVELS, raw) ? LEVELS[raw] : LEVELS.info;
595
595
  }
@@ -563,7 +563,7 @@ function sign(opts) {
563
563
  // signed-part bytes plus the armored signature.
564
564
  // MIME-boundary uniqueness only (not a security token); operator
565
565
  // key/cert material flows through createSign/verify, not this path.
566
- // allow:raw-randombytes-token — boundary string, not auth credential
566
+ // allow:raw-randombytes-token-mime-boundary — boundary string, not auth credential
567
567
  var boundary = "blamejs-pgp-" + nodeCrypto.randomBytes(12).toString("hex");
568
568
  // The OpenPGP signature covers the signed-part bytes exactly, so the
569
569
  // multipart/signed wrapper is assembled as a Buffer — a JS-string round
@@ -213,7 +213,7 @@ async function _applyMtaStsPolicy(domain, mxs, policyMode, auditEmit) {
213
213
  if (policyMode === "off") return mxs;
214
214
  var sts;
215
215
  try {
216
- sts = await smtpPolicy().mtaSts.fetch(domain); // allow:raw-outbound-http — method call on b.network.smtp.policy wrapper, not a raw `fetch(`
216
+ sts = await smtpPolicy().mtaSts.fetch(domain); // allow:raw-outbound-http-framework-internal — method call on b.network.smtp.policy wrapper, not a raw `fetch(`
217
217
  } catch (e) {
218
218
  if (policyMode === "enforce") {
219
219
  throw new DeliverError("deliver/mta-sts-fetch-failed",
@@ -72,7 +72,7 @@ function _scheme(req) {
72
72
  // client used (forwarded), NOT a Secure/HSTS/origin trust decision. Routing
73
73
  // through trustedProtocol would drop the forwarded scheme from spans behind a
74
74
  // proxy (less accurate telemetry) for no security gain.
75
- // allow:raw-xfp — telemetry label, not a trust sink (see above).
75
+ // allow:raw-xfp-telemetry-only — telemetry label, not a trust sink (see above).
76
76
  var x = req.headers && (req.headers["x-forwarded-proto"] || "");
77
77
  if (typeof x === "string" && x.length > 0) {
78
78
  var first = x.split(",")[0].trim().toLowerCase();
@@ -82,7 +82,7 @@ function _scheme(req) {
82
82
  }
83
83
 
84
84
  function _serverAddress(req) {
85
- // allow:raw-xfp — display-only: server.address span attribute (telemetry),
85
+ // allow:raw-xfp-telemetry-only — display-only: server.address span attribute (telemetry),
86
86
  // not an authority trust decision. Same rationale as _scheme above.
87
87
  var hostHeader = req.headers && (req.headers["x-forwarded-host"] || req.headers.host);
88
88
  if (typeof hostHeader === "string" && hostHeader.length > 0) {
@@ -488,7 +488,7 @@ async function _wireLookup(name, qtype, timeoutMs) {
488
488
  // Raw DoH wire-format request — bypasses b.httpClient envelope
489
489
  // because we need the raw binary response bytes for safeDns to
490
490
  // parse (httpClient assumes JSON/text shapes).
491
- var req = https.request({ // allow:raw-outbound-http — DoH wire-format response bytes; b.httpClient envelopes assume text/JSON, and httpClient → ssrfGuard → DNS → DoH would form a cycle
491
+ var req = https.request({ // allow:raw-outbound-http-framework-internal — DoH wire-format response bytes; b.httpClient envelopes assume text/JSON, and httpClient → ssrfGuard → DNS → DoH would form a cycle
492
492
  hostname: u.hostname,
493
493
  port: u.port || 443, // HTTPS port
494
494
  path: u.pathname + u.search,
@@ -660,7 +660,7 @@ async function tlsRptSubmit(report, opts) {
660
660
  try {
661
661
  if (/^https:\/\//i.test(uri)) {
662
662
  entry.kind = "https";
663
- // allow:raw-outbound-http — `client` is the framework httpClient
663
+ // allow:raw-outbound-http-framework-internal — `client` is the framework httpClient
664
664
  // (or operator-supplied test mock); SSRF + DNS-pin already
665
665
  // applied through the framework wrapper.
666
666
  var rv = await client.request({
@@ -232,7 +232,7 @@ function create(opts) {
232
232
  // each chunk to read the record-length prefix) before deciding
233
233
  // whether more data is needed. boundedChunkCollector is append-
234
234
  // only with no peek, doesn't fit.
235
- // allow:handrolled-buffer-collect — see comment above
235
+ // allow:handrolled-buffer-collect-bounded-framing — see comment above
236
236
  var buf = Buffer.concat(chunks);
237
237
  var recordLen = buf.readUInt16BE(3);
238
238
  var neededLen = 5 + recordLen;
@@ -1409,7 +1409,7 @@ function create(opts) {
1409
1409
  // pre-allocate a closure for every served byte. Tracked for extraction.
1410
1410
  var idleTimer = null;
1411
1411
  function resetIdleTimer() {
1412
- if (idleTimer) clearTimeout(idleTimer); // allow:handrolled-debounce — file-stream idle deadline
1412
+ if (idleTimer) clearTimeout(idleTimer); // allow:handrolled-debounce-stream-idle — file-stream idle deadline
1413
1413
  idleTimer = setTimeout(function () {
1414
1414
  try { fileStream.destroy(_err("IDLE_TIMEOUT", "client idle for " + maxIdleMs + "ms")); }
1415
1415
  catch (_) { /* stream already torn down */ }
@@ -1420,7 +1420,7 @@ function create(opts) {
1420
1420
 
1421
1421
  // Cancellation propagation: when the client disconnects mid-stream.
1422
1422
  function onClientClose() {
1423
- try { fileStream.destroy(); } catch (_) { /* allow:silent-catch — stream already torn down */ }
1423
+ try { fileStream.destroy(); } catch (_) { /* allow:silent-catch-stream-teardown — stream already torn down */ }
1424
1424
  releaseSlot();
1425
1425
  if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; }
1426
1426
  }
@@ -1442,7 +1442,7 @@ function create(opts) {
1442
1442
  error: e && e.message,
1443
1443
  }, actorCtx));
1444
1444
  }
1445
- try { res.destroy(e); } catch (_) { /* allow:silent-catch — response already torn down */ }
1445
+ try { res.destroy(e); } catch (_) { /* allow:silent-catch-stream-teardown — response already torn down */ }
1446
1446
  releaseSlot();
1447
1447
  if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; }
1448
1448
  });
@@ -99,7 +99,7 @@ function _canonicalize(parts) {
99
99
  valLenBuf.writeUInt32BE(valBuf.length);
100
100
  chunks.push(keyLenBuf, keyBuf, valLenBuf, valBuf);
101
101
  }
102
- return Buffer.concat(chunks); // allow:handrolled-buffer-collect — AAD canonicalization, bounded by length-prefixed field shape
102
+ return Buffer.concat(chunks); // allow:handrolled-buffer-collect-bounded-framing — AAD canonicalization, bounded by length-prefixed field shape
103
103
  }
104
104
 
105
105
  function buildColumnAad(opts) {
@@ -113,7 +113,7 @@ function _timingSafeHexEqual(a, b) {
113
113
  // length now guarantees equal byte length.
114
114
  var ba = Buffer.from(a, "utf8");
115
115
  var bb = Buffer.from(b, "utf8");
116
- return nodeCrypto.timingSafeEqual(ba, bb); // allow:raw-timing-safe-equal — hex + length pre-check above guarantees same-length ASCII inputs; b.crypto wrapper would be circular at boot
116
+ return nodeCrypto.timingSafeEqual(ba, bb); // allow:raw-timing-safe-equal-boot-prechecked — hex + length pre-check above guarantees same-length ASCII inputs; b.crypto wrapper would be circular at boot
117
117
  }
118
118
 
119
119
  // KNOWN_VENDOR_DATA — the canonical list of vendored data names. Each
@@ -528,7 +528,7 @@ function create(opts) {
528
528
  throw new WatcherError("watcher/start-failed",
529
529
  "watcher.create: initial poll walk failed: " + ((e && e.message) || String(e)));
530
530
  }
531
- pollTimer = setInterval(_pollTick, pollIntervalMs); // allow:timer-no-unref — .unref() called immediately below; timer doesn't pin the event loop
531
+ pollTimer = setInterval(_pollTick, pollIntervalMs); // allow:timer-no-unref-unrefed-below — .unref() called immediately below; timer doesn't pin the event loop
532
532
  if (typeof pollTimer.unref === "function") pollTimer.unref();
533
533
  } else {
534
534
  try {
@@ -492,7 +492,7 @@ class WsClient extends EventEmitter {
492
492
  }
493
493
 
494
494
  _consumeHandshake(chunk) {
495
- // allow:handrolled-buffer-collect — handshake header capped at 64 KiB below; once handshake parses we switch to FrameParser
495
+ // allow:handrolled-buffer-collect-bounded-framing — handshake header capped at 64 KiB below; once handshake parses we switch to FrameParser
496
496
  this._handshakeBuf = Buffer.concat([this._handshakeBuf, chunk]);
497
497
  var headerEnd = this._handshakeBuf.indexOf("\r\n\r\n");
498
498
  if (headerEnd === -1) {
@@ -737,7 +737,7 @@ class WsClient extends EventEmitter {
737
737
  return;
738
738
  }
739
739
  if (frame.fin) {
740
- var fullPayload = Buffer.concat(this._fragmentChunks); // allow:handrolled-buffer-collect — bounded by maxMessageBytes below
740
+ var fullPayload = Buffer.concat(this._fragmentChunks); // allow:handrolled-buffer-collect-bounded-framing — bounded by maxMessageBytes below
741
741
  if (safeBuffer.byteLengthOf(fullPayload) > this._opts.maxMessageBytes) {
742
742
  this._handleSocketError(new WsClientError("ws-client/message-too-big",
743
743
  "incoming message exceeds maxMessageBytes (" + this._opts.maxMessageBytes + ")"));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.15.30",
3
+ "version": "0.15.32",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "../scripts/release-notes-schema.json",
3
+ "version": "0.15.31",
4
+ "date": "2026-06-26",
5
+ "headline": "Internal test-suite hardening only — the published library's runtime behavior and public API are unchanged (source-comment marker text aside)",
6
+ "summary": "The codebase-patterns guard suite gains a gate that retires a renamed suppression-marker token: once a suppression class is re-verified and renamed, its old token cannot be silently re-registered, so a later suppression has to be re-examined under the new name instead of inheriting a stale approval. Eight suppression classes were re-verified and renamed to descriptive tokens this release, and their in-source marker comments were updated to match. No runtime code, public API, or wire format changed.",
7
+ "sections": [
8
+ {
9
+ "heading": "Detectors",
10
+ "items": [
11
+ {
12
+ "title": "Re-verification gate retires a renamed suppression-marker token",
13
+ "body": "A new guard refuses to re-register a retired suppression-marker token, and the orphan check points a stale marker at its current name. When a suppression class is re-verified, it is renamed to a descriptive token and the old name is recorded as retired, so the old approval cannot be quietly resurrected — a future suppression must be re-examined and re-stamped under the new name. This is test-suite tooling; no shipped framework behavior changed."
14
+ }
15
+ ]
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "../scripts/release-notes-schema.json",
3
+ "version": "0.15.32",
4
+ "date": "2026-06-26",
5
+ "headline": "Internal test-suite hardening only — the published library's runtime behavior and public API are unchanged (source-comment marker text aside)",
6
+ "summary": "Three more suppression classes in the codebase-patterns guard suite were re-verified from scratch and renamed to descriptive tokens, with their old names recorded as retired so the prior approval cannot be silently resurrected and their in-source marker comments updated to match. No runtime code, public API, or wire format changed.",
7
+ "sections": [
8
+ {
9
+ "heading": "Detectors",
10
+ "items": [
11
+ {
12
+ "title": "Three more suppression-marker classes re-verified and their tokens retired",
13
+ "body": "Continuing the re-verification pass: each marked site for the process-exit, hand-rolled buffer-collect, and raw-outbound-http guard classes was re-read and confirmed (operator-opt-in exits; bounded protocol-framing / TLV assembly; framework-routed outbound calls), then the class was renamed to a descriptive token and its old name added to the retired-token set. This is test-suite tooling; no shipped framework behavior changed."
14
+ }
15
+ ]
16
+ }
17
+ ]
18
+ }
@@ -309,9 +309,9 @@ var VALID_ALLOW_CLASSES = {
309
309
  "hand-rolled-sql": 1,
310
310
  "fs-path-from-operator-identifier-without-traversal-refusal": 1,
311
311
  "gitleaks-entropy": 1,
312
- "handrolled-buffer-collect": 1,
313
- "handrolled-debounce": 1,
314
- "hostname-compare-trailing-dot": 1,
312
+ "handrolled-buffer-collect-bounded-framing": 1,
313
+ "handrolled-debounce-stream-idle": 1,
314
+ "hostname-compare-trailing-dot-pre-split-refused": 1,
315
315
  "inline-numeric-bounds-cascade": 1,
316
316
  "inline-require": 1,
317
317
  "inline-require-non-empty-string-validation": 1,
@@ -322,24 +322,68 @@ var VALID_ALLOW_CLASSES = {
322
322
  "no-number-money-arithmetic": 1,
323
323
  "numeric-opt-Infinity": 1,
324
324
  "primitive-unreachable": 1,
325
- "process-exit": 1,
325
+ "process-exit-operator-optin": 1,
326
326
  "raw-byte-literal": 1,
327
327
  "raw-hash-compare": 1,
328
328
  "raw-new-url": 1,
329
- "raw-outbound-http": 1,
330
- "raw-process-env": 1,
331
- "raw-randombytes-token": 1,
329
+ "raw-outbound-http-framework-internal": 1,
330
+ "raw-process-env-bootstrap": 1,
331
+ "raw-randombytes-token-mime-boundary": 1,
332
332
  "raw-time-literal": 1,
333
- "raw-timing-safe-equal": 1,
334
- "raw-xfp": 1,
333
+ "raw-timing-safe-equal-boot-prechecked": 1,
334
+ "raw-xfp-telemetry-only": 1,
335
335
  "regex-no-length-cap": 1,
336
336
  "seal-without-aad": 1,
337
- "silent-catch": 1,
337
+ "silent-catch-stream-teardown": 1,
338
338
  "slsa-framework-action-not-sha-pinned": 1,
339
- "timer-no-unref": 1,
339
+ "timer-no-unref-unrefed-below": 1,
340
340
  "wildcard-suffix-match-without-single-label-check": 1,
341
341
  };
342
342
 
343
+ // Retired allow-class tokens — a class renamed during a re-verification pass so
344
+ // its OLD name can never be silently re-granted (operator 2026-06-26: "rename the
345
+ // per-class token so it doesn't get re-added as the same 'silent-catch'"). Key =
346
+ // retired token; value = where it went + the rule. Two protections below:
347
+ // (1) testNoRetiredAllowTokenReRegistered refuses re-adding a retired token to
348
+ // VALID_ALLOW_CLASSES (the old name cannot be quietly resurrected), and
349
+ // (2) testNoOrphanAllowClass reports a `// allow:<retired>` marker with a
350
+ // pointer to the current token instead of a generic "unregistered" note.
351
+ // To re-open a class for re-verification: rename its token in VALID_ALLOW_CLASSES
352
+ // AND its detector's _filterMarkers() arg, add the old token here, then re-stamp
353
+ // each surviving site under the NEW token only after re-checking it. Never reuse
354
+ // a retired name; never bulk find-replace the markers.
355
+ var RETIRED_ALLOW_TOKENS = {
356
+ "silent-catch": "renamed to 'silent-catch-stream-teardown' (2026-06-26 re-verify pass) — re-examine each empty-catch site before reusing; the old token is retired and must not be re-registered",
357
+ "raw-randombytes-token": "renamed to 'raw-randombytes-token-mime-boundary' (2026-06-26 re-verify pass) — the one site is a MIME boundary, not an auth credential; re-verify before reusing",
358
+ "raw-timing-safe-equal": "renamed to 'raw-timing-safe-equal-boot-prechecked' (2026-06-26 re-verify pass) — node timingSafeEqual used directly (b.crypto circular at boot) with a length pre-check; re-verify before reusing",
359
+ "handrolled-debounce": "renamed to 'handrolled-debounce-stream-idle' (2026-06-26 re-verify pass) — a single file-stream idle-deadline reset, not a reusable debounce; re-verify before reusing",
360
+ "hostname-compare-trailing-dot": "renamed to 'hostname-compare-trailing-dot-pre-split-refused' (2026-06-26 re-verify pass) — List-Id parts are pre-split on '.', so an empty trailing-dot label is refused upstream; re-verify before reusing",
361
+ "raw-xfp": "renamed to 'raw-xfp-telemetry-only' (2026-06-26 re-verify pass) — both sites use X-Forwarded-Proto as a telemetry/display label, not a trust sink; re-verify before reusing",
362
+ "raw-process-env": "renamed to 'raw-process-env-bootstrap' (2026-06-26 re-verify pass) — both sites read a bootstrap signal (TZ / log header) with no operator-supplied default needed; re-verify before reusing",
363
+ "timer-no-unref": "renamed to 'timer-no-unref-unrefed-below' (2026-06-26 re-verify pass) — both sites call .unref() immediately below, so the timer doesn't pin the event loop; re-verify before reusing",
364
+ "handrolled-buffer-collect": "renamed to 'handrolled-buffer-collect-bounded-framing' (2026-06-26 re-verify pass) — every site is a bounded protocol-framing / TLV-serialization assembly, not a collect-to-end stream; re-verify before reusing",
365
+ "process-exit": "renamed to 'process-exit-operator-optin' (2026-06-26 re-verify pass) — every site exits only on an explicit operator opt-in (exitAfterPhases / { exit: true } bin shim / watchdog); re-verify before reusing",
366
+ "raw-outbound-http": "renamed to 'raw-outbound-http-framework-internal' (2026-06-26 re-verify pass) — every site routes through b.httpClient / a framework wrapper, or is the documented DoH cycle exception; re-verify before reusing",
367
+ };
368
+
369
+ function testNoRetiredAllowTokenReRegistered() {
370
+ // A retired token must never reappear as a VALID_ALLOW_CLASSES key — that would
371
+ // silently re-grant the old blessing under its original name, defeating the
372
+ // rename-to-re-verify forcing function. Pick a fresh distinct token instead.
373
+ var bad = [];
374
+ Object.keys(RETIRED_ALLOW_TOKENS).forEach(function (tok) {
375
+ if (Object.prototype.hasOwnProperty.call(VALID_ALLOW_CLASSES, tok)) {
376
+ bad.push({
377
+ file: "test/layer-0-primitives/codebase-patterns.test.js",
378
+ line: 0,
379
+ content: "retired allow-token '" + tok + "' was re-registered in " +
380
+ "VALID_ALLOW_CLASSES — " + RETIRED_ALLOW_TOKENS[tok],
381
+ });
382
+ }
383
+ });
384
+ _report("no retired allow-token is re-registered in VALID_ALLOW_CLASSES", bad);
385
+ }
386
+
343
387
  function testNoOrphanAllowClass() {
344
388
  // scanScope: lib + test (every shipped + test source).
345
389
  var files = _libFiles().concat(_testFiles());
@@ -365,11 +409,14 @@ function testNoOrphanAllowClass() {
365
409
  while ((m = re.exec(comment)) !== null) {
366
410
  var cls = m[1];
367
411
  if (!Object.prototype.hasOwnProperty.call(VALID_ALLOW_CLASSES, cls)) {
412
+ var isRetired = Object.prototype.hasOwnProperty.call(RETIRED_ALLOW_TOKENS, cls);
368
413
  bad.push({
369
414
  file: rel,
370
415
  line: li + 1,
371
- content: "unregistered allow-class '" + cls + "' — names no detector " +
372
- "(fix the typo, or register it in VALID_ALLOW_CLASSES)",
416
+ content: isRetired
417
+ ? "retired allow-class '" + cls + "' " + RETIRED_ALLOW_TOKENS[cls]
418
+ : "unregistered allow-class '" + cls + "' — names no detector " +
419
+ "(fix the typo, or register it in VALID_ALLOW_CLASSES)",
373
420
  });
374
421
  }
375
422
  }
@@ -1653,7 +1700,7 @@ function testFormatValidatorLengthCap() {
1653
1700
 
1654
1701
  function testNoProcessExitInLib() {
1655
1702
  var matches = _scan(/\bprocess\.exit\(/);
1656
- matches = _filterMarkers(matches, "process-exit");
1703
+ matches = _filterMarkers(matches, "process-exit-operator-optin");
1657
1704
  _report("no process.exit() in lib/ (CLI surface only)", matches);
1658
1705
  }
1659
1706
 
@@ -1741,8 +1788,8 @@ function testNoSilentCatchSwallow() {
1741
1788
  // re-throw, log, or have an allow marker explaining why dropping
1742
1789
  // is correct (e.g., best-effort cleanup, audit-safe drops).
1743
1790
  var matches = _scan(/catch\s*\(\s*_\w*\s*\)\s*\{\s*\}/);
1744
- matches = _filterMarkers(matches, "silent-catch");
1745
- _report("empty catch(_e) {} blocks have an explicit silent-catch allow marker",
1791
+ matches = _filterMarkers(matches, "silent-catch-stream-teardown");
1792
+ _report("empty catch(_e) {} blocks have an explicit silent-catch-stream-teardown allow marker",
1746
1793
  matches);
1747
1794
  }
1748
1795
 
@@ -1800,7 +1847,7 @@ function testNoRawForwardedProtoHostRead() {
1800
1847
  var matches = _scan(/req\.headers\s*\[\s*["']x-forwarded-(?:proto|host)["']\s*\]/i);
1801
1848
  // request-helpers.js IS the canonical reader (the primitive home).
1802
1849
  matches = matches.filter(function (m) { return m.file !== "lib/request-helpers.js"; });
1803
- matches = _filterMarkers(matches, "raw-xfp");
1850
+ matches = _filterMarkers(matches, "raw-xfp-telemetry-only");
1804
1851
  _report("req.headers['x-forwarded-proto'|'x-forwarded-host'] routes through requestHelpers.trustedProtocol/trustedHost",
1805
1852
  matches);
1806
1853
  }
@@ -1836,7 +1883,7 @@ function testNoRawProcessEnv() {
1836
1883
  return m.file !== "lib/safe-env.js" &&
1837
1884
  m.file !== "lib/parsers/safe-env.js";
1838
1885
  });
1839
- matches = _filterMarkers(matches, "raw-process-env");
1886
+ matches = _filterMarkers(matches, "raw-process-env-bootstrap");
1840
1887
  _report("process.env reads route through safeEnv.readVar (or have allow marker)",
1841
1888
  matches);
1842
1889
  }
@@ -1854,7 +1901,7 @@ function testNoRawTimingSafeEqual() {
1854
1901
  var matches = _scan(/\bnodeCrypto\.timingSafeEqual\(/);
1855
1902
  // The framework crypto module is the canonical wrapper.
1856
1903
  matches = matches.filter(function (m) { return m.file !== "lib/crypto.js"; });
1857
- matches = _filterMarkers(matches, "raw-timing-safe-equal");
1904
+ matches = _filterMarkers(matches, "raw-timing-safe-equal-boot-prechecked");
1858
1905
  _report("crypto.timingSafeEqual routes through b.crypto.timingSafeEqual " +
1859
1906
  "(length-tolerant wrapper)",
1860
1907
  matches);
@@ -1970,7 +2017,7 @@ function testTimersUnref() {
1970
2017
  });
1971
2018
  }
1972
2019
  }
1973
- bad = _filterMarkers(bad, "timer-no-unref");
2020
+ bad = _filterMarkers(bad, "timer-no-unref-unrefed-below");
1974
2021
  _report("setInterval timers call .unref() (or have allow marker for " +
1975
2022
  "process-pinning intent)",
1976
2023
  bad);
@@ -1987,7 +2034,7 @@ function testNoRawRandomBytesToken() {
1987
2034
  var matches = _scan(/\b(nodeCrypto|crypto)\.randomBytes\([^)]+\)\s*\.\s*toString\s*\(/);
1988
2035
  // crypto.js itself wraps these.
1989
2036
  matches = matches.filter(function (m) { return m.file !== "lib/crypto.js"; });
1990
- matches = _filterMarkers(matches, "raw-randombytes-token");
2037
+ matches = _filterMarkers(matches, "raw-randombytes-token-mime-boundary");
1991
2038
  _report("nodeCrypto.randomBytes(n).toString routes through " +
1992
2039
  "b.crypto.generateToken / generateBytes (or has allow marker)",
1993
2040
  matches);
@@ -2027,7 +2074,7 @@ function testNoRawOutboundHttp() {
2027
2074
  "lib/testing.js": true,
2028
2075
  };
2029
2076
  matches = matches.filter(function (m) { return !exemptFiles[m.file]; });
2030
- matches = _filterMarkers(matches, "raw-outbound-http");
2077
+ matches = _filterMarkers(matches, "raw-outbound-http-framework-internal");
2031
2078
  _report("http(s).request / fetch route through b.httpClient (or have allow marker)",
2032
2079
  matches);
2033
2080
  }
@@ -2175,7 +2222,7 @@ function testNoHandrolledBufferCollect() {
2175
2222
  }
2176
2223
  }
2177
2224
  }
2178
- bad = _filterMarkers(bad, "handrolled-buffer-collect");
2225
+ bad = _filterMarkers(bad, "handrolled-buffer-collect-bounded-framing");
2179
2226
  _report("hand-rolled chunks-array buffer collect → use " +
2180
2227
  "safeBuffer.boundedChunkCollector (cap-bounded)",
2181
2228
  bad);
@@ -2215,7 +2262,7 @@ function testNoHandrolledDebounce() {
2215
2262
  }
2216
2263
  }
2217
2264
  }
2218
- bad = _filterMarkers(bad, "handrolled-debounce");
2265
+ bad = _filterMarkers(bad, "handrolled-debounce-stream-idle");
2219
2266
  _report("hand-rolled clearTimeout/setTimeout debounce → use " +
2220
2267
  "safeAsync.debounce (timer lifecycle owned)",
2221
2268
  bad);
@@ -10373,7 +10420,7 @@ function testHostnameCompareTrailingDotNormalize() {
10373
10420
  content: "hostname compared against reserved-name set without trailing-dot normalize — `localhost.` resolves to the same target as `localhost` (RFC 1034 §3.1); strip trailing dots BEFORE the equality check or attackers bypass the gate by appending a dot",
10374
10421
  });
10375
10422
  }
10376
- bad = _filterMarkers(bad, "hostname-compare-trailing-dot");
10423
+ bad = _filterMarkers(bad, "hostname-compare-trailing-dot-pre-split-refused");
10377
10424
  _report("reserved-hostname string-equality compare must strip trailing root-zone dot first (RFC 1034 §3.1; SSRF gate bypass class — guard-list-unsubscribe v0.10.7 finding)",
10378
10425
  bad);
10379
10426
  }
@@ -13125,6 +13172,7 @@ async function run() {
13125
13172
  testDenyPathComposesDenyResponse();
13126
13173
  testNoInternalNarrativeComments();
13127
13174
  testNoOrphanAllowClass();
13175
+ testNoRetiredAllowTokenReRegistered();
13128
13176
  testNoRawByteLiterals();
13129
13177
  testNoRawTimeLiterals();
13130
13178
  testNumericOptsValidate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.4.117",
3
+ "version": "0.4.119",
4
4
  "description": "Open-source framework built on blamejs. Vendored stack, zero npm runtime deps, PQC-first crypto, security-on by default.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {