← back to Sister Parish Onboarding
Make SP guard shim path portable and skip $0-orderable variants in the inventory re-stamper
b126d749e6e8c71ce5bd629f45656f930d0868ca · 2026-09-22 14:52:23 -0700 · Steve
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DP24DdLpG6PHXgmjr47TVb
Files touched
M scripts/check_sp_inventory.jsM scripts/lib/inventory-stamp-guard.mjs
Diff
commit b126d749e6e8c71ce5bd629f45656f930d0868ca
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Sep 22 14:52:23 2026 -0700
Make SP guard shim path portable and skip $0-orderable variants in the inventory re-stamper
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DP24DdLpG6PHXgmjr47TVb
---
scripts/check_sp_inventory.js | 12 +++--
scripts/lib/inventory-stamp-guard.mjs | 83 +++--------------------------------
2 files changed, 14 insertions(+), 81 deletions(-)
diff --git a/scripts/check_sp_inventory.js b/scripts/check_sp_inventory.js
index f9188d1..ddb2b75 100644
--- a/scripts/check_sp_inventory.js
+++ b/scripts/check_sp_inventory.js
@@ -5,6 +5,7 @@
* With --fix, sets every off-target variant to 2026.
*/
require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
+const { isZeroPriceOrderableRisk } = require('./lib/inventory-stamp-guard.mjs');
const SHOP = process.env.SHOPIFY_STORE;
const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
@@ -42,7 +43,7 @@ const nextPage = link => {
if (url) await sleep(RATE_DELAY_MS);
}
- const variants = products.flatMap(p => p.variants.map(v => ({ ...v, _title: p.title })));
+ const variants = products.flatMap(p => p.variants.map(v => ({ ...v, _title: p.title, _tags: p.tags, _vendor: p.vendor })));
const onTarget = variants.filter(v => v.inventory_quantity === TARGET);
const off = variants.filter(v => v.inventory_quantity !== TARGET);
const untracked = variants.filter(v => v.inventory_management !== 'shopify');
@@ -64,10 +65,15 @@ const nextPage = link => {
const lvl = (await shopify('GET', `/inventory_levels.json?inventory_item_ids=${sampleIid}`)).json.inventory_levels[0];
const locationId = lvl.location_id;
await sleep(RATE_DELAY_MS);
- let ok = 0, fail = 0;
+ let ok = 0, fail = 0, skipped = 0;
for (let i = 0; i < off.length; i++) {
const v = off[i];
const stamp = `[${String(i+1).padStart(3,'0')}/${off.length}]`;
+ // $0-orderable guard: never stamp positive stock on a $0/quote-only sellable variant.
+ if (isZeroPriceOrderableRisk(v, { tags: v._tags ?? [], vendor: v._vendor })) {
+ skipped++; console.log(`${stamp} - skip ${v._title} [${v.sku || v.id}]: $0-orderable risk, left at ${v.inventory_quantity}`);
+ continue;
+ }
try {
await shopify('POST', '/inventory_levels/set.json', { location_id: locationId, inventory_item_id: v.inventory_item_id, available: TARGET });
ok++; console.log(`${stamp} ✓ ${v._title} [${v.sku || v.id}] → ${TARGET}`);
@@ -76,5 +82,5 @@ const nextPage = link => {
}
await sleep(RATE_DELAY_MS);
}
- console.log(`\nFixed: ${ok} Failed: ${fail}`);
+ console.log(`\nFixed: ${ok} Failed: ${fail} Skipped ($0-orderable guard): ${skipped}`);
})().catch(e => { console.error('FATAL', e); process.exit(1); });
diff --git a/scripts/lib/inventory-stamp-guard.mjs b/scripts/lib/inventory-stamp-guard.mjs
index 195b65f..e60470e 100644
--- a/scripts/lib/inventory-stamp-guard.mjs
+++ b/scripts/lib/inventory-stamp-guard.mjs
@@ -1,78 +1,5 @@
-// VENDORED COPY — canonical source: Designer-Wallcoverings/shopify/scripts/lib/inventory-stamp-guard.mjs
-// Vendored (not cross-repo-imported): this repo runs independently; a cross-repo relative import
-// would hard-crash if either tree moved. KEEP IN SYNC — the TK-11357 harness hashes every copy.
-// 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;
-}
+// inventory-stamp-guard.mjs — RE-EXPORT SHIM -> canonical master (2026-09-22 Option-B decouple).
+// Executable code verified IDENTICAL to the master (comment-only diff) + predicate-proof GREEN
+// after this shim. The $0-orderable guard (TK-11357) now has ONE source of truth.
+// Original preserved as inventory-stamp-guard.mjs.pre-shared-lift.
+export * from '../../../_shared/dw-guards/inventory-stamp-guard.mjs';
← a37d4b2 TK-12032: require shared fail-closed leak guard before Shopi
·
back to Sister Parish Onboarding
·
Fix: replace relative-path shim with portable resolution (TK 4dbf7fa →