@blamejs/blamejs-shop 0.4.49 → 0.4.51

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 (54) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/asset-manifest.json +1 -1
  3. package/lib/storefront.js +145 -0
  4. package/lib/vendor/MANIFEST.json +58 -46
  5. package/lib/vendor/blamejs/.github/workflows/ci.yml +134 -1
  6. package/lib/vendor/blamejs/.gitignore +5 -1
  7. package/lib/vendor/blamejs/CHANGELOG.md +4 -0
  8. package/lib/vendor/blamejs/README.md +1 -1
  9. package/lib/vendor/blamejs/SECURITY.md +3 -1
  10. package/lib/vendor/blamejs/api-snapshot.json +10 -2
  11. package/lib/vendor/blamejs/lib/bundler.js +2 -7
  12. package/lib/vendor/blamejs/lib/config-drift.js +17 -3
  13. package/lib/vendor/blamejs/lib/crypto-field.js +30 -0
  14. package/lib/vendor/blamejs/lib/db-declare-row-policy.js +20 -1
  15. package/lib/vendor/blamejs/lib/db-schema.js +29 -0
  16. package/lib/vendor/blamejs/lib/db.js +7 -0
  17. package/lib/vendor/blamejs/lib/guard-csv.js +13 -4
  18. package/lib/vendor/blamejs/lib/local-db-thin.js +23 -1
  19. package/lib/vendor/blamejs/lib/mail-bimi.js +16 -3
  20. package/lib/vendor/blamejs/lib/mail-scan.js +2 -5
  21. package/lib/vendor/blamejs/lib/mail.js +16 -9
  22. package/lib/vendor/blamejs/lib/mcp.js +28 -6
  23. package/lib/vendor/blamejs/lib/middleware/bot-disclose.js +7 -5
  24. package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +6 -4
  25. package/lib/vendor/blamejs/lib/numeric-bounds.js +32 -0
  26. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +12 -1
  27. package/lib/vendor/blamejs/lib/object-store/gcs.js +12 -1
  28. package/lib/vendor/blamejs/lib/object-store/http-put.js +11 -1
  29. package/lib/vendor/blamejs/lib/object-store/index.js +4 -0
  30. package/lib/vendor/blamejs/lib/object-store/local.js +11 -1
  31. package/lib/vendor/blamejs/lib/object-store/sigv4.js +86 -5
  32. package/lib/vendor/blamejs/lib/parsers/safe-env.js +6 -3
  33. package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +6 -6
  34. package/lib/vendor/blamejs/lib/safe-buffer.js +69 -1
  35. package/lib/vendor/blamejs/lib/safe-decompress.js +3 -12
  36. package/lib/vendor/blamejs/lib/seeders.js +33 -39
  37. package/lib/vendor/blamejs/lib/storage.js +71 -7
  38. package/lib/vendor/blamejs/lib/vault/rotate.js +4 -13
  39. package/lib/vendor/blamejs/package.json +1 -1
  40. package/lib/vendor/blamejs/release-notes/v0.15.10.json +53 -0
  41. package/lib/vendor/blamejs/release-notes/v0.15.11.json +52 -0
  42. package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +90 -16
  43. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +150 -39
  44. package/lib/vendor/blamejs/test/layer-0-primitives/config-drift.test.js +19 -0
  45. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-aad-downgrade.test.js +96 -0
  46. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-transaction.test.js +110 -0
  47. package/lib/vendor/blamejs/test/layer-0-primitives/declare-row-policy.test.js +43 -1
  48. package/lib/vendor/blamejs/test/layer-0-primitives/local-db-thin.test.js +28 -0
  49. package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +25 -0
  50. package/lib/vendor/blamejs/test/layer-0-primitives/numeric-bounds.test.js +29 -0
  51. package/lib/vendor/blamejs/test/layer-0-primitives/object-store-versioned-delete.test.js +97 -0
  52. package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer-linear-scans.test.js +94 -0
  53. package/lib/vendor/blamejs/test/layer-5-integration/bundler-output.test.js +52 -0
  54. 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.51 (2026-06-14) — **The order page now shows a dated activity timeline of the order's lifecycle.** A customer viewing their order now sees a chronological activity feed of what has happened to it — placed, payment received, shipped, delivered, cancelled, and refunds — newest first, each with its date and the relevant detail. A shipped event shows the carrier and tracking number when one was recorded; a refund shows its amount (and marks a partial refund as such); a click-and-collect delivery shows the pickup location. The feed is built from the order's own transition history and is scoped to the order being viewed, so it appears only to the order's owner or a guest holding the order's access link. Internal fulfillment bookkeeping and operator-process detail are not surfaced — only customer-meaningful milestones. The order page renders this server-side with no client JavaScript. No migration to apply. **Added:** *Order activity timeline on the order page* — The order detail page renders a dated, newest-first timeline of the order's lifecycle events: placed, payment received, shipped (with carrier and tracking number when present), delivered (with pickup location for click-and-collect), cancelled, and refunds (with the refunded amount, labelled partial when applicable). Events are drawn from the order's transition history and filtered to customer-meaningful milestones — internal fulfillment steps, raw state transitions, operator-process reasons, and payment-provider identifiers are never shown. Every rendered value is HTML-escaped, and the feed is shown only within the order's existing access scope (the signed-in owner or a guest order's access link).
12
+
13
+ - v0.4.50 (2026-06-14) — **Refresh the vendored blamejs framework to 0.15.11.** Refreshes the vendored blamejs framework from 0.15.9 to 0.15.11 (through 0.15.10). 0.15.11 replaces a family of quadratic-time regexes that hostile input could exploit to stall a worker with linear-time scans, refuses a relocatable sealed-cell downgrade on the read side, fails closed when row-level security is enabled behind a non-native driver, and verifies the vendored crypto against a reviewed pin. 0.15.10 makes S3 Object-Lock versioned erasure reachable through the object store — a versioned delete that targets a specific object version (refused under an active retention rather than silently writing a delete-marker), plus version enumeration for right-to-erasure workflows — and pins the build toolchain's native bundler binary to a reviewed hash. This refresh carries no shop-facing API change and applies no migration; it keeps the bundled framework current and the security posture aligned with the latest release. **Changed:** *Vendored blamejs refreshed to 0.15.11* — The bundled framework is updated to blamejs 0.15.11. Across 0.15.10 and 0.15.11 it hardens a family of regular expressions against quadratic-time blow-up on hostile input (linear-time scans), refuses a relocatable sealed-cell downgrade when reading, fails closed when row-level security is enabled behind a driver that can't enforce it, adds versioned object erasure for S3 Object-Lock buckets (a versioned delete that refuses an active retention instead of leaving the data behind a delete-marker, plus version enumeration), and pins the build toolchain's native binary to a reviewed hash. Storefront and admin behaviour is unchanged by the refresh; the framework's PQC-first crypto, security middleware, and request lifecycle are carried forward as-is.
14
+
11
15
  - v0.4.49 (2026-06-14) — **A stock write-off or audit adjustment can no longer strand a paid hold by dropping on-hand below what's reserved.** An operator stock write-off and a cycle-count audit adjustment both debit on-hand stock through the location adjustment, which guarded only against the shelf going below zero — not below the quantity already reserved by outstanding held allocations. So a write-off (or an audit-applied negative variance) could drop a location's on-hand below its outstanding holds, and when one of those holds later committed at fulfillment its debit would fail, stranding a paid order that could no longer be picked. Write-offs and audit adjustments now refuse a debit that would push on-hand below the held quantity for that SKU at that location (un-pinned holds count against every location), enforced inside the write so concurrent debits can't slip past it. A hold's own commit debit is unaffected — that stock is already reserved to it. No migration to apply. **Fixed:** *Stock write-offs and audit adjustments respect reserved holds* — The location stock adjustment now takes a hold-respecting mode used by write-offs and audit variance application: a debit is refused when it would drop on-hand below the outstanding held quantity for the SKU at that location, evaluated atomically as part of the write (un-pinned holds count against every location, matching the availability rule). Previously these operator adjustments only prevented on-hand from going negative, so they could shrink the shelf below what paid/committed holds had reserved — and the hold's later commit would then fail at fulfillment. Committing a hold still debits normally, since that stock is already reserved to it.
12
16
 
13
17
  - v0.4.48 (2026-06-14) — **Hardening: pickup scheduling integrity, the save-for-later CSRF token, a store-credit expiry race, and gift-card ledger verification.** A batch of correctness and security hardening. A click-and-collect pickup already marked ready could be silently un-readied by re-scheduling it, which the pickup state machine has no transition for; re-scheduling in place is now restricted to a pending pickup. The pickup capacity gate counted bookings and then inserted in two steps, so two concurrent checkouts could over-book a full time slot; the cap is now enforced inside the write atomically. The authenticated Save-for-later cart action (POST /cart/lines/:line_id/save) inherited the edge cart forms' CSRF exemption by a path-prefix accident and shipped without requiring a token; it now demands the double-submit CSRF token like any other authenticated mutation, while the genuinely token-less edge cart forms stay exempt. Two concurrent store-credit expiry sweeps could over-burn still-valid credit; the sweep is now atomic and idempotent. And the gift-card ledger's chain verification accepted a populated ledger whose hash columns had all been cleared — a full-ledger rewrite read as verified; an unanchored populated chain now fails verification, while a genuinely empty ledger still passes. No migration to apply. **Fixed:** *A ready pickup can't be un-readied by re-scheduling* — Re-scheduling a click-and-collect pickup in place is now allowed only while it is still scheduled. A pickup already marked ready (its goods on the hold shelf) no longer regresses to scheduled and loses its ready timestamp when re-scheduled — the pickup state machine has no ready-to-scheduled transition, so the operator completes or escalates a ready pickup instead. · *Pickup time slots can't be over-booked under concurrency* — The pickup capacity limit is now enforced inside the booking write as a single conditional insert gated on the live count for the time slot, so two checkouts booking the same nearly-full slot at once can't both slip past the limit. Previously the count and the insert were separate steps, leaving a window where concurrent bookings over-filled a slot. · *Concurrent store-credit expiry sweeps can't over-burn valid credit* — The store-credit expiry sweep now burns expired credit atomically: the amount to expire is computed inside the write and conditioned on the credit still being unexpired, so a second sweep running at the same time finds nothing left to burn and can't dip into still-valid balance. Previously the sweep read the expirable total and then wrote in separate steps, so two concurrent sweeps could double-burn. **Security:** *The save-for-later cart action now requires its CSRF token* — POST /cart/lines/:line_id/save is a login-required cart mutation rendered only on the session-bound cart page, but it sat under the /cart/lines path prefix that exempts the cookie-less, token-less edge cart forms from CSRF — so it inherited that exemption and accepted state changes without a double-submit token. The exemption now carves this authenticated path back into CSRF protection from a single source shared by the request guard and the form renderer (so the set the guard enforces and the set the renderer tokenizes can't drift), while the legitimate edge cart forms remain exempt. · *A hash-cleared gift-card ledger no longer verifies as valid* — The gift-card ledger's chain verification reported a populated ledger whose hash-chain columns had all been nulled as verified — so an attacker who rewrote balances and cleared the chain hashes could pass verification undetected. A populated ledger with no chain anchor now fails verification; a genuinely empty ledger (no rows) still passes.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.49",
2
+ "version": "0.4.51",
3
3
  "assets": {
4
4
  "css/admin.css": {
5
5
  "integrity": "sha384-imfe0otYErcB8rr2h6KLSGTtStirysptpXETSPY4zLv3bZoIT75Lo1dOvkOav+xL",
package/lib/storefront.js CHANGED
@@ -7589,6 +7589,7 @@ var ORDER_PAGE =
7589
7589
  " </div>\n" +
7590
7590
  " <aside class=\"order-page__totals\">\n" +
7591
7591
  " RAW_ORDER_TIMELINE" +
7592
+ " RAW_ORDER_EVENTS" +
7592
7593
  " RAW_ORDER_TRACKING" +
7593
7594
  " <h2 class=\"pdp__variants-title\">Totals</h2>\n" +
7594
7595
  " <dl class=\"totals-list\">\n" +
@@ -7717,6 +7718,135 @@ function _orderTimelineBlock(status) {
7717
7718
  "<ol class=\"order-timeline__steps\">" + steps + "</ol></div>";
7718
7719
  }
7719
7720
 
7721
+ // The customer-facing rich event timeline reads the order's own
7722
+ // transition ledger (order.get() hydrates `order.transitions` from
7723
+ // `order_transitions`, oldest-first). Each row carries an `on_event`,
7724
+ // an `occurred_at`, and a `metadata_json` blob; this block flattens the
7725
+ // ledger into a readable, dated, chronological feed — placed, paid,
7726
+ // shipped, delivered, refunded (with amounts), cancelled — that the
7727
+ // customer sees alongside the status rail.
7728
+ //
7729
+ // Customer-meaningful events only. The FSM also records mid-fulfillment
7730
+ // bookkeeping (`start_fulfillment` — the warehouse picking the parcel)
7731
+ // and the `__init__` placeholder edge; neither is something a customer
7732
+ // acts on, so both stay out of the feed. The raw `from_state →
7733
+ // to_state` string, the internal `reason` (operator-process strings
7734
+ // like `admin:console` / `stale-pending-reap`), and the rest of the
7735
+ // metadata blob are NEVER rendered — only the per-event detail the
7736
+ // customer cares about (a refund amount, a tracking number) is pulled
7737
+ // out, by key, and escaped at the sink.
7738
+ var ORDER_EVENT_LABELS = {
7739
+ "create": "Order placed",
7740
+ "mark_paid": "Payment received",
7741
+ "mark_shipped": "Order shipped",
7742
+ "mark_delivered": "Order delivered",
7743
+ "cancel": "Order cancelled",
7744
+ // `refund` covers both a partial refund (a same-state self-loop row)
7745
+ // and the terminal balance-clearing refund; the amount in metadata
7746
+ // distinguishes them in the detail line.
7747
+ "refund": "Refund issued",
7748
+ };
7749
+
7750
+ // Events the customer sees on their own order page. Everything not in
7751
+ // this set (start_fulfillment, any future internal-only FSM edge) is
7752
+ // dropped from the customer feed.
7753
+ var ORDER_EVENT_CUSTOMER_VISIBLE = {
7754
+ "create": true,
7755
+ "mark_paid": true,
7756
+ "mark_shipped": true,
7757
+ "mark_delivered": true,
7758
+ "cancel": true,
7759
+ "refund": true,
7760
+ };
7761
+
7762
+ // Format an order-transition `occurred_at` (epoch ms) the same way the
7763
+ // shipment timeline does — "YYYY-MM-DD HH:MM" UTC — so the two feeds
7764
+ // read consistently. Returns "" for an absent/garbage timestamp.
7765
+ function _orderEventWhen(occurredAt) {
7766
+ if (occurredAt == null) return "";
7767
+ var n = Number(occurredAt);
7768
+ if (!isFinite(n)) return "";
7769
+ return new Date(n).toISOString().slice(0, 16).replace("T", " ");
7770
+ }
7771
+
7772
+ // Build the per-event customer-facing detail line from the transition's
7773
+ // metadata — by key, never the raw blob. A refund row surfaces its
7774
+ // amount (formatted in the order currency); a shipped row surfaces a
7775
+ // carrier + tracking number when the metadata carried one. Everything
7776
+ // returned here is escaped by the caller at the render sink. Returns ""
7777
+ // when there's no customer-relevant detail for the event.
7778
+ function _orderEventDetail(onEvent, meta, currency) {
7779
+ if (!meta || typeof meta !== "object") return "";
7780
+ if (onEvent === "refund") {
7781
+ var amt = meta.amount_minor;
7782
+ if (Number.isInteger(amt) && amt > 0) {
7783
+ var label = meta.partial === true ? "Partial refund: " : "Amount: ";
7784
+ return label + pricing.format(amt, currency || "USD");
7785
+ }
7786
+ return "";
7787
+ }
7788
+ if (onEvent === "mark_shipped") {
7789
+ var parts = [];
7790
+ if (meta.carrier) parts.push(String(meta.carrier));
7791
+ if (meta.tracking_number) parts.push("Tracking " + String(meta.tracking_number));
7792
+ return parts.join(" · ");
7793
+ }
7794
+ if (onEvent === "mark_delivered") {
7795
+ // A click-and-collect delivery stamps a pickup location into the
7796
+ // metadata; surface it as the delivered-detail when present.
7797
+ if (meta.pickup_location_code) return "Picked up at " + String(meta.pickup_location_code);
7798
+ return "";
7799
+ }
7800
+ return "";
7801
+ }
7802
+
7803
+ // Render the rich, dated, chronological event timeline from the order's
7804
+ // transition ledger. Newest-first. Each event is a label + a UTC
7805
+ // timestamp + an optional customer-relevant detail (refund amount,
7806
+ // tracking number, pickup location). Internal-only transitions
7807
+ // (start_fulfillment, the __init__ placeholder, unknown future events)
7808
+ // are filtered out. Every data-derived value — label, detail, time — is
7809
+ // HTML-escaped at the sink, matching the escape-by-default convention of
7810
+ // the rest of this file. Returns "" when the order carries no
7811
+ // customer-visible transitions (the status rail above still renders), so
7812
+ // a brand-new order with a single placed event still shows that event.
7813
+ function _orderEventTimelineBlock(o) {
7814
+ if (!o || !Array.isArray(o.transitions) || !o.transitions.length) return "";
7815
+ var esc = b.template.escapeHtml;
7816
+ var currency = o.currency || "USD";
7817
+ var events = [];
7818
+ for (var i = 0; i < o.transitions.length; i += 1) {
7819
+ var t = o.transitions[i];
7820
+ if (!t || !ORDER_EVENT_CUSTOMER_VISIBLE[t.on_event]) continue;
7821
+ var meta = {};
7822
+ try { meta = JSON.parse(t.metadata_json || "{}"); }
7823
+ catch (_e) { meta = {}; } // drop-silent — bad JSON falls through to no-detail
7824
+ events.push({
7825
+ label: ORDER_EVENT_LABELS[t.on_event] || t.on_event,
7826
+ occurred_at: Number(t.occurred_at) || 0,
7827
+ detail: _orderEventDetail(t.on_event, meta, currency),
7828
+ });
7829
+ }
7830
+ if (!events.length) return "";
7831
+ // Newest-first for display. The ledger arrives oldest-first; sort a
7832
+ // copy descending by occurred_at, stable on the original order for ties
7833
+ // (same-ms events keep ledger sequence, reversed) so the feed is
7834
+ // deterministic across runs.
7835
+ events.reverse();
7836
+ events.sort(function (a, c) { return c.occurred_at - a.occurred_at; });
7837
+ var rows = events.map(function (e) {
7838
+ var when = _orderEventWhen(e.occurred_at);
7839
+ return "<li class=\"order-events__event\">" +
7840
+ "<span class=\"order-events__label\">" + esc(e.label) + "</span>" +
7841
+ (e.detail ? " <span class=\"order-events__detail\">" + esc(e.detail) + "</span>" : "") +
7842
+ (when ? " <time class=\"order-events__when\" datetime=\"" + esc(when) + "\">" + esc(when) + "</time>" : "") +
7843
+ "</li>";
7844
+ }).join("");
7845
+ return "<div class=\"order-events\">" +
7846
+ "<h2 class=\"pdp__variants-title\">Order activity</h2>" +
7847
+ "<ol class=\"order-events__list\">" + rows + "</ol></div>";
7848
+ }
7849
+
7720
7850
  // Cap on how many carrier events the per-shipment timeline renders. A
7721
7851
  // long-haul international parcel can accumulate dozens of scans; the
7722
7852
  // customer-facing panel shows the most recent MAX_SHIPMENT_TIMELINE
@@ -8142,6 +8272,13 @@ function renderOrder(opts) {
8142
8272
  // the order status alone, so they render even without tracking wired.
8143
8273
  var shipments = opts.shipments || [];
8144
8274
  var timelineHtml = _orderTimelineBlock(o.status);
8275
+ // Rich, dated, chronological event feed from the order's own
8276
+ // transition ledger (order.get() hydrates o.transitions). Read-only,
8277
+ // customer-visible events only, every value escaped at the sink. Empty
8278
+ // string for an order whose ledger wasn't hydrated (the status rail
8279
+ // above still renders), so legacy callers that pass no transitions are
8280
+ // unaffected.
8281
+ var eventsHtml = _orderEventTimelineBlock(o);
8145
8282
  var trackingHtml = _orderTrackingBlock(shipments);
8146
8283
  // The receipt-download link carries the guest order's ?k= access token when
8147
8284
  // the page was opened from the emailed capability link, so the download
@@ -8174,6 +8311,7 @@ function renderOrder(opts) {
8174
8311
  total: total,
8175
8312
  ship_to: o.ship_to || null,
8176
8313
  timeline_html: timelineHtml,
8314
+ events_html: eventsHtml,
8177
8315
  tracking_html: trackingHtml,
8178
8316
  pickup_html: _orderPickupBlock(opts.pickup),
8179
8317
  gift_html: _orderGiftBlock(opts.gift_options),
@@ -8229,9 +8367,16 @@ function renderOrder(opts) {
8229
8367
  .replace("RAW_REORDER_NOTICE", reorderNotice + cancelNotice)
8230
8368
  .replace("RAW_ORDER_TIMELINE", timelineHtml)
8231
8369
  .replace("RAW_ORDER_TRACKING", trackingHtml)
8370
+ .replace("RAW_ORDER_EVENTS", "RAW_ORDER_EVENTS_PLACEHOLDER")
8232
8371
  .replace("RAW_ORDER_PICKUP", _orderPickupBlock(opts.pickup))
8233
8372
  .replace("RAW_ORDER_ACTIONS", actionsHtml)
8234
8373
  .replace("RAW_SHIP_TO", _shipToAddressBlock(o.ship_to));
8374
+ // The event timeline carries operator-influenced free text (carrier
8375
+ // names, tracking numbers — already escaped into the block, but a `$`
8376
+ // in that escaped text would still trip String.replace's dollar
8377
+ // substitution) — splice it via the replacer-function helper, never a
8378
+ // replacement-string .replace.
8379
+ body = _spliceRaw(body, "RAW_ORDER_EVENTS_PLACEHOLDER", eventsHtml);
8235
8380
  // The gift block carries customer free text (escaped, but a `$` in it would
8236
8381
  // still trip String.replace's dollar substitution) — splice it via the
8237
8382
  // replacer-function helper, never a replacement-string .replace.