← 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
A scripts/tk10002-null-sample/apply-residual.jsA scripts/tk10002-null-sample/tk10002_residual_apply_results.json
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 →