[object Object]

← back to Ticket System

TK-10002 Phase-2a: pre-flight found all 8 target collisions already resolved live; no SKU writes needed; guard hardening already committed (de4cd0e3)

5389ccb1ed316afcdeb09a4c28db31bd0de5d8d0 · 2026-07-28 15:31:05 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 5389ccb1ed316afcdeb09a4c28db31bd0de5d8d0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 28 15:31:05 2026 -0700

    TK-10002 Phase-2a: pre-flight found all 8 target collisions already resolved live; no SKU writes needed; guard hardening already committed (de4cd0e3)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 tk10002-phase2a/RESULTS.md           | 91 ++++++++++++++++++++++++++++++++++++
 tk10002-phase2a/verify-live.js       | 70 +++++++++++++++++++++++++++
 tk10002-phase2a/verify-stragglers.js | 37 +++++++++++++++
 3 files changed, 198 insertions(+)

diff --git a/tk10002-phase2a/RESULTS.md b/tk10002-phase2a/RESULTS.md
new file mode 100644
index 00000000..6324bc94
--- /dev/null
+++ b/tk10002-phase2a/RESULTS.md
@@ -0,0 +1,91 @@
+# TK-10002 Phase-2a — Execution Results
+
+**Ticket:** TK-10002-dw-duplicate-active-products-hunt-yolo
+**Agent:** vp-dw-commerce
+**Date:** 2026-07-28
+**Authorization:** memo `~/.claude/yolo-queue/pending-approval/2026-07-28-tk10002-phase2-and-governance.md` — both boxes checked (RATIFY Phase-1, PHASE-2a ONLY execute now).
+**Store:** `designer-laboratory-sandbox.myshopify.com` (LIVE prod) · API 2024-10
+**Cost:** `$0 (local)` — PG mirror reads + git; Shopify GraphQL reads (read-only, no metered API). No paid-model calls.
+
+---
+
+## Step 1 — Mandatory pre-flight (corrected collision query + live verification)
+
+### Query-bug caught before any write
+The mirror stores `status` in **MIXED CASE** (`ACTIVE`/`DRAFT`/`archived`/`ARCHIVED`/…).
+The memo's reproduce query used `status='active'` (lowercase) → **0 rows** (there is no
+lowercase `active`). Corrected to `upper(status)='ACTIVE'` and dropped the
+`variant_sku !~* 'null'` filter (the filter that hid the null-Sample class).
+
+### Corrected collision picture (fresh mirror, synced 2026-07-28 15:03, 0 stale-30d active rows)
+Active-active `variant_sku` collision groups (count > 1):
+
+| variant_sku | active products | vendors | disposition |
+|---|---|---|---|
+| `(NULL)` | 151 | Koroseal 86 / Pixels 52 / Steve Abrams 13 | DEFERRED (Koroseal quote-only + FAA policy) |
+| `null-Sample` | 112 | Phillipe Romano | DEFERRED (112 DRAFT twins carry correct SKU — merge project) |
+| `DIG-240434-vinyl-2x1-sample` | 3 | DW Bespoke | DEFERRED (Phase-2b merge) |
+| 34 × two-product `DIG-`/`MIS-`/`MTG-`/`PRW-` groups | 2 each | DW Bespoke / Missoni / MTG / PR | DEFERRED (Phase-2b merge) |
+
+**Two NEW collisions surfaced by the case-fold that were NOT in the CSV** (out of Phase-2a scope, noted only, no action):
+- `DWC-Arte-40323-Sample` — Arte International (2 active)
+- `DWK-30522` — Koroseal (2 active)
+
+### Live Shopify verification of the 8 Phase-2a targets (READ-ONLY)
+Verified each target against the LIVE store (not just the mirror), per the memo's lower-bound caveat:
+
+| # | Target group | vendor | LIVE result | action |
+|---|---|---|---|---|
+| 1 | `None-Sample` | Scalamandre / Zoffany | 0 exact-SKU matches live, 0 ACTIVE | **SKIP — already resolved** |
+| 2 | `MON-LetterG1-Sample` | Designer Wallcoverings | only **1** ACTIVE product live (intra-product Default+Sample pair, no cross-product collision) | **SKIP — no collision** |
+| 3 | `-25-gold-2x27` | DW Bespoke Studio | **1** ACTIVE + 2 DRAFT share the base-`-25` variant set; active-active collision gone; the 2 drafts belong to the deferred merge project | **SKIP — no active-active collision** |
+| 4 | `-Sample` | Pierre Frey | 0 exact-SKU matches live, 0 ACTIVE | **SKIP — already resolved** |
+| 5 | `Water Color Poppies - Sepia Mural` | Designer Wallcoverings | only **1** ACTIVE product live (2 variants on same product, not 2 products) | **SKIP — no collision** |
+| 6 | `(null)` straggler | Romo | 580 active, **0** with all-null/empty SKUs live | **SKIP — already resolved** |
+| 7 | `(null)` straggler | DW Bespoke Studio | 935 active, **0** with all-null/empty SKUs live | **SKIP — already resolved** |
+| 8 | `(null)` straggler | Innovations USA | 209 active, **0** with all-null/empty SKUs live | **SKIP — already resolved** |
+
+**Conclusion:** All 8 Phase-2a target collision groups were **already resolved on the live store**
+since the CSV snapshot (`/tmp/tk10002_remaining_backlog_classified.csv`) was built. The true count for
+the 8 targets is materially **LOWER** (zero), not higher — so the "stop if materially higher" rule does
+not trigger. **Zero SKU writes were required; none were made.**
+
+---
+
+## Step 2 — SKU assignments (before/after)
+
+**None.** All 8 targets already resolved live. No mirror write, no Shopify variant-SKU write performed.
+No bad SKU minted, no product set to DRAFT (nothing to DRAFT — the active-active collisions no longer exist).
+
+### Follow-up noted (NOT acted on — outside Phase-2a scope)
+- GRP3 active product `Hilly's Hippo Vintage 1970's` (id `7664592289843`) carries a malformed base-`-25`
+  variant SKU set. This is a data-quality issue but NOT a collision. Re-SKUing a live customer-facing
+  product's entire variant set is exactly the handle/SKU churn the memo said to avoid outside a real
+  collision → left as a follow-up for a scoped Bespoke cleanup, not a Phase-2a write.
+- 2 new case-fold collisions (`DWC-Arte-40323-Sample`, `DWK-30522`) — surface to a future collision pass.
+
+---
+
+## Step 3 — Guard hardening (`skuTakenOnActiveProduct`)
+
+**Already committed** under this ticket at commit `de4cd0e3`
+("harden skuTakenOnActiveProduct — fail-closed on error + case-fold comparison (both lib copies)"),
+applied to **both** library copies. Verified this session:
+
+- **Fail CLOSED:** the `catch` now `return { taken: true, error: err.message }` (was fail-open `{taken:false}`)
+  so a DB blip / lock-storm blocks the import instead of silently admitting a colliding SKU.
+- **Case-fold:** comparison uses `UPPER(variant_sku)`, `UPPER(sku)`, `UPPER(dw_sku)` vs `UPPER($1)` /
+  `UPPER($2)` so `DWAT-65039` and `dwat-65039` collide.
+- Both copies pass `node --check`. Working tree clean.
+
+Files:
+- `/Users/macstudio3/Projects/Designer-Wallcoverings/DW-Programming/vendor-crawlers/lib/sku-registry.js`
+- `/Users/macstudio3/Projects/Designer-Wallcoverings/shopify/scripts/lib/sku-registry.js`
+
+---
+
+## Deferred (untouched, per memo)
+33 same-vendor merges (Phase-2b), the 112 null-Sample PR merge project, 65 FAA prints (policy),
+86 Koroseal (grandfathered quote-only no-op), and the partial UNIQUE index (only after all collisions clear).
+
+## Total cost: `$0 (local)`
diff --git a/tk10002-phase2a/verify-live.js b/tk10002-phase2a/verify-live.js
new file mode 100644
index 00000000..bc7fec5e
--- /dev/null
+++ b/tk10002-phase2a/verify-live.js
@@ -0,0 +1,70 @@
+#!/usr/bin/env node
+// TK-10002 Phase-2a: live Shopify verification of the 8 target collision groups.
+// READ-ONLY. Confirms each target product's current status + variant SKUs on the LIVE store,
+// and searches live for each colliding SKU string to catch mirror-lag.
+const fs = require('fs');
+for (const line of fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8').split('\n')) {
+  const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
+  if (m) process.env[m[1]] = m[2].replace(/^["']|["']$/g,'');
+}
+const fetch = globalThis.fetch;
+const STORE = process.env.SHOPIFY_STORE_DOMAIN || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_ADMIN_ACCESS_TOKEN;
+const API = '2024-10';
+
+async function gql(query, variables={}) {
+  const r = await fetch(`https://${STORE}/admin/api/${API}/graphql.json`, {
+    method:'POST',
+    headers:{'Content-Type':'application/json','X-Shopify-Access-Token':TOKEN},
+    body: JSON.stringify({query, variables})
+  });
+  return r.json();
+}
+
+// The product IDs the mirror still knows for target groups (active + draft siblings)
+const targetIds = [
+  ['GRP2_MON-LetterG1-Sample','gid://shopify/Product/2034442010689'],
+  ['GRP3_-25-gold-2x27','gid://shopify/Product/7664592289843'],
+  ['GRP3_-25-gold-2x27','gid://shopify/Product/7664585080883'],
+  ['GRP3_-25-gold-2x27','gid://shopify/Product/7664591568947'],
+  ['GRP5_Poppies','gid://shopify/Product/6977982038067'],
+];
+
+// Live SKU searches to catch mirror-lag (any product, any status carrying these variant SKUs)
+const skuSearches = [
+  ['GRP1_None-Sample','None-Sample'],
+  ['GRP2_MON-LetterG1-Sample','MON-LetterG1-Sample'],
+  ['GRP3_-25-gold-2x27','-25-gold-2x27'],
+  ['GRP4_PierreFrey_-Sample','-Sample'],
+  ['GRP5_Poppies','Water Color Poppies - Sepia Mural'],
+];
+
+(async () => {
+  console.log('=== TARGET PRODUCT LIVE STATUS ===');
+  for (const [grp,id] of targetIds) {
+    const q = `query($id:ID!){ product(id:$id){ id title status vendor
+      variants(first:20){ nodes{ id title sku } } } }`;
+    const d = await gql(q,{id});
+    const p = d.data && d.data.product;
+    if (!p) { console.log(`${grp}\t${id}\tNOT_FOUND (deleted live)`); continue; }
+    const skus = p.variants.nodes.map(v=>`${v.title}:${v.sku||'(null)'}`).join(' | ');
+    console.log(`${grp}\t${p.status}\t${p.vendor}\t${p.title.slice(0,40)}\tVARIANTS[ ${skus} ]`);
+    await new Promise(r=>setTimeout(r,350));
+  }
+
+  console.log('\n=== LIVE SKU SEARCH (mirror-lag catch) — ACTIVE products carrying each SKU ===');
+  for (const [grp,sku] of skuSearches) {
+    // Shopify variant search by sku, then filter to product status ACTIVE
+    const q = `query($q:String!){ productVariants(first:50, query:$q){ nodes{ sku
+      product{ id title status vendor } } } }`;
+    const d = await gql(q,{q:`sku:'${sku.replace(/'/g,"")}'`});
+    const nodes = (d.data && d.data.productVariants && d.data.productVariants.nodes) || [];
+    // exact-match sku (Shopify sku: search is prefix-ish); case-insensitive
+    const exact = nodes.filter(n => (n.sku||'').toLowerCase() === sku.toLowerCase());
+    const active = exact.filter(n => n.product && n.product.status === 'ACTIVE');
+    console.log(`${grp}\tsku='${sku}'\texact_matches=${exact.length}\tACTIVE=${active.length}`);
+    active.forEach(n => console.log(`   ACTIVE -> ${n.product.id} | ${n.product.vendor} | ${n.product.title.slice(0,45)}`));
+    if (d.errors) console.log('   ERRORS', JSON.stringify(d.errors));
+    await new Promise(r=>setTimeout(r,400));
+  }
+})();
diff --git a/tk10002-phase2a/verify-stragglers.js b/tk10002-phase2a/verify-stragglers.js
new file mode 100644
index 00000000..db518752
--- /dev/null
+++ b/tk10002-phase2a/verify-stragglers.js
@@ -0,0 +1,37 @@
+#!/usr/bin/env node
+const fs = require('fs');
+for (const line of fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8').split('\n')) {
+  const m = line.match(/^([A-Z0-9_]+)=(.*)$/); if (m) process.env[m[1]] = m[2].replace(/^["']|["']$/g,'');
+}
+const fetch = globalThis.fetch;
+const STORE = process.env.SHOPIFY_STORE_DOMAIN || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const API = '2024-10';
+async function gql(query, variables={}) {
+  const r = await fetch(`https://${STORE}/admin/api/${API}/graphql.json`, {
+    method:'POST', headers:{'Content-Type':'application/json','X-Shopify-Access-Token':TOKEN},
+    body: JSON.stringify({query, variables})}); return r.json();
+}
+(async () => {
+  for (const vendor of ['Romo','DW Bespoke Studio','Innovations USA','Innovations']) {
+    let cursor=null, total=0, nullSkuActive=[];
+    do {
+      const q = `query($q:String!,$after:String){ products(first:100, query:$q, after:$after){
+        pageInfo{hasNextPage endCursor} nodes{ id title status
+          variants(first:30){ nodes{ sku } } } } }`;
+      const d = await gql(q,{q:`vendor:'${vendor}' status:active`, after:cursor});
+      const conn = d.data && d.data.products;
+      if (!conn){ console.log(vendor,'ERR',JSON.stringify(d.errors||d)); break; }
+      for (const p of conn.nodes){
+        total++;
+        const skus = p.variants.nodes.map(v=>v.sku);
+        const allNullOrEmpty = skus.every(s => !s || s.trim()==='');
+        if (allNullOrEmpty) nullSkuActive.push(`${p.id} | ${p.title.slice(0,45)}`);
+      }
+      cursor = conn.pageInfo.hasNextPage ? conn.pageInfo.endCursor : null;
+      await new Promise(r=>setTimeout(r,300));
+    } while(cursor);
+    console.log(`VENDOR '${vendor}': active=${total}, active-with-ALL-null/empty-SKUs=${nullSkuActive.length}`);
+    nullSkuActive.forEach(x=>console.log('   NULL-SKU ACTIVE ->', x));
+  }
+})();

← 1fffe67d nightly-cleanup: add pass 3 — CNCP panel retire + cache-relo  ·  back to Ticket System  ·  TK-10002 Phase-2b: same-vendor merge plan (3 true dupes to a af440a28 →