[object Object]

← back to Dw Validator Debug TK11314

fix(TK-10965): guard remaining importer inventory-stamp sites (completeness pass)

00ba31fa2e0d19a31fb489155dea216d117358c4 · 2026-08-30 23:41:55 -0700 · Steve

Completeness pass over the 7 remaining unguarded 2026 inventory-stamp sites
found after b83a37b.

Guarded (real sellable stamps, now route through safeStampQuantity):
- shopify/scripts/cadence/remediate-channels-inventory.js — SCAN now fetches
  variant price + product tags; a $0/quote-only sellable variant is no longer
  pushed to skusToSet (never stamped 2026). Wolf Gordon/LA-Fabrics/Innovations
  $0 quote lines were the real risk.
- DW-Programming/ralph-lauren-fabric-updater-9899.js — variants query fetches
  title/price + product tags/vendor; the per-variant quantity flows through
  safeStampQuantity. RL fabrics are priced (defensive-only) but guarded regardless.

Intentionally UNGUARDED, comment added so a future audit doesn't re-flag:
- shopify/scripts/add-missing-samples-v2.js — stamps the new $4.25 Sample only.
- shopify/scripts/fix-samples-graphql.js — creates the $4.25 Sample only.

No live stamp in code (2026 was a header-comment claim only) — clarifying note added:
- DW-Programming/as-creation-shopify-push.js
- DW-Programming/hollywood-shopify-push.js
- shopify/scripts/wolf-gordon-shopify-push.js

Behavior-preserving for all priced lines. node --check passes on all 7; guard
require() resolves + behaves from both new locations (Node v26 require(esm)).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 00ba31fa2e0d19a31fb489155dea216d117358c4
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Aug 30 23:41:55 2026 -0700

    fix(TK-10965): guard remaining importer inventory-stamp sites (completeness pass)
    
    Completeness pass over the 7 remaining unguarded 2026 inventory-stamp sites
    found after b83a37b.
    
    Guarded (real sellable stamps, now route through safeStampQuantity):
    - shopify/scripts/cadence/remediate-channels-inventory.js — SCAN now fetches
      variant price + product tags; a $0/quote-only sellable variant is no longer
      pushed to skusToSet (never stamped 2026). Wolf Gordon/LA-Fabrics/Innovations
      $0 quote lines were the real risk.
    - DW-Programming/ralph-lauren-fabric-updater-9899.js — variants query fetches
      title/price + product tags/vendor; the per-variant quantity flows through
      safeStampQuantity. RL fabrics are priced (defensive-only) but guarded regardless.
    
    Intentionally UNGUARDED, comment added so a future audit doesn't re-flag:
    - shopify/scripts/add-missing-samples-v2.js — stamps the new $4.25 Sample only.
    - shopify/scripts/fix-samples-graphql.js — creates the $4.25 Sample only.
    
    No live stamp in code (2026 was a header-comment claim only) — clarifying note added:
    - DW-Programming/as-creation-shopify-push.js
    - DW-Programming/hollywood-shopify-push.js
    - shopify/scripts/wolf-gordon-shopify-push.js
    
    Behavior-preserving for all priced lines. node --check passes on all 7; guard
    require() resolves + behaves from both new locations (Node v26 require(esm)).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 DW-Programming/as-creation-shopify-push.js         |   8 +++++++-
 DW-Programming/hollywood-shopify-push.js           |   8 +++++++-
 DW-Programming/ralph-lauren-fabric-updater-9899.js |  21 +++++++++++++++++++--
 shopify/scripts/add-missing-samples-v2.js          |   4 ++++
 .../cadence/remediate-channels-inventory.js        |  14 ++++++++++++--
 shopify/scripts/fix-samples-graphql.js             |   4 ++++
 shopify/scripts/wolf-gordon-shopify-push.js        | Bin 22923 -> 23475 bytes
 7 files changed, 53 insertions(+), 6 deletions(-)

diff --git a/DW-Programming/as-creation-shopify-push.js b/DW-Programming/as-creation-shopify-push.js
index 98c25c0e..cc6c9cab 100644
--- a/DW-Programming/as-creation-shopify-push.js
+++ b/DW-Programming/as-creation-shopify-push.js
@@ -10,9 +10,15 @@
  * Body: 3 sentences from AI description, NO specs
  * Sample variant: $4.25, weight 0
  * Roll variant: price from DB or $0, weight 2 lbs
- * inventory_quantity: 2026 for all variants
+ * inventory_quantity: 2026 for all variants  (documentation only — see TK-10965 note)
  * Residential vendor — fire_rating: "Inquire for more Information"
  *
+ * TK-10965 note: this "2026" is a DOCUMENTATION claim, not a code stamp. This script
+ * only ENQUEUES productCreate + _launchPadFollowUp jobs (no inventory literal in the
+ * payload — buildVariants sets no inventory_quantity). Inventory is stamped later by
+ * the queue worker / cadence path, which already routes through the inventory-stamp
+ * guard (safeStampQuantity). Nothing to guard here.
+ *
  * Usage:
  *   node as-creation-shopify-push.js                  # Queue first 25 (Launch Pad batch)
  *   node as-creation-shopify-push.js --limit 100      # Queue first 100
diff --git a/DW-Programming/hollywood-shopify-push.js b/DW-Programming/hollywood-shopify-push.js
index fbd3e7f7..a97612aa 100644
--- a/DW-Programming/hollywood-shopify-push.js
+++ b/DW-Programming/hollywood-shopify-push.js
@@ -10,7 +10,13 @@
  * Body: 3 sentences from AI description, NO specs
  * Sample variant: $4.25, weight 0
  * Roll variant: $0 (quote model), weight 2 lbs
- * inventory_quantity: 2026 for all variants
+ * inventory_quantity: 2026 for all variants  (documentation only — see TK-10965 note)
+ *
+ * TK-10965 note: this "2026" is a DOCUMENTATION claim, not a code stamp. This script
+ * builds a productCreate payload whose buildVariants sets NO inventory_quantity literal
+ * (Sample @ $4.25, roll @ $0.00). Inventory is stamped downstream by the queue worker /
+ * cadence path, which already routes through the inventory-stamp guard. Nothing to
+ * guard here.
  *
  * Usage:
  *   node hollywood-shopify-push.js                  # Queue first 25 (Launch Pad batch)
diff --git a/DW-Programming/ralph-lauren-fabric-updater-9899.js b/DW-Programming/ralph-lauren-fabric-updater-9899.js
index 0fb52a29..d48c4375 100644
--- a/DW-Programming/ralph-lauren-fabric-updater-9899.js
+++ b/DW-Programming/ralph-lauren-fabric-updater-9899.js
@@ -17,6 +17,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
 Object.defineProperty(exports, "__esModule", { value: true });
 const express_1 = __importDefault(require("express"));
 const playwright_1 = require("playwright");
+// TK-10965: never stock a $0/quote-only SELLABLE variant (inventory-stamp guard)
+const { safeStampQuantity } = require("../shopify/scripts/lib/inventory-stamp-guard.mjs");
 const app = (0, express_1.default)();
 const PORT = 9899;
 // Shopify Configuration
@@ -1573,11 +1575,15 @@ async function updateProduct(product, specs, aiAnalysis, needsReview = false //
             const variantsQuery = `
         query {
           product(id: "${product.id}") {
+            tags
+            vendor
             variants(first: 10) {
               edges {
                 node {
                   id
                   sku
+                  title
+                  price
                   inventoryItem {
                     id
                   }
@@ -1596,6 +1602,10 @@ async function updateProduct(product, specs, aiAnalysis, needsReview = false //
                 body: JSON.stringify({ query: variantsQuery }),
             });
             const variantsData = await variantsResponse.json();
+            const productForGuard = {
+                tags: variantsData.data?.product?.tags || [],
+                vendor: variantsData.data?.product?.vendor || '',
+            };
             const variants = variantsData.data?.product?.variants?.edges || [];
             // Get location ID
             const locationQuery = `query { locations(first: 1) { edges { node { id } } } }`;
@@ -1614,6 +1624,13 @@ async function updateProduct(product, specs, aiAnalysis, needsReview = false //
                 for (const variant of variants) {
                     const inventoryItemId = variant.node.inventoryItem?.id;
                     if (inventoryItemId) {
+                        // TK-10965: route the stamp through the guard — a $0/quote-only
+                        // SELLABLE variant gets 0 (never positive stock); the Sample and
+                        // normal priced variants get 2026 unchanged (behavior-preserving).
+                        const stampQty = safeStampQuantity(
+                            { title: variant.node.title, price: variant.node.price },
+                            productForGuard
+                        );
                         const setInventoryMutation = `
               mutation {
                 inventorySetOnHandQuantities(
@@ -1622,7 +1639,7 @@ async function updateProduct(product, specs, aiAnalysis, needsReview = false //
                     setQuantities: [{
                       inventoryItemId: "${inventoryItemId}"
                       locationId: "${locationId}"
-                      quantity: 2026
+                      quantity: ${stampQty}
                     }]
                   }
                 ) {
@@ -1639,7 +1656,7 @@ async function updateProduct(product, specs, aiAnalysis, needsReview = false //
                             body: JSON.stringify({ query: setInventoryMutation }),
                         });
                         await sleep(100);
-                        console.log(`   📦 Set inventory to 2026 for ${variant.node.sku || 'variant'}`);
+                        console.log(`   📦 Set inventory to ${stampQty} for ${variant.node.sku || 'variant'}`);
                     }
                 }
             }
diff --git a/shopify/scripts/add-missing-samples-v2.js b/shopify/scripts/add-missing-samples-v2.js
index 14575b6a..60d28ec1 100644
--- a/shopify/scripts/add-missing-samples-v2.js
+++ b/shopify/scripts/add-missing-samples-v2.js
@@ -232,6 +232,10 @@ async function scanProducts(progress, args) {
 }
 
 // ─── Create ────────────────────────────────────────────────────────────
+// TK-10965: this 2026 stamp is INTENTIONALLY UNGUARDED. It only ever fires on the
+// newly-created $4.25 Sample variant (createMissingVariants passes the Sample's
+// inventory_item_id) — positive Sample stock is by-design. The inventory-stamp guard
+// only zeroes a SELLABLE ($0/quote-only) variant, so it does not apply here.
 async function setInventory(inventoryItemId) {
   const mutation = `
     mutation ($input: InventorySetQuantitiesInput!) {
diff --git a/shopify/scripts/cadence/remediate-channels-inventory.js b/shopify/scripts/cadence/remediate-channels-inventory.js
index 4bc50be7..b2e3bfd6 100644
--- a/shopify/scripts/cadence/remediate-channels-inventory.js
+++ b/shopify/scripts/cadence/remediate-channels-inventory.js
@@ -36,6 +36,7 @@ const RECENT = parseInt(val('--recent', '1000'), 10); // else: last N active pro
 const TOTAL_CHANNELS = 13;
 
 const { isInternal } = require('../lib/internal-guard.js'); // never publish an INTERNAL line to sales channels
+const { safeStampQuantity } = require('../lib/inventory-stamp-guard.mjs'); // TK-10965: never stock a $0/quote-only sellable variant
 
 const env = fs.readFileSync(os.homedir() + '/Projects/secrets-manager/.env', 'utf8');
 const TOKEN = (env.match(/^SHOPIFY_ADMIN_TOKEN=(.*)$/m) || [])[1].replace(/['"]/g, '').trim();
@@ -121,7 +122,8 @@ async function setInventory2026(skus) {
 }
 
 // ---- scan: iterate the cohort, classify each ACTIVE product ----
-const SCAN = `query($q:String!,$cursor:String){products(first:50,query:$q,sortKey:CREATED_AT,reverse:true,after:$cursor){pageInfo{hasNextPage endCursor}edges{node{id title status vendor resourcePublicationsCount{count} variants(first:10){edges{node{sku inventoryQuantity}}}}}}}`;
+// TK-10965: fetch tags + variant price so the sellable stamp routes through safeStampQuantity
+const SCAN = `query($q:String!,$cursor:String){products(first:50,query:$q,sortKey:CREATED_AT,reverse:true,after:$cursor){pageInfo{hasNextPage endCursor}edges{node{id title status vendor tags resourcePublicationsCount{count} variants(first:10){edges{node{sku price inventoryQuantity}}}}}}}`;
 
 (async () => {
   if (!TOKEN) { console.error('no SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
@@ -163,7 +165,15 @@ const SCAN = `query($q:String!,$cursor:String){products(first:50,query:$q,sortKe
       }
       if (!invOk) {
         needInv++;
-        for (const v of sellable) if (v.sku && v.inventoryQuantity !== 2026) skusToSet.push(v.sku);
+        // TK-10965: only stamp 2026 on a sellable variant the guard says is SAFE.
+        // safeStampQuantity returns 0 for a $0-priced or quote-only/price-suppressed
+        // sellable variant — those must never receive positive stock (would make a
+        // $0 line checkout-orderable). Feed title=sku so the -Sample suffix classifies.
+        for (const v of sellable) {
+          if (!v.sku || v.inventoryQuantity === 2026) continue;
+          const safe = safeStampQuantity({ title: v.sku, price: v.price }, { tags: n.tags, vendor: n.vendor });
+          if (safe === 2026) skusToSet.push(v.sku);
+        }
       }
     }
     cursor = conn.pageInfo.hasNextPage ? conn.pageInfo.endCursor : null;
diff --git a/shopify/scripts/fix-samples-graphql.js b/shopify/scripts/fix-samples-graphql.js
index a8c0d7e8..d71fdc46 100644
--- a/shopify/scripts/fix-samples-graphql.js
+++ b/shopify/scripts/fix-samples-graphql.js
@@ -198,6 +198,10 @@ async function createSampleVariant(productId, optionName, baseSku) {
     }
   }`;
 
+  // TK-10965: this 2026 stamp is INTENTIONALLY UNGUARDED — it creates the $4.25
+  // Sample variant only (name: "Sample"), and positive Sample stock is by-design.
+  // The inventory-stamp guard only zeroes a SELLABLE ($0/quote-only) variant, not
+  // the Sample, so it does not apply here.
   const variantInput = {
     price: "4.25",
     optionValues: [{ optionName, name: "Sample" }],
diff --git a/shopify/scripts/wolf-gordon-shopify-push.js b/shopify/scripts/wolf-gordon-shopify-push.js
index c346fdc7..ea74547b 100644
Binary files a/shopify/scripts/wolf-gordon-shopify-push.js and b/shopify/scripts/wolf-gordon-shopify-push.js differ

← b83a37b1 fix(TK-10965): guard importer inventory stamp — never stock  ·  back to Dw Validator Debug TK11314  ·  auto-data-snapshot: 2026-08-31T00:07:16 (2 data files) — sho db57aff5 →