← back to Designer Wallcoverings
guard TK-11357 Fix D: 3 kravet activators found by a widened writer enumeration + harden the hourly newest-1000 guard against a price-LESS variant
bafa2f6022d66fa7a6352a63c90c7729a0eb63a5 · 2026-09-10 09:37:23 -0700 · Steve
My first enumeration grepped only the GraphQL inventory mutations, which is the same blind spot
this lineage has hit before (a .js/GraphQL-only grep misses a REST inventory_levels writer such
as fentucci-naturals/scripts/go-live.py). Re-ran across *.mjs *.js *.cjs *.py including the REST
inventory_levels/{set,adjust,connect} endpoints. That surfaced 10 writers outside the brief's
list. Seven are structurally safe and deliberately untouched (active-gate-add-samples,
add-missing-samples-v2, create-missing-sample-variants, fix-missing-sample-variants,
fix-missing-samples, resume-sample-variant-creation — all create and stock only the $4.25 SAMPLE
variant at a hardcoded price; and fentucci go-live.py, which already drops the $0/yard variant).
Three were genuinely unguarded:
kravet-3b-activate-validated.js had a PRODUCT-level gate only ("a real roll variant priced
> $5"), then setInvAll() stamped a flat TARGET_QTY on EVERY variant with an inventoryItem —
the same variant-level hole as activate-1838.
kravet25-reactivate.js NO price check on the stamp at all.
kravet-full-monty-single.js NO price check, flat qty=1000 on every variant. (The guard's
`desired` argument carries 1000 here, so the file's own quantity is preserved.)
CORRECTNESS CATCH in kravet25-reactivate: step 2 repricies the main variant to MAP and step 5
stocks from the PRE-reprice snapshot, so a naive guard would refuse to stock a roll that had just
been legitimately priced — breaking Steve's 2026-06-20 rule. The guard is fed the POST-reprice
price; `map > 0` is already asserted upstream, so this cannot launder a real $0. (Also fixed an
undefined `prod` reference I introduced in the same hunk — caught by reading the call site, not
by node --check.)
Separately, WIDENED the existing TK-10965 guard in inventory-set-2026-newest.mjs (the hourly
cadence sibling): `Number(v.price) === 0` let a variant with an ABSENT/unparseable price through,
because Number(undefined) is NaN and NaN === 0 is false — so a price-less variant was still
stamped 2026. Now `!(Number(v.price) > 0)`, which skips 0, '', null, undefined and NaN. Left
price-ONLY on purpose (it is a LIVE hourly job and has no product tags in scope); the harness
declares fixture R6 N/A for that file rather than pretending it passes.
Harness extended with a PREDICATE mode that extracts a real `if (<expr>) continue;` skip-filter —
the exact shape of the TK-11299 reference commit 23987f86 — and evaluates it. All 22 writers now
covered: 4/4 required each; 3/3 regression (block mode), 2/2 + R6 N/A (predicate mode).
RESULT: PASS.
SOURCE-ONLY: nothing run with --apply, no Shopify write. Reversible: git revert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Files touched
M shopify/scripts/data/kravet-map-pricing/kravet-3b-activate-validated.jsM shopify/scripts/data/kravet-map-pricing/kravet25-reactivate.jsM shopify/scripts/inventory-set-2026-newest.mjsM shopify/scripts/kravet-full-monty-single.jsM shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs
Diff
commit bafa2f6022d66fa7a6352a63c90c7729a0eb63a5
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Sep 10 09:37:23 2026 -0700
guard TK-11357 Fix D: 3 kravet activators found by a widened writer enumeration + harden the
hourly newest-1000 guard against a price-LESS variant
My first enumeration grepped only the GraphQL inventory mutations, which is the same blind spot
this lineage has hit before (a .js/GraphQL-only grep misses a REST inventory_levels writer such
as fentucci-naturals/scripts/go-live.py). Re-ran across *.mjs *.js *.cjs *.py including the REST
inventory_levels/{set,adjust,connect} endpoints. That surfaced 10 writers outside the brief's
list. Seven are structurally safe and deliberately untouched (active-gate-add-samples,
add-missing-samples-v2, create-missing-sample-variants, fix-missing-sample-variants,
fix-missing-samples, resume-sample-variant-creation — all create and stock only the $4.25 SAMPLE
variant at a hardcoded price; and fentucci go-live.py, which already drops the $0/yard variant).
Three were genuinely unguarded:
kravet-3b-activate-validated.js had a PRODUCT-level gate only ("a real roll variant priced
> $5"), then setInvAll() stamped a flat TARGET_QTY on EVERY variant with an inventoryItem —
the same variant-level hole as activate-1838.
kravet25-reactivate.js NO price check on the stamp at all.
kravet-full-monty-single.js NO price check, flat qty=1000 on every variant. (The guard's
`desired` argument carries 1000 here, so the file's own quantity is preserved.)
CORRECTNESS CATCH in kravet25-reactivate: step 2 repricies the main variant to MAP and step 5
stocks from the PRE-reprice snapshot, so a naive guard would refuse to stock a roll that had just
been legitimately priced — breaking Steve's 2026-06-20 rule. The guard is fed the POST-reprice
price; `map > 0` is already asserted upstream, so this cannot launder a real $0. (Also fixed an
undefined `prod` reference I introduced in the same hunk — caught by reading the call site, not
by node --check.)
Separately, WIDENED the existing TK-10965 guard in inventory-set-2026-newest.mjs (the hourly
cadence sibling): `Number(v.price) === 0` let a variant with an ABSENT/unparseable price through,
because Number(undefined) is NaN and NaN === 0 is false — so a price-less variant was still
stamped 2026. Now `!(Number(v.price) > 0)`, which skips 0, '', null, undefined and NaN. Left
price-ONLY on purpose (it is a LIVE hourly job and has no product tags in scope); the harness
declares fixture R6 N/A for that file rather than pretending it passes.
Harness extended with a PREDICATE mode that extracts a real `if (<expr>) continue;` skip-filter —
the exact shape of the TK-11299 reference commit 23987f86 — and evaluates it. All 22 writers now
covered: 4/4 required each; 3/3 regression (block mode), 2/2 + R6 N/A (predicate mode).
RESULT: PASS.
SOURCE-ONLY: nothing run with --apply, no Shopify write. Reversible: git revert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---
.../kravet-3b-activate-validated.js | 34 +++++++++++++++---
.../data/kravet-map-pricing/kravet25-reactivate.js | 42 ++++++++++++++++++----
shopify/scripts/inventory-set-2026-newest.mjs | 5 ++-
shopify/scripts/kravet-full-monty-single.js | 37 +++++++++++++++++--
.../tk11357-source-fix-proof/predicate-proof.mjs | 41 +++++++++++++++++++--
5 files changed, 141 insertions(+), 18 deletions(-)
diff --git a/shopify/scripts/data/kravet-map-pricing/kravet-3b-activate-validated.js b/shopify/scripts/data/kravet-map-pricing/kravet-3b-activate-validated.js
index 71fe0fce..31499b8c 100644
--- a/shopify/scripts/data/kravet-map-pricing/kravet-3b-activate-validated.js
+++ b/shopify/scripts/data/kravet-map-pricing/kravet-3b-activate-validated.js
@@ -86,6 +86,29 @@ const Q_DETAIL = `query($id:ID!){product(id:$id){
const M_STATUS = `mutation($input:ProductInput!){productUpdate(input:$input){product{id status} userErrors{field message}}}`;
const M_PUBLISH = `mutation($id:ID!,$input:[PublicationInput!]!){publishablePublish(id:$id,input:$input){userErrors{field message}}}`;
const M_TAGS = `mutation($id:ID!,$tags:[String!]!){tagsAdd(id:$id,tags:$tags){userErrors{field message}}}`;
+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 M_SET = `mutation($input:InventorySetQuantitiesInput!){inventorySetQuantities(input:$input){userErrors{field message}}}`;
const M_ACTIVATE = `mutation($itemId:ID!,$locId:ID!,$qty:Int!){inventoryActivate(inventoryItemId:$itemId,locationId:$locId,available:$qty){userErrors{field message}}}`;
@@ -111,9 +134,10 @@ function mf(metafields, ns, key) {
return m ? m.value : '';
}
-async function setInvAll(variants) {
- const qs = variants.filter(v => v.inventoryItem?.id)
- .map(v => ({ inventoryItemId: v.inventoryItem.id, locationId: LOCATION_ID, quantity: TARGET_QTY }));
+async function setInvAll(product, variants) {
+ // GUARD TK-11357: the sellableRollOK check upstream is PRODUCT-level (a roll priced > $5), so a
+ // $0 variant riding alongside the priced roll still reached this flat TARGET_QTY stamp. Per variant.
+ const qs = safeQuantities(product, variants.filter(v => v.inventoryItem?.id), LOCATION_ID, TARGET_QTY);
if (!qs.length) return { ok: false, err: 1 };
let r = await gqlR(M_SET, { input: { reason: 'correction', name: 'available', ignoreCompareQuantity: true, quantities: qs } });
if (!(r.data?.inventorySetQuantities?.userErrors || []).length) return { ok: true, err: 0 };
@@ -121,7 +145,7 @@ async function setInvAll(variants) {
for (const q of qs) {
const sr = await gqlR(M_SET, { input: { reason: 'correction', name: 'available', ignoreCompareQuantity: true, quantities: [q] } });
if (!(sr.data?.inventorySetQuantities?.userErrors || []).length) continue;
- const ar = await gqlR(M_ACTIVATE, { itemId: q.inventoryItemId, locId: LOCATION_ID, qty: TARGET_QTY });
+ const ar = await gqlR(M_ACTIVATE, { itemId: q.inventoryItemId, locId: LOCATION_ID, qty: q.quantity }); // GUARD TK-11357
if ((ar.data?.inventoryActivate?.userErrors || []).length) err++;
await sleep(60);
}
@@ -223,7 +247,7 @@ async function setInvAll(variants) {
console.log(` ⚠ publish ${JSON.stringify(ppr.data.publishablePublish.userErrors)}`);
}
// 3. inventory = 2026 every variant
- const inv = await setInvAll(variants);
+ const inv = await setInvAll(p, variants);
if (!inv.ok) console.log(` ⚠ inv errors ${inv.err}`);
activated.push(baseSku);
await sleep(150);
diff --git a/shopify/scripts/data/kravet-map-pricing/kravet25-reactivate.js b/shopify/scripts/data/kravet-map-pricing/kravet25-reactivate.js
index cea1ed6a..dae085f3 100644
--- a/shopify/scripts/data/kravet-map-pricing/kravet25-reactivate.js
+++ b/shopify/scripts/data/kravet-map-pricing/kravet25-reactivate.js
@@ -63,8 +63,31 @@ async function gqlR(q, v = {}, tries = 5) {
return gql(q, v);
}
-const Q = `query($id:ID!){product(id:$id){id title status ${PUB_ALIASES}
+const Q = `query($id:ID!){product(id:$id){id title status vendor tags ${PUB_ALIASES}
variants(first:30){nodes{id title sku price inventoryItem{id unitCost{amount}}}}}}`;
+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 M_STATUS = `mutation($input:ProductInput!){productUpdate(input:$input){product{id status} userErrors{field message}}}`;
const M_VAR = `mutation($pid:ID!,$variants:[ProductVariantsBulkInput!]!){productVariantsBulkUpdate(productId:$pid,variants:$variants){productVariants{id price} userErrors{field message}}}`;
const M_MF = `mutation($mf:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mf){userErrors{field message}}}`;
@@ -89,9 +112,9 @@ function parseCsv(p) {
return lines.filter(Boolean).map(l => { const v = splitCsvLine(l); const o = {}; cols.forEach((c, i) => o[c] = v[i]); return o; });
}
-async function setInvAll(variants) {
- const qs = variants.filter(v => v.inventoryItem?.id)
- .map(v => ({ inventoryItemId: v.inventoryItem.id, locationId: LOCATION_ID, quantity: TARGET_QTY }));
+async function setInvAll(product, variants) {
+ // GUARD TK-11357: this stamped a flat TARGET_QTY on EVERY variant with no price check at all.
+ const qs = safeQuantities(product, variants.filter(v => v.inventoryItem?.id), LOCATION_ID, TARGET_QTY);
if (!qs.length) return { set: 0, act: 0, err: 0 };
let r = await gqlR(M_SET, { input: { reason: 'correction', name: 'available', ignoreCompareQuantity: true, quantities: qs } });
if (!(r.data?.inventorySetQuantities?.userErrors || []).length) return { set: qs.length, act: 0, err: 0 };
@@ -99,7 +122,7 @@ async function setInvAll(variants) {
for (const q of qs) {
const sr = await gqlR(M_SET, { input: { reason: 'correction', name: 'available', ignoreCompareQuantity: true, quantities: [q] } });
if (!(sr.data?.inventorySetQuantities?.userErrors || []).length) { set++; continue; }
- const ar = await gqlR(M_ACTIVATE, { itemId: q.inventoryItemId, locId: LOCATION_ID, qty: TARGET_QTY });
+ const ar = await gqlR(M_ACTIVATE, { itemId: q.inventoryItemId, locId: LOCATION_ID, qty: q.quantity }); // GUARD TK-11357
if ((ar.data?.inventoryActivate?.userErrors || []).length) err++; else act++;
await sleep(60);
}
@@ -148,8 +171,13 @@ async function setInvAll(variants) {
const pe = ppr.data?.publishablePublish?.userErrors || [];
if (pe.length) { console.log(` ⚠ publish ${JSON.stringify(pe)}`); } else published++;
} else published++;
- // 5. inventory 2026 every variant
- const inv = await setInvAll(vs);
+ // 5. inventory 2026 every variant.
+ // GUARD TK-11357 CORRECTNESS NOTE: `vs` is the PRE-reprice snapshot, so the main variant may
+ // still read $0 there even though step 2 just set it to MAP. Pass the POST-reprice price or the
+ // guard would refuse to stock a legitimately-priced roll (breaking Steve's 2026-06-20 rule).
+ // `map > 0` is already asserted above, so this substitution can never launder a real $0.
+ const vsForGuard = vs.map(v => (v.id === main.id ? { ...v, price: map.toFixed(2) } : v));
+ const inv = await setInvAll(p, vsForGuard);
if (inv.err) console.log(` ⚠ inv errors ${inv.err}`); else invOK++;
await sleep(150);
}
diff --git a/shopify/scripts/inventory-set-2026-newest.mjs b/shopify/scripts/inventory-set-2026-newest.mjs
index 8a21d20a..b83a9778 100644
--- a/shopify/scripts/inventory-set-2026-newest.mjs
+++ b/shopify/scripts/inventory-set-2026-newest.mjs
@@ -71,7 +71,10 @@ async function scan() {
let totV = 0, correct = 0; const fixes = [];
for (const p of prods) for (const v of p.variants.nodes) {
totV++;
- if (Number(v.price) === 0) continue; // GUARD TK-10965: never re-inflate $0 quote-only variants
+ // GUARD TK-10965, widened by TK-11357: `=== 0` let a variant with an ABSENT/unparseable price
+ // through (Number(undefined) is NaN, and NaN === 0 is false), so a price-less variant was still
+ // stamped 2026 = orderable at $0. `!(> 0)` is fail-safe: 0, '', null, undefined and NaN all skip.
+ if (!(Number(v.price) > 0)) continue; // GUARD TK-11357-PREDICATE: never re-inflate a $0/price-less variant
const ii = v.inventoryItem;
const tracked = ii?.tracked, lvl = ii?.inventoryLevel;
const onh = lvl?.quantities?.find(x => x.name === 'on_hand')?.quantity;
diff --git a/shopify/scripts/kravet-full-monty-single.js b/shopify/scripts/kravet-full-monty-single.js
index c6b4d7bc..bcb12ff0 100644
--- a/shopify/scripts/kravet-full-monty-single.js
+++ b/shopify/scripts/kravet-full-monty-single.js
@@ -104,6 +104,29 @@ async function loadDefinitions() {
}
const TEXT_TYPES = new Set(['single_line_text_field', 'multi_line_text_field']);
+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 QUERY = `query($sku: String!) {
products(first: 1, query: $sku) {
@@ -111,7 +134,8 @@ const QUERY = `query($sku: String!) {
id title handle descriptionHtml status tags
metafields(first: 100) { edges { node { id namespace key value type } } }
images(first: 30) { edges { node { url altText } } }
- variants(first: 10) { edges { node { id sku inventoryItem { id tracked } inventoryPolicy inventoryQuantity } } }
+ vendor
+ variants(first: 10) { edges { node { id sku title price inventoryItem { id tracked } inventoryPolicy inventoryQuantity } } }
} }
}
}`;
@@ -380,7 +404,14 @@ function buildTags(mf, existingTags) {
const locationId = locJ.data.locations.edges[0]?.node.id;
if (!locationId) console.log(' ⚠ no location — skipping inventory set');
else {
- for (const { node: v } of p.variants.edges) {
+ // GUARD TK-11357: this stamped a flat qty=1000 on EVERY variant with NO price check at all —
+ // any $0 variant became availableForSale=true, i.e. orderable at $0. Decide per variant.
+ // (qty 1000 here, not 2026 — the guard's `desired` argument carries whatever the file uses.)
+ const _rows = safeQuantities({ vendor: p.vendor, tags: p.tags },
+ p.variants.edges.map(e => e.node).filter(v => v.inventoryItem?.id), locationId, 1000);
+ for (const row of _rows) {
+ const v = p.variants.edges.map(e => e.node).find(n => n.inventoryItem.id === row.inventoryItemId);
+ if (row.quantity === 0) { console.log(` ⛔ ${v?.sku || row.inventoryItemId}: $${v?.price} — NOT stocked (TK-11357 $0-orderable guard)`); continue; }
if (!v.inventoryItem.tracked) {
await gql(INV_ITEM_UPDATE, { id: v.inventoryItem.id, input: { tracked: true } });
}
@@ -388,7 +419,7 @@ function buildTags(mf, existingTags) {
name: 'available',
reason: 'correction',
ignoreCompareQuantity: true,
- quantities: [{ inventoryItemId: v.inventoryItem.id, locationId, quantity: 1000 }],
+ quantities: [row],
} });
}
console.log(` ✓ inventory set (qty=1000, tracked) on ${p.variants.edges.length} variants`);
diff --git a/shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs b/shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs
index 3dc601fb..f155b7af 100644
--- a/shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs
+++ b/shopify/scripts/tk11357-source-fix-proof/predicate-proof.mjs
@@ -55,8 +55,17 @@ const TARGETS = [
[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'],
+ [DWM, 'shopify/scripts/data/kravet-map-pricing/kravet-3b-activate-validated.js'],
+ [DWM, 'shopify/scripts/data/kravet-map-pricing/kravet25-reactivate.js'],
+ [DWM, 'shopify/scripts/kravet-full-monty-single.js'],
[SDG, 'scripts/create_sdg.mjs'],
];
+// PREDICATE-mode targets: writers guarded by a `if (<expr>) continue;` skip-filter rather than a
+// safeQuantities() block (the exact shape of the 23987f86 reference diff). The harness extracts the
+// REAL expression from the marked line and evaluates it — expected: skip iff the safe quantity is 0.
+const PREDICATE_TARGETS = [
+ [DWM, 'shopify/scripts/inventory-set-2026-newest.mjs'],
+];
const LOC = 'gid://shopify/Location/5795643504';
const DESIRED = 2026;
@@ -89,7 +98,7 @@ const FIXTURES = [
product: { vendor: 'Phillipe Romano', tags: ['quote-only'] },
variants: [{ title: 'Sample', price: '4.25', inventoryQuantity: 0, inventoryItem: iv(6) }],
expect: [2026] },
- { id: 'R6', required: false,
+ { id: 'R6', required: false, needsProduct: true,
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) }],
@@ -171,6 +180,32 @@ for (const [repo, rel] of TARGETS) {
results.push({ rel, state, fails, bytes: block.length });
}
+// ── predicate-mode files ────────────────────────────────────────────────────────────────
+for (const [repo, rel] of PREDICATE_TARGETS) {
+ const abs = `${repo}/${rel}`;
+ const src = fs.existsSync(abs) ? fs.readFileSync(abs, 'utf8') : null;
+ if (!src) { results.push({ rel, state: 'MISSING FILE' }); allOk = false; continue; }
+ const m = src.match(/^\s*if \((.+?)\) continue; \/\/ GUARD TK-11357-PREDICATE/m);
+ if (!m) { results.push({ rel, state: 'NO GUARD PREDICATE' }); allOk = false; continue; }
+ // eslint-disable-next-line no-new-func
+ const skips = new Function('v', `return !!(${m[1]});`);
+ let req = 0, reqTotal = 0, reg = 0, regTotal = 0; const fails = []; const na = [];
+ for (const f of FIXTURES) {
+ // A price-only skip-filter cannot see product tags/vendor, so the shared guard's
+ // price-suppressed rule is STRUCTURALLY out of reach here. Declared N/A, not silently passed.
+ // This is the same semantics as the TK-11299 reference commit 23987f86 (`Number(v.price) > 0`).
+ if (f.needsProduct) { na.push(f.id); continue; }
+ // expectation: the variant is SKIPPED exactly when its required safe quantity is 0
+ const gotSkip = f.variants.map(v => skips(v));
+ const wantSkip = f.expect.map(q => q === 0);
+ const pass = JSON.stringify(gotSkip) === JSON.stringify(wantSkip);
+ if (f.required) { reqTotal++; if (pass) req++; else fails.push(`${f.id} want skip ${JSON.stringify(wantSkip)} got ${JSON.stringify(gotSkip)}`); }
+ else { regTotal++; if (pass) reg++; else fails.push(`${f.id} want skip ${JSON.stringify(wantSkip)} got ${JSON.stringify(gotSkip)}`); }
+ }
+ if (req !== reqTotal || reg !== regTotal) allOk = false;
+ results.push({ rel: rel + ' [predicate]', state: `${req}/${reqTotal} required · ${reg}/${regTotal} regression` + (na.length ? ` · N/A ${na.join(',')} (price-only guard: no product tags in scope)` : ''), fails });
+}
+
console.log('\nper-file extracted-predicate results');
console.log('-'.repeat(78));
for (const r of results) {
@@ -181,5 +216,7 @@ for (const r of results) {
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');
+console.log(allOk
+ ? 'RESULT: PASS — every patched writer is 4/4 required; 3/3 regression (block mode) / 2/2 + R6 N/A (predicate mode)'
+ : 'RESULT: FAIL');
process.exit(allOk ? 0 : 1);
← 39156e23 guard TK-11357 Fix D: $0 gate in the 5 catalog-wide inventor
·
back to Designer Wallcoverings
·
TK-11387: add gated, reversible chronic-bouncer suppression 950f19d5 →