[object Object]

← back to Designer Wallcoverings

activate-gated: add title-based re-introduction guard — never promote a DRAFT whose pattern is already ACTIVE/ARCHIVED/DELETED (Steve: do not reintroduce old patterns as new)

0086af1ad1152ba37565719f5a113b823a31072e · 2026-08-11 12:48:29 -0700 · steve@designerwallcoverings.com

Files touched

Diff

commit 0086af1ad1152ba37565719f5a113b823a31072e
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Tue Aug 11 12:48:29 2026 -0700

    activate-gated: add title-based re-introduction guard — never promote a DRAFT whose pattern is already ACTIVE/ARCHIVED/DELETED (Steve: do not reintroduce old patterns as new)
---
 shopify/scripts/cadence/activate-gated.js | 24 +++++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/shopify/scripts/cadence/activate-gated.js b/shopify/scripts/cadence/activate-gated.js
index d1cda227..b2b907e3 100644
--- a/shopify/scripts/cadence/activate-gated.js
+++ b/shopify/scripts/cadence/activate-gated.js
@@ -170,7 +170,7 @@ const SL = 'single_line_text_field';
   if (!TOKEN) { console.error('no SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
   fs.mkdirSync(DATADIR, { recursive: true });
   const tables = Object.entries(VENDORS).filter(([v]) => !ONLY || v.toLowerCase() === ONLY.toLowerCase());
-  let promoted = 0, publishedCount = 0, alreadyActive = 0, skippedArchived = 0, totalCand = 0;
+  let promoted = 0, publishedCount = 0, alreadyActive = 0, skippedArchived = 0, totalCand = 0, reintroBlocked = 0;
 
   let pushedMf = 0, taggedGaps = 0;
   for (const [vendor, cfg] of tables) {
@@ -250,6 +250,24 @@ const SL = 'single_line_text_field';
           variants: (n.variants?.nodes || []).map(v => ({ sku: v.sku })),
         });
         if (gate.ok) {
+          // RE-INTRODUCTION GUARD (Steve 2026-08-11, emphatic: "Do not reintroduce old
+          // patterns as new patterns!!!"). Skip a DRAFT whose TITLE (= pattern identity,
+          // "Pattern Color | Brand") already exists on the store as ACTIVE, or was
+          // intentionally ARCHIVED/DELETED before. Matching on title (not SKU) is what
+          // catches a re-imported / renamed-prefix dupe that the completeness gate misses.
+          // Mirrors the proven rotation-order.js guard. Fail-safe: only ever keeps DRAFT.
+          const t = String(n.title || '').replace(/'/g, "''");
+          const reintro = t && (psql(
+            `SELECT 1 FROM shopify_products WHERE title='${t}' `
+            + `AND status IN ('ACTIVE','ARCHIVED','DELETED_FROM_SHOPIFY') `
+            + `AND shopify_id <> '${String(n.id).replace(/'/g,"''")}' LIMIT 1;`
+          ) || '').trim() === '1';
+          if (reintro) {
+            reintroBlocked++;
+            console.log(`  ↩︎ re-intro guard: "${n.title}" already on the store — kept DRAFT (not a new pattern)`);
+            fs.appendFileSync(AUDIT, JSON.stringify({ ts:new Date().toISOString(), vendor, dw_sku:(c&&c.dw_sku), product_id:n.id, action:'reintro-blocked', title:n.title }) + '\n');
+            continue;
+          }
           drafts.push({ gid: n.id, mfPush, dw_sku: (c && c.dw_sku) || '' });
         } else {
           gateBlocked++;
@@ -263,7 +281,7 @@ const SL = 'single_line_text_field';
         }
       }
     }
-    console.log(`  ${vendor}: ${cands.length} gate-pass · ${drafts.length} DRAFT to promote · ${gateBlocked} gate-blocked · ${alreadyActive} already ACTIVE`);
+    console.log(`  ${vendor}: ${cands.length} gate-pass · ${drafts.length} DRAFT to promote · ${gateBlocked} gate-blocked · ${reintroBlocked} re-intro-blocked · ${alreadyActive} already ACTIVE`);
 
     // PROMOTE: push any recoverable width/specs metafield FIRST, then activate.
     for (const d of drafts) {
@@ -316,7 +334,7 @@ const SL = 'single_line_text_field';
       fs.appendFileSync(AUDIT, JSON.stringify({ ts:new Date().toISOString(), vendor, dw_sku:g.dw_sku, product_id:g.gid, action:'tagged', tags:g.tags }) + '\n');
     }
   }
-  console.log(`\n${COMMIT ? `PROMOTED ${promoted} DRAFT→ACTIVE (${publishedCount} published to Online Store)` : `DRY-RUN: ${totalCand} gate-passing rows scanned`}, ${alreadyActive} already active, ${skippedArchived} archived/other skipped.`);
+  console.log(`\n${COMMIT ? `PROMOTED ${promoted} DRAFT→ACTIVE (${publishedCount} published to Online Store)` : `DRY-RUN: ${totalCand} gate-passing rows scanned`}, ${alreadyActive} already active, ${reintroBlocked} re-intro-blocked, ${skippedArchived} archived/other skipped.`);
   if (COMMIT && promoted !== publishedCount) console.log(`  ⚠ ${promoted - publishedCount} product(s) went ACTIVE but did NOT publish to the Online Store — re-run or let the publish canary reconcile.`);
   console.log(COMMIT
     ? `  pushed ${pushedMf} width/spec metafield(s) · tagged ${taggedGaps} gap product(s)`

← 1953fb75 TK-10046: manual-path applier for the 2 held products (dry-r  ·  back to Designer Wallcoverings  ·  auto-data-snapshot: 2026-08-11T12:47:11 (1 data files) — sho 77860be2 →