@tokenoftrust/storefront-runner 3.9.0 → 3.9.2

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.
@@ -113,7 +113,30 @@ function fullCartProduct(value: unknown, handle: string, scope: string): value i
113
113
  Object.values(variant.option_values).every(option => typeof option === "string") &&
114
114
  variant.inventory && typeof variant.inventory === "object" && !Array.isArray(variant.inventory) && typeof variant.inventory.available === "boolean");
115
115
  }
116
+ /** The caller's per-request id (its token's `jti`), echoed so the caller's decision log joins this one. */
117
+ const CORRELATION_ID = /^cart-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
118
+ /**
119
+ * One `commerce-final-cart-decision` line per request: the caller's correlation id, the finding, the
120
+ * HTTP status and the elapsed time. Closed values only — never cart, tenant-config or shopper data.
121
+ * A throw answers JSON `authority-error` so the caller maps it to a shopper sentence.
122
+ */
116
123
  export async function POST(context: APIContext): Promise<Response> {
124
+ const started = Date.now();
125
+ const header = context.request.headers.get("x-commerce-correlation-id") ?? "";
126
+ const correlationId = CORRELATION_ID.test(header) ? header : "absent";
127
+ let response: Response;
128
+ let threw = false;
129
+ try { response = await authorize(context); } catch { threw = true; response = deny("authority-error", 500); }
130
+ let finding = "approved";
131
+ if (response.status !== 200 || threw) {
132
+ try { finding = String((await response.clone().json() as { finding?: unknown }).finding ?? "unreadable").slice(0, 80); } catch { finding = "unreadable"; }
133
+ }
134
+ try {
135
+ console.info("commerce-final-cart-decision", JSON.stringify({ correlationId, ok: response.status === 200, finding, status: response.status, threw, ms: Date.now() - started }));
136
+ } catch { /* Diagnostic delivery cannot alter the decision. */ }
137
+ return response;
138
+ }
139
+ async function authorize(context: APIContext): Promise<Response> {
117
140
  if (Number(context.request.headers.get("content-length") ?? 0) > 65536) return deny("malformed-request");
118
141
  const raw = await context.request.text();
119
142
  if (raw.length > 65536) return deny("malformed-request");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "3.9.0",
3
+ "version": "3.9.2",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
6
6
  "packageManager": "pnpm@11.9.0",
@@ -331,12 +331,15 @@ export type CartFieldVisibility = "native" | "internal" | "visible" | "variant-o
331
331
 
332
332
  /**
333
333
  * Characters the checkout store does not keep in a field name, each mapped to a look-alike it
334
- * keeps. The store validates the name as it rewrites it, not as it was signed: ASCII space, `.`
335
- * and `[` become `_` (a `[…]` pair turns the field into an array), `||` ends the name, and a name
336
- * holding `&` or `"` is dropped from validation. Measured 2026-10-23 against tot-preview.foxycart.com
337
- * by submitting each name with a non-matching hash and reading the name back from its
338
- * "Cart Validation Error: <name>" refusal. Every other character (NBSP, `/`, `'`, `+`, `-`,
339
- * non-ASCII) came back unchanged.
334
+ * keeps. The store hashes `code + name + value` with the name as it received it
335
+ * (https://wiki.foxycart.com/v/2.0/hmac_validation), and it receives request names through PHP,
336
+ * which converts "dots and spaces in variable names ... to underscores"
337
+ * (https://www.php.net/manual/en/language.variables.external.php, "HTML Forms (GET and POST)").
338
+ * Not documented, measured 2026-10-23 against tot-preview.foxycart.com by submitting each name
339
+ * with a non-matching hash and reading it back from the "Cart Validation Error: <name>" refusal:
340
+ * `[` also becomes `_` (a `[…]` pair turns the field into an array), leading spaces are dropped,
341
+ * `||` ends the name, and a name holding `&` or `"` is never validated. Every other character
342
+ * (NBSP, `/`, `'`, `+`, `-`, `#`, `=`, `%`, `,`, `:`, a single `|`, non-ASCII) came back unchanged.
340
343
  */
341
344
  const CHECKOUT_NAME_LOOKALIKES: Readonly<Record<string, string>> = {
342
345
  " ": " ",