[object Object]

← back to Ticket System

auto-save: 2026-07-30T10:16:54 (3 files) — tk10002-resku/apply-resku.mjs tk10002-resku/apply-results.json tk10002-resku/check-state.mjs

1eb5274f4a3fad74fb250fcb0794fdfb20dc5e88 · 2026-07-30 10:16:55 -0700 · Steve Abrams

Files touched

Diff

commit 1eb5274f4a3fad74fb250fcb0794fdfb20dc5e88
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 30 10:16:55 2026 -0700

    auto-save: 2026-07-30T10:16:54 (3 files) — tk10002-resku/apply-resku.mjs tk10002-resku/apply-results.json tk10002-resku/check-state.mjs
---
 tk10002-resku/apply-resku.mjs    | 246 ++++++++++++++++++
 tk10002-resku/apply-results.json | 538 +++++++++++++++++++++++++++++++++++++++
 tk10002-resku/check-state.mjs    |  26 ++
 3 files changed, 810 insertions(+)

diff --git a/tk10002-resku/apply-resku.mjs b/tk10002-resku/apply-resku.mjs
new file mode 100644
index 00000000..ed54150e
--- /dev/null
+++ b/tk10002-resku/apply-resku.mjs
@@ -0,0 +1,246 @@
+#!/usr/bin/env node
+/* TK-10002 APPLY — re-SKU the 64 disjoint confident-STRAY variant fixes.
+ * Steve-approved 2026-07-30. LIVE PROD writes (mirror-first, Shopify-authoritative).
+ *
+ * Scope: final-verdict.json confident[] (68) MINUS 4 EXCLUDED pids
+ *   7664488284211, 7664489529395, 7774188404787 (3-way Dig-240416 conflict)
+ *   7391684657203 (blocked by out-of-sweep DIG-24001 foreigner)
+ * = 64 products.
+ *
+ * Per stray variant:
+ *   0. Read LIVE variants; find the variant whose current sku == stray (case-fold).
+ *      - If none carries stray but one already carries `corrected` => DONE, skip (idempotent).
+ *      - If neither => SKIP + flag (drifted).
+ *   1. RE-VERIFY guard on `corrected` IMMEDIATELY (case-folded, base + -Sample,
+ *      exclude ARCHIVED, exclude THIS product via bareId normalization). BLOCKED => SKIP + DRAFT-tag.
+ *   2. Mirror write: UPDATE the row matching this pid+stray to corrected (variant_sku,sku), synced_at=now().
+ *   3. Shopify write: productVariantsBulkUpdate(productId, [{id, inventoryItem:{sku:corrected}}]).
+ *      Verify userErrors=[]. On failure => re-queue (don't advance), mirror already led but
+ *      Shopify is authoritative + re-run guard next pass; record failure.
+ *
+ * Coordinated sweep: iterate passes; each pass writes whichever strays are guard-CLEAR now.
+ * A blocker fixed this pass frees its dependent next pass. Stop when a pass makes no progress.
+ * Residual BLOCKED after convergence => DRAFT + tag Needs-ReSKU-Review.
+ *
+ * Idempotent/resumable: reads apply-audit.jsonl; a stray already at corrected (live) is skipped.
+ */
+import fs from 'fs';
+import { createRequire } from 'module';
+const require = createRequire('/Users/macstudio3/Projects/Designer-Wallcoverings/');
+const pg = require('pg');
+const { Pool } = pg;
+
+const DIR = '/Users/macstudio3/Projects/ticket-system/tk10002-resku';
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+const TOKEN = fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8')
+  .split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN=')).split('=').slice(1).join('=').trim();
+const GQL = `https://${SHOP}/admin/api/${API}/graphql.json`;
+const AUDIT = `${DIR}/apply-audit.jsonl`;
+const DRY = process.argv.includes('--dry');
+const BATCH_GAP_MS = 800; // per-write courtesy gap (well under bulk-push 90s rule; this is single-variant writes)
+
+const EXCLUDED = new Set(['7664488284211','7664489529395','7774188404787','7391684657203']);
+
+const pool = new Pool({ connectionString: 'postgresql://dw_admin@127.0.0.1:5432/dw_unified' });
+const bareId = x => String(x||'').replace(/^gid:\/\/shopify\/Product\//,'');
+const cf = x => String(x||'').trim().toUpperCase(); // case-fold
+const sleep = ms => new Promise(r=>setTimeout(r,ms));
+function audit(rec){ fs.appendFileSync(AUDIT, JSON.stringify({ts:new Date().toISOString(),...rec})+'\n'); }
+
+async function gql(query, variables){
+  const res = await fetch(GQL, { method:'POST',
+    headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},
+    body: JSON.stringify({query, variables}) });
+  const j = await res.json();
+  if(j.errors) throw new Error('GQL '+JSON.stringify(j.errors));
+  return j.data;
+}
+
+async function readVariants(pid){
+  const d = await gql(`query($id:ID!){ product(id:$id){ id status variants(first:50){ nodes{ id sku title } } } }`,
+    { id:`gid://shopify/Product/${pid}` });
+  return d.product;
+}
+
+// Case-folded guard on corrected SKU: taken if any NON-archived product OTHER than selfPid
+// carries corrected as an exact SKU or as base + '-%' across variant_sku/sku/dw_sku.
+async function guard(corrected, selfPid){
+  const { rows } = await pool.query(
+    `SELECT shopify_id, status, variant_sku, sku, dw_sku FROM shopify_products
+      WHERE (UPPER(variant_sku)=UPPER($1) OR UPPER(variant_sku) LIKE UPPER($2)
+          OR UPPER(sku)        =UPPER($1) OR UPPER(sku)         LIKE UPPER($2)
+          OR UPPER(dw_sku)     =UPPER($1) OR UPPER(dw_sku)      LIKE UPPER($2))
+        AND UPPER(COALESCE(status,''))<>'ARCHIVED'`,
+    [corrected, corrected+'-%']);
+  const foreign = rows.filter(r => bareId(r.shopify_id) !== bareId(selfPid));
+  return foreign.length
+    ? { clear:false, by: foreign.map(r=>`${bareId(r.shopify_id)}/${r.status}/${r.variant_sku||r.sku||r.dw_sku}`) }
+    : { clear:true };
+}
+
+async function mirrorUpdate(pid, stray, corrected){
+  // Update rows for THIS product whose stored sku matches the stray (case-fold), to corrected.
+  const q = await pool.query(
+    `UPDATE shopify_products SET variant_sku=$1, sku=$1, synced_at=now()
+      WHERE shopify_id=$2 AND (UPPER(variant_sku)=UPPER($3) OR UPPER(sku)=UPPER($3))`,
+    [corrected, `gid://shopify/Product/${pid}`, stray]);
+  return q.rowCount;
+}
+
+async function shopifyUpdate(pid, variantGid, corrected){
+  const d = await gql(
+    `mutation($pid:ID!,$v:[ProductVariantsBulkInput!]!){
+       productVariantsBulkUpdate(productId:$pid, variants:$v){
+         userErrors{ field message }
+         productVariants{ id sku }
+       } }`,
+    { pid:`gid://shopify/Product/${pid}`, v:[{ id:variantGid, inventoryItem:{ sku:corrected } }] });
+  return d.productVariantsBulkUpdate;
+}
+
+async function tag(pid, tagName){
+  const d = await gql(`mutation($id:ID!,$tags:[String!]!){ tagsAdd(id:$id, tags:$tags){ userErrors{message} } }`,
+    { id:`gid://shopify/Product/${pid}`, tags:[tagName] });
+  return d.tagsAdd;
+}
+
+// ---- Build the work set: (pid, stray, corrected, variantTitle) rows for the 64 ----
+const fv = JSON.parse(fs.readFileSync(`${DIR}/final-verdict.json`,'utf8'));
+const work = [];
+for(const p of fv.confident){
+  if(EXCLUDED.has(String(p.pid))) continue;
+  for(const s of p.strays){
+    work.push({ pid:String(p.pid), title:p.title, variantTitle:s.variantTitle,
+                stray:s.stray, corrected:s.corrected });
+  }
+}
+const products = new Set(work.map(w=>w.pid));
+console.log(`Work set: ${products.size} products / ${work.length} stray variants (excluded ${EXCLUDED.size} pids).`);
+
+const done = new Set();      // key pid|stray fully applied
+const skipped = [];          // {key, reason}
+const draftTagged = new Set();
+const results = [];          // audit-ready rows
+
+function key(w){ return `${w.pid}|${cf(w.stray)}`; }
+
+// ---- Coordinated multi-pass sweep ----
+let pass = 0;
+let remaining = work.slice();
+while(remaining.length){
+  pass++;
+  let progress = 0;
+  const next = [];
+  console.log(`\n=== PASS ${pass} — ${remaining.length} strays remaining ===`);
+  for(const w of remaining){
+    if(done.has(key(w))) continue;
+    // 0. read live variants
+    let prod;
+    try { prod = await readVariants(w.pid); }
+    catch(e){ console.log(`  READ-FAIL ${w.pid}: ${e.message}`); next.push(w); continue; }
+    const vsByStray = prod.variants.nodes.find(v=>cf(v.sku)===cf(w.stray));
+    const vsByCorrected = prod.variants.nodes.find(v=>cf(v.sku)===cf(w.corrected));
+    if(!vsByStray && vsByCorrected){
+      // already corrected (idempotent)
+      done.add(key(w)); progress++;
+      results.push({pid:w.pid, variantTitle:w.variantTitle, from:w.stray, to:w.corrected, action:'ALREADY-DONE'});
+      console.log(`  ALREADY-DONE ${w.pid} ${w.corrected}`);
+      continue;
+    }
+    if(!vsByStray){
+      // stray not present and corrected not present => drift
+      skipped.push({key:key(w), pid:w.pid, reason:`stray SKU ${w.stray} not found on product (drift); no corrected variant either`});
+      results.push({pid:w.pid, variantTitle:w.variantTitle, from:w.stray, to:w.corrected, action:'SKIP', reason:'stray-not-found-drift'});
+      console.log(`  SKIP-DRIFT ${w.pid} stray ${w.stray} not on product`);
+      done.add(key(w)); // don't retry; it's terminal
+      continue;
+    }
+    // 1. guard re-verify immediately before write
+    const g = await guard(w.corrected, w.pid);
+    if(!g.clear){
+      // may self-clear next pass if blocker is in our work set; keep for a later pass
+      next.push(w);
+      continue;
+    }
+    // 2. mirror write
+    if(DRY){
+      console.log(`  DRY would write ${w.pid} ${w.stray} -> ${w.corrected} (variant ${vsByStray.id.split('/').pop()})`);
+      done.add(key(w)); progress++;
+      results.push({pid:w.pid, variantTitle:w.variantTitle, from:w.stray, to:w.corrected, action:'DRY'});
+      continue;
+    }
+    const mrows = await mirrorUpdate(w.pid, w.stray, w.corrected);
+    // 3. shopify write
+    let sres;
+    try { sres = await shopifyUpdate(w.pid, vsByStray.id, w.corrected); }
+    catch(e){
+      audit({phase:'shopify-write-fail', pid:w.pid, stray:w.stray, corrected:w.corrected, err:e.message, mirrorRows:mrows});
+      console.log(`  SHOPIFY-FAIL ${w.pid} ${w.corrected}: ${e.message} — re-queue`);
+      next.push(w); continue;
+    }
+    if(sres.userErrors && sres.userErrors.length){
+      audit({phase:'shopify-usererrors', pid:w.pid, stray:w.stray, corrected:w.corrected, userErrors:sres.userErrors, mirrorRows:mrows});
+      console.log(`  USERERRORS ${w.pid} ${w.corrected}: ${JSON.stringify(sres.userErrors)}`);
+      skipped.push({key:key(w), pid:w.pid, reason:`userErrors ${JSON.stringify(sres.userErrors)}`});
+      results.push({pid:w.pid, variantTitle:w.variantTitle, from:w.stray, to:w.corrected, action:'SKIP', reason:'shopify-userErrors'});
+      done.add(key(w));
+      continue;
+    }
+    const wroteSku = sres.productVariants && sres.productVariants[0] && sres.productVariants[0].sku;
+    audit({phase:'applied', pid:w.pid, stray:w.stray, corrected:w.corrected, variantGid:vsByStray.id, mirrorRows:mrows, wroteSku});
+    results.push({pid:w.pid, variantTitle:w.variantTitle, from:w.stray, to:w.corrected, action:'APPLIED', mirrorRows:mrows, wroteSku});
+    console.log(`  APPLIED ${w.pid} ${w.stray} -> ${wroteSku} (mirror rows=${mrows})`);
+    done.add(key(w)); progress++;
+    await sleep(BATCH_GAP_MS);
+  }
+  remaining = next.filter(w=>!done.has(key(w)));
+  console.log(`  pass ${pass}: progress=${progress}, still-blocked=${remaining.length}`);
+  if(progress===0) break; // no forward progress => residue
+}
+
+// ---- Residue: still-blocked after convergence => DRAFT + tag ----
+for(const w of remaining){
+  if(done.has(key(w))) continue;
+  const g = await guard(w.corrected, w.pid);
+  const reason = `guard BLOCKED at convergence by ${g.by ? g.by.join(', ') : '?'}`;
+  skipped.push({key:key(w), pid:w.pid, reason});
+  results.push({pid:w.pid, variantTitle:w.variantTitle, from:w.stray, to:w.corrected, action:'SKIP-BLOCKED', reason});
+  if(!DRY && !draftTagged.has(w.pid)){
+    // DRAFT + tag Needs-ReSKU-Review
+    try {
+      await gql(`mutation($id:ID!){ productUpdate(input:{id:$id, status:DRAFT}){ userErrors{message} } }`,
+        { id:`gid://shopify/Product/${w.pid}` });
+      await tag(w.pid, 'Needs-ReSKU-Review');
+      draftTagged.add(w.pid);
+      audit({phase:'draft-tagged', pid:w.pid, reason});
+      console.log(`  DRAFT+TAG ${w.pid}: ${reason}`);
+    } catch(e){ console.log(`  DRAFT-TAG-FAIL ${w.pid}: ${e.message}`); }
+  } else {
+    console.log(`  ${DRY?'DRY ':''}RESIDUE-BLOCKED ${w.pid}: ${reason}`);
+  }
+}
+
+// ---- Summary ----
+const applied = results.filter(r=>r.action==='APPLIED');
+const already = results.filter(r=>r.action==='ALREADY-DONE');
+const dryRows = results.filter(r=>r.action==='DRY');
+const skips = results.filter(r=>r.action.startsWith('SKIP'));
+const productsFixed = new Set(applied.concat(already).map(r=>r.pid));
+console.log(`\n=== SUMMARY ===`);
+console.log(`products with >=1 variant fixed/already: ${productsFixed.size}`);
+console.log(`variants APPLIED: ${applied.length}`);
+console.log(`variants ALREADY-DONE: ${already.length}`);
+if(DRY) console.log(`variants DRY (would-apply): ${dryRows.length}`);
+console.log(`variants SKIPPED: ${skips.length}`);
+console.log(`products DRAFT-tagged: ${draftTagged.size}`);
+
+fs.writeFileSync(`${DIR}/apply-results.json`, JSON.stringify({
+  generated_at:new Date().toISOString(), dry:DRY,
+  products_in_scope:products.size, variants_in_scope:work.length,
+  variants_applied:applied.length, variants_already_done:already.length,
+  variants_dry:dryRows.length, variants_skipped:skips.length,
+  products_draft_tagged:[...draftTagged], passes:pass, results
+},null,2));
+console.log(`\nwrote apply-results.json`);
+await pool.end();
diff --git a/tk10002-resku/apply-results.json b/tk10002-resku/apply-results.json
new file mode 100644
index 00000000..cdfc6112
--- /dev/null
+++ b/tk10002-resku/apply-results.json
@@ -0,0 +1,538 @@
+{
+  "generated_at": "2026-07-30T17:02:57.742Z",
+  "dry": true,
+  "products_in_scope": 64,
+  "variants_in_scope": 71,
+  "variants_applied": 0,
+  "variants_already_done": 0,
+  "variants_dry": 44,
+  "variants_skipped": 27,
+  "products_draft_tagged": [],
+  "passes": 2,
+  "results": [
+    {
+      "pid": "6649656049715",
+      "variantTitle": "Priced per single roll (2'x12)'- Packaged in Doubles",
+      "from": "DIG-513061 - 04 Black White",
+      "to": "DIG-5130614 - 04 Black White",
+      "action": "DRY"
+    },
+    {
+      "pid": "6836337868851",
+      "variantTitle": "Priced per single roll (2'x12)'- Packaged in Doubles",
+      "from": "DIGGM-7400755-ROLL",
+      "to": "DIG-7400755-ROLL",
+      "action": "DRY"
+    },
+    {
+      "pid": "7535350022195",
+      "variantTitle": "Sold as Complete Mural 12' x 10",
+      "from": "Dig-352501-Set",
+      "to": "Dig-352500-Set",
+      "action": "DRY"
+    },
+    {
+      "pid": "7548957392947",
+      "variantTitle": "Mural 144\" x 120\"",
+      "from": "DIG-320255-mural",
+      "to": "DIG-320256-mural",
+      "action": "DRY"
+    },
+    {
+      "pid": "7582548623411",
+      "variantTitle": "Priced per single roll (2'x12)'- Packaged in Doubles",
+      "from": "Dig-510130-Roll",
+      "to": "DIG-510132-Roll",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664462364723",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-2400089-25-type-2-vinyl-Sample",
+      "to": "DIG-2400085-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664487890995",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-2400083-25-type-2-vinyl-Sample",
+      "to": "DIG-27173-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664488218675",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-7700743-25-type-2-vinyl-Sample",
+      "to": "Dig-240426-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664488251443",
+      "variantTitle": "Prepasted / 2ft x 1ft Sample",
+      "from": "Dig-2400089-25-pre-Sample",
+      "to": "Dig-240425-25-pre-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664488251443",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-20009-25-type-2-vinyl-Sample",
+      "to": "Dig-240425-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664490643507",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-435098-25-type-2-vinyl-Sample",
+      "to": "Dig-240413-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664493002803",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-240407-25-type-2-vinyl-Sample",
+      "to": "DIG-2400065-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664511418419",
+      "variantTitle": "Prepasted / 2ft x 1ft Sample",
+      "from": "Dig-25-25-pre-Sample",
+      "to": "DIG_198404-25-pre-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664511451187",
+      "variantTitle": "Prepasted / 2ft x 1ft Sample",
+      "from": "Dig-25-25-pre-Sample",
+      "to": "DIG_198405-25-pre-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664511451187",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-198401-25-type-2-vinyl-Sample",
+      "to": "DIG_198405-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664511778867",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-3002-25-type-2-vinyl-Sample",
+      "to": "DIG-9100-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664511877171",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-60015-25-type-2-vinyl-Sample",
+      "to": "DIG-60014-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664512106547",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-40051-25-type-2-vinyl-Sample",
+      "to": "DIG-40041-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664512237619",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-240416-25-type-2-vinyl-Sample",
+      "to": "DIG-68000-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664512303155",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-5101406-25-type-2-vinyl-Sample",
+      "to": "DIG-5101407-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664512466995",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-85337-25-type-2-vinyl-Sample",
+      "to": "DIG-85336-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664512696371",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-4350959-25-type-2-vinyl-Sample",
+      "to": "DIG-1985-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664512761907",
+      "variantTitle": "Prepasted / 2ft x 1ft Sample",
+      "from": "Dig-3001-25-pre-Sample",
+      "to": "Book-3001-25-pre-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664512761907",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-1952-25-type-2-vinyl-Sample",
+      "to": "Book-3001-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664512827443",
+      "variantTitle": "Prepasted / 2ft x 1ft Sample",
+      "from": "Dig-1100-25-pre-Sample",
+      "to": "Vin-1100-25-pre-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664512827443",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-1100-25-type-2-vinyl-Sample",
+      "to": "Vin-1100-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664513646643",
+      "variantTitle": "Prepasted / 2ft x 1ft Sample",
+      "from": "Dig-25-25-pre-sample",
+      "to": "DIG_10082002-25-pre-sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664513646643",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-25-25-type-2-vinyl-Sample",
+      "to": "DIG_10082002-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664515285043",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-4350100-25-type-2-vinyl-Sample",
+      "to": "DIG-435003-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664583704627",
+      "variantTitle": "Prepasted / 2ft x 1ft Sample",
+      "from": "Dig-1010-25-pre-Sample",
+      "to": "Vin-1010-25-pre-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664584196147",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-770351-25-type-2-vinyl-Sample",
+      "to": "Dig-540002-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664584851507",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-770089-25-type-2-vinyl-Sample",
+      "to": "DIG-740108-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664584884275",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-240407-25-type-2-vinyl-Sample",
+      "to": "DIG-74118-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664585015347",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-553342-25-type-2-vinyl-Sample",
+      "to": "DIG-553341-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664585965619",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-762174-25-type-2-vinyl-Sample",
+      "to": "DIG-762168-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664586326067",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-553794-25-type-2-vinyl-Sample",
+      "to": "DIG-553793-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664586686515",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-25-25-type-2-vinyl-Sample",
+      "to": "DIG-439241-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664631349299",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-51235-25-type-2-vinyl-Sample",
+      "to": "DIG-51288-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664636067891",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-435086-25-type-2-vinyl-Sample",
+      "to": "DIG-435095-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664636100659",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-770089-25-type-2-vinyl-Sample",
+      "to": "DIG-435086-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664640917555",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-700005-25-type-2-vinyl-Sample",
+      "to": "WPEX-700005-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664640917555",
+      "variantTitle": "Prepasted / 2ft x 1ft Sample",
+      "from": "Dig-700005-25-pre-sample",
+      "to": "WPEX-700005-25-pre-sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664640983091",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-700007-25-type-2-vinyl-Sample",
+      "to": "WPEX-700007-25-type-2-vinyl-Sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664640983091",
+      "variantTitle": "Prepasted / 2ft x 1ft Sample",
+      "from": "Dig-700007-25-pre-sample",
+      "to": "WPEX-700007-25-pre-sample",
+      "action": "DRY"
+    },
+    {
+      "pid": "7664462168115",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-5101409-25-type-2-vinyl-Sample",
+      "to": "DIG-2400091-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664512270387/ACTIVE/Dig-2400091-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664489496627",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-240412-25-type-2-vinyl-Sample",
+      "to": "Dig-240417-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664489529395/ACTIVE/Dig-240417-25-type-2-vinyl-Sample, 7774188404787/ACTIVE/Dig-240417-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664490545203",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-171053-25-type-2-vinyl-Sample",
+      "to": "DIG-171052-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664490577971/ACTIVE/Dig-171052-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664490577971",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-171052-25-type-2-vinyl-Sample",
+      "to": "DIG-171051-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664585867315/ACTIVE/Dig-171051-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664511549491",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-198402-25-type-2-vinyl-Sample",
+      "to": "DIG-198401-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664511451187/ACTIVE/Dig-198401-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664511582259",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-98527-25-type-2-vinyl-Sample",
+      "to": "DIG-198402-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664511549491/ACTIVE/Dig-198402-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664511647795",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-98521-25-type-2-vinyl-Sample",
+      "to": "DIG-98527-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664511582259/ACTIVE/Dig-98527-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664511680563",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-5531532-25-type-2-vinyl-Sample",
+      "to": "DIG-98521-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664511647795/ACTIVE/Dig-98521-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664511909939",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-60016-25-type-2-vinyl-Sample",
+      "to": "DIG-60015-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664511877171/ACTIVE/Dig-60015-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664511942707",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-60018-25-type-2-vinyl-Sample",
+      "to": "DIG-60016-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664511909939/ACTIVE/Dig-60016-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512008243",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-40011-25-type-2-vinyl-Sample",
+      "to": "DIG-60018-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664511942707/ACTIVE/Dig-60018-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512270387",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-2400091-25-type-2-vinyl-Sample",
+      "to": "DIG-435098-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664490643507/ACTIVE/Dig-435098-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512335923",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-85330-25-type-2-vinyl-Sample",
+      "to": "DIG-5101406-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664512303155/ACTIVE/Dig-5101406-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512401459",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-85335-25-type-2-vinyl-Sample",
+      "to": "DIG-85330-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664512335923/ACTIVE/Dig-85330-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512434227",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-40021-25-type-2-vinyl-Sample",
+      "to": "DIG-85335-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664512401459/ACTIVE/Dig-85335-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512499763",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-85331-25-type-2-vinyl-Sample",
+      "to": "DIG-85337-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664512466995/ACTIVE/Dig-85337-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512532531",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-85333-25-type-2-vinyl-Sample",
+      "to": "DIG-85331-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664512499763/ACTIVE/Dig-85331-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512565299",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-85334-25-type-2-vinyl-Sample",
+      "to": "DIG-85333-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664512532531/ACTIVE/Dig-85333-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512598067",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-85332-25-type-2-vinyl-Sample",
+      "to": "DIG-85334-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664512565299/ACTIVE/Dig-85334-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512630835",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-98621-25-type-2-vinyl-Sample",
+      "to": "DIG-85332-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664512598067/ACTIVE/Dig-85332-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512663603",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-240416-25-type-2-vinyl-Sample",
+      "to": "DIG-98621-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664512630835/ACTIVE/Dig-98621-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664512794675",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-1100-25-type-2-vinyl-Sample",
+      "to": "DIG-1952-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664512761907/ACTIVE/Dig-1952-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664584949811",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-5531533-25-type-2-vinyl-Sample",
+      "to": "DIG-5531532-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664511680563/ACTIVE/Dig-5531532-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664585048115",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-2010-25-type-2-vinyl-Sample",
+      "to": "DIG-553342-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664585015347/ACTIVE/Dig-553342-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664585867315",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-171051-25-type-2-vinyl-Sample",
+      "to": "DIG-747481-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664488284211/ACTIVE/Dig-747481-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664585998387",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-762179-25-type-2-vinyl-Sample",
+      "to": "DIG-762174-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664585965619/ACTIVE/Dig-762174-25-type-2-vinyl-Sample"
+    },
+    {
+      "pid": "7664586031155",
+      "variantTitle": "Type 2 Textured Vinyl / 2ft x 1ft Sample",
+      "from": "Dig-762165-25-type-2-vinyl-Sample",
+      "to": "DIG-762179-25-type-2-vinyl-Sample",
+      "action": "SKIP-BLOCKED",
+      "reason": "guard BLOCKED at convergence by 7664585998387/ACTIVE/Dig-762179-25-type-2-vinyl-Sample"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tk10002-resku/check-state.mjs b/tk10002-resku/check-state.mjs
new file mode 100644
index 00000000..66a93296
--- /dev/null
+++ b/tk10002-resku/check-state.mjs
@@ -0,0 +1,26 @@
+#!/usr/bin/env node
+// Read-only: for the 64 clean confident-STRAY products, check LIVE Shopify whether each
+// stray variant now carries its `corrected` SKU (DONE) or still the `stray` SKU (PENDING).
+import fs from 'node:fs';
+const TOKEN=(process.env.SHOPIFY_ADMIN_TOKEN||'').trim();
+const STORE='designer-laboratory-sandbox.myshopify.com', API='2024-10';
+const EXCLUDE=new Set(['7664488284211','7664489529395','7774188404787','7391684657203']);
+const fv=JSON.parse(fs.readFileSync(new URL('./final-verdict.json',import.meta.url)));
+const products=fv.confident.filter(p=>!EXCLUDE.has(String(p.pid)));
+const gql=async(q)=>{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:q})});return (await r.json());};
+let done=0,pending=0,other=0,errs=0; const pend=[];
+for(const p of products){
+  const q=`{product(id:"gid://shopify/Product/${p.pid}"){status variants(first:40){nodes{sku}}}}`;
+  let d; try{d=(await gql(q)).data.product;}catch(e){errs++;continue;}
+  if(!d){other++;continue;}
+  const skus=new Set(d.variants.nodes.map(v=>(v.sku||'').trim()));
+  for(const s of p.strays){
+    const corr=s.corrected.trim(), stray=s.stray.trim();
+    if(skus.has(corr)) done++;
+    else if(skus.has(stray)){ pending++; pend.push({pid:p.pid,stray,corrected:corr}); }
+    else other++;
+  }
+}
+console.log(`products checked=${products.length}  strayVariants: DONE=${done} PENDING=${pending} OTHER=${other} fetchErrs=${errs}`);
+if(pend.length){console.log('\nPENDING (still to write):'); pend.slice(0,80).forEach(x=>console.log(`  ${x.pid}: ${x.stray} -> ${x.corrected}`));}
+fs.writeFileSync(new URL('./state-check.json',import.meta.url),JSON.stringify({done,pending,other,errs,pend},null,2));

← dda63dba TK-10002 PLAN v2: bound full stray family (71 products/81 st  ·  back to Ticket System  ·  TK-10002 re-SKU: 65 stray variants fixed live (6 skipped, he 467d9321 →