[object Object]

← back to Sanderson Onboard

TK-11357 Fix D: route create_sdg's inventory stamp through the shared $0 guard (defence-in-depth)

edc210b268d1d6028ed6feb9ae4e5ee878996f61 · 2026-09-10 09:30:29 -0700 · Steve

NOT a live-risk closure — stated plainly. create_sdg.mjs is ALREADY effectively guarded:
gate() pushes 'no-price' when !(parseFloat(it.retail_usd) > 0) and the caller `continue`s on
SKIP-HELD before any product create or inventory mutation; and it is CREATE-ONLY and
manifest-bounded (manifest-{harlequin,morris,sanderson,zoffany}.json = 5,655 items, ZERO
Phillipe Romano, no productByHandle / no products(first: sweep), so it only writes to pids it
created and cannot re-inflate a pre-existing cohort). Verified, not assumed.

What it lacked was the guarantee AT THE WRITE SITE. goLive() re-reads the product and stamped
TARGET_QTY on every variant, and the finish-pending path calls goLive() on drafts created by an
EARLIER run whose live prices it never re-checks. The stamp now goes through the shared guard
(lib/inventory-stamp-guard.mjs, vendored) so the invariant holds at the mutation itself rather
than only in an upstream manifest field.

Q_V now also selects variant title+price and product vendor+tags. Confirmed empirically safe for
this cadence: all 4 manifests carry ZERO quote/needs-price/contact-for-price tags and no vendor
is a price-suppressed line, so the guard's quote-only branch cannot fire and this is a strict
no-op for today's population — it only changes behaviour if a $0 variant ever reaches the stamp.

This matters because com.steve.sdg-publish-cadence is LOADED and runs cadence_resume.sh daily
at 04:30 PDT, so the write site is unattended.

Proven by Designer-Wallcoverings/shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs:
4/4 required + 3/3 regression on the predicate EXTRACTED from this file.
SOURCE-ONLY: nothing run with --apply, no Shopify write. Reversible: git revert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Files touched

Diff

commit edc210b268d1d6028ed6feb9ae4e5ee878996f61
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Sep 10 09:30:29 2026 -0700

    TK-11357 Fix D: route create_sdg's inventory stamp through the shared $0 guard (defence-in-depth)
    
    NOT a live-risk closure — stated plainly. create_sdg.mjs is ALREADY effectively guarded:
    gate() pushes 'no-price' when !(parseFloat(it.retail_usd) > 0) and the caller `continue`s on
    SKIP-HELD before any product create or inventory mutation; and it is CREATE-ONLY and
    manifest-bounded (manifest-{harlequin,morris,sanderson,zoffany}.json = 5,655 items, ZERO
    Phillipe Romano, no productByHandle / no products(first: sweep), so it only writes to pids it
    created and cannot re-inflate a pre-existing cohort). Verified, not assumed.
    
    What it lacked was the guarantee AT THE WRITE SITE. goLive() re-reads the product and stamped
    TARGET_QTY on every variant, and the finish-pending path calls goLive() on drafts created by an
    EARLIER run whose live prices it never re-checks. The stamp now goes through the shared guard
    (lib/inventory-stamp-guard.mjs, vendored) so the invariant holds at the mutation itself rather
    than only in an upstream manifest field.
    
    Q_V now also selects variant title+price and product vendor+tags. Confirmed empirically safe for
    this cadence: all 4 manifests carry ZERO quote/needs-price/contact-for-price tags and no vendor
    is a price-suppressed line, so the guard's quote-only branch cannot fire and this is a strict
    no-op for today's population — it only changes behaviour if a $0 variant ever reaches the stamp.
    
    This matters because com.steve.sdg-publish-cadence is LOADED and runs cadence_resume.sh daily
    at 04:30 PDT, so the write site is unattended.
    
    Proven by Designer-Wallcoverings/shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs:
    4/4 required + 3/3 regression on the predicate EXTRACTED from this file.
    SOURCE-ONLY: nothing run with --apply, no Shopify write. Reversible: git revert.
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---
 scripts/create_sdg.mjs                | 40 ++++++++++++++++--
 scripts/lib/inventory-stamp-guard.mjs | 80 +++++++++++++++++++++++++++++++++++
 2 files changed, 117 insertions(+), 3 deletions(-)

diff --git a/scripts/create_sdg.mjs b/scripts/create_sdg.mjs
index ac20acf..b6bd725 100644
--- a/scripts/create_sdg.mjs
+++ b/scripts/create_sdg.mjs
@@ -82,7 +82,30 @@ async function gql(query, variables) {
     return j.data;
   }
 }
-const Q_V = `query($id:ID!){product(id:$id){status variants(first:10){edges{node{id sku inventoryItem{id}}}}}}`;
+const Q_V = `query($id:ID!){product(id:$id){status vendor tags variants(first:10){edges{node{id sku title price inventoryItem{id}}}}}}`;
+import { safeStampQuantity } from './lib/inventory-stamp-guard.mjs';  // GUARD TK-11357 (shared guard)
+// ── GUARD TK-11357 BEGIN ─ do not edit without re-running the fixture proof ──────────
+// A $0 / quote-only sellable variant must NEVER receive positive stock: positive stock is what
+// flips availableForSale=true, making it checkout-orderable at $0 (lineage TK-10825 -> 10965 ->
+// 11140 -> 11299 -> 11301 -> 11357). $0 is the LIVE theme's deliberate quote-only SENTINEL
+// (snippets/product-form-content.liquid renders the "Contact Us" button iff variant.price == 0),
+// so the remedy is NEVER to write a placeholder price - it is "do not stock it".
+// Steve's 2026-06-20 "active products are never out of stock" rule is PRESERVED for PRICED goods:
+// a priced variant still gets `desired`. The quote-only tag/vendor decision is delegated to the
+// shared guard (lib/inventory-stamp-guard.mjs); this adds one strictly-safer rule of its own -
+// price <= 0 / NaN is ALWAYS 0, even on a variant labelled "Sample" (a $0 "sample" is the same
+// $0-orderable defect). The real $4.25 memo sample is unaffected and keeps its existing quantity.
+function safeQuantities(product, variants, locationId, desired) {
+  return (variants || []).map(v => ({
+    inventoryItemId: v.inventoryItem.id,
+    locationId,
+    quantity: Number(v.price) > 0
+      ? safeStampQuantity({ title: v.title, price: v.price }, product, desired)
+      : 0,
+  }));
+}
+// ── GUARD TK-11357 END ────────────────────────────────────────────
+
 const M_TRACK = `mutation($id:ID!){inventoryItemUpdate(id:$id,input:{tracked:true}){userErrors{message}}}`;
 const M_ACT = `mutation($iid:ID!,$loc:ID!){inventoryActivate(inventoryItemId:$iid,locationId:$loc){userErrors{message}}}`;
 const M_QTY = `mutation($input:InventorySetQuantitiesInput!){inventorySetQuantities(input:$input){userErrors{message}}}`;
@@ -92,9 +115,20 @@ const M_ACTIVE = `mutation($id:ID!){productUpdate(input:{id:$id,status:ACTIVE}){
 async function goLive(pid) {
   const gid = `gid://shopify/Product/${pid}`;
   const d = await gql(Q_V, { id: gid });
-  const items = d.product.variants.edges.map(e => e.node.inventoryItem.id);
+  const vnodes = d.product.variants.edges.map(e => e.node);
+  const items = vnodes.map(v => v.inventoryItem.id);
+  // GUARD TK-11357 (DEFENCE-IN-DEPTH, not a live-risk closure). This script is already
+  // effectively guarded upstream: gate() pushes 'no-price' when !(parseFloat(retail_usd) > 0)
+  // and the caller `continue`s on SKIP-HELD BEFORE any create or inventory mutation, and it is
+  // CREATE-ONLY + manifest-bounded (harlequin/morris/sanderson/zoffany — zero Phillipe Romano),
+  // so it cannot re-inflate a pre-existing cohort. What it lacked was the guarantee AT THE WRITE
+  // SITE: goLive() re-reads the product and stamped TARGET_QTY on every variant, and the
+  // finish-pending path calls goLive() on drafts created by an EARLIER run whose live prices it
+  // never re-checks. Routing the stamp through the shared guard makes the invariant hold at the
+  // mutation itself rather than only in an upstream manifest field.
+  const quantities = safeQuantities(d.product, vnodes, LOCATION_ID, TARGET_QTY);
   for (const iid of items) { await gql(M_TRACK, { id: iid }); await gql(M_ACT, { iid, loc: LOCATION_ID }); }
-  await gql(M_QTY, { input: { name: 'on_hand', reason: 'correction', ignoreCompareQuantity: true, quantities: items.map(iid => ({ inventoryItemId: iid, locationId: LOCATION_ID, quantity: TARGET_QTY })) } });
+  await gql(M_QTY, { input: { name: 'on_hand', reason: 'correction', ignoreCompareQuantity: true, quantities } });
   await gql(M_PUB, { id: gid, pubs: PUBLICATIONS.map(p => ({ publicationId: p })) });
   const r = await gql(M_ACTIVE, { id: gid });
   return { status: r.productUpdate?.product?.status, varCount: items.length };
diff --git a/scripts/lib/inventory-stamp-guard.mjs b/scripts/lib/inventory-stamp-guard.mjs
new file mode 100644
index 0000000..29d1810
--- /dev/null
+++ b/scripts/lib/inventory-stamp-guard.mjs
@@ -0,0 +1,80 @@
+// VENDORED COPY — canonical source: Designer-Wallcoverings/shopify/scripts/lib/inventory-stamp-guard.mjs
+// Vendored (not cross-repo-imported) on purpose: this repo ships and runs independently, and a
+// cross-repo relative import would hard-crash the scheduled cadence if either tree moved.
+// KEEP IN SYNC — the TK-11357 fixture harness hashes both copies and FAILS on drift.
+// TK-11357 (lineage TK-10825/10965/11140/11299/11301/11357).
+// 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;
+}

← f7d2426 auto-data-snapshot: 2026-09-10T05:55:20 (3 data files) — pil  ·  back to Sanderson Onboard  ·  auto-data-snapshot: 2026-09-11T01:40:13 (1 data files) — dat 5c8dbdf →