← back to Japan Enrich
TK-10649 remediation executors: M2 Novasuede images, M4-B Freshwater Grass mis-key fix, M1 mfr-backfill (dry-run staged)
bf1295dce166790c0bc4f21bb67940a5075689bb · 2026-08-18 09:08:21 -0700 · Steve Abrams
Files touched
A tk10649-m1-mfr-backfill.jsA tk10649-m2-novasuede-images.jsA tk10649-m4b-freshwater-fix.js
Diff
commit bf1295dce166790c0bc4f21bb67940a5075689bb
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Aug 18 09:08:21 2026 -0700
TK-10649 remediation executors: M2 Novasuede images, M4-B Freshwater Grass mis-key fix, M1 mfr-backfill (dry-run staged)
---
tk10649-m1-mfr-backfill.js | 99 ++++++++++++++++++++++++++++++++++++++++++
tk10649-m2-novasuede-images.js | 72 ++++++++++++++++++++++++++++++
tk10649-m4b-freshwater-fix.js | 71 ++++++++++++++++++++++++++++++
3 files changed, 242 insertions(+)
diff --git a/tk10649-m1-mfr-backfill.js b/tk10649-m1-mfr-backfill.js
new file mode 100644
index 0000000..a0947cd
--- /dev/null
+++ b/tk10649-m1-mfr-backfill.js
@@ -0,0 +1,99 @@
+#!/usr/bin/env node
+// TK-10649 M1 — COALESCE-guarded, FILL-EMPTY-ONLY mfr backfill for the 1,257
+// published+sellable ACTIVE products with NO mfr sku anywhere.
+// DRY_RUN=1 (default) => report only, NO writes. DRY_RUN=0 => fire (Steve-gated GO).
+//
+// HARD RAILS baked in:
+// - fill-empty ONLY: never touches a product that already has ANY mfr (column or either metafield)
+// - joins per-vendor on the RELIABLE key found in the audit (shopify_id for Zoffany;
+// Tres Tintas/Scalamandre/Sister Parish have NO reliable staging join → reported, NOT written)
+// - Malibu is PRIVATE-LABEL (WallQuest->Malibu): its staging "mfr_sku" is a slug that can
+// LEAK the real product identity → held out of the auto-backfill (flagged for owner)
+// - writes BOTH the top-level mirror mfr_sku (local read) AND the Shopify dwc.manufacturer_sku
+// metafield (customer-facing authoritative); Shopify write is the source of truth
+//
+// This file is the executor; the DRY-RUN report is what goes to Steve for the GO.
+
+const fs = require('fs');
+const { execSync } = require('child_process');
+const https = require('https');
+const path = require('path');
+
+for (const l of fs.readFileSync(path.join(process.env.HOME, 'Projects/secrets-manager/.env'), 'utf8').split('\n')) {
+ const m = l.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
+ if (m && !(m[1] in process.env)) process.env[m[1]] = m[2];
+}
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
+const DRY = process.env.DRY_RUN !== '0';
+const PSQL = 'psql -h /tmp -d dw_unified -tAc';
+
+// Per-vendor backfill plan derived from the 2026-08-18 join-key audit.
+// key = the ONLY reliable join found; null => no reliable staging join (report, do not write).
+const PLAN = [
+ { vendor: 'Zoffany', table: 'zoffany_catalog', key: 'shopify_id', backfillable: true },
+ { vendor: 'Tres Tintas', table: 'tres_tintas_catalog', key: null, backfillable: false, reason: 'live dw_sku empty + DWTB variant-prefix vs staging DWTS dw_sku; staging shopify_product_id empty → 0 matches by any key' },
+ { vendor: 'Scalamandre', table: 'scalamandre_catalog', key: null, backfillable: false, reason: '0 matches by dw_sku / variant_sku / shopify_id' },
+ { vendor: 'Sister Parish', table: 'sisterparish_catalog', key: null, backfillable: false, reason: 'staging has NO mfr_sku column (Shopify-mirror scrape); private-label DWPH line' },
+ { vendor: 'Malibu Wallpaper', table: 'malibu_catalog', key: 'shopify_id', backfillable: false, reason: 'PRIVATE-LABEL (WallQuest->Malibu): staging mfr_sku is a real-vendor slug that would LEAK the true product identity customer-facing — owner call' },
+];
+
+function q(sql) { return execSync(`${PSQL} "${sql.replace(/"/g, '\\"')}"`, { encoding: 'utf8' }).trim(); }
+
+function gql(query, variables) {
+ return new Promise((res, rej) => {
+ const b = JSON.stringify({ query, variables });
+ const r = https.request({ host: DOMAIN, path: '/admin/api/2024-10/graphql.json', method: 'POST',
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(b) } },
+ x => { let d = ''; x.on('data', c => d += c); x.on('end', () => { try { res(JSON.parse(d)); } catch (e) { rej(new Error(d)); } }); });
+ r.on('error', rej); r.write(b); r.end();
+ });
+}
+
+const NO_MFR = `(mfr_sku IS NULL OR mfr_sku='') AND COALESCE(metafields->'dwc'->'manufacturer_sku'->>'value','')='' AND COALESCE(metafields->'custom'->'manufacturer_sku'->>'value','')=''`;
+
+(async () => {
+ console.log(`\n=== TK-10649 M1 — mfr backfill (${DRY ? 'DRY-RUN (report only)' : 'LIVE'}) ===\n`);
+ let totalBackfillable = 0, totalBlocked = 0;
+ const fireList = [];
+ for (const p of PLAN) {
+ const total = +q(`SELECT count(*) FROM shopify_products WHERE status='ACTIVE' AND online_store_published IS TRUE AND has_product_variant IS TRUE AND vendor='${p.vendor}' AND ${NO_MFR}`);
+ if (!p.backfillable) {
+ totalBlocked += total;
+ console.log(`[BLOCKED] ${p.vendor}: ${total} no-mfr — ${p.reason}`);
+ continue;
+ }
+ // Zoffany-style: join staging by shopify_id, mfr_sku real & non-empty
+ const rows = q(`SELECT regexp_replace(l.shopify_id,'.*/','')||'|'||s.mfr_sku
+ FROM shopify_products l JOIN ${p.table} s ON s.shopify_product_id::text=regexp_replace(l.shopify_id,'.*/','')
+ WHERE l.status='ACTIVE' AND l.online_store_published IS TRUE AND l.has_product_variant IS TRUE AND l.vendor='${p.vendor}'
+ AND ${NO_MFR.replace(/mfr_sku/g,'l.mfr_sku').replace(/metafields/g,'l.metafields')}
+ AND s.mfr_sku IS NOT NULL AND s.mfr_sku<>''`).split('\n').filter(Boolean);
+ totalBackfillable += rows.length;
+ console.log(`[OK] ${p.vendor}: ${total} no-mfr → ${rows.length} backfillable via ${p.key}`);
+ for (const r of rows) { const [sid, mfr] = r.split('|'); fireList.push({ vendor: p.vendor, sid, mfr }); }
+ }
+ console.log(`\n=== TOTALS ===`);
+ console.log(` backfillable NOW (reliable join): ${totalBackfillable}`);
+ console.log(` BLOCKED (no reliable join / private-label leak risk): ${totalBlocked}`);
+ console.log(` fireList size: ${fireList.length}`);
+
+ if (DRY) { console.log(`\n[dry-run] no writes. fireList staged. Await Steve GO for DRY_RUN=0.\n`); fs.writeFileSync('/tmp/tk10649-m1-firelist.json', JSON.stringify(fireList, null, 2)); return; }
+
+ // LIVE: fill-empty only — set mirror mfr_sku + Shopify dwc.manufacturer_sku
+ let done = 0;
+ for (const f of fireList) {
+ const gid = `gid://shopify/Product/${f.sid}`;
+ // guard: re-read live, skip if any mfr already present
+ const cur = await gql(`{ product(id:"${gid}"){ mfrCol: metafield(namespace:"dwc",key:"manufacturer_sku"){ value } cust: metafield(namespace:"custom",key:"manufacturer_sku"){ value } } }`);
+ const pm = cur.data && cur.data.product;
+ if (pm && ((pm.mfrCol && pm.mfrCol.value) || (pm.cust && pm.cust.value))) { console.log(` SKIP ${f.sid} already has mfr`); continue; }
+ await gql(`mutation($mf:[MetafieldsSetInput!]!){ metafieldsSet(metafields:$mf){ userErrors{ message } } }`,
+ { mf: [{ ownerId: gid, namespace: 'dwc', key: 'manufacturer_sku', type: 'single_line_text_field', value: f.mfr }] });
+ // mirror column fill-empty
+ execSync(`${PSQL} "UPDATE shopify_products SET mfr_sku='${f.mfr.replace(/'/g,"''")}' WHERE regexp_replace(shopify_id,'.*/','')='${f.sid}' AND (mfr_sku IS NULL OR mfr_sku='')"`);
+ done++;
+ if (done % 10 === 0) console.log(` ...${done}/${fireList.length}`);
+ }
+ console.log(`\nLIVE backfill complete: ${done} products.`);
+})();
diff --git a/tk10649-m2-novasuede-images.js b/tk10649-m2-novasuede-images.js
new file mode 100644
index 0000000..11046e0
--- /dev/null
+++ b/tk10649-m2-novasuede-images.js
@@ -0,0 +1,72 @@
+#!/usr/bin/env node
+// TK-10649 M2: set the already-existing catalog image as featured media on the
+// 3 imageless ACTIVE Novasuede products (Java/Indigo/Black).
+// DRY_RUN=1 (default) => plan only. DRY_RUN=0 => fire productCreateMedia.
+// Reversible: adds a media item; no delete, no overwrite of a populated image
+// (guard: skip if the live product already has media).
+
+const fs = require('fs');
+const https = require('https');
+const path = require('path');
+
+function loadEnv(p) {
+ for (const line of fs.readFileSync(p, 'utf8').split('\n')) {
+ const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
+ if (m && !(m[1] in process.env)) process.env[m[1]] = m[2];
+ }
+}
+loadEnv(path.join(process.env.HOME, 'Projects/secrets-manager/.env'));
+
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
+const DRY = process.env.DRY_RUN !== '0';
+
+// mirror-verified: shopify_id -> {title, image_url from novasuede_catalog by mfr_sku}
+const TARGETS = [
+ { pid: '7692851937331', color: 'Java', img: 'https://cdn.shopify.com/s/files/1/0015/4117/7456/files/novasuede-java.jpg?v=1761251983', sellable: true },
+ { pid: '7692851839027', color: 'Indigo', img: 'https://novasuede.com/images/colors/novasuede-indigo.jpg', sellable: false },
+ { pid: '7692850298931', color: 'Black', img: 'https://novasuede.com/images/colors/novasuede-black.jpg', sellable: false },
+];
+
+function gql(query) {
+ return new Promise((resolve, reject) => {
+ const body = JSON.stringify({ query });
+ const req = https.request({
+ host: DOMAIN, path: '/admin/api/2024-10/graphql.json', method: 'POST',
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
+ }, (r) => { let d = ''; r.on('data', c => d += c); r.on('end', () => { try { resolve(JSON.parse(d)); } catch (e) { reject(new Error(d)); } }); });
+ req.on('error', reject); req.write(body); req.end();
+ });
+}
+
+(async () => {
+ console.log(`\n=== TK-10649 M2 — Novasuede featured-image restore (${DRY ? 'DRY-RUN' : 'LIVE'}) ===\n`);
+ const results = [];
+ for (const t of TARGETS) {
+ const gid = `gid://shopify/Product/${t.pid}`;
+ // Step 0: live media check (skip if already has media)
+ const cur = await gql(`{ product(id:"${gid}"){ id title featuredImage{url} media(first:5){nodes{ id mediaContentType status }} } }`);
+ const p = cur.data && cur.data.product;
+ if (!p) { results.push({ color: t.color, status: 'ERROR_read', detail: JSON.stringify(cur).slice(0, 200) }); continue; }
+ const mediaCount = p.media.nodes.length;
+ const hasFeatured = !!(p.featuredImage && p.featuredImage.url);
+ console.log(`[${t.color}] ${p.title}`);
+ console.log(` live BEFORE: featured=${hasFeatured ? p.featuredImage.url : 'NONE'} | media=${mediaCount} | sellable=${t.sellable}`);
+ console.log(` will SET featured -> ${t.img}`);
+ if (hasFeatured || mediaCount > 0) {
+ console.log(` SKIP: live already has media (guard) — no write.\n`);
+ results.push({ color: t.color, status: 'SKIP_has_media' });
+ continue;
+ }
+ if (DRY) { console.log(` [dry-run] would productCreateMedia\n`); results.push({ color: t.color, status: 'PLANNED' }); continue; }
+ const mut = await gql(`mutation { productCreateMedia(productId:"${gid}", media:[{ originalSource:"${t.img}", mediaContentType: IMAGE, alt:"Novasuede ${t.color}" }]){ media{ ... on MediaImage{ id status } } mediaUserErrors{ field message } userErrors{ field message } } }`);
+ const mres = mut.data && mut.data.productCreateMedia;
+ const errs = [...(mres && mres.mediaUserErrors || []), ...(mres && mres.userErrors || [])];
+ if (errs.length) { console.log(` ERROR: ${JSON.stringify(errs)}\n`); results.push({ color: t.color, status: 'ERROR_write', errs }); continue; }
+ const mid = mres.media && mres.media[0] && mres.media[0].id;
+ console.log(` FIRED: media ${mid} status=${mres.media[0] && mres.media[0].status}\n`);
+ results.push({ color: t.color, status: 'FIRED', media_id: mid });
+ }
+ console.log('=== SUMMARY ===');
+ for (const r of results) console.log(` ${r.color}: ${r.status}${r.media_id ? ' ' + r.media_id : ''}`);
+})();
diff --git a/tk10649-m4b-freshwater-fix.js b/tk10649-m4b-freshwater-fix.js
new file mode 100644
index 0000000..a614efa
--- /dev/null
+++ b/tk10649-m4b-freshwater-fix.js
@@ -0,0 +1,71 @@
+#!/usr/bin/env node
+// TK-10649 M4-B (DTD verdict X, 5/5): fix the mis-keyed DW-house "Freshwater Grass"
+// row (gid 7432702033971) — the ONLY unambiguously-wrong customer-facing data.
+// 1) custom.manufacturer_sku WP30020 (MTG's Grandma's-Tapestry code) -> WP20732 (real Freshwater Grass code)
+// 2) dwc.manufacturer_sku null/wrong -> WP20732 (align)
+// 3) body_html copied Chinoiserie text -> the ACCURATE Freshwater Grass copy,
+// taken VERBATIM from the genuine Freshwater Grass product DWC-268059 (NOT AI-generated)
+// Collision A (Arte+Koroseal DWK-32014) gets NO write — legitimate mfr/distributor
+// relationship, live variant SKUs already differ (DTD X).
+// DRY_RUN=1 (default) => plan only. DRY_RUN=0 => fire. All reversible field writes.
+
+const fs = require('fs');
+const https = require('https');
+const path = require('path');
+
+for (const l of fs.readFileSync(path.join(process.env.HOME, 'Projects/secrets-manager/.env'), 'utf8').split('\n')) {
+ const m = l.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
+ if (m && !(m[1] in process.env)) process.env[m[1]] = m[2];
+}
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
+const DRY = process.env.DRY_RUN !== '0';
+
+const GID = 'gid://shopify/Product/7432702033971'; // DW-house Freshwater Grass (mis-keyed)
+const REAL_MFR = 'WP20732'; // the genuine Freshwater Grass code (from DWC-268059)
+const WRONG_MFR = 'WP30020'; // MTG Grandma's Tapestry code (does not belong here)
+const CORRECT_BODY = '<p>This wallcovering features a dense arrangement of freshwater grass in shades of blue, green, ochre, and turquoise. It evokes a tropical and organic mood, bringing a touch of nature indoors.</p>';
+
+function gql(query, variables) {
+ return new Promise((res, rej) => {
+ const b = JSON.stringify({ query, variables });
+ const r = https.request({ host: DOMAIN, path: '/admin/api/2024-10/graphql.json', method: 'POST',
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(b) } },
+ x => { let d = ''; x.on('data', c => d += c); x.on('end', () => { try { res(JSON.parse(d)); } catch (e) { rej(new Error(d)); } }); });
+ r.on('error', rej); r.write(b); r.end();
+ });
+}
+const strip = s => (s || '').replace(/<[^>]+>/g, ' ').trim().slice(0, 140);
+
+(async () => {
+ console.log(`\n=== TK-10649 M4-B — Freshwater Grass mis-key fix (${DRY ? 'DRY-RUN' : 'LIVE'}) ===\n`);
+ const cur = await gql(`{ product(id:"${GID}"){ title bodyHtml
+ customMfr: metafield(namespace:"custom", key:"manufacturer_sku"){ id value }
+ dwcMfr: metafield(namespace:"dwc", key:"manufacturer_sku"){ id value } } }`);
+ const p = cur.data.product;
+ console.log('title:', p.title);
+ console.log('\ncustom.manufacturer_sku BEFORE:', JSON.stringify(p.customMfr && p.customMfr.value), ' AFTER:', JSON.stringify(REAL_MFR));
+ console.log('dwc.manufacturer_sku BEFORE:', JSON.stringify(p.dwcMfr && p.dwcMfr.value), ' AFTER:', JSON.stringify(REAL_MFR));
+ console.log('body_html BEFORE:', JSON.stringify(strip(p.bodyHtml)));
+ console.log('body_html AFTER :', JSON.stringify(strip(CORRECT_BODY)), '(verbatim from genuine DWC-268059)');
+
+ // Safety guard: only proceed if the wrong code is actually present
+ if (!(p.customMfr && p.customMfr.value === WRONG_MFR)) {
+ console.log('\nGUARD: custom.manufacturer_sku is not the expected WRONG code (' + WRONG_MFR + '). Aborting to avoid clobbering. Current=' + JSON.stringify(p.customMfr && p.customMfr.value));
+ return;
+ }
+ if (DRY) { console.log('\n[dry-run] would set 2 metafields + productUpdate body_html\n'); return; }
+
+ // 1+2) metafields
+ const mf = await gql(`mutation($mf:[MetafieldsSetInput!]!){ metafieldsSet(metafields:$mf){ metafields{ namespace key value } userErrors{ field message } } }`,
+ { mf: [
+ { ownerId: GID, namespace: 'custom', key: 'manufacturer_sku', type: 'single_line_text_field', value: REAL_MFR },
+ { ownerId: GID, namespace: 'dwc', key: 'manufacturer_sku', type: 'single_line_text_field', value: REAL_MFR },
+ ] });
+ console.log('\nmetafieldsSet:', JSON.stringify(mf.data.metafieldsSet.userErrors.length ? mf.data.metafieldsSet.userErrors : mf.data.metafieldsSet.metafields));
+
+ // 3) body_html
+ const upd = await gql(`mutation($input:ProductInput!){ productUpdate(input:$input){ product{ id } userErrors{ field message } } }`,
+ { input: { id: GID, bodyHtml: CORRECT_BODY } });
+ console.log('productUpdate body:', JSON.stringify(upd.data.productUpdate.userErrors.length ? upd.data.productUpdate.userErrors : 'OK'));
+})();
(oldest)
·
back to Japan Enrich
·
feat: Carnegie split rollout gate — import canonical mfr_sku 1609532 →