@blamejs/blamejs-shop 0.4.82 → 0.4.83

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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.4.x
10
10
 
11
+ - v0.4.83 (2026-06-22) — **Downloading a subject-access export now requires the customer-write grant, not just read access.** Closes an access-control gap on the data-subject-request surface. Downloading a fulfilled subject-access export streams the customer's full personal-data bundle, but the download route was gated as a plain read, so a read-only operator (a viewer with no customers-write grant) could pull the entire PII bundle. The download now gates on the customers-write capability — the same grant fulfilling, dispatching, and erasing a request already require — so a read-only viewer is refused on the verb and the denial is recorded in the operator audit log, on both the API and browser paths. No configuration changes; upgrade to pick it up. **Security:** *Subject-access export download requires customers-write* — The DSR export-download route streams a customer's full PII bundle, so it is a customers-write-class capability like fulfilling, dispatching, or erasing a request — not a read. It now gates on customers-write on both the bearer-API and cookie/browser paths, so a read-only viewer can no longer download the bundle; the refused attempt is chained through the operator audit log like the other DSR write denials. Viewing the DSR queue (a read) is unchanged.
12
+
11
13
  - v0.4.82 (2026-06-22) — **Return-authorization and shipping-label state transitions are exactly-once under concurrency.** Two fulfillment surfaces gain the atomic-claim discipline the refund path already uses. Approving, receiving, and rejecting a return authorization now gate the transition on the observed status inside the UPDATE, so two operators (or a double-clicked action) acting on the same return can no longer both apply — exactly one transition wins and the other is refused, instead of a read-then-write letting both pass last-write-wins. Likewise, recording a shipping label as purchased, voided, or used is now a single guarded claim, so two concurrent broker callbacks for the same label can't both record (which previously left conflicting tracking numbers and costs last-write-wins). The losing caller gets the same transition-refused error a sequential illegal transition already produced. No configuration changes; upgrade to pick them up. **Fixed:** *Return-authorization transitions are atomic claims* — returns approve, markReceived, and reject now fold the expected source status into the UPDATE (approve from pending, markReceived from approved, reject from pending or approved) and treat the row count as the claim signal, matching the refund path. Two concurrent transitions on the same return — two operators, or a double-submit — can no longer both write last-write-wins; exactly one wins and the loser is refused with the same transition-refused error, mapped to a 409. Sequential not-found and illegal-transition errors are unchanged. · *Shipping-label transitions are atomic claims* — shipping-labels markPurchased, voidLabel, and markUsed now gate on the observed status inside the UPDATE. Two concurrent broker callbacks marking the same pending label purchased (a worker-drain double-fire) can no longer both record, leaving conflicting tracking numbers and costs by last-write-wins — exactly one records and the other is refused. The void 30-day window and not-found / wrong-status refusals are unchanged.
12
14
 
13
15
  - v0.4.81 (2026-06-22) — **Notification transitions are atomic, a missing insurance policy no longer crashes claim approval, and a returns summary no longer zeroes its refund totals under a status filter.** Three correctness fixes on the fulfillment surface. Notification status transitions (mark-sent / mark-failed / mark-read) are now single atomic claims that fold the expected status into the write, so two concurrent delivery callbacks can't both stamp the row last-write-wins, a mark-failed can't clobber a mark-sent, and two concurrent mark-failed calls each count once (the retry counter is incremented in the database rather than read-then-written). Approving a shipping-insurance claim whose parent policy row is missing now returns a clear typed error instead of dereferencing null and throwing an opaque crash. And the operator returns summary builds its refund-value totals from a status-free base filter, so supplying a status filter (e.g. to view rejected returns) no longer collapses the refunded and pending payout totals to zero through a self-contradictory query. No configuration changes; upgrade to pick them up. **Fixed:** *Notification status transitions are atomic claims* — mark-sent, mark-failed, and mark-read now gate the transition on the observed status inside the UPDATE and treat the row count as the claim signal, instead of checking status in a prior read and writing unconditionally. Concurrent delivery callbacks therefore can't both transition one notification last-write-wins, a mark-failed can't overwrite a mark-sent, and the retry counter is incremented in the database (count + 1) so two simultaneous failures can't lose an increment. mark-sent now applies only to a pending notification: a failed delivery is a durable failure record (the scheduler only re-dispatches pending notifications), so a stale or duplicate success callback can no longer flip a failed notification to sent and clear its error — retrying a failed notification is an explicit re-queue. · *Approving an insurance claim with a missing parent policy fails cleanly* — shipping-insurance claim approval now refuses with a typed not-found error when the claim's parent insurance row can't be loaded, matching the null-guard the rest of the module already applies, instead of dereferencing a null row while computing the payout ceiling. · *A returns summary keeps its refund totals when a status filter is applied* — The operator returns summary computes its refunded and pending payout totals from a base filter scoped only to the date window, so they pin their own status independently of the caller's status filter. Previously, supplying a status filter grafted a second, conflicting status predicate onto the payout queries (for example status = 'rejected' AND status = 'refunded'), silently zeroing both rollups; the per-status counts were always correct and remain so.
package/lib/admin.js CHANGED
@@ -5682,8 +5682,16 @@ function mount(router, deps) {
5682
5682
  // streams header-first; status can't change mid-stream). The route
5683
5683
  // re-reads the readers rather than re-running fulfillRequest (which would
5684
5684
  // re-flip status on every download).
5685
+ // Downloading the export streams the customer's full PII bundle, so it is
5686
+ // a customers.write-class capability like the rest of the DSR surface
5687
+ // (fulfill / dispatch / delete) — NOT a plain read. Wrapping the JSON
5688
+ // handler in W("customer.dsr_download", ...) gates BOTH paths: the bearer
5689
+ // JSON path via _wrap's permission check, and the cookie/browser path via
5690
+ // _pageOrApi reading the _adminWriteAction tag. A read-only viewer is
5691
+ // refused on the verb (and the denial is chained through the operator audit
5692
+ // log) rather than able to exfiltrate a customer's PII.
5685
5693
  router.get("/admin/dsr/:id/export.json", _pageOrApi(true,
5686
- R(async function (req, res) {
5694
+ W("customer.dsr_download", async function (req, res) {
5687
5695
  var row = await _dsrOr404(req, res, true); if (!row) return;
5688
5696
  if (row.request_kind !== "export" || (row.status !== "fulfilled" && row.status !== "delivered")) {
5689
5697
  return _problem(res, 404, "dsr-export-not-ready");
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.82",
2
+ "version": "0.4.83",
3
3
  "assets": {
4
4
  "css/admin.css": {
5
5
  "integrity": "sha384-imfe0otYErcB8rr2h6KLSGTtStirysptpXETSPY4zLv3bZoIT75Lo1dOvkOav+xL",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.4.82",
3
+ "version": "0.4.83",
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": {