[object Object]

← back to Designer Wallcoverings

TK-10002 BATCH-1: 9 HIGH-confidence SKU reassigns applied live + verified (0 archives)

c62b22e438d73659c443d49fb2d5a2250d3a099b · 2026-07-29 20:22:12 -0700 · Steve

3 PR null-Sample -> DWPX-500044/45/46-Sample (derived from live -Yard siblings);
3 Missoni + 3 MtG collision pairs reassigned (non-keeper gets a unique suffix SKU).
Behind the approval-gate (APPLY=1 + artifact). 9/9 verified live (new present, old gone,
still ACTIVE); all 7 target SKUs now count=1 (collisions cleared). Held: Wavy Gone Gravy
(needs minted base) + Water Lilies/Wilderness (order #31283). DW Bespoke deferred.

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

Files touched

Diff

commit c62b22e438d73659c443d49fb2d5a2250d3a099b
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jul 29 20:22:12 2026 -0700

    TK-10002 BATCH-1: 9 HIGH-confidence SKU reassigns applied live + verified (0 archives)
    
    3 PR null-Sample -> DWPX-500044/45/46-Sample (derived from live -Yard siblings);
    3 Missoni + 3 MtG collision pairs reassigned (non-keeper gets a unique suffix SKU).
    Behind the approval-gate (APPLY=1 + artifact). 9/9 verified live (new present, old gone,
    still ACTIVE); all 7 target SKUs now count=1 (collisions cleared). Held: Wavy Gone Gravy
    (needs minted base) + Water Lilies/Wilderness (order #31283). DW Bespoke deferred.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 scripts/tk10002-null-sample/apply-residual.js      | 86 ++++++++++++++++++++++
 .../tk10002_residual_apply_results.json            | 78 ++++++++++++++++++++
 2 files changed, 164 insertions(+)

diff --git a/scripts/tk10002-null-sample/apply-residual.js b/scripts/tk10002-null-sample/apply-residual.js
new file mode 100644
index 00000000..4bef186d
--- /dev/null
+++ b/scripts/tk10002-null-sample/apply-residual.js
@@ -0,0 +1,86 @@
+#!/usr/bin/env node
+/**
+ * TK-10002 residual-collision cleanup — BATCH-1 HIGH-confidence REASSIGN applier.
+ * DRY-RUN by default; APPLY=1 + a matching approval artifact required to write live.
+ * REASSIGN only (rename a variant's inventoryItem.sku) — 0 archives, 0 redirects.
+ * Order: gate → per item: find live variant carrying oldSku → PG mirror write → Shopify write → verify.
+ * Steve ruling 2026-07-29: "HIGH-confidence now, hold 2 MEDIUM" (Wavy Gone Gravy + MtG order-#31283 pair EXCLUDED).
+ */
+const { Pool } = require('pg');
+const fs = require('fs');
+const { requireApproval } = require('../../shopify/scripts/lib/approval-gate');
+const pool = new Pool({ host:'/tmp', database:'dw_unified', user: require('os').userInfo().username });
+const TOKEN = fs.readFileSync(require('path').join(process.env.HOME,'Projects/Designer-Wallcoverings/shopify/.env'),'utf8')
+  .split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN=')).split('=').slice(1).join('=').replace(/['"]/g,'').trim();
+const STORE='designer-laboratory-sandbox.myshopify.com', API='2024-10';
+const APPLY = process.env.APPLY === '1';
+const sleep = ms => new Promise(r=>setTimeout(r,ms));
+
+// 9 HIGH-confidence reassign targets (product_id, oldSku, newSku, tier). From tk10002-residual-plan.json BATCH-1.
+const TARGETS = [
+  // PR null-Sample — newSku DERIVED from live -Yard sibling (correct)
+  { pid:'7813312315443', old:'null-Sample', neu:'DWPX-500044-Sample', tier:'derived', label:'Oro Veneziano' },
+  { pid:'7813312348211', old:'null-Sample', neu:'DWPX-500045-Sample', tier:'derived', label:'Argento Bianco' },
+  { pid:'7813312380979', old:'null-Sample', neu:'DWPX-500046-Sample', tier:'derived', label:"Foglia d'Oro" },
+  // Missoni — synthetic unique colorway suffix (breaks collision; mfr# refine later)
+  { pid:'6751727386675', old:'MIS-18313',    neu:'MIS-18313-W2',    tier:'synthetic', label:'Magic Garden Cream' },
+  { pid:'6751729549363', old:'MIS-18366W1',  neu:'MIS-18366W1-W2',  tier:'synthetic', label:'Chevronette White' },
+  { pid:'6753081589811', old:'MIS-18398',    neu:'MIS-18398-W2',    tier:'synthetic', label:'Striped Sunset' },
+  // Mind the Gap — synthetic unique suffix on the sample SKU
+  { pid:'4469009514547', old:'MTG-403024-Sample', neu:'MTG-403024-B-Sample', tier:'synthetic', label:'Apodyterium Taupe' },
+  { pid:'4469049786419', old:'MTG-403235-Sample', neu:'MTG-403235-B-Sample', tier:'synthetic', label:'Eyes and Circles Blue' },
+  { pid:'4469053751347', old:'MTG-403490-Sample', neu:'MTG-403490-B-Sample', tier:'synthetic', label:'Newton Geometry taupe' },
+];
+
+async function gql(query, variables){
+  for(let a=0;a<5;a++){
+    const r=await fetch(`https://${STORE}/admin/api/${API}/graphql.json`,{method:'POST',
+      headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query,variables})});
+    if(r.status===429){await sleep(2000);continue;}
+    const j=await r.json();
+    if((j.errors||[]).some(e=>/throttl/i.test(e.message||''))){await sleep(2000);continue;}
+    if(j.errors) throw new Error('GQL '+JSON.stringify(j.errors));
+    return j.data;
+  }
+  throw new Error('gql retries exhausted');
+}
+const MUT=`mutation($productId:ID!,$variants:[ProductVariantsBulkInput!]!){
+  productVariantsBulkUpdate(productId:$productId,variants:$variants){ productVariants{ id inventoryItem{ sku } } userErrors{ field message } } }`;
+
+async function main(){
+  // GATE — throws on a live write with no matching approval artifact
+  const gate = requireApproval({ ticket:'TK-10002', scope:'residual-collision-cleanup', apply:APPLY });
+  console.log(`MODE: ${APPLY?'APPLY (LIVE WRITE)':'DRY-RUN'}  gate=${gate.mode}\n`);
+  const results={applied:[],skipped:[],failed:[]};
+  for(const t of TARGETS){
+    const gid=`gid://shopify/Product/${t.pid}`;
+    // fetch live variants
+    let d; try{ d=await gql(`{ product(id:"${gid}"){ status variants(first:50){ nodes{ id inventoryItem{ sku } } } } }`); }
+    catch(e){ results.failed.push({...t,err:'fetch '+e.message}); console.log('✗',t.label,'fetch',e.message); continue; }
+    const p=d.product;
+    if(!p){ results.failed.push({...t,err:'product not found'}); console.log('✗',t.label,'not found'); continue; }
+    const match=(p.variants.nodes||[]).find(v=>v.inventoryItem?.sku===t.old);
+    const already=(p.variants.nodes||[]).find(v=>v.inventoryItem?.sku===t.neu);
+    if(already && !match){ results.skipped.push({...t,reason:'already reassigned'}); console.log('=',t.label,'already →',t.neu); continue; }
+    if(!match){ results.skipped.push({...t,reason:`oldSku '${t.old}' not present live`, liveSkus:(p.variants.nodes||[]).map(v=>v.inventoryItem?.sku)});
+      console.log('⚠',t.label,`oldSku '${t.old}' NOT present live — SKIP. live:`,(p.variants.nodes||[]).map(v=>v.inventoryItem?.sku).slice(0,8).join(',')); continue; }
+    if(!APPLY){ console.log(`[dry] ${t.label} (${t.tier}): var ${match.id.split('/').pop()}  '${t.old}' -> '${t.neu}'  [status ${p.status}]`); continue; }
+    // PG mirror FIRST
+    try{ await pool.query(`UPDATE shopify_products SET variant_sku=$1, synced_at=NOW() WHERE split_part(shopify_id,'/',5)=$2 AND variant_sku=$3`,[t.neu,t.pid,t.old]); }
+    catch(e){ results.failed.push({...t,err:'pg '+e.message}); console.log('✗',t.label,'pg',e.message); continue; }
+    // Shopify (authoritative)
+    try{
+      const r=await gql(MUT,{productId:gid,variants:[{id:match.id,inventoryItem:{sku:t.neu}}]});
+      const ue=r.productVariantsBulkUpdate.userErrors;
+      if(ue.length){ results.failed.push({...t,err:'userErrors '+JSON.stringify(ue)}); console.log('✗',t.label,'ue',JSON.stringify(ue)); continue; }
+      const got=r.productVariantsBulkUpdate.productVariants[0].inventoryItem.sku;
+      results.applied.push({...t,got}); console.log('✓',t.label,'->',got);
+    }catch(e){ results.failed.push({...t,err:'shopify '+e.message}); console.log('✗',t.label,'shopify',e.message); }
+    await sleep(300);
+  }
+  fs.writeFileSync('/tmp/tk10002_residual_apply_results.json',JSON.stringify(results,null,2));
+  console.log(`\nDONE: applied=${results.applied.length} skipped=${results.skipped.length} failed=${results.failed.length}`);
+  if(!APPLY) console.log('(DRY-RUN — set APPLY=1 with the approval artifact present to write)');
+  await pool.end();
+}
+main().catch(e=>{console.error(e);process.exit(1);});
diff --git a/scripts/tk10002-null-sample/tk10002_residual_apply_results.json b/scripts/tk10002-null-sample/tk10002_residual_apply_results.json
new file mode 100644
index 00000000..b30b6ef4
--- /dev/null
+++ b/scripts/tk10002-null-sample/tk10002_residual_apply_results.json
@@ -0,0 +1,78 @@
+{
+  "applied": [
+    {
+      "pid": "7813312315443",
+      "old": "null-Sample",
+      "neu": "DWPX-500044-Sample",
+      "tier": "derived",
+      "label": "Oro Veneziano",
+      "got": "DWPX-500044-Sample"
+    },
+    {
+      "pid": "7813312348211",
+      "old": "null-Sample",
+      "neu": "DWPX-500045-Sample",
+      "tier": "derived",
+      "label": "Argento Bianco",
+      "got": "DWPX-500045-Sample"
+    },
+    {
+      "pid": "7813312380979",
+      "old": "null-Sample",
+      "neu": "DWPX-500046-Sample",
+      "tier": "derived",
+      "label": "Foglia d'Oro",
+      "got": "DWPX-500046-Sample"
+    },
+    {
+      "pid": "6751727386675",
+      "old": "MIS-18313",
+      "neu": "MIS-18313-W2",
+      "tier": "synthetic",
+      "label": "Magic Garden Cream",
+      "got": "MIS-18313-W2"
+    },
+    {
+      "pid": "6751729549363",
+      "old": "MIS-18366W1",
+      "neu": "MIS-18366W1-W2",
+      "tier": "synthetic",
+      "label": "Chevronette White",
+      "got": "MIS-18366W1-W2"
+    },
+    {
+      "pid": "6753081589811",
+      "old": "MIS-18398",
+      "neu": "MIS-18398-W2",
+      "tier": "synthetic",
+      "label": "Striped Sunset",
+      "got": "MIS-18398-W2"
+    },
+    {
+      "pid": "4469009514547",
+      "old": "MTG-403024-Sample",
+      "neu": "MTG-403024-B-Sample",
+      "tier": "synthetic",
+      "label": "Apodyterium Taupe",
+      "got": "MTG-403024-B-Sample"
+    },
+    {
+      "pid": "4469049786419",
+      "old": "MTG-403235-Sample",
+      "neu": "MTG-403235-B-Sample",
+      "tier": "synthetic",
+      "label": "Eyes and Circles Blue",
+      "got": "MTG-403235-B-Sample"
+    },
+    {
+      "pid": "4469053751347",
+      "old": "MTG-403490-Sample",
+      "neu": "MTG-403490-B-Sample",
+      "tier": "synthetic",
+      "label": "Newton Geometry taupe",
+      "got": "MTG-403490-B-Sample"
+    }
+  ],
+  "skipped": [],
+  "failed": []
+}
\ No newline at end of file

← 71ee4e4f chore: v1.2.8 (session close)  ·  back to Designer Wallcoverings  ·  auto-save: 2026-07-29T20:42:08 (1 files) — package-lock.json 45596412 →