← back to Designer Wallcoverings
guard TK-11357 Fix D: $0 gate in the 5 catalog-wide inventory sweeps + the proof harness
39156e236d6e392ebca9ac45a0dc9efa35a1c723 · 2026-09-10 09:30:28 -0700 · Steve
These are the highest-blast-radius re-inflaters of the $0-orderable defect (lineage
TK-10825/10965/11140/11299/11301/11357). PRE-EXISTING GUARD STATE — verified per file, all five
had NO price gate anywhere on the write path:
sweep-all-active.mjs full-catalog (~75k active products / ~130k variants):
inventorySetQuantities on_hand=2026 on EVERY active variant,
plus inventoryActivate(available:2026) for not-stocked items.
active-2026-and-all-channels.js store-wide: available=TARGET_QTY on every variant of every
active product + an activate(qty:TARGET_QTY) per-item fallback.
inventory-set-2026.js every SKU in /tmp/inv_skus.txt → on_hand=2026.
set-active-zero-to-2026.js + activate-null-level-to-2026.js — the sharpest pair: their
candidate set (active + tracked + available 0/null/negative)
IS BY CONSTRUCTION the output of any $0-orderable stopgap, so
one unfiltered run reverses a whole remediation in one shot.
PRICE WAS STRUCTURALLY UNAVAILABLE in three of them and had to be added:
- sweep-all-active / active-2026-and-all-channels / inventory-set-2026 selected only
inventoryItem{id}; they now also select variant title+price and product vendor+tags.
- the two zero-inventory appliers read candidate records from scan-active-zero-inventory.js,
which captured no price at all. That scanner now records variant price + product
vendor/tags. Candidates from a PRE-GUARD scan have no price field: the appliers treat that
as unproveable and SKIP them loudly rather than stock blind (fail-safe, not fail-open).
Adopts the shared guard lib/inventory-stamp-guard.mjs; per-item activate calls now use the
row's own safe quantity instead of a flat 2026, so activation cannot re-arm a $0 variant.
Steve's 2026-06-20 "active products are never out of stock" rule is preserved for PRICED goods.
Adds shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs — the deliverable. It
EXTRACTS each writer's real guard block from the file (no hand-copying), compiles it against
that repo's own guard copy, and asserts 4 required + 3 regression fixtures; it also hashes the
three repo copies of the guard for drift. All 18 patched writers: 4/4 + 3/3, RESULT: PASS.
SOURCE-ONLY: no script run with --apply, no Shopify write, no dw_unified write, no deploy.
Reversible: git revert. On branch tk11357-source-fix-d (main was the checked-out branch).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Files touched
M shopify/scripts/activate-null-level-to-2026.jsM shopify/scripts/active-2026-and-all-channels.jsM shopify/scripts/inventory-set-2026.jsM shopify/scripts/scan-active-zero-inventory.jsM shopify/scripts/set-active-zero-to-2026.jsM shopify/scripts/sweep-all-active.mjsA shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs
Diff
commit 39156e236d6e392ebca9ac45a0dc9efa35a1c723
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Sep 10 09:30:28 2026 -0700
guard TK-11357 Fix D: $0 gate in the 5 catalog-wide inventory sweeps + the proof harness
These are the highest-blast-radius re-inflaters of the $0-orderable defect (lineage
TK-10825/10965/11140/11299/11301/11357). PRE-EXISTING GUARD STATE — verified per file, all five
had NO price gate anywhere on the write path:
sweep-all-active.mjs full-catalog (~75k active products / ~130k variants):
inventorySetQuantities on_hand=2026 on EVERY active variant,
plus inventoryActivate(available:2026) for not-stocked items.
active-2026-and-all-channels.js store-wide: available=TARGET_QTY on every variant of every
active product + an activate(qty:TARGET_QTY) per-item fallback.
inventory-set-2026.js every SKU in /tmp/inv_skus.txt → on_hand=2026.
set-active-zero-to-2026.js + activate-null-level-to-2026.js — the sharpest pair: their
candidate set (active + tracked + available 0/null/negative)
IS BY CONSTRUCTION the output of any $0-orderable stopgap, so
one unfiltered run reverses a whole remediation in one shot.
PRICE WAS STRUCTURALLY UNAVAILABLE in three of them and had to be added:
- sweep-all-active / active-2026-and-all-channels / inventory-set-2026 selected only
inventoryItem{id}; they now also select variant title+price and product vendor+tags.
- the two zero-inventory appliers read candidate records from scan-active-zero-inventory.js,
which captured no price at all. That scanner now records variant price + product
vendor/tags. Candidates from a PRE-GUARD scan have no price field: the appliers treat that
as unproveable and SKIP them loudly rather than stock blind (fail-safe, not fail-open).
Adopts the shared guard lib/inventory-stamp-guard.mjs; per-item activate calls now use the
row's own safe quantity instead of a flat 2026, so activation cannot re-arm a $0 variant.
Steve's 2026-06-20 "active products are never out of stock" rule is preserved for PRICED goods.
Adds shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs — the deliverable. It
EXTRACTS each writer's real guard block from the file (no hand-copying), compiles it against
that repo's own guard copy, and asserts 4 required + 3 regression fixtures; it also hashes the
three repo copies of the guard for drift. All 18 patched writers: 4/4 + 3/3, RESULT: PASS.
SOURCE-ONLY: no script run with --apply, no Shopify write, no dw_unified write, no deploy.
Reversible: git revert. On branch tk11357-source-fix-d (main was the checked-out branch).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---
shopify/scripts/activate-null-level-to-2026.js | 42 ++++-
shopify/scripts/active-2026-and-all-channels.js | 39 ++++-
shopify/scripts/inventory-set-2026.js | 39 ++++-
shopify/scripts/scan-active-zero-inventory.js | 17 +-
shopify/scripts/set-active-zero-to-2026.js | 43 ++++-
shopify/scripts/sweep-all-active.mjs | 45 ++++-
.../tk11357-source-fix-proof/predicate-proof.mjs | 185 +++++++++++++++++++++
7 files changed, 382 insertions(+), 28 deletions(-)
diff --git a/shopify/scripts/activate-null-level-to-2026.js b/shopify/scripts/activate-null-level-to-2026.js
index 4e65080e..e578e412 100644
--- a/shopify/scripts/activate-null-level-to-2026.js
+++ b/shopify/scripts/activate-null-level-to-2026.js
@@ -25,6 +25,29 @@ async function gql(query, variables = {}) {
}
}
+const { safeStampQuantity } = require('./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 ACTIVATE = `
mutation($itemId: ID!, $locId: ID!, $qty: Int!) {
inventoryActivate(inventoryItemId: $itemId, locationId: $locId, available: $qty) {
@@ -36,14 +59,25 @@ const ACTIVATE = `
(async () => {
const scanFile = fs.readdirSync(__dirname).filter(n => /^active-zero-inventory-scan-.*\.json$/.test(n)).sort().pop();
const scan = JSON.parse(fs.readFileSync(path.join(__dirname, scanFile), 'utf8'));
- const targets = scan.candidates.filter(c => c.available === null && c.inventoryItemGid);
- console.log(`inventoryActivate available=${TARGET_QTY} for ${targets.length} not-stocked variants`);
+ const raw = scan.candidates.filter(c => c.available === null && c.inventoryItemGid);
+ // GUARD TK-11357: activating a $0/quote-only variant AT 2026 is exactly what makes it
+ // availableForSale=true → orderable at $0. Activate each item at its SAFE quantity, and
+ // refuse to touch candidates from a pre-guard scan that carries no price at all.
+ const stale = raw.filter(c => c.price === undefined || c.price === null);
+ const targets = raw.filter(c => c.price !== undefined && c.price !== null);
+ if (stale.length) console.log(`⛔ TK-11357 guard: ${stale.length} candidates carry no price (scan predates the guard) — SKIPPED. Re-run scan-active-zero-inventory.js.`);
+ const safeFor = c => safeQuantities({ vendor: c.vendor, tags: c.tags },
+ [{ title: c.variantTitle, price: c.price, inventoryItem: { id: c.inventoryItemGid } }],
+ LOCATION_ID, TARGET_QTY)[0].quantity;
+ const heldZero = targets.filter(c => safeFor(c) === 0).length;
+ console.log(`inventoryActivate for ${targets.length} not-stocked variants (${targets.length - heldZero} at ${TARGET_QTY}, ${heldZero} at 0 by the TK-11357 $0/quote-only guard)`);
let ok = 0; const errors = [];
for (const c of targets) {
- const { data } = await gql(ACTIVATE, { itemId: c.inventoryItemGid, locId: LOCATION_ID, qty: TARGET_QTY });
+ const qty = safeFor(c);
+ const { data } = await gql(ACTIVATE, { itemId: c.inventoryItemGid, locId: LOCATION_ID, qty });
const errs = data.inventoryActivate?.userErrors || [];
if (errs.length) { errors.push({ sku: c.sku, errs }); console.log(` ❌ ${c.sku} — ${errs[0].message}`); }
- else { ok++; console.log(` ✅ ${c.sku}`); }
+ else { ok++; console.log(` ✅ ${c.sku}${qty === 0 ? ' (activated at 0 — $0/quote-only, NOT orderable)' : ''}`); }
await sleep(350);
}
console.log(`\n activated=${ok} errors=${errors.length}`);
diff --git a/shopify/scripts/active-2026-and-all-channels.js b/shopify/scripts/active-2026-and-all-channels.js
index 9556b350..3250a5a7 100644
--- a/shopify/scripts/active-2026-and-all-channels.js
+++ b/shopify/scripts/active-2026-and-all-channels.js
@@ -73,6 +73,29 @@ const FAILLOG = path.join(OUTDIR, 'failures.jsonl');
const logFail = o => fs.appendFileSync(FAILLOG, JSON.stringify(o) + '\n');
let invBuf = []; // {inventoryItemId, locationId, quantity}
+const { safeStampQuantity } = require('./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 ────────────────────────────────────────────
+
let stats = { products: 0, published: 0, pubErrors: 0, invSet: 0, invActivated: 0, invErrors: 0 };
async function flushInv() {
@@ -88,7 +111,8 @@ async function flushInv() {
const sr = await gqlR(M_SET, { input: { reason: 'correction', name: 'available', ignoreCompareQuantity: true, quantities: [q] } });
const se = sr.data?.inventorySetQuantities?.userErrors || [];
if (!se.length) { stats.invSet++; continue; }
- const ar = await gqlR(M_ACTIVATE, { itemId: q.inventoryItemId, locId: LOCATION_ID, qty: TARGET_QTY });
+ // GUARD TK-11357: activate at the ROW's safe quantity (0 for $0/quote-only), never a flat TARGET_QTY.
+ const ar = await gqlR(M_ACTIVATE, { itemId: q.inventoryItemId, locId: LOCATION_ID, qty: q.quantity });
const ae = ar.data?.inventoryActivate?.userErrors || [];
if (ae.length) { stats.invErrors++; logFail({ inventoryItemId: q.inventoryItemId, ae, se }); }
else stats.invActivated++;
@@ -101,7 +125,7 @@ async function flushInv() {
let cursor = null, page = 0;
outer: while (true) {
const q = `query($c:String){products(first:25,after:$c,query:"status:active"){pageInfo{hasNextPage endCursor}
- nodes{id vendor ${PUB_ALIASES} variants(first:50){nodes{id inventoryItem{id}}}}}}`;
+ nodes{id vendor tags ${PUB_ALIASES} variants(first:50){nodes{id title price inventoryItem{id}}}}}}`;
const r = await gqlR(q, { c: cursor });
if (r.errors) { console.log('PAGE ERR', JSON.stringify(r.errors).slice(0, 200)); break; }
const pg = r.data.products;
@@ -121,11 +145,12 @@ async function flushInv() {
else stats.published++;
} else stats.published++;
}
- // inventory: every variant → 2026
- for (const v of n.variants.nodes) {
- if (!v.inventoryItem?.id) continue;
- if (APPLY) { invBuf.push({ inventoryItemId: v.inventoryItem.id, locationId: LOCATION_ID, quantity: TARGET_QTY });
- if (invBuf.length >= SET_BATCH) await flushInv(); }
+ // inventory: every PRICED variant → 2026. GUARD TK-11357: this store-wide sweep previously
+ // set available=TARGET_QTY on EVERY variant of EVERY active product with no price filter,
+ // so it re-armed any $0/quote-only variant to availableForSale=true (orderable at $0).
+ const safeRows = safeQuantities(n, n.variants.nodes.filter(v => v.inventoryItem?.id), LOCATION_ID, TARGET_QTY);
+ for (const row of safeRows) {
+ if (APPLY) { invBuf.push(row); if (invBuf.length >= SET_BATCH) await flushInv(); }
else stats.invSet++;
}
if (stats.products % 500 === 0) console.log(` …${stats.products} products | published+${stats.published} | invSet ${stats.invSet} activated ${stats.invActivated} | errs pub ${stats.pubErrors} inv ${stats.invErrors}`);
diff --git a/shopify/scripts/inventory-set-2026.js b/shopify/scripts/inventory-set-2026.js
index 4bb07862..f8c85704 100644
--- a/shopify/scripts/inventory-set-2026.js
+++ b/shopify/scripts/inventory-set-2026.js
@@ -47,9 +47,32 @@ async function gql(query, variables = {}) {
const VARIANT_LOOKUP = `
query($q: String!) {
productVariants(first: 250, query: $q) {
- edges { node { sku inventoryItem { id } } }
+ edges { node { sku title price inventoryItem { id } product { vendor tags } } }
}
}`;
+const { safeStampQuantity } = require('./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 SET_QTY = `
mutation($input: InventorySetQuantitiesInput!) {
@@ -67,7 +90,9 @@ async function buildMap() {
const q = batch.map(s => `sku:"${s.replace(/"/g, '\\"')}"`).join(' OR ');
const { data } = await gql(VARIANT_LOOKUP, { q });
for (const e of data.productVariants.edges) {
- if (e.node.sku && e.node.inventoryItem?.id) map.set(e.node.sku, e.node.inventoryItem.id);
+ // GUARD TK-11357: keep the whole node (title/price/product), not just the id — the safe
+ // quantity is uncomputable from an id alone.
+ if (e.node.sku && e.node.inventoryItem?.id) map.set(e.node.sku, e.node);
}
if ((i / SKU_BATCH) % 10 === 0 || i + SKU_BATCH >= SKUS.length) {
process.stdout.write(`\r lookup ${Math.min(i + SKU_BATCH, SKUS.length)}/${SKUS.length} map=${map.size}`);
@@ -78,9 +103,15 @@ async function buildMap() {
}
async function applyQuantities(map, dry) {
- const pairs = SKUS.filter(s => map.has(s)).map(s => ({ inventoryItemId: map.get(s), locationId: LOCATION_ID, quantity: TARGET_QTY }));
+ // GUARD TK-11357: previously every mapped SKU from /tmp/inv_skus.txt was set to TARGET_QTY with
+ // no price check — a $0/quote-only SKU in that list was re-armed to orderable-at-$0.
+ const pairs = SKUS.filter(s => map.has(s)).map(s => {
+ const n = map.get(s);
+ return safeQuantities(n.product, [n], LOCATION_ID, TARGET_QTY)[0];
+ });
+ const zeroed = pairs.filter(q => q.quantity === 0).length;
const unmatched = SKUS.filter(s => !map.has(s));
- console.log(`\n will set ${pairs.length} inventory items to ${TARGET_QTY}`);
+ console.log(`\n will set ${pairs.length} inventory items (${pairs.length - zeroed} to ${TARGET_QTY}, ${zeroed} held at 0 by the TK-11357 $0/quote-only guard)`);
console.log(` unmatched SKUs: ${unmatched.length}`);
if (dry) {
console.log(' DRY — first 3 pairs:', pairs.slice(0, 3));
diff --git a/shopify/scripts/scan-active-zero-inventory.js b/shopify/scripts/scan-active-zero-inventory.js
index 67335185..89d6c361 100644
--- a/shopify/scripts/scan-active-zero-inventory.js
+++ b/shopify/scripts/scan-active-zero-inventory.js
@@ -1,4 +1,12 @@
#!/usr/bin/env node
+/**
+ * GUARD TK-11357: the scan now also captures variant `price` and product `vendor`/`tags`.
+ * Without them the downstream appliers (set-active-zero-to-2026.js /
+ * activate-null-level-to-2026.js) CANNOT tell a legitimately-oversold priced roll from a
+ * $0 quote-only variant, and re-stocking the latter is what makes it orderable at $0.
+ * Candidates written by an OLDER build of this scanner have no `price` field; the appliers
+ * treat that as unproveable and SKIP rather than stock blind.
+ */
/**
* READ-ONLY scan: enumerate all ACTIVE products (newest → oldest) and report
* every variant whose AVAILABLE inventory at the Ventura Blvd. location is 0
@@ -48,11 +56,14 @@ const QUERY = `
id
title
createdAt
+ vendor
+ tags
variants(first: 25) {
edges {
node {
sku
title
+ price
inventoryItem {
id
tracked
@@ -94,14 +105,14 @@ const QUERY = `
if (avail === null || avail === undefined) {
// tracked but no level row at this location
noLevelTracked++;
- candidates.push({ sku: v.sku, variantTitle: v.title, productId: p.id, productTitle: p.title,
+ candidates.push({ sku: v.sku, variantTitle: v.title, price: v.price, vendor: p.vendor, tags: p.tags, productId: p.id, productTitle: p.title,
createdAt: p.createdAt, inventoryItemGid: ii.id, available: null, on_hand: onhand ?? null });
} else if (avail === 0) {
- candidates.push({ sku: v.sku, variantTitle: v.title, productId: p.id, productTitle: p.title,
+ candidates.push({ sku: v.sku, variantTitle: v.title, price: v.price, vendor: p.vendor, tags: p.tags, productId: p.id, productTitle: p.title,
createdAt: p.createdAt, inventoryItemGid: ii.id, available: 0, on_hand: onhand ?? null });
} else if (avail < 0) {
negativeFound++;
- candidates.push({ sku: v.sku, variantTitle: v.title, productId: p.id, productTitle: p.title,
+ candidates.push({ sku: v.sku, variantTitle: v.title, price: v.price, vendor: p.vendor, tags: p.tags, productId: p.id, productTitle: p.title,
createdAt: p.createdAt, inventoryItemGid: ii.id, available: avail, on_hand: onhand ?? null });
} else {
positiveSkipped++;
diff --git a/shopify/scripts/set-active-zero-to-2026.js b/shopify/scripts/set-active-zero-to-2026.js
index 0d7b84e1..30988c90 100644
--- a/shopify/scripts/set-active-zero-to-2026.js
+++ b/shopify/scripts/set-active-zero-to-2026.js
@@ -49,6 +49,29 @@ async function gql(query, variables = {}) {
}
}
+const { safeStampQuantity } = require('./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 SET_QTY = `
mutation($input: InventorySetQuantitiesInput!) {
inventorySetQuantities(input: $input) {
@@ -65,11 +88,23 @@ const SET_QTY = `
// Exclude available===null (not stocked at location) — those go through
// inventoryActivate separately; inventorySetQuantities is atomic per batch
// so a single not-stocked item rolls back the whole 250-item chunk.
- const cands = scan.candidates.filter(c => c.inventoryItemGid && c.available !== null);
+ const raw = scan.candidates.filter(c => c.inventoryItemGid && c.available !== null);
+ // GUARD TK-11357: this script's candidate set IS, by construction, the output of any
+ // $0-orderable stopgap (active + tracked + available 0/negative), so running it unfiltered
+ // reversed the whole fix in one shot. Filter per variant with the shared guard.
+ const stale = raw.filter(c => c.price === undefined || c.price === null);
+ const cands = raw.filter(c => c.price !== undefined && c.price !== null);
+ if (stale.length) console.log(` ⛔ TK-11357 guard: ${stale.length} candidates carry no price (scan predates the guard) — SKIPPED, not stocked blind. Re-run scan-active-zero-inventory.js.`);
console.log(`${dry ? '[DRY] ' : ''}set available=${TARGET_QTY} for ${cands.length} variants @ ${LOCATION_ID}`);
console.log(` source scan: ${path.basename(scanPath)}`);
- const pairs = cands.map(c => ({ inventoryItemId: c.inventoryItemGid, locationId: LOCATION_ID, quantity: TARGET_QTY }));
+ const allPairs = cands.map(c => safeQuantities(
+ { vendor: c.vendor, tags: c.tags },
+ [{ title: c.variantTitle, price: c.price, inventoryItem: { id: c.inventoryItemGid } }],
+ LOCATION_ID, TARGET_QTY)[0]);
+ const pairs = allPairs.filter(q => q.quantity > 0);
+ const heldZero = allPairs.length - pairs.length;
+ if (heldZero) console.log(` ⛔ TK-11357 guard: ${heldZero} $0/quote-only variants held at 0 (never stocked)`);
if (dry) {
console.log(' first 3:', cands.slice(0, 3).map(c => `${c.sku}(${c.available})`).join(', '));
@@ -99,7 +134,9 @@ const SET_QTY = `
fs.writeFileSync(out, JSON.stringify({
location: LOCATION_ID, targetQty: TARGET_QTY, sourceScan: path.basename(scanPath),
attempted: pairs.length, set, errors, adjustmentGroupIds: groups,
- skus: cands.map(c => c.sku),
+ // GUARD TK-11357: report what was ACTUALLY attempted, plus what the guard withheld
+ guardHeldZero: heldZero, guardSkippedNoPrice: stale.length,
+ skus: pairs.map(q => (cands.find(c => c.inventoryItemGid === q.inventoryItemId) || {}).sku),
}, null, 2));
console.log(`\n attempted=${pairs.length} set≈${set} errors=${errors.length} groups=${groups.length}`);
if (errors.length) console.log(' first errors:', errors.slice(0, 5));
diff --git a/shopify/scripts/sweep-all-active.mjs b/shopify/scripts/sweep-all-active.mjs
index 415ad4dc..f53cdd42 100644
--- a/shopify/scripts/sweep-all-active.mjs
+++ b/shopify/scripts/sweep-all-active.mjs
@@ -30,10 +30,34 @@ const h={'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'};
async function gql(q,v){for(let a=0;a<8;a++){const r=await fetch(`https://${STORE}/admin/api/${API}/graphql.json`,{method:'POST',headers:h,body:JSON.stringify({query:q,variables:v})});const j=await r.json();if((j.errors&&JSON.stringify(j.errors).includes('Throttled'))||(j.errors&&JSON.stringify(j.errors).includes('throttled'))){await new Promise(s=>setTimeout(s,3000));continue;}return j;}return{errors:'retry-exhausted'};}
// 1) scan ALL active products → variant inventoryItem id + tracked
-const SCAN=`query($c:String){products(first:40,after:$c,query:"status:active"){pageInfo{hasNextPage endCursor} nodes{variants(first:15){nodes{sku inventoryItem{id tracked}}}}}}`;
+const SCAN=`query($c:String){products(first:40,after:$c,query:"status:active"){pageInfo{hasNextPage endCursor} nodes{vendor tags variants(first:15){nodes{sku title price inventoryItem{id tracked}}}}}}`;
+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 ────────────────────────────────────────────
+
let cur=null,vars=[],pages=0;
while(true){const j=await gql(SCAN,{c:cur});const d=j.data?.products;if(!d){console.log('scan error',JSON.stringify(j.errors).slice(0,200));break;}
- for(const p of d.nodes)for(const v of p.variants.nodes){if(v.inventoryItem?.id)vars.push({iid:v.inventoryItem.id,sku:v.sku||'',tracked:v.inventoryItem.tracked});}
+ // GUARD TK-11357: carry title/price + the parent product so the safe quantity is computable.
+ for(const p of d.nodes)for(const v of p.variants.nodes){if(v.inventoryItem?.id)vars.push({iid:v.inventoryItem.id,sku:v.sku||'',tracked:v.inventoryItem.tracked,title:v.title,price:v.price,product:{vendor:p.vendor,tags:p.tags}});}
pages++;if(pages%25===0)process.stdout.write(` scan ${vars.length} variants (${pages} pages)\r`);
if(!d.pageInfo.hasNextPage)break;cur=d.pageInfo.endCursor;}
const cork=vars.filter(v=>/^cork-/i.test(v.sku));
@@ -52,7 +76,13 @@ if(untracked.length)console.log(`\ntracking enabled: ${trkOk} ok, ${trkErr.lengt
// 3) set on_hand=2026 on ALL active variants — chunks of 200
const INV_SET=`mutation($input:InventorySetQuantitiesInput!){inventorySetQuantities(input:$input){userErrors{field message code}}}`;
-const pairs=vars.map(v=>({inventoryItemId:v.iid,locationId:LOC,quantity:2026}));
+// GUARD TK-11357: this is a FULL-CATALOG sweep (~75k active products / ~130k variants) that
+// previously set on_hand=2026 on EVERY active variant with no price filter whatsoever — the
+// single highest-blast-radius re-inflater of the $0-orderable defect. Decide per variant.
+const safeQty=new Map();
+const pairs=vars.map(v=>{const q=safeQuantities(v.product,[{title:v.title,price:v.price,inventoryItem:{id:v.iid}}],LOC,2026)[0];safeQty.set(v.iid,q.quantity);return q;});
+const zeroed=pairs.filter(q=>q.quantity===0).length;
+console.log(` guard TK-11357: ${zeroed}/${pairs.length} variants are $0/quote-only → will be set 0, NOT 2026`);
let setOk=0;const notStocked=[];const otherErr=[];
for(let i=0;i<pairs.length;i+=200){const chunk=pairs.slice(i,i+200);
const r=await gql(INV_SET,{input:{name:'on_hand',reason:'correction',ignoreCompareQuantity:true,quantities:chunk}});
@@ -67,10 +97,11 @@ console.log(`\ninventory set on_hand=2026: ~${setOk}/${pairs.length} ok notStoc
// 4) activate any not-stocked items at the location, then set
const ns=notStocked.filter(Boolean);
if(ns.length){console.log(`activating ${ns.length} not-stocked items at Ventura Blvd, then setting...`);
- const ACT=`mutation($id:ID!,$loc:ID!){inventoryActivate(inventoryItemId:$id,locationId:$loc,available:2026){userErrors{message}}}`;
- let actOk=0;for(const id of ns){const r=await gql(ACT,{id,loc:LOC});if(!(r.data?.inventoryActivate?.userErrors||[]).length)actOk++;}
- // re-set on_hand for those
- const p2=ns.map(id=>({inventoryItemId:id,locationId:LOC,quantity:2026}));
+ // GUARD TK-11357: activate at the item's SAFE quantity (0 for $0/quote-only), never a flat 2026.
+ const ACT=`mutation($id:ID!,$loc:ID!,$qty:Int!){inventoryActivate(inventoryItemId:$id,locationId:$loc,available:$qty){userErrors{message}}}`;
+ let actOk=0;for(const id of ns){const r=await gql(ACT,{id,loc:LOC,qty:safeQty.get(id)??0});if(!(r.data?.inventoryActivate?.userErrors||[]).length)actOk++;}
+ // re-set on_hand for those (same safe quantity)
+ const p2=ns.map(id=>({inventoryItemId:id,locationId:LOC,quantity:safeQty.get(id)??0}));
for(let i=0;i<p2.length;i+=200){await gql(INV_SET,{input:{name:'on_hand',reason:'correction',ignoreCompareQuantity:true,quantities:p2.slice(i,i+200)}});}
console.log(` activated ${actOk}/${ns.length}, re-set on_hand=2026`);}
diff --git a/shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs b/shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs
new file mode 100644
index 00000000..3dc601fb
--- /dev/null
+++ b/shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs
@@ -0,0 +1,185 @@
+#!/usr/bin/env node
+/**
+ * TK-11357 "Fix D" fixture proof — closes the $0-orderable RE-INTRODUCTION paths at SOURCE.
+ *
+ * Modelled on the TK-11299 proof harness for commit 23987f86: it does NOT hand-copy the
+ * predicate. For every patched writer it READS THE FILE, EXTRACTS the real decision block
+ * verbatim (between the `GUARD TK-11357 BEGIN` / `END` markers), evaluates that extracted
+ * source with the file's OWN repo copy of the shared guard injected, and runs it against
+ * fixtures. If someone edits a guard block, this harness tests the edited code.
+ *
+ * The 4 required fixtures (all must pass for every file):
+ * F1 $0 tracked quote-only variant, just zeroed -> qty 0 (not re-stocked)
+ * F2 legitimately priced oversold product -> qty 2026 (Steve's 2026-06-20 rule intact)
+ * F3 mixed: priced variant + residual $0 "Per Yard" -> priced 2026, $0 rider 0
+ * F4 untracked POD/sample at $0 -> qty 0 (skipped)
+ * Plus 3 regression fixtures that must also hold:
+ * R5 real $4.25 memo sample -> 2026 (pre-existing behaviour deliberately UNCHANGED)
+ * R6 quote-only line, priced -> 0 (shared-guard tag/vendor rule delegated, not re-implemented)
+ * R7 NaN / missing price -> 0 (fail-safe)
+ *
+ * Also asserts the 3 repo copies of lib/inventory-stamp-guard.mjs have not DRIFTED.
+ *
+ * $0 (local). No network, no Shopify call, no DB. Run: node predicate-proof.mjs
+ * exit 0 = every file 4/4 (+3/3 regression); exit 1 = a file regressed.
+ */
+import fs from 'node:fs';
+import { createRequire } from 'node:module';
+const require = createRequire(import.meta.url);
+const HOME = process.env.HOME;
+
+// ── the writers this fix touched, with the repo whose guard copy they import ────────────
+const DWC = `${HOME}/Projects/designerwallcoverings`;
+const DWM = `${HOME}/Projects/Designer-Wallcoverings`;
+const SDG = `${HOME}/Projects/sanderson-onboard`;
+const GUARD = {
+ [DWC]: `${DWC}/scripts/lib/inventory-stamp-guard.mjs`,
+ [DWM]: `${DWM}/shopify/scripts/lib/inventory-stamp-guard.mjs`,
+ [SDG]: `${SDG}/scripts/lib/inventory-stamp-guard.mjs`,
+};
+const TARGETS = [
+ [DWC, 'scripts/maharam-onboard/go-live.mjs'],
+ [DWC, 'scripts/artmura-onboard/go-live-artmura.js'],
+ [DWC, 'scripts/fallingstar-onboard/go-live-fallingstar.js'],
+ [DWC, 'scripts/knoll-onboard/go-live.mjs'],
+ [DWC, 'scripts/muralsource-onboard/go-live.mjs'],
+ [DWC, 'scripts/osborne-onboard/go-live.mjs'],
+ [DWC, 'scripts/stout-onboard/go-live.mjs'],
+ [DWC, 'scripts/stroheim-onboard/go-live.mjs'],
+ [DWC, 'scripts/tres-tintas-onboard/go-live-tres-tintas.js'],
+ [DWC, 'scripts/activate-1838/activate-1838.mjs'],
+ [DWC, 'scripts/activate-1838/activate-1838-showroom.mjs'],
+ [DWC, 'scripts/osborne-onboard/roll-add-apply.mjs'],
+ [DWM, 'shopify/scripts/sweep-all-active.mjs'],
+ [DWM, 'shopify/scripts/active-2026-and-all-channels.js'],
+ [DWM, 'shopify/scripts/inventory-set-2026.js'],
+ [DWM, 'shopify/scripts/set-active-zero-to-2026.js'],
+ [DWM, 'shopify/scripts/activate-null-level-to-2026.js'],
+ [SDG, 'scripts/create_sdg.mjs'],
+];
+
+const LOC = 'gid://shopify/Location/5795643504';
+const DESIRED = 2026;
+const iv = (id, tracked = true) => ({ id: `gid://shopify/InventoryItem/${id}`, tracked });
+
+const FIXTURES = [
+ { id: 'F1', required: true,
+ name: '$0 tracked quote-only variant, just zeroed -> NOT re-stocked (qty 0)',
+ product: { vendor: 'Phillipe Romano', tags: ['quote-only', 'contract-vinyl'] },
+ variants: [{ title: 'Full Roll', price: '0.00', inventoryQuantity: 0, inventoryItem: iv(1) }],
+ expect: [0] },
+ { id: 'F2', required: true,
+ name: 'legitimately priced oversold product -> still stamped 2026 (Steve 2026-06-20 rule)',
+ product: { vendor: 'Osborne & Little', tags: ['wallcovering', 'new-arrival'] },
+ variants: [{ title: 'Roll', price: '185.00', inventoryQuantity: -3, inventoryItem: iv(2) }],
+ expect: [2026] },
+ { id: 'F3', required: true,
+ name: 'mixed: priced variant + residual $0 "Per Yard" rider -> priced variant ONLY',
+ product: { vendor: 'Stout Textiles', tags: ['fabric', 'new-arrival'] },
+ variants: [{ title: 'Roll', price: '92.00', inventoryQuantity: 0, inventoryItem: iv(3) },
+ { title: 'Per Yard', price: '0.00', inventoryQuantity: 0, inventoryItem: iv(4) }],
+ expect: [2026, 0] },
+ { id: 'F4', required: true,
+ name: 'untracked POD/sample at $0 -> skipped (qty 0)',
+ product: { vendor: 'Contrado', tags: ['pod'] },
+ variants: [{ title: 'Sample', price: '0.00', inventoryQuantity: 0, inventoryItem: iv(5, false) }],
+ expect: [0] },
+ { id: 'R5', required: false,
+ name: 'real $4.25 memo sample -> 2026 (pre-existing behaviour deliberately UNCHANGED)',
+ product: { vendor: 'Phillipe Romano', tags: ['quote-only'] },
+ variants: [{ title: 'Sample', price: '4.25', inventoryQuantity: 0, inventoryItem: iv(6) }],
+ expect: [2026] },
+ { id: 'R6', required: false,
+ name: 'quote-only line whose sellable IS priced -> 0 (shared-guard tag/vendor rule)',
+ product: { vendor: 'Fentucci Naturals', tags: ['quotes', 'Needs-Price'] },
+ variants: [{ title: 'Full Roll', price: '58.00', inventoryQuantity: 0, inventoryItem: iv(7) }],
+ expect: [0] },
+ { id: 'R7', required: false,
+ name: 'NaN / missing price on the sellable variant -> 0 (fail-safe)',
+ product: { vendor: 'Whoever', tags: [] },
+ variants: [{ title: 'Roll', price: undefined, inventoryQuantity: 0, inventoryItem: iv(8) }],
+ expect: [0] },
+];
+
+const BEGIN = /\/\/ [^\n]*GUARD TK-11357 BEGIN[^\n]*\n/;
+const END = /\/\/ [^\n]*GUARD TK-11357 END/;
+
+function extractBlock(abs) {
+ const src = fs.readFileSync(abs, 'utf8');
+ const b = src.match(BEGIN);
+ const e = src.match(END);
+ if (!b || !e) return null;
+ const start = b.index + b[0].length;
+ if (e.index <= start) throw new Error('END marker before BEGIN in ' + abs);
+ return src.slice(start, e.index);
+}
+
+/** Build the file's REAL safeQuantities from its own extracted source + its own repo guard. */
+function compile(block, guardPath) {
+ const { safeStampQuantity, isPriceSuppressed, isSellableVariant } = require(guardPath);
+ // eslint-disable-next-line no-new-func
+ const factory = new Function('safeStampQuantity', 'isPriceSuppressed', 'isSellableVariant',
+ `${block}\n;return safeQuantities;`);
+ const fn = factory(safeStampQuantity, isPriceSuppressed, isSellableVariant);
+ if (typeof fn !== 'function') throw new Error('extracted block did not define safeQuantities()');
+ return fn;
+}
+
+// ── 0) guard-copy drift check ───────────────────────────────────────────────────────────
+function driftCheck() {
+ const mods = Object.values(GUARD).map(p => ({ p, m: require(p) }));
+ const matrix = [];
+ for (const f of FIXTURES) for (const v of f.variants) matrix.push([v, f.product]);
+ let ok = true;
+ for (const [v, p] of matrix) {
+ const out = mods.map(({ m }) => m.safeStampQuantity({ title: v.title, price: v.price }, p, DESIRED));
+ if (new Set(out).size !== 1) { ok = false; console.log(` ✗ DRIFT on ${v.title} $${v.price}: ${JSON.stringify(out)}`); }
+ }
+ console.log(`${ok ? ' ✓' : ' ✗'} guard copies in sync across ${mods.length} repos (${matrix.length} probes)`);
+ return ok;
+}
+
+// ── run ─────────────────────────────────────────────────────────────────────────────────
+console.log('TK-11357 Fix D — source-fix fixture proof');
+console.log('='.repeat(78));
+console.log('\nshared-guard copy drift check');
+let allOk = driftCheck();
+
+const results = [];
+for (const [repo, rel] of TARGETS) {
+ const abs = `${repo}/${rel}`;
+ if (!fs.existsSync(abs)) { results.push({ rel, state: 'MISSING FILE' }); allOk = false; continue; }
+ let block;
+ try { block = extractBlock(abs); } catch (e) { results.push({ rel, state: 'EXTRACT ERR: ' + e.message }); allOk = false; continue; }
+ if (block === null) { results.push({ rel, state: 'NO GUARD BLOCK' }); allOk = false; continue; }
+ let fn;
+ try { fn = compile(block, GUARD[repo]); } catch (e) { results.push({ rel, state: 'COMPILE ERR: ' + e.message }); allOk = false; continue; }
+
+ let req = 0, reqTotal = 0, reg = 0, regTotal = 0; const fails = [];
+ for (const f of FIXTURES) {
+ const got = fn(f.product, f.variants, LOC, DESIRED);
+ const gotQ = got.map(r => r.quantity);
+ const pass = JSON.stringify(gotQ) === JSON.stringify(f.expect);
+ // structural assertion: the guard must still emit one row per variant, with ids + location intact
+ const shapeOk = got.length === f.variants.length
+ && got.every((r, i) => r.inventoryItemId === f.variants[i].inventoryItem.id && r.locationId === LOC);
+ if (f.required) { reqTotal++; if (pass && shapeOk) req++; else fails.push(`${f.id} exp ${JSON.stringify(f.expect)} got ${JSON.stringify(gotQ)}${shapeOk ? '' : ' [SHAPE]'}`); }
+ else { regTotal++; if (pass && shapeOk) reg++; else fails.push(`${f.id} exp ${JSON.stringify(f.expect)} got ${JSON.stringify(gotQ)}${shapeOk ? '' : ' [SHAPE]'}`); }
+ }
+ const state = `${req}/${reqTotal} required · ${reg}/${regTotal} regression`;
+ if (req !== reqTotal || reg !== regTotal) allOk = false;
+ results.push({ rel, state, fails, bytes: block.length });
+}
+
+console.log('\nper-file extracted-predicate results');
+console.log('-'.repeat(78));
+for (const r of results) {
+ const bad = /ERR|MISSING|NO GUARD/.test(r.state) || (r.fails && r.fails.length);
+ console.log(`${bad ? '✗' : '✓'} ${r.rel.padEnd(52)} ${r.state}`);
+ if (r.fails) for (const f of r.fails) console.log(` ! ${f}`);
+}
+console.log('-'.repeat(78));
+console.log(FIXTURES.map(f => `${f.id} ${f.name}`).join('\n'));
+console.log('-'.repeat(78));
+console.log(allOk ? 'RESULT: PASS — every patched writer is 4/4 required + 3/3 regression' : 'RESULT: FAIL');
+process.exit(allOk ? 0 : 1);
← 013f4c34 auto-data-snapshot: 2026-09-10T09:22:46 (1 data files) — sho
·
back to Designer Wallcoverings
·
guard TK-11357 Fix D: 3 kravet activators found by a widened bafa2f60 →