[object Object]

← back to Dw Rotation Activator

activator: private-label leak guard — never activate a product with a source name (Greenland/WallQuest/etc) in title or vendor

ade810f688d64a28f4c267933921c5cdc0ffe008 · 2026-07-21 15:28:10 -0700 · steve@designerwallcoverings.com

Files touched

Diff

commit ade810f688d64a28f4c267933921c5cdc0ffe008
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Tue Jul 21 15:28:10 2026 -0700

    activator: private-label leak guard — never activate a product with a source name (Greenland/WallQuest/etc) in title or vendor
---
 rotate-activate.js | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/rotate-activate.js b/rotate-activate.js
index 28bc1a4..e297b2e 100644
--- a/rotate-activate.js
+++ b/rotate-activate.js
@@ -223,6 +223,23 @@ function fiveFieldExtra(n) {
   return { ok: reasons.length === 0, reasons };
 }
 
+// ── PRIVATE-LABEL LEAK GUARD ──────────────────────────────────────────────
+// A product must NEVER go customer-facing (DRAFT→ACTIVE) with the upstream SOURCE
+// name in its title or vendor. This is the durable fix for the 2026-07-21 incident
+// where the activator flipped Greenland cork duplicates ACTIVE with "| Greenland"
+// still in the title (vendor="Greenland" should be "Phillipe Romano"). Fail-safe:
+// this can only BLOCK an activation, never cause one. See memory dw-leak-scanner.
+// HARD tokens are never a legitimate pattern/color name → checked in title AND vendor.
+// VENDOR-only tokens can appear as a real pattern name (e.g. a "Chesapeake" pattern) →
+// blocked only when they are the VENDOR (the source), not merely a word in the title.
+const PL_LEAK_HARD = ['greenland', 'wallquest', 'command54', 'seabrook', 'nextwall', 'desima', 'carlsten'];
+const PL_LEAK_VENDOR = [...PL_LEAK_HARD, 'momentum', 'versa', 'schumacher', 'chesapeake', 'nicolette mayer'];
+function leakGuard(title, vendor) {
+  const t = (title || '').toLowerCase(), v = (vendor || '').toLowerCase();
+  const hit = PL_LEAK_HARD.find((x) => t.includes(x)) || PL_LEAK_VENDOR.find((x) => v.includes(x));
+  return hit ? { ok: false, reason: `private-label-leak:${hit}` } : { ok: true };
+}
+
 (async () => {
   const queue = loadQueue();
   const tier0 = queue.filter((q) => q.mat_tier === 0).length;
@@ -273,11 +290,13 @@ function fiveFieldExtra(n) {
 
       const gate = gateFromLive(n, q && q.dw_sku, q && q.vendor);
       const extra = fiveFieldExtra(n);
-      const passes = gate.ok && extra.ok;
+      const leak = leakGuard(n.title, q && q.vendor);
+      const passes = gate.ok && extra.ok && leak.ok;
       const rec = { ts: new Date().toISOString(), shopify_id: n.id, vendor: q && q.vendor,
         dw_sku: q && q.dw_sku, mat_tier: q && q.mat_tier, rr: q && q.rr,
         title: n.title, passes,
-        reasons: [...(gate.ok ? [] : gate.reasons), ...(extra.ok ? [] : extra.reasons)] };
+        reasons: [...(gate.ok ? [] : gate.reasons), ...(extra.ok ? [] : extra.reasons),
+                  ...(leak.ok ? [] : [leak.reason])] };
 
       if (!passes) {
         skipped++;

← 329e3f2 Add per-product settlement gate to rotation activator (textu  ·  back to Dw Rotation Activator  ·  activator: align leak guard to canonical dw-leak-scanner den eb84eed →