← back to Gmc Titlefix
TK-11449: residual-3 dedup follow-on orphan check — 3 new Google orphans found, gated memo drafted
f939270bba9e23469dd519434f5d3ff76e7e90e2 · 2026-09-13 01:14:00 -0700 · Steve
Read-only check (tk11449-residual3-orphan-check.mjs) confirms the 3 products archived by the
residual-3 dedup (executed 2026-09-13 06:47Z, after the main 61-offer orphan sweep already ran)
left the identical orphan-offer gap: $195.00 advertised, lands on the survivor's $4.25 sample.
All 3 survivors already carry their own offer, so no carve-out needed. Gated memo drafted, nothing fired.
Files touched
A data/tk11449-residual3-orphan-check.jsonA tk11449-residual3-orphan-check.mjs
Diff
commit f939270bba9e23469dd519434f5d3ff76e7e90e2
Author: Steve <steve@designerwallcoverings.com>
Date: Sun Sep 13 01:14:00 2026 -0700
TK-11449: residual-3 dedup follow-on orphan check — 3 new Google orphans found, gated memo drafted
Read-only check (tk11449-residual3-orphan-check.mjs) confirms the 3 products archived by the
residual-3 dedup (executed 2026-09-13 06:47Z, after the main 61-offer orphan sweep already ran)
left the identical orphan-offer gap: $195.00 advertised, lands on the survivor's $4.25 sample.
All 3 survivors already carry their own offer, so no carve-out needed. Gated memo drafted, nothing fired.
---
data/tk11449-residual3-orphan-check.json | 58 ++++++++++++++
tk11449-residual3-orphan-check.mjs | 130 +++++++++++++++++++++++++++++++
2 files changed, 188 insertions(+)
diff --git a/data/tk11449-residual3-orphan-check.json b/data/tk11449-residual3-orphan-check.json
new file mode 100644
index 0000000..3fe88a0
--- /dev/null
+++ b/data/tk11449-residual3-orphan-check.json
@@ -0,0 +1,58 @@
+{
+ "generated_at": "2026-09-13T08:11:57.435Z",
+ "ticket": "TK-11449",
+ "enumeration_complete": true,
+ "offers_scanned": 62295,
+ "orig_matches": [
+ {
+ "mc_id": "online:en:US:43605859270707",
+ "offerId": "43605859270707",
+ "pair": "pico-stripe-margot-magenta-1",
+ "title": "Pico 6\" Fentucci Stripe - Margot Magenta",
+ "price": "195.00 USD",
+ "link": "https://www.designerwallcoverings.com/products/pico-stripe-margot-magenta-1"
+ },
+ {
+ "mc_id": "online:en:US:43605859696691",
+ "offerId": "43605859696691",
+ "pair": "pico-stripe-golden-1",
+ "title": "Pipa 12\" Fentucci Stripe - Golden",
+ "price": "195.00 USD",
+ "link": "https://www.designerwallcoverings.com/products/pico-stripe-golden-1"
+ },
+ {
+ "mc_id": "online:en:US:43605859663923",
+ "offerId": "43605859663923",
+ "pair": "pico-stripe-golden-2",
+ "title": "Pano 24\" Fentucci Stripe - Golden",
+ "price": "195.00 USD",
+ "link": "https://www.designerwallcoverings.com/products/pico-stripe-golden-2"
+ }
+ ],
+ "surv_matches": [
+ {
+ "mc_id": "online:en:US:43608487526451",
+ "offerId": "43608487526451",
+ "pair": "pico-stripe-margot-magenta-1",
+ "title": "Pico 6\" Fentucci Stripe - Margot Magenta Wallcovering | DW Bespoke Studios",
+ "price": "558.77 USD",
+ "link": "https://www.designerwallcoverings.com/products/pico-stripe-margot-magenta-1-1"
+ },
+ {
+ "mc_id": "online:en:US:43672712511539",
+ "offerId": "43672712511539",
+ "pair": "pico-stripe-golden-2",
+ "title": "Pano 24\" Fentucci Stripe - Golden Wallcovering | DW Bespoke Studios",
+ "price": "558.77 USD",
+ "link": "https://www.designerwallcoverings.com/products/pico-stripe-golden-2-1?variant=43672712511539"
+ },
+ {
+ "mc_id": "online:en:US:43608500699187",
+ "offerId": "43608500699187",
+ "pair": "pico-stripe-golden-1",
+ "title": "Pipa 12\" Fentucci Stripe - Golden Wallcovering | DW Bespoke Studios",
+ "price": "558.77 USD",
+ "link": "https://www.designerwallcoverings.com/products/pico-stripe-golden-1-1"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tk11449-residual3-orphan-check.mjs b/tk11449-residual3-orphan-check.mjs
new file mode 100644
index 0000000..29cceaa
--- /dev/null
+++ b/tk11449-residual3-orphan-check.mjs
@@ -0,0 +1,130 @@
+#!/usr/bin/env node
+// TK-11449 — READ-ONLY. Does the residual-3 dedup (executed 2026-09-13 06:47Z) leave the same
+// orphan-Google-offer gap the 67-pair dedup left? Checks: (1) do the 3 newly-archived (legacy)
+// products still carry live Merchant Center offers (would be orphans, same shape as the 61
+// already deleted), (2) do the 3 survivors already have their OWN Google offer (if not,
+// unpublishing an orphan would zero out Google presence on a create-dead channel — the exact
+// hole found and carved out on the original 67-pair run).
+import fs from 'fs';
+const HOME = process.env.HOME;
+const SHOP = 'designer-laboratory-sandbox';
+const MERCHANT = '146735262';
+
+const shopTok = fs.readFileSync(HOME + '/Projects/secrets-manager/.env', 'utf8').match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m)[1];
+
+async function shopGql(q, v) {
+ for (let a = 0; a < 5; a++) {
+ const r = await fetch(`https://${SHOP}.myshopify.com/admin/api/2024-10/graphql.json`, {
+ method: 'POST', headers: { 'X-Shopify-Access-Token': shopTok, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query: q, variables: v }), signal: AbortSignal.timeout(60000) });
+ const j = await r.json();
+ if (j.errors) { if (JSON.stringify(j.errors).includes('THROTTLED') && a < 4) { await new Promise(z => setTimeout(z, 2000 * (a + 1))); continue; } throw new Error(JSON.stringify(j.errors).slice(0, 300)); }
+ return j.data;
+ }
+ throw new Error('shopify throttled out');
+}
+
+const b64 = b => Buffer.from(b).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
+async function mcToken() {
+ const SA = JSON.parse(fs.readFileSync(HOME + '/Projects/secrets-manager/gmc-sa-146735262.json', 'utf8'));
+ const now = Math.floor(Date.now() / 1000);
+ const si = b64(JSON.stringify({ alg: 'RS256', typ: 'JWT' })) + '.' + b64(JSON.stringify({ iss: SA.client_email, scope: 'https://www.googleapis.com/auth/content', aud: 'https://oauth2.googleapis.com/token', iat: now, exp: now + 3600 }));
+ const crypto = await import('crypto');
+ const s = crypto.createSign('RSA-SHA256'); s.update(si); const jwt = si + '.' + b64(s.sign(SA.private_key));
+ const r = await fetch('https://oauth2.googleapis.com/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: jwt }) });
+ const j = await r.json(); if (!j.access_token) throw new Error('mc token fail: ' + JSON.stringify(j)); return j.access_token;
+}
+async function mcGetJson(url, H) {
+ for (let a = 0; a < 6; a++) {
+ try {
+ const r = await fetch(url, { headers: H, signal: AbortSignal.timeout(30000) });
+ if (r.status === 429 || r.status >= 500) { await new Promise(res => setTimeout(res, 2000 * (a + 1))); continue; }
+ return await r.json();
+ } catch (e) { await new Promise(res => setTimeout(res, 2000 * (a + 1))); }
+ }
+ throw new Error('mc get exhausted: ' + url);
+}
+function shopifyRefFor(offerId) {
+ let m = /^shopify_[A-Za-z]+_(\d+)_(\d+)$/.exec(offerId);
+ if (m) return { kind: 'product', productId: m[1], variantId: m[2] };
+ if (/^\d+$/.test(offerId)) return { kind: 'variant', variantId: offerId };
+ return null;
+}
+
+async function main() {
+ const rows = fs.readFileSync(HOME + '/Projects/gmc-titlefix/data/tk11449-residual3-RESTOREMAP-2026-09-13T06-41-33-083Z.jsonl', 'utf8')
+ .trim().split('\n').map(l => JSON.parse(l));
+ if (rows.length !== 3) throw new Error(`expected 3 residual rows, got ${rows.length}`);
+
+ // Resolve current variant ids for both orig (archived) and survivor products.
+ const allIds = rows.flatMap(r => [r.orig_id, r.surv_id]);
+ const Q1 = `query($ids:[ID!]!){ nodes(ids:$ids){ __typename ... on Product { id handle status
+ variants(first:100){ edges{ node{ id } } } } } }`;
+ const shopData = await shopGql(Q1, { ids: allIds });
+ const byGid = new Map();
+ (shopData.nodes || []).forEach((n, i) => byGid.set(allIds[i], n));
+
+ const origPidNum = new Map(), origVidNum = new Map(); // numeric -> row
+ const survPidNum = new Map(), survVidNum = new Map();
+ for (const r of rows) {
+ const on = byGid.get(r.orig_id), sn = byGid.get(r.surv_id);
+ if (!on) throw new Error(`orig ${r.orig_id} not found`);
+ if (!sn) throw new Error(`surv ${r.surv_id} not found`);
+ origPidNum.set(r.orig_id.split('/').pop(), r);
+ survPidNum.set(r.surv_id.split('/').pop(), r);
+ for (const e of on.variants.edges) origVidNum.set(e.node.id.split('/').pop(), r);
+ for (const e of sn.variants.edges) survVidNum.set(e.node.id.split('/').pop(), r);
+ console.log(`shopify state: ${r.clean} -> orig(legacy) status=${on.status} handle=${on.handle} | survivor status=${sn.status} handle=${sn.handle}`);
+ }
+
+ // Full MC enumeration (fresh).
+ const token = await mcToken();
+ const H = { Authorization: `Bearer ${token}` };
+ let pageToken = null, allOffers = [], pages = 0;
+ do {
+ const url = `https://merchantapi.googleapis.com/products/v1/accounts/${MERCHANT}/products?pageSize=1000` + (pageToken ? `&pageToken=${pageToken}` : '');
+ const j = await mcGetJson(url, H);
+ for (const p of (j.products || [])) allOffers.push(p);
+ pageToken = j.nextPageToken || null;
+ pages++;
+ } while (pageToken);
+ console.log(`enumerated ${allOffers.length} offers across ${pages} pages (enumeration_complete=${!pageToken})`);
+
+ const origMatches = [], survMatches = [];
+ for (const p of allOffers) {
+ const offerId = p.offerId; if (!offerId) continue;
+ const ref = shopifyRefFor(offerId); if (!ref) continue;
+ const lang = p.contentLanguage || 'en', feedLabel = p.feedLabel || 'US';
+ const attrs = p.productAttributes || {};
+ let row = null, side = null;
+ if (ref.kind === 'product' && origPidNum.has(ref.productId)) { row = origPidNum.get(ref.productId); side = 'orig'; }
+ else if (ref.kind === 'variant' && origVidNum.has(ref.variantId)) { row = origVidNum.get(ref.variantId); side = 'orig'; }
+ else if (ref.kind === 'product' && survPidNum.has(ref.productId)) { row = survPidNum.get(ref.productId); side = 'surv'; }
+ else if (ref.kind === 'variant' && survVidNum.has(ref.variantId)) { row = survVidNum.get(ref.variantId); side = 'surv'; }
+ if (!row) continue;
+ const rec = {
+ mc_id: `online:${lang}:${feedLabel}:${offerId}`, offerId,
+ pair: row.clean,
+ title: attrs.title || null,
+ price: attrs.price ? `${(parseInt(attrs.price.amountMicros, 10) / 1e6).toFixed(2)} ${attrs.price.currencyCode}` : null,
+ link: attrs.link || null,
+ };
+ if (side === 'orig') origMatches.push(rec); else survMatches.push(rec);
+ }
+
+ console.log('\n=== ORPHAN CHECK (offers still pointing at the now-ARCHIVED originals) ===');
+ console.log(`orig-side live offers found: ${origMatches.length} (expect 0 if already clean, >0 = new orphans same shape as the 61 already deleted)`);
+ origMatches.forEach(m => console.log(' ORPHAN:', m.mc_id, m.pair, m.price, m.link));
+
+ console.log('\n=== SURVIVOR COVERAGE (does the kept product already have its own offer?) ===');
+ for (const r of rows) {
+ const has = survMatches.filter(m => m.pair === r.clean);
+ console.log(` ${r.clean} survivor offers: ${has.length}`, has.map(h => `${h.mc_id} ${h.price}`).join(' | ') || '(none — would go to ZERO Google presence if its orphan were unpublished with no replacement, same carve-out class as pano-stripe-peck-drive-plum)');
+ }
+
+ fs.writeFileSync(HOME + '/Projects/gmc-titlefix/data/tk11449-residual3-orphan-check.json', JSON.stringify({
+ generated_at: new Date().toISOString(), ticket: 'TK-11449', enumeration_complete: !pageToken, offers_scanned: allOffers.length,
+ orig_matches: origMatches, surv_matches: survMatches,
+ }, null, 1));
+}
+main().catch(e => { console.error('FATAL', e); process.exit(1); });
← 8bb2326 auto-data-snapshot: 2026-09-13T00:21:19 (1 data files) — dat
·
back to Gmc Titlefix
·
TK-11449: read-only final verification sweep — 61 deletions 3c89606 →