← back to Hollywood Mfr Trim
HWC remediation: whitespace trim (applied) + gated Group-A/B scaffolding + DTD-vetted report
987146d73443eb9080864d8db2d9dbfe02701ba3 · 2026-08-18 10:06:32 -0700 · steve
Files touched
A .gitignoreA GROUP-B-REPORT.mdA remediate.mjsA trim.mjsA worklist.json
Diff
commit 987146d73443eb9080864d8db2d9dbfe02701ba3
Author: steve <steve@designerwallcoverings.com>
Date: Tue Aug 18 10:06:32 2026 -0700
HWC remediation: whitespace trim (applied) + gated Group-A/B scaffolding + DTD-vetted report
---
.gitignore | 5 ++++
GROUP-B-REPORT.md | 41 ++++++++++++++++++++++++++
remediate.mjs | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
trim.mjs | 49 +++++++++++++++++++++++++++++++
worklist.json | 52 +++++++++++++++++++++++++++++++++
5 files changed, 234 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ff2422c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
diff --git a/GROUP-B-REPORT.md b/GROUP-B-REPORT.md
new file mode 100644
index 0000000..617353e
--- /dev/null
+++ b/GROUP-B-REPORT.md
@@ -0,0 +1,41 @@
+# HWC Group-B brand-as-vendor bug — investigation report
+_Generated 2026-08-18 · read-only · no writes performed · ties TK-10630 / TK-10633 · guarded by dw-hollywood-sku-canary_
+
+## What Group B is
+113 **LIVE (ACTIVE)** products, vendor "Hollywood Wallcoverings", `real_vendor = Momentum` (correctly hidden in the `private_label` namespace — no customer-facing leak). Each has:
+- mirror-column `dw_sku` = **NULL** (no DW canonical assigned)
+- a fabricated `DWHD-######` code stamped into `custom.dw_sku` / `dwc.dw_sku` (the TK-10630 brand-as-vendor bug)
+- so the fabricated code is currently the product's *only* DW identity.
+
+The older 221 HWC-610xx products (Group A) are all ARCHIVED and DO have a correct `HWC-` code in the column — trivially fixable, separate track.
+
+## Series continuation point
+Max existing `HWC-` number = **HWC-61244** → a mint would continue at **HWC-61245**.
+
+## The 113 split into three risk buckets
+
+| Bucket | Count | Meaning | Remediation readiness |
+|---|---|---|---|
+| **ok** — clean real mfr code | 75 | Has a unique, usable Momentum mfr_sku (e.g. `ASL-127164`, `T2-NN-05`, `AZ53862`). | Ready — mint HWC-61245+, keep the real code in `mfr_sku`. |
+| **MFR_COLLIDES** | 14 | Real mfr_sku already exists on ANOTHER product — can't be trusted as a unique key until deduped. | Needs dedup/verify FIRST. |
+| **NO_MFR** | 24 | No manufacturer code at all — only the fabricated DWHD code. All "Goat Rock" (12) + "Hearst Castle" (12), created Aug 2026, DWHD-508043..508066. | Identity unrecoverable from our data — needs Momentum source. |
+
+- Fabricated-code collisions across products: **0** (the DWHD codes are sequential/unique).
+
+## The 14 MFR_COLLIDES (verify before trusting mfr_sku)
+Boulder Creek-Carnelian `T2-MS-25`, China Camp-Jade Garden `A163-326`, Corona Del-Vapor `A217-040`, Del Mar-Natural `A203-552`, Felton-Onyx `T2-MR-16`, Fernald Point-Mushroom `T2-FT-15`, Gorda-Silverworks `A183-500`, Green Drakes-Atmospheric `T2-SL-15`, Laguna Beach-Ecru `HAR-04`, Moonstone Beach-Zurich Snow `TR-CL-07`, Pfeiffer Beach-Jet Stream `T2-LS-04`, Pfeiffer Big-Plum `T2-GD-03`, Sand City-Charcoal `A175-579`, Zipper Zing-Toasted Almond `T2-ZN-12`.
+
+## The 24 NO_MFR (no recoverable manufacturer identity)
+Goat Rock: Alpine, Anchor, Cove, Dover, Glacier, Gravel, Jadeite, Khaki, Sky, Smoke, Soy, Zinc (DWHD-508055..508066).
+Hearst Castle: Antique, Copper, Halo, Icicle, Manta, Neptune, Pewter, Rosegold, Sable, Sisal, Sterling, Vapor (DWHD-508043..508054).
+
+## Verification (codex-vetted, read-only)
+- **No hidden HWC code to recover:** 0 of the 113 carry an HWC code in their handle or variant SKU → minting a new canonical IS required (nothing to copy from). Only **1** product in the entire catalog holds an HWC code in a dw_sku metafield, so minting HWC-61245+ into metafields won't collide.
+- **The 14 "collides" are NOT all safe to treat as dups:** 6 collide with another *Hollywood* product (likely a legit sibling/colorway) and **9 collide with a DIFFERENT vendor's product** — the 9 cross-vendor ones must be individually verified before their mfr_sku can be trusted as a unique key.
+
+## Recommendation (NOT executed — awaiting Steve)
+Remediate in the order of readiness, each a gated Shopify metafield write:
+1. **Group A (221 archived):** overwrite `custom/dwc/global.dw_sku` metafields with the existing correct column `HWC-` code. Lowest risk, no minting.
+2. **Group B "ok" (75 live):** mint sequential `HWC-61245+`, write into the dw_sku metafields, retain the real Momentum code in `mfr_sku`.
+3. **Group B MFR_COLLIDES (14):** resolve the mfr_sku duplication first, then mint.
+4. **Group B NO_MFR (24):** mint HWC canonicals; flag that the true manufacturer code must be recovered from Momentum (may stay unknown).
diff --git a/remediate.mjs b/remediate.mjs
new file mode 100644
index 0000000..056beb8
--- /dev/null
+++ b/remediate.mjs
@@ -0,0 +1,87 @@
+#!/usr/bin/env node
+// HWC Group-A/B remediation — writes the correct HWC canonical code into the
+// dw_sku metafields (custom/dwc/global) on the LIVE store. Dry-run unless --apply.
+// node remediate.mjs --part archived (221 archived: copy existing HWC col -> metafields)
+// node remediate.mjs --part mint75 (75 clean verified: mint HWC-61245+ -> metafields)
+// Worklist is pulled fresh from the local dw_unified mirror (psql, /tmp socket).
+import { execFileSync } from 'node:child_process';
+import fs from 'node:fs';
+
+const APPLY = process.argv.includes('--apply');
+const PART = (process.argv[process.argv.indexOf('--part')+1]) || '';
+const SHOP='designer-laboratory-sandbox.myshopify.com', API='2024-10';
+const TOKEN=(fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1]?.trim();
+if(!TOKEN){console.error('No SHOPIFY_ADMIN_TOKEN');process.exit(1);}
+
+const psql = sql => JSON.parse(execFileSync('psql',['-h','/tmp','-d','dw_unified','-tAc',sql],{encoding:'utf8'}).trim()||'[]');
+
+// Base Group-B / archived predicate (Hollywood line, fabricated dw_sku metafield)
+const FAB = `(metafields->'custom'->'dw_sku'->>'value' ~ '^DWH[DW]' OR metafields->'dwc'->'dw_sku'->>'value' ~ '^DWH[DW]' OR metafields->'global'->'dw_sku'->>'value' ~ '^DWH[DW]')`;
+
+let SQL;
+if(PART==='archived'){
+ // 221 ARCHIVED: correct HWC already in dw_sku column -> copy into each fabricated dw_sku metafield
+ SQL = `WITH a AS (
+ SELECT DISTINCT regexp_replace(shopify_id,'\\D','','g') AS pid, dw_sku AS code,
+ (metafields->'custom'->'dw_sku'->>'value' ~ '^DWH[DW]') c,
+ (metafields->'dwc'->'dw_sku'->>'value' ~ '^DWH[DW]') d,
+ (metafields->'global'->'dw_sku'->>'value' ~ '^DWH[DW]') g
+ FROM shopify_products
+ WHERE (vendor='Hollywood Wallcoverings' OR dw_sku LIKE 'HWC-%')
+ AND status='ARCHIVED' AND dw_sku ~ '^HWC-\\d+$' AND ${FAB}
+ ), w AS (
+ SELECT pid,'custom' ns,code FROM a WHERE c
+ UNION ALL SELECT pid,'dwc',code FROM a WHERE d
+ UNION ALL SELECT pid,'global',code FROM a WHERE g
+ )
+ SELECT COALESCE(json_agg(json_build_object('ownerId','gid://shopify/Product/'||pid,'namespace',ns,'key','dw_sku','type','single_line_text_field','value',code) ORDER BY pid,ns),'[]') FROM w;`;
+} else if(PART==='mint75'){
+ // 75 CLEAN: verified against momentum_colorways (pattern_sku/lcode) AND mfr_sku unique in catalog.
+ // Deterministic assignment HWC-(61244 + rank) ordered by pid. Written to the fabricated dw_sku metafields.
+ SQL = `WITH b AS (
+ SELECT DISTINCT regexp_replace(shopify_id,'\\D','','g') AS pid, TRIM(mfr_sku) AS mfr,
+ (metafields->'custom'->'dw_sku'->>'value' ~ '^DWH[DW]') c,
+ (metafields->'dwc'->'dw_sku'->>'value' ~ '^DWH[DW]') d,
+ (metafields->'global'->'dw_sku'->>'value' ~ '^DWH[DW]') g
+ FROM shopify_products sp
+ WHERE (vendor='Hollywood Wallcoverings' OR dw_sku LIKE 'HWC-%')
+ AND status='ACTIVE' AND dw_sku IS NULL AND ${FAB}
+ AND mfr_sku IS NOT NULL AND TRIM(mfr_sku)<>''
+ AND EXISTS (SELECT 1 FROM momentum_colorways m WHERE TRIM(mfr_sku) IN (TRIM(m.pattern_sku),TRIM(m.lcode)))
+ AND NOT EXISTS (SELECT 1 FROM shopify_products o WHERE TRIM(o.mfr_sku)=TRIM(sp.mfr_sku) AND o.shopify_id<>sp.shopify_id)
+ ), r AS (SELECT pid,c,d,g,'HWC-'||(61244 + row_number() OVER (ORDER BY pid))::text AS code FROM b),
+ w AS (
+ SELECT pid,'custom' ns,code FROM r WHERE c
+ UNION ALL SELECT pid,'dwc',code FROM r WHERE d
+ UNION ALL SELECT pid,'global',code FROM r WHERE g
+ )
+ SELECT COALESCE(json_agg(json_build_object('ownerId','gid://shopify/Product/'||pid,'namespace',ns,'key','dw_sku','type','single_line_text_field','value',code) ORDER BY pid,ns),'[]') FROM w;`;
+} else { console.error('Pass --part archived | mint75'); process.exit(1); }
+
+const payload = psql(SQL); // json_agg -> a single JSON array, parsed by the psql() helper
+
+const prods = new Set(payload.map(p=>p.ownerId));
+console.log(`Part: ${PART} | ${payload.length} dw_sku metafield writes across ${prods.size} products`);
+console.log(APPLY ? '>>> APPLY MODE — writing to LIVE Shopify' : '>>> DRY RUN (no writes). Pass --apply to execute.');
+
+if(!APPLY){
+ const byProd={}; for(const p of payload){ (byProd[p.ownerId] ||= []).push(`${p.namespace}=${p.value}`); }
+ for(const [gid,sets] of Object.entries(byProd)) console.log(` ${gid.split('/').pop()} ${sets.join(' ')}`);
+ console.log(`\nDry run complete. ${payload.length} writes staged across ${prods.size} products.`);
+ process.exit(0);
+}
+
+const MUT=`mutation($mf:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mf){metafields{id} userErrors{field message}}}`;
+let ok=0,errs=[];
+for(let i=0;i<payload.length;i+=25){
+ const batch=payload.slice(i,i+25);
+ const r=await fetch(`https://${SHOP}/admin/api/${API}/graphql.json`,{method:'POST',headers:{'Content-Type':'application/json','X-Shopify-Access-Token':TOKEN},body:JSON.stringify({query:MUT,variables:{mf:batch}})});
+ const j=await r.json();
+ const ue=j?.data?.metafieldsSet?.userErrors||[]; const set=j?.data?.metafieldsSet?.metafields||[];
+ if(j.errors){errs.push(JSON.stringify(j.errors));console.error('GraphQL',j.errors);}
+ if(ue.length){errs.push(...ue.map(e=>JSON.stringify(e)));console.error('userErrors',ue);}
+ ok+=set.length; console.log(` batch ${i/25+1}: set ${set.length}/${batch.length}`);
+ await new Promise(r=>setTimeout(r,600));
+}
+console.log(`\nDone. ${ok}/${payload.length} dw_sku metafields set. Errors: ${errs.length}`);
+if(errs.length) process.exit(1);
diff --git a/trim.mjs b/trim.mjs
new file mode 100644
index 0000000..f46a594
--- /dev/null
+++ b/trim.mjs
@@ -0,0 +1,49 @@
+#!/usr/bin/env node
+// Trim whitespace on Hollywood (HWC) manufacturer_sku metafields (custom + dwc).
+// Target: LIVE designer-laboratory-sandbox store. Dry-run unless --apply.
+import fs from 'node:fs';
+
+const APPLY = process.argv.includes('--apply');
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+
+// pull token from the secrets .env
+const envTxt = fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env', 'utf8');
+const TOKEN = (envTxt.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1]?.trim();
+if (!TOKEN) { console.error('No SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+
+const work = JSON.parse(fs.readFileSync(new URL('./worklist.json', import.meta.url), 'utf8'));
+console.log(`Worklist: ${work.length} metafield writes across ${new Set(work.map(w=>w.ownerId)).size} products`);
+console.log(APPLY ? '>>> APPLY MODE — writing to LIVE Shopify' : '>>> DRY RUN (no writes). Pass --apply to execute.');
+
+if (!APPLY) {
+ for (const w of work) console.log(` ${w.ownerId.split('/').pop()} ${w.namespace}.${w.key} -> "${w.value}"`);
+ console.log(`\nDry run complete. ${work.length} writes staged.`);
+ process.exit(0);
+}
+
+const MUT = `mutation($mf:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mf){metafields{id namespace key value} userErrors{field message}}}`;
+
+async function gql(variables){
+ const r = await fetch(`https://${SHOP}/admin/api/${API}/graphql.json`, {
+ method:'POST',
+ headers:{'Content-Type':'application/json','X-Shopify-Access-Token':TOKEN},
+ body: JSON.stringify({query:MUT, variables})
+ });
+ return r.json();
+}
+
+let ok=0, errs=[];
+for (let i=0; i<work.length; i+=25){
+ const batch = work.slice(i, i+25);
+ const res = await gql({mf: batch});
+ const ue = res?.data?.metafieldsSet?.userErrors || [];
+ const set = res?.data?.metafieldsSet?.metafields || [];
+ if (res.errors) { errs.push(JSON.stringify(res.errors)); console.error('GraphQL error', res.errors); }
+ if (ue.length) { errs.push(...ue.map(e=>JSON.stringify(e))); console.error('userErrors', ue); }
+ ok += set.length;
+ console.log(` batch ${i/25+1}: set ${set.length}/${batch.length}`);
+ await new Promise(r=>setTimeout(r, 600)); // gentle rate limit
+}
+console.log(`\nDone. ${ok}/${work.length} metafields set. Errors: ${errs.length}`);
+if (errs.length) process.exit(1);
diff --git a/worklist.json b/worklist.json
new file mode 100644
index 0000000..f397bd0
--- /dev/null
+++ b/worklist.json
@@ -0,0 +1,52 @@
+[
+{"ownerId":"gid://shopify/Product/1495072407664","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-94"},
+{"ownerId":"gid://shopify/Product/1495072669808","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-91"},
+{"ownerId":"gid://shopify/Product/1495072833648","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-89"},
+{"ownerId":"gid://shopify/Product/1495073128560","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-85"},
+{"ownerId":"gid://shopify/Product/1495073357936","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-83"},
+{"ownerId":"gid://shopify/Product/1495073521776","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-81"},
+{"ownerId":"gid://shopify/Product/1495073652848","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-74"},
+{"ownerId":"gid://shopify/Product/1495073816688","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-70"},
+{"ownerId":"gid://shopify/Product/1495074209904","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-67"},
+{"ownerId":"gid://shopify/Product/1495074439280","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-63"},
+{"ownerId":"gid://shopify/Product/1495074537584","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-62"},
+{"ownerId":"gid://shopify/Product/1495074668656","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-58"},
+{"ownerId":"gid://shopify/Product/1495074930800","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-37"},
+{"ownerId":"gid://shopify/Product/1495075127408","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-36"},
+{"ownerId":"gid://shopify/Product/1495075324016","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-33"},
+{"ownerId":"gid://shopify/Product/1495075487856","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-31"},
+{"ownerId":"gid://shopify/Product/1495075618928","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-28"},
+{"ownerId":"gid://shopify/Product/1495075881072","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-24"},
+{"ownerId":"gid://shopify/Product/1495076077680","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-22"},
+{"ownerId":"gid://shopify/Product/1495076339824","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-19"},
+{"ownerId":"gid://shopify/Product/1495076601968","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-17"},
+{"ownerId":"gid://shopify/Product/1495076733040","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-15"},
+{"ownerId":"gid://shopify/Product/1495076896880","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-12"},
+{"ownerId":"gid://shopify/Product/1495077093488","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-10"},
+{"ownerId":"gid://shopify/Product/1495077224560","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-06"},
+{"ownerId":"gid://shopify/Product/1495077421168","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"dy24-03"},
+{"ownerId":"gid://shopify/Product/1495081484400","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-96"},
+{"ownerId":"gid://shopify/Product/1495081648240","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-91"},
+{"ownerId":"gid://shopify/Product/1495081844848","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-87"},
+{"ownerId":"gid://shopify/Product/1495081975920","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-85"},
+{"ownerId":"gid://shopify/Product/1495082238064","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-82"},
+{"ownerId":"gid://shopify/Product/1495082369136","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-78"},
+{"ownerId":"gid://shopify/Product/1495082664048","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-77"},
+{"ownerId":"gid://shopify/Product/1495082795120","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-70"},
+{"ownerId":"gid://shopify/Product/1495083090032","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-68"},
+{"ownerId":"gid://shopify/Product/1495083286640","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-66"},
+{"ownerId":"gid://shopify/Product/1495083548784","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-61"},
+{"ownerId":"gid://shopify/Product/1495083745392","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-58"},
+{"ownerId":"gid://shopify/Product/3740358574145","namespace":"custom","key":"manufacturer_sku","type":"single_line_text_field","value":"ITA103"},
+{"ownerId":"gid://shopify/Product/1495081484400","namespace":"dwc","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-96"},
+{"ownerId":"gid://shopify/Product/1495081648240","namespace":"dwc","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-91"},
+{"ownerId":"gid://shopify/Product/1495081844848","namespace":"dwc","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-87"},
+{"ownerId":"gid://shopify/Product/1495082369136","namespace":"dwc","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-78"},
+{"ownerId":"gid://shopify/Product/1495082664048","namespace":"dwc","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-77"},
+{"ownerId":"gid://shopify/Product/1495082795120","namespace":"dwc","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-70"},
+{"ownerId":"gid://shopify/Product/1495083090032","namespace":"dwc","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-68"},
+{"ownerId":"gid://shopify/Product/1495083286640","namespace":"dwc","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-66"},
+{"ownerId":"gid://shopify/Product/1495083548784","namespace":"dwc","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-61"},
+{"ownerId":"gid://shopify/Product/1495083745392","namespace":"dwc","key":"manufacturer_sku","type":"single_line_text_field","value":"5021-58"},
+{"ownerId":"gid://shopify/Product/3740358574145","namespace":"dwc","key":"manufacturer_sku","type":"single_line_text_field","value":"ITA103"}
+]
(oldest)
·
back to Hollywood Mfr Trim
·
Fan-out resolves deferred Group-B: 12/14 collides safe, 24 n 92f7fd7 →