@blamejs/blamejs-shop 0.4.95 → 0.4.96

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.96 (2026-06-25) — **Compose two newly-public framework primitives directly, removing an internal-path dependency and a drift-prone duplicate transition table.** Two internal refactors that adopt framework surface the application previously had to work around, with no change in behaviour. The text-guard module composed the framework's codepoint-threat catalog by reaching into an internal vendored module path, because the catalog was not on the framework's public surface; it now composes the public b.codepointClass (the same module, reference-identical), so a future framework refresh that moves the internal file can't break the import. The quotes module kept a hand-rolled loop that re-derived a quote's next status from a second copy of its transition table; it now resolves the destination through the state machine's own side-effect-free resolver, off the single transition definition, so the two can no longer drift apart. No configuration changes. **Changed:** *Text-guard composes the public codepoint-threat catalog* — The free-text guard built its bidi / control / null-byte / zero-width / mixed-script screening on the framework's codepoint catalog by requiring an internal vendored module path, since the catalog was not yet exported publicly. It now composes the public b.codepointClass — the identical module — so the screening no longer depends on an internal file location that an upstream refresh could relocate. · *Quote transitions resolve through the state machine instead of a duplicate table* — Validating a quote's lifecycle edge (respond / accept / reject / cancel / expire / convert) used the state machine to check the edge was legal, then a separate hand-rolled loop over a second copy of the transition table to read the destination status. The destination is now resolved by the state machine's own side-effect-free resolver, off the single transition definition the machine is built from, removing the duplicate table walk that could drift from it.
12
+
11
13
  - v0.4.95 (2026-06-25) — **An order state change no longer logs a phantom transition when it loses a concurrent race, and a transient failure writing the history row no longer strands a stock hold.** Advancing an order through its lifecycle reads the order's current status, resolves the next state, then writes it with an UPDATE guarded on the row still being in the state it was read in — so when two writers race the same order (the stale-order reaper cancelling while a payment webhook marks it paid), exactly one wins and the loser collapses to a no-op. Two issues in that path are fixed. First, the state machine's transition audit was emitted before the guarded write resolved, so a writer that LOST the race still recorded a 'success' transition into the audit trail for a state change that never landed; the audit is now written only for the writer that actually moved the row. Second, the denormalized history-row insert ran between the committed status write and the inventory settlement with no failure handling: a transient error writing that row aborted the whole call after the status had already advanced, so the stock hold was never converted to a shelf debit (or released) and a webhook retry — finding the order already advanced — skipped settlement entirely, stranding the hold. The history insert is now best-effort (the state-machine audit is the authoritative record), so a failure writing it never prevents the hold from settling. No configuration changes. **Changed:** *Vendored framework refreshed to 0.15.24* — Updates the vendored blamejs framework to 0.15.24, an upstream build supply-chain hardening release with no runtime API changes. No operator action required. **Fixed:** *A lost order-transition race no longer records a phantom 'success' in the audit trail* — The order state machine emitted its transition audit during the in-memory replay, before the guarded status write that decides the race. A writer that lost the race (its conditional update matched zero rows and it no-opped) had nonetheless already written a 'success' transition into the audit trail — a record an auditor could not tell apart from a real one. The destination state is now resolved side-effect-free, and the transition audit is emitted only after the guarded write wins, so the audit trail records exactly the transitions that actually landed. · *A transient failure writing an order's history row no longer strands its stock hold* — After an order's status write committed, the order_transitions history row was inserted with no failure handling, ahead of the inventory settlement. A transient error on that insert aborted the call with the order already advanced, so the held stock was never debited or released; a webhook retry then found the order past its starting state, no-opped at the concurrency guard, and skipped settlement — leaving the hold stranded with no automatic recovery. A history-row write failure no longer aborts settlement, so the hold always settles once the state change commits. Because that row is also where a PayPal capture id is recovered from, a write failure is not silently dropped: the row's full payload — including any capture metadata — is recorded to the durable error feed (and the audit chain) for reconciliation.
12
14
 
13
15
  - v0.4.94 (2026-06-25) — **A print-on-demand fulfillment status change is now an atomic claim — concurrent updates can no longer clobber each other or record a phantom transition.** Advancing a print-on-demand fulfillment through its lifecycle (submitted → shipped / failed / cancelled) read the row's current status, validated the move in memory, and then wrote the new status with an UPDATE keyed on the row id alone. Two updates racing the same fulfillment — a worker marking it shipped while an operator marks it failed, or a re-delivered supplier webhook — both read the same starting status, both passed the in-memory check, and both wrote unconditionally, so the later write silently overwrote the earlier one's status and columns (a lost update), and each emitted a 'success' transition into the audit trail even though only one write reflected reality. The status write is now a single-statement atomic claim gated on the row still being in the state it was read in (the same compare-and-swap the dropship-forwarding and returns lifecycles already use): exactly one update wins, the loser changes zero rows and is refused, and the audit record is written only for the update that actually changed the row. No configuration changes. **Fixed:** *Concurrent print-on-demand fulfillment transitions no longer clobber each other or log a phantom transition* — The fulfillment status update was keyed on the row id with no guard on the starting state, so two concurrent transitions both wrote unconditionally — last-writer-wins overwrote the other's status and stamped columns, and both emitted a 'success' transition audit. The update is now an atomic compare-and-swap gated on the row still being in the from-state, with the destination resolved side-effect-free and the audit emitted only on the winning write. The losing transition changes zero rows and is refused with a clear concurrency error instead of corrupting the record.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.95",
2
+ "version": "0.4.96",
3
3
  "assets": {
4
4
  "css/admin.css": {
5
5
  "integrity": "sha384-imfe0otYErcB8rr2h6KLSGTtStirysptpXETSPY4zLv3bZoIT75Lo1dOvkOav+xL",
package/lib/quotes.js CHANGED
@@ -212,35 +212,27 @@ function _getQuoteFsm() {
212
212
  return _quoteFsm;
213
213
  }
214
214
 
215
- // Resolve the to-status for a legal (from, event) pair off the transition
216
- // table — the FSM's `can()` only answers yes/no, so the destination comes from
217
- // the declaration the machine was built from (one source of truth).
218
- function _toStatus(fromStatus, event) {
219
- for (var i = 0; i < QUOTE_TRANSITIONS.length; i += 1) {
220
- var t = QUOTE_TRANSITIONS[i];
221
- if (t.from === fromStatus && t.on === event) return t.to;
222
- }
223
- return null;
224
- }
225
-
226
215
  // Validate one status-changing edge through the FSM. `event` is the verb name
227
216
  // above; `fromStatus` is the row's CURRENT status. Returns the resolved
228
217
  // to-status on success. Throws a coded QUOTE_TRANSITION_REFUSED Error when the
229
218
  // machine refuses the edge (terminal state, wrong source state, or unknown
230
219
  // event) so the caller maps it to a uniform 409 regardless of which guard
231
- // tripped — the same shape the hand-rolled status checks used to throw. The
232
- // machine's synchronous `can()` is the gate (no guards declared, so it never
233
- // awaits); the destination is read back off the transition table.
220
+ // tripped — the same shape the hand-rolled status checks used to throw.
221
+ // `target` resolves the destination state side-effect-free (no audit emit, no
222
+ // state mutation) and returns null on an illegal edge — so it is BOTH the gate
223
+ // and the to-status resolver in one call, read off the single FSM source of
224
+ // truth rather than a parallel transition-table walk that could drift from it.
234
225
  function _assertTransition(fromStatus, event, verbLabel) {
235
226
  var fsm = _getQuoteFsm();
236
227
  var instance = fsm.restore({ state: fromStatus, history: [], context: {} });
237
- if (!instance.can(event)) {
228
+ var toStatus = instance.target(event);
229
+ if (toStatus == null) {
238
230
  var refused = new Error("quotes." + verbLabel + ": refused — quote is " +
239
231
  fromStatus + ", cannot " + event);
240
232
  refused.code = "QUOTE_TRANSITION_REFUSED";
241
233
  throw refused;
242
234
  }
243
- return _toStatus(fromStatus, event);
235
+ return toStatus;
244
236
  }
245
237
 
246
238
  // ---- validators ---------------------------------------------------------
package/lib/text-guard.js CHANGED
@@ -15,13 +15,9 @@
15
15
  * The dangerous-codepoint surface (bidi overrides, C0 control bytes,
16
16
  * null bytes, zero-width / invisible formatting, mixed-script
17
17
  * confusables) is NOT reinvented here — it composes the framework's
18
- * own codepoint catalog, the single source of truth the guard-*
19
- * family already builds on. The framework does not yet re-export the
20
- * catalog on its public index; until it does, this module reaches
21
- * the catalog leaf directly. The leaf has no dependency on any shop
22
- * module, so requiring it is refresh-safe; an upstream one-line
23
- * `module.exports` add would let this collapse to `b.codepointClass`
24
- * with no behavior change.
18
+ * own codepoint catalog (`b.codepointClass`), the single source of
19
+ * truth the guard-* family already builds on, so the tables and
20
+ * regexes never drift into a second hand-maintained copy.
25
21
  *
26
22
  * Validators THROW a `TypeError` on bad input. The admin / account
27
23
  * request wrappers map a thrown `TypeError` to a clean 400, so a
@@ -50,13 +46,11 @@
50
46
  var b = require("./vendor/blamejs");
51
47
 
52
48
  // The framework's codepoint catalog (bidi / control / null / zero-width
53
- // tables + regexes, the script ranges, and the detect/assert/strip
54
- // helpers the guard-* family composes). It is not on the framework's
55
- // public index yet; the catalog leaf has no shop-module dependency, so
56
- // composing it directly stays refresh-safe and keeps a single source of
57
- // truth instead of a second copy of the tables.
58
- // allow:vendor-hand-edit — composing the framework's codepoint catalog leaf (no shop dependency, refresh-safe); collapses to b.codepointClass once upstream re-exports it
59
- var codepointClass = require("./vendor/blamejs/lib/codepoint-class");
49
+ // tables + regexes, the script ranges, and the detect/assert/strip helpers
50
+ // the guard-* family composes), exposed on the public surface as
51
+ // b.codepointClass so this composes a single source of truth instead of a
52
+ // second copy of the tables.
53
+ var codepointClass = b.codepointClass;
60
54
 
61
55
  // ---- shop validators ----------------------------------------------------
62
56
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.4.95",
3
+ "version": "0.4.96",
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": {