[object Object]

← back to Dw Yolo Loop

gmc: staged GID-scoped exclude for the 9 true $4.25 leaks (Thibaut+DW), reversible (session close)

28e22d5d6e5f214c0ca0952c9b6413cd7805cbb1 · 2026-08-25 08:08:40 -0700 · Steve

Files touched

Diff

commit 28e22d5d6e5f214c0ca0952c9b6413cd7805cbb1
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Aug 25 08:08:40 2026 -0700

    gmc: staged GID-scoped exclude for the 9 true $4.25 leaks (Thibaut+DW), reversible (session close)
---
 gmc-thibaut-dw-9-exclude.js | 79 +++++++++++++++++++++++++++++++++++++++++++++
 package.json                |  2 +-
 2 files changed, 80 insertions(+), 1 deletion(-)

diff --git a/gmc-thibaut-dw-9-exclude.js b/gmc-thibaut-dw-9-exclude.js
new file mode 100644
index 0000000..0a0e6e7
--- /dev/null
+++ b/gmc-thibaut-dw-9-exclude.js
@@ -0,0 +1,79 @@
+#!/usr/bin/env node
+// GMC Thibaut+DW 9-scoped feed-exclude — GMC-remediation (2026-08-25)
+// SCOPED unpublish of EXACTLY the 9 TRUE $4.25 memo-sample leaks (7 Thibaut Chloe/Dickinson
+// + 2 DW/Harlequin legacy) off the "Google & YouTube" channel. These advertise the $4.25 memo
+// sample as the product price and have NO sourceable per-roll price locally → the reversible fix
+// is to drop the wrong offer from Google. Sibling of gmc-fentucci-exclude.js (identical mechanism).
+// Reversible: writes a restore-map of exactly which GIDs were on Google (re-publish to reverse).
+//
+//   node gmc-thibaut-dw-9-exclude.js                              # DRY-RUN: how many of the 9 are on Google
+//   node gmc-thibaut-dw-9-exclude.js --apply --yes-i-am-steve     # exclude all on-Google of the 9
+import fs from 'node:fs';
+
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const VERSION = '2024-10';
+const ENV = fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env', 'utf8');
+const TOKEN = (ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || [])[1];
+if (!TOKEN) { console.error('no SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+const URL = `https://${SHOP}/admin/api/${VERSION}/graphql.json`;
+const GOOGLE_PUBLICATION_ID = 'gid://shopify/Publication/29646651457';
+const WORKLIST = '/Users/macstudio3/.claude/yolo-queue/gmc-leak-worklist-2026-08-24.json';
+const RESTORE = '/Users/macstudio3/.claude/yolo-queue/gmc-thibaut-dw-9-exclude-restore-map.json';
+
+const args = process.argv.slice(2);
+const APPLY = args.includes('--apply');
+const CONFIRMED = args.includes('--yes-i-am-steve');
+
+async function gql(query, variables) {
+  for (let attempt = 0; attempt < 5; attempt++) {
+    const res = await fetch(URL, {
+      method: 'POST',
+      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+      body: JSON.stringify({ query, variables }),
+    });
+    if (res.status === 429 || res.status >= 500) { await new Promise(r => setTimeout(r, 1000 * (attempt + 1))); continue; }
+    const j = await res.json();
+    if (j.errors) { await new Promise(r => setTimeout(r, 800 * (attempt + 1))); continue; }
+    return j.data;
+  }
+  throw new Error('gql failed after retries');
+}
+
+// Self-select the 9 from the master worklist: everything that is NOT Fentucci Naturals.
+const wl = JSON.parse(fs.readFileSync(WORKLIST, 'utf8'));
+const nine = (wl.leaks || []).filter(l => (l.vendor || '') !== 'Fentucci Naturals');
+const ids = nine.map(l => l.product_gid).filter(Boolean);
+console.log(`Thibaut+DW 9-scoped GMC exclude — ${APPLY ? (CONFIRMED ? 'APPLY' : 'APPLY-BLOCKED(no --yes-i-am-steve)') : 'DRY-RUN'}`);
+console.log(`Worklist: ${ids.length} GIDs (vendors: ${[...new Set(nine.map(l => l.vendor))].join(', ')})`);
+if (ids.length !== 9) console.log(`  ⚠️ note: selected ${ids.length} (expected 9) — check worklist vendor field before --apply`);
+nine.forEach(l => console.log(`   • ${l.vendor} ${l.dw_sku || ''} "${l.title}" google=${l.current_google_offer_price}`));
+
+// 1. Which are actually ON Google right now.
+const onGoogle = [];
+for (const id of ids) {
+  const d = await gql(
+    `query($id:ID!){ product(id:$id){ id title status onGoogle: publishedOnPublication(publicationId:"${GOOGLE_PUBLICATION_ID}") } }`,
+    { id });
+  const p = d?.product;
+  if (p && p.onGoogle) onGoogle.push({ id: p.id, title: p.title, status: p.status });
+}
+console.log(`\nOf ${ids.length}: ${onGoogle.length} currently ON Google (would be excluded), ${ids.length - onGoogle.length} already off.`);
+
+if (!APPLY) { console.log(`\nDRY-RUN only — no writes. Re-run with --apply --yes-i-am-steve to exclude the ${onGoogle.length}.`); process.exit(0); }
+if (!CONFIRMED) { console.error('\nREFUSING: --apply requires --yes-i-am-steve (customer-facing Shopify write).'); process.exit(2); }
+
+// 2. Apply: restore-map first (reversible), then unpublish.
+fs.writeFileSync(RESTORE, JSON.stringify({ generated_at: new Date().toISOString(), publication: GOOGLE_PUBLICATION_ID,
+  note: 'Re-publish to reverse: publishablePublish(id, [{publicationId}]). Do this after sourcing the real Thibaut per-roll price.', excluded: onGoogle }, null, 2));
+console.log(`\nRestore-map written (${onGoogle.length} GIDs) -> ${RESTORE}\nExcluding ${onGoogle.length}...`);
+let ok = 0, err = 0;
+for (const t of onGoogle) {
+  try {
+    const d = await gql(
+      `mutation($id:ID!,$pubs:[PublicationInput!]!){ publishableUnpublish(id:$id, input:$pubs){ userErrors{ message } } }`,
+      { id: t.id, pubs: [{ publicationId: GOOGLE_PUBLICATION_ID }] });
+    const ue = d?.publishableUnpublish?.userErrors;
+    if (ue && ue.length) { err++; console.log(`  ✗ ${t.id}: ${ue.map(e => e.message).join(';')}`); } else { ok++; console.log(`  ✓ ${t.title}`); }
+  } catch (e) { err++; console.log(`  ✗ ${t.id}: ${e.message}`); }
+}
+console.log(`\nDONE — excluded ${ok}, errors ${err}. Reverse via ${RESTORE}.`);
diff --git a/package.json b/package.json
index b2de913..57c3405 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "designerwallcoverings-ai",
-  "version": "0.1.2",
+  "version": "0.1.3",
   "private": true,
   "description": "designerwallcoverings.ai — AI room-render landing. Upload a room photo, get matching wallcoverings.",
   "main": "server.js",

← 751727e feat: gmc-fentucci-exclude.js — scoped 462-Fentucci Google f  ·  back to Dw Yolo Loop  ·  feat: read-only GMC full-leak-list exporter (TK-10862 sessio 25ac473 →