← 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
A verification/TK-11521/apply-repair.mjsA verification/TK-11521/postcheck.mjsA verification/TK-11521/rollback-map-2026-09-12T14-39-17-597Z.jsonA verification/TK-11521/rollback-map-2026-09-12T14-40-03-847Z.json
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 →