[object Object]

← back to Designerwallcoverings

TK-11544 APPLIED + verified: 11 foreign Sanderson gallery images removed, 0 failed

eac87b16a4f4a9e2cc917096a4bae91327684a39 · 2026-09-12 07:41:33 -0700 · Steve Abrams

Steve-authorized in-session. deleted 11/11, 0 failed. Verified 3 ways: postcheck populations
(confirmed_gone 11/11, products_nonempty 9/9, imageless 0), hardened dw-image-identity-canary
(verdict PASS, foreign 0/0, gallery 49->38, unattributed 0), and the pre-delete dry-run
(verified-deletable 11 / REFUSED 0). Rollback map committed (11 entries, POST re-add undo).
Ledgered to executed-reversible/ledger.jsonl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2z3sPEypEzq9yKfdWmbAR

Files touched

Diff

commit eac87b16a4f4a9e2cc917096a4bae91327684a39
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Sep 12 07:41:33 2026 -0700

    TK-11544 APPLIED + verified: 11 foreign Sanderson gallery images removed, 0 failed
    
    Steve-authorized in-session. deleted 11/11, 0 failed. Verified 3 ways: postcheck populations
    (confirmed_gone 11/11, products_nonempty 9/9, imageless 0), hardened dw-image-identity-canary
    (verdict PASS, foreign 0/0, gallery 49->38, unattributed 0), and the pre-delete dry-run
    (verified-deletable 11 / REFUSED 0). Rollback map committed (11 entries, POST re-add undo).
    Ledgered to executed-reversible/ledger.jsonl.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01W2z3sPEypEzq9yKfdWmbAR
---
 verification/TK-11521/apply-repair.mjs             | 66 +++++++++++++++
 verification/TK-11521/postcheck.mjs                | 43 ++++++++++
 .../rollback-map-2026-09-12T14-39-17-597Z.json     | 93 ++++++++++++++++++++++
 .../rollback-map-2026-09-12T14-40-03-847Z.json     | 93 ++++++++++++++++++++++
 4 files changed, 295 insertions(+)

diff --git a/verification/TK-11521/apply-repair.mjs b/verification/TK-11521/apply-repair.mjs
new file mode 100644
index 0000000..6049250
--- /dev/null
+++ b/verification/TK-11521/apply-repair.mjs
@@ -0,0 +1,66 @@
+#!/usr/bin/env node
+// TK-11461 selective gallery repair. DRY-RUN BY DEFAULT; --apply performs deletes.
+// Verify-before-act: re-GETs each product live and refuses any drift vs the plan.
+// Writes a rollback map BEFORE the first delete.
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const APPLY = process.argv.includes('--apply');
+const DIR = path.dirname(fileURLToPath(import.meta.url));
+const VER = '2024-10';
+const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
+const plan = JSON.parse(fs.readFileSync(path.join(DIR, 'repair-plan-76-images.json'), 'utf8'));
+const tok = fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.env', 'utf8')
+  .match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)[1].trim().replace(/^["']|["']$/g, '');
+const H = { 'X-Shopify-Access-Token': tok, 'Content-Type': 'application/json' };
+const fname = s => s.split('?')[0].split('/').pop();
+
+const byPid = {};
+for (const d of plan.drop) { (byPid[d.pid] = byPid[d.pid] || []).push(d); }
+
+const rollback = [], refusals = [], ok = [];
+for (const pid of Object.keys(byPid)) {
+  const r = await fetch(`https://${DOMAIN}/admin/api/${VER}/products/${pid}.json?fields=id,handle,images`, { headers: H });
+  if (!r.ok) { refusals.push({ pid, why: `live GET failed HTTP ${r.status}` }); continue; }
+  const live = (await r.json()).product;
+  const liveById = {};
+  for (const im of live.images) liveById[im.id] = im;
+  const targets = byPid[pid];
+  let bad = false;
+  // GUARD 1: each planned image must still exist, not be the hero, and match filename
+  for (const t of targets) {
+    const im = liveById[t.img_id];
+    if (!im) { refusals.push({ pid, handle: live.handle, img_id: t.img_id, why: 'image no longer on product (drift)' }); bad = true; continue; }
+    if (im.position === 1) { refusals.push({ pid, handle: live.handle, img_id: t.img_id, why: 'image is now position 1 (hero) — refusing' }); bad = true; continue; }
+    if (fname(im.src) !== fname(t.src)) { refusals.push({ pid, handle: live.handle, img_id: t.img_id, why: `filename drift ${fname(im.src)} != ${fname(t.src)}` }); bad = true; }
+  }
+  // GUARD 2: product must retain at least one image
+  if (live.images.length - targets.length < 1) { refusals.push({ pid, handle: live.handle, why: 'would leave product imageless — refusing' }); bad = true; }
+  if (bad) continue;
+  for (const t of targets) {
+    const im = liveById[t.img_id];
+    rollback.push({ product_id: Number(pid), handle: live.handle, image_id: im.id, src: im.src, position: im.position, alt: im.alt || null });
+    ok.push({ pid: Number(pid), handle: live.handle, img_id: im.id, fn: fname(im.src), position: im.position });
+  }
+}
+
+const stamp = new Date().toISOString().replace(/[:.]/g, '-');
+fs.writeFileSync(path.join(DIR, `rollback-map-${stamp}.json`),
+  JSON.stringify({ note: 're-add via POST /products/{product_id}/images.json {image:{src,position,alt}}', images: rollback }, null, 1));
+
+console.log(`plan                  : ${plan.drop.length} images / ${plan.products} products`);
+console.log(`verified-deletable    : ${ok.length}`);
+console.log(`REFUSED (drift/safety): ${refusals.length}`);
+if (refusals.length) console.log(JSON.stringify(refusals.slice(0, 10), null, 1));
+console.log(`rollback map written  : rollback-map-${stamp}.json (${rollback.length} entries)`);
+
+if (!APPLY) { console.log('\nDRY RUN — no deletes performed. Re-run with --apply to execute.'); process.exit(0); }
+
+let done = 0, fail = 0;
+for (const t of ok) {
+  const d = await fetch(`https://${DOMAIN}/admin/api/${VER}/products/${t.pid}/images/${t.img_id}.json`, { method: 'DELETE', headers: H });
+  if (d.ok) { done++; } else { fail++; console.error(`FAIL ${t.handle} ${t.fn} HTTP ${d.status}`); }
+  await new Promise(r => setTimeout(r, 550));
+}
+console.log(`\nAPPLIED: deleted ${done}, failed ${fail}`);
diff --git a/verification/TK-11521/postcheck.mjs b/verification/TK-11521/postcheck.mjs
new file mode 100644
index 0000000..bcdfbaa
--- /dev/null
+++ b/verification/TK-11521/postcheck.mjs
@@ -0,0 +1,43 @@
+#!/usr/bin/env node
+// TK-11461 READ-ONLY post-apply read-back with EXPLICIT POPULATIONS.
+// Run AFTER the repair to prove the 76 targets are gone and no product is imageless.
+// "0 of 0" can never be read as "76 of 76": every count carries its denominator.
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+const DIR = path.dirname(fileURLToPath(import.meta.url));
+const VER = '2024-10';
+const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
+const plan = JSON.parse(fs.readFileSync(path.join(DIR, 'repair-plan-76-images.json'), 'utf8'));
+const tok = fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.env', 'utf8')
+  .match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)[1].trim().replace(/^["']|["']$/g, '');
+const H = { 'X-Shopify-Access-Token': tok, 'Content-Type': 'application/json' };
+const byPid = {}; for (const d of plan.drop) (byPid[d.pid] = byPid[d.pid] || []).push(d);
+const affected = Object.keys(byPid);
+
+let gone = 0, present = 0, nonempty = 0, empty = 0, geterr = 0;
+const stillHere = [];
+for (const pid of affected) {
+  const r = await fetch(`https://${DOMAIN}/admin/api/${VER}/products/${pid}.json?fields=id,handle,images`, { headers: H });
+  if (!r.ok) { geterr++; await new Promise(s => setTimeout(s, 120)); continue; }
+  const live = (await r.json()).product;
+  const ids = new Set(live.images.map(i => i.id));
+  for (const t of byPid[pid]) { if (ids.has(t.img_id)) { present++; stillHere.push({ pid, handle: live.handle, img_id: t.img_id, fn: t.fn }); } else gone++; }
+  if (live.images.length >= 1) nonempty++; else empty++;
+  await new Promise(s => setTimeout(s, 120));
+}
+const attempted = plan.drop.length;
+console.log(JSON.stringify({
+  planned_targets: attempted,
+  affected_products: affected.length,
+  products_fetched_ok: affected.length - geterr,
+  get_errors: geterr,
+  confirmed_gone: `${gone} of ${attempted}`,
+  still_present_BAD: present,
+  products_nonempty: `${nonempty} of ${affected.length}`,
+  products_imageless_BAD: empty,
+  verdict: (geterr === 0 && present === 0 && empty === 0 && gone === attempted)
+    ? 'PASS — all 76 gone, every product retains >=1 image'
+    : (geterr > 0 ? 'UNKNOWN — some products unreadable, cannot certify' : 'FAIL — residual present or a product went imageless')
+}, null, 1));
+if (stillHere.length) console.log('still_present:', JSON.stringify(stillHere.slice(0, 20), null, 1));
diff --git a/verification/TK-11521/rollback-map-2026-09-12T14-39-17-597Z.json b/verification/TK-11521/rollback-map-2026-09-12T14-39-17-597Z.json
new file mode 100644
index 0000000..45346ea
--- /dev/null
+++ b/verification/TK-11521/rollback-map-2026-09-12T14-39-17-597Z.json
@@ -0,0 +1,93 @@
+{
+ "note": "re-add via POST /products/{product_id}/images.json {image:{src,position,alt}}",
+ "images": [
+  {
+   "product_id": 7938891448371,
+   "handle": "alnwick-logs-ash-sanderson",
+   "image_id": 37995068653619,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DEBB216508_2df9.jpg?v=1788210744",
+   "position": 2,
+   "alt": "Alnwick Logs"
+  },
+  {
+   "product_id": 7938891481139,
+   "handle": "alnwick-logs-birch-sanderson",
+   "image_id": 37995071307827,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DEBB216508_2df9_6aed8978-78da-47d5-92ab-c7bc33a3d68d.jpg?v=1788210757",
+   "position": 2,
+   "alt": "Alnwick Logs"
+  },
+  {
+   "product_id": 7938891776051,
+   "handle": "anaar-tree-annato-blueberry-sanderson",
+   "image_id": 37995073339443,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DCPW216792_ba9a.jpg?v=1788210827",
+   "position": 2,
+   "alt": "Anaar Tree"
+  },
+  {
+   "product_id": 7938893316147,
+   "handle": "aperigon-parade-porcelain-sanderson",
+   "image_id": 37995081793587,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DGDW217302_0ba9_9ffb7be5-97ee-4fc2-b78a-398d4094ce58.jpg?v=1788211021",
+   "position": 2,
+   "alt": "Aperigon Parade"
+  },
+  {
+   "product_id": 7938895282227,
+   "handle": "calathea-orchid-eucalyptus-sanderson",
+   "image_id": 37995093884979,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DGLW216631_3286.jpg?v=1788211218",
+   "position": 2,
+   "alt": "Calathea"
+  },
+  {
+   "product_id": 7938895577139,
+   "handle": "carpet-garden-paradise-sanderson",
+   "image_id": 37995095851059,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DHIP217519_97ef.jpg?v=1788211294",
+   "position": 2,
+   "alt": "Carpet Garden"
+  },
+  {
+   "product_id": 7939312058419,
+   "handle": "dorothy-linen-sanderson",
+   "image_id": 37996782583859,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DHPO216382_17e3_125e4a6c-65d0-441b-85a8-d6055d7de8e2.jpg?v=1788247612",
+   "position": 2,
+   "alt": "Dorothy"
+  },
+  {
+   "product_id": 7944135835699,
+   "handle": "squirrel-amp-dove-dove-linen-ivory-sanderson",
+   "image_id": 38012551397427,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DVIN214597_6eb4.jpg?v=1788495761",
+   "position": 2,
+   "alt": "Squirrel &Amp; Dove & Dove Linen/Ivory | Sanderson detail 1"
+  },
+  {
+   "product_id": 7944135835699,
+   "handle": "squirrel-amp-dove-dove-linen-ivory-sanderson",
+   "image_id": 38012551430195,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DVIN214598_01d7.jpg?v=1788495761",
+   "position": 3,
+   "alt": "Squirrel &Amp; Dove & Dove Linen/Ivory | Sanderson detail 2"
+  },
+  {
+   "product_id": 7946197663795,
+   "handle": "yucca-botanical-green-sanderson",
+   "image_id": 38020681105459,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DGLW216631_3286_99b1440a-739e-43e1-bfe8-e3748ae5fb10.jpg?v=1788668526",
+   "position": 3,
+   "alt": "Yucca Botanical Green | Sanderson detail 4"
+  },
+  {
+   "product_id": 7946197663795,
+   "handle": "yucca-botanical-green-sanderson",
+   "image_id": 38020681138227,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DGLW216630_2f50.jpg?v=1788668526",
+   "position": 4,
+   "alt": "Yucca Botanical Green | Sanderson detail 5"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/verification/TK-11521/rollback-map-2026-09-12T14-40-03-847Z.json b/verification/TK-11521/rollback-map-2026-09-12T14-40-03-847Z.json
new file mode 100644
index 0000000..45346ea
--- /dev/null
+++ b/verification/TK-11521/rollback-map-2026-09-12T14-40-03-847Z.json
@@ -0,0 +1,93 @@
+{
+ "note": "re-add via POST /products/{product_id}/images.json {image:{src,position,alt}}",
+ "images": [
+  {
+   "product_id": 7938891448371,
+   "handle": "alnwick-logs-ash-sanderson",
+   "image_id": 37995068653619,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DEBB216508_2df9.jpg?v=1788210744",
+   "position": 2,
+   "alt": "Alnwick Logs"
+  },
+  {
+   "product_id": 7938891481139,
+   "handle": "alnwick-logs-birch-sanderson",
+   "image_id": 37995071307827,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DEBB216508_2df9_6aed8978-78da-47d5-92ab-c7bc33a3d68d.jpg?v=1788210757",
+   "position": 2,
+   "alt": "Alnwick Logs"
+  },
+  {
+   "product_id": 7938891776051,
+   "handle": "anaar-tree-annato-blueberry-sanderson",
+   "image_id": 37995073339443,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DCPW216792_ba9a.jpg?v=1788210827",
+   "position": 2,
+   "alt": "Anaar Tree"
+  },
+  {
+   "product_id": 7938893316147,
+   "handle": "aperigon-parade-porcelain-sanderson",
+   "image_id": 37995081793587,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DGDW217302_0ba9_9ffb7be5-97ee-4fc2-b78a-398d4094ce58.jpg?v=1788211021",
+   "position": 2,
+   "alt": "Aperigon Parade"
+  },
+  {
+   "product_id": 7938895282227,
+   "handle": "calathea-orchid-eucalyptus-sanderson",
+   "image_id": 37995093884979,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DGLW216631_3286.jpg?v=1788211218",
+   "position": 2,
+   "alt": "Calathea"
+  },
+  {
+   "product_id": 7938895577139,
+   "handle": "carpet-garden-paradise-sanderson",
+   "image_id": 37995095851059,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DHIP217519_97ef.jpg?v=1788211294",
+   "position": 2,
+   "alt": "Carpet Garden"
+  },
+  {
+   "product_id": 7939312058419,
+   "handle": "dorothy-linen-sanderson",
+   "image_id": 37996782583859,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DHPO216382_17e3_125e4a6c-65d0-441b-85a8-d6055d7de8e2.jpg?v=1788247612",
+   "position": 2,
+   "alt": "Dorothy"
+  },
+  {
+   "product_id": 7944135835699,
+   "handle": "squirrel-amp-dove-dove-linen-ivory-sanderson",
+   "image_id": 38012551397427,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DVIN214597_6eb4.jpg?v=1788495761",
+   "position": 2,
+   "alt": "Squirrel &Amp; Dove & Dove Linen/Ivory | Sanderson detail 1"
+  },
+  {
+   "product_id": 7944135835699,
+   "handle": "squirrel-amp-dove-dove-linen-ivory-sanderson",
+   "image_id": 38012551430195,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DVIN214598_01d7.jpg?v=1788495761",
+   "position": 3,
+   "alt": "Squirrel &Amp; Dove & Dove Linen/Ivory | Sanderson detail 2"
+  },
+  {
+   "product_id": 7946197663795,
+   "handle": "yucca-botanical-green-sanderson",
+   "image_id": 38020681105459,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DGLW216631_3286_99b1440a-739e-43e1-bfe8-e3748ae5fb10.jpg?v=1788668526",
+   "position": 3,
+   "alt": "Yucca Botanical Green | Sanderson detail 4"
+  },
+  {
+   "product_id": 7946197663795,
+   "handle": "yucca-botanical-green-sanderson",
+   "image_id": 38020681138227,
+   "src": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DGLW216630_2f50.jpg?v=1788668526",
+   "position": 4,
+   "alt": "Yucca Botanical Green | Sanderson detail 5"
+  }
+ ]
+}
\ No newline at end of file

← 0824967 TK-11521/TK-11544: gated 11-image Sanderson repair plan (har  ·  back to Designerwallcoverings  ·  TK-11486: pre-built dry-run restore-map (287 Kravet below-MA e1c8f15 →