← back to Tk 10965 Zero Price Analysis
feat: Fix B prevention guard (inventory-stamp invariant) + test + apply guide, v1.1.0
0bf5296a41e36b2a96c502cbe92dc04193f95d82 · 2026-08-30 20:55:22 -0700 · codex-10965
Files touched
M VERSIONA prevention/APPLY.mdA prevention/inventory-stamp-guard.mjsA prevention/inventory-stamp-guard.test.mjs
Diff
commit 0bf5296a41e36b2a96c502cbe92dc04193f95d82
Author: codex-10965 <steve@designerwallcoverings.com>
Date: Sun Aug 30 20:55:22 2026 -0700
feat: Fix B prevention guard (inventory-stamp invariant) + test + apply guide, v1.1.0
---
VERSION | 2 +-
prevention/APPLY.md | 65 +++++++++++++++++++++++++++
prevention/inventory-stamp-guard.mjs | 75 +++++++++++++++++++++++++++++++
prevention/inventory-stamp-guard.test.mjs | 56 +++++++++++++++++++++++
4 files changed, 197 insertions(+), 1 deletion(-)
diff --git a/VERSION b/VERSION
index 3eefcb9..9084fa2 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.0
+1.1.0
diff --git a/prevention/APPLY.md b/prevention/APPLY.md
new file mode 100644
index 0000000..a215740
--- /dev/null
+++ b/prevention/APPLY.md
@@ -0,0 +1,65 @@
+# TK-10965 — Fix B (prevention) apply guide — GATED for deploy
+
+`inventory-stamp-guard.mjs` closes the root cause: it stops importers from ever
+stamping positive stock (`2026`) on a $0 / quote-only **sellable** variant, so no
+future activation can recreate the zero-price-orderable defect. Fix A (zeroing the
+1,743 existing variants) is still required for the products already stamped; this
+prevents the population from re-growing after A.
+
+**Status:** SAFE ARTIFACT (pure code + passing test) committed in this repo. Editing
+the live importers is a customer-facing behavior change → **Steve-gated deploy.**
+
+## Two-site patch per importer
+
+Each affected importer stamps `2026` in exactly two places. Import the guard once:
+
+```js
+import { safeStampQuantity } from './inventory-stamp-guard.mjs'; // adjust path
+```
+
+**Site 1 — product-create variant payload** (e.g. `command54-shopify-push.js:395`,
+the `Full Roll` variant, and the identical block in `cadence-import.js` +
+`templates/new-product-import-template.js`):
+
+```js
+// before:
+{ option1: 'Full Roll', sku: row.dw_sku, price: '0.00', /* … */ inventory_quantity: 2026 }
+// after:
+{ option1: 'Full Roll', sku: row.dw_sku, price: buildPrice(row), /* … */
+ inventory_quantity: safeStampQuantity({ title: 'Full Roll', price: buildPrice(row) },
+ { vendor: row.vendor, tags: row.tags }) }
+```
+(For lines that are legitimately $0-by-design the value becomes `0`; priced lines
+still get `2026`. NOTE the standing `price:'0.00'` literal is itself the deeper smell —
+a sellable variant should carry a real price; guard makes the stock side safe regardless.)
+
+**Site 2 — the reconcile restamp** (`setInventory2026()`, `command54-shopify-push.js:105`):
+
+```js
+// The function is passed bare SKUs, so it must also fetch price + product tags in its
+// variant lookup, then compute the quantity per-variant instead of the hard-coded 2026:
+const pairs = skus.filter(s => map.has(s)).map(s => ({
+ inventoryItemId: map.get(s).inventoryItemId,
+ locationId: INV_LOCATION_2026,
+ quantity: safeStampQuantity(map.get(s).variant, map.get(s).product), // was: 2026
+}));
+```
+(Extend the GraphQL in `setInventory2026` to also select `price` and the parent
+`product{ tags vendor }` so the guard has its inputs — one-line query edit.)
+
+## Affected importers (grep to confirm before deploy)
+
+```sh
+grep -rl "setInventory2026\|inventory_quantity: 2026\|quantity: 2026" \
+ ~/Projects/Designer-Wallcoverings
+```
+Canonical live path: `shopify/scripts/cadence/cadence-import.js` +
+`.../templates/new-product-import-template.js`. `command54-shopify-push.js` is
+RETIRED (header line 3) — patch it only for consistency, it is not live.
+
+## Acceptance
+
+1. `node prevention/inventory-stamp-guard.test.mjs` → all pass (offline).
+2. After deploy, activate one quote-only test product → its sellable variant reads
+ `availableForSale=false` (qty 0), the $4.25 Sample still non-orderable.
+3. `node ../verify.mjs` stays CLEAN over time (no NEW $0-orderable rows appear).
diff --git a/prevention/inventory-stamp-guard.mjs b/prevention/inventory-stamp-guard.mjs
new file mode 100644
index 0000000..9cc1679
--- /dev/null
+++ b/prevention/inventory-stamp-guard.mjs
@@ -0,0 +1,75 @@
+// TK-10965 — Fix B (prevention): the inventory-stamp invariant, as a pure guard.
+//
+// ROOT CAUSE (see ../FINDINGS.md): importers stamp a positive "cap-free" stock
+// number (the year literal 2026) on the SELLABLE non-Sample variant of every
+// activated product — both in the product-create payload (`inventory_quantity: 2026`)
+// and on reconcile (`setInventory2026()`). When that sellable variant is ALSO
+// priced $0 (quote-only / contact-for-price lines like Phillipe Romano, Fentucci
+// Naturals), positive stock makes it `availableForSale` → checkout-orderable for $0.
+//
+// THE INVARIANT this module enforces (one place, both call sites):
+// A sellable variant that is priced $0 OR belongs to a quote-only / price-
+// suppressed line must NEVER receive positive inventory. It gets 0 → not orderable.
+// (The $4.25 Sample variant is unaffected — it is not the sellable variant and is
+// already qty=0/non-orderable by design.)
+//
+// PURE + dependency-free on purpose: no network, no env, no Shopify client, so it
+// unit-tests offline and drops into any importer runtime unchanged. $0 (local).
+
+// Tag family that means "this line has no public retail price" — a superset of the
+// single `quote-only` tag the standing canary keyed on (which is why Fentucci, tagged
+// `quotes`/`Needs-Price`, was the canary's 462-product blind spot).
+export const PRICE_SUPPRESSED_TAGS = new Set([
+ 'quote-only', 'quote only', 'quote_only',
+ 'quotes', 'contact-for-price', 'contact for price', 'needs-price', 'needs price',
+]);
+
+const norm = t => String(t).trim().toLowerCase();
+
+/**
+ * Is this product a quote-only / price-suppressed line?
+ * @param {{tags?: string[]|string, vendor?: string}} product
+ */
+export function isPriceSuppressed(product = {}) {
+ const tags = Array.isArray(product.tags)
+ ? product.tags
+ : String(product.tags || '').split(',');
+ if (tags.some(t => PRICE_SUPPRESSED_TAGS.has(norm(t)))) return true;
+ // Vendor fallback for untagged cohorts (Fentucci Naturals ships quote-only with
+ // zero quote-only tags). Extend as new price-on-request lines are onboarded.
+ return norm(product.vendor) === 'fentucci naturals';
+}
+
+/**
+ * A variant is the "sellable" one iff it is NOT the Sample variant.
+ * (Importers create exactly two variants: `Sample` @ $4.25 and the real unit @ price.)
+ * @param {{title?: string, option1?: string}} variant
+ */
+export function isSellableVariant(variant = {}) {
+ const label = variant.title ?? variant.option1 ?? '';
+ return !/sample/i.test(label);
+}
+
+/**
+ * Would giving this sellable variant positive stock make it a $0-orderable defect?
+ * True iff it's the sellable variant AND (price is 0 OR the line is price-suppressed).
+ * @param {object} variant the variant about to be stamped
+ * @param {object} product its parent (for tags/vendor)
+ */
+export function isZeroPriceOrderableRisk(variant = {}, product = {}) {
+ if (!isSellableVariant(variant)) return false;
+ const price = Number(variant.price);
+ return price === 0 || Number.isNaN(price) || isPriceSuppressed(product);
+}
+
+/**
+ * THE GUARD. Return the inventory quantity that is SAFE to stamp on this variant.
+ * Drop-in replacement for the literal `2026` at both call sites:
+ * - create payload: inventory_quantity: safeStampQuantity(variant, product)
+ * - setInventory2026: quantity: safeStampQuantity(variant, product)
+ * Returns `desired` (2026) for normal priced variants; 0 for the defect class.
+ * @returns {number} 0 for a zero-price-orderable risk, else `desired`
+ */
+export function safeStampQuantity(variant, product, desired = 2026) {
+ return isZeroPriceOrderableRisk(variant, product) ? 0 : desired;
+}
diff --git a/prevention/inventory-stamp-guard.test.mjs b/prevention/inventory-stamp-guard.test.mjs
new file mode 100644
index 0000000..f661320
--- /dev/null
+++ b/prevention/inventory-stamp-guard.test.mjs
@@ -0,0 +1,56 @@
+// TK-10965 — Fix B guard unit test. Pure/offline. Run: node prevention/inventory-stamp-guard.test.mjs
+// exit 0 = all pass, exit 1 = a case regressed. $0 (local).
+import assert from 'node:assert/strict';
+import { safeStampQuantity, isZeroPriceOrderableRisk, isPriceSuppressed } from './inventory-stamp-guard.mjs';
+
+let pass = 0;
+const t = (name, fn) => { fn(); pass++; console.log(` ✓ ${name}`); };
+
+// --- The defect classes the guard MUST neutralize (stamp 0, not 2026) ---
+t('PR quote-only $0 Full Roll → 0', () => {
+ const product = { vendor: 'Phillipe Romano', tags: ['quote-only', 'contract-vinyl'] };
+ const variant = { title: 'Full Roll', price: '0.00' };
+ assert.equal(isZeroPriceOrderableRisk(variant, product), true);
+ assert.equal(safeStampQuantity(variant, product), 0);
+});
+
+t('Fentucci UNTAGGED (canary blind spot) $0 → 0 via vendor fallback', () => {
+ const product = { vendor: 'Fentucci Naturals', tags: ['quotes', 'Needs-Price'] };
+ const variant = { title: 'Full Roll', price: '0.00' };
+ assert.equal(isPriceSuppressed(product), true);
+ assert.equal(safeStampQuantity(variant, product), 0);
+});
+
+t('price-suppressed even if price were nonzero → 0', () => {
+ // Belt-and-suspenders: a quote-only line should never advertise stock regardless of price.
+ const product = { vendor: 'X', tags: ['contact-for-price'] };
+ assert.equal(safeStampQuantity({ title: 'Full Roll', price: '12.00' }, product), 0);
+});
+
+t('NaN/missing price on sellable variant → 0 (fail safe)', () => {
+ assert.equal(safeStampQuantity({ title: 'Full Roll', price: undefined }, { vendor: 'X', tags: [] }), 0);
+});
+
+t('tags as comma-string (Shopify REST shape) still detected → 0', () => {
+ const product = { vendor: 'X', tags: 'contract-vinyl, quote_only, new' };
+ assert.equal(safeStampQuantity({ title: 'Full Roll', price: '0.00' }, product), 0);
+});
+
+// --- The rows the guard MUST NOT touch (still stamp 2026) ---
+t('normal priced line (De Gournay-style) → 2026', () => {
+ const product = { vendor: 'De Gournay', tags: ['hand-painted'] };
+ assert.equal(safeStampQuantity({ title: 'Full Roll', price: '480.00' }, product), 2026);
+});
+
+t('Sample variant is never the sellable one → untouched (2026)', () => {
+ // The $4.25 Sample variant is not the sellable variant; guard is a no-op on it.
+ const product = { vendor: 'Phillipe Romano', tags: ['quote-only'] };
+ assert.equal(isZeroPriceOrderableRisk({ title: 'Sample', price: '4.25' }, product), false);
+ assert.equal(safeStampQuantity({ title: 'Sample', price: '4.25' }, product), 2026);
+});
+
+t('custom desired value is honored for safe rows', () => {
+ assert.equal(safeStampQuantity({ title: 'Full Roll', price: '99.00' }, { tags: [] }, 100), 100);
+});
+
+console.log(`\nALL ${pass} CASES PASS ✅ — guard stamps 0 on the $0-orderable class, 2026 otherwise.`);
← f46558f paginate zero-price canary variants
·
back to Tk 10965 Zero Price Analysis
·
TK-10965 Fix A executed: 1,743 zero-price sellable variants bb56e7c →