← back to Gmc Titlefix
gmc-titlefix: migrate remaining v2.1 read tools to _mc-read-v1.js shim + flag spent one-offs (TK10993)
f78c7a1e61d85b75528b4b8c373e7a67786eb07f · 2026-09-10 00:15:48 -0700 · Steve
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQ7X5JUdRkJVZ7HbNtzw4e
Files touched
M build-fresh-canary.mjsM diag2.jsM enumerate-genuine-rollleaks.mjsM gmc-dwla-peryard-primary.jsM gmc-price-override.jsM primary-fix-batch.mjsM primary-fix-canary.mjsM spotcheck500.jsM step-c-verify-one.jsM step-e-verify-all.jsM step-e-verify-retry.jsM tk10993-disapproval-snapshot.mjsM tk10993-track-d-executor.mjsM tk10993-track-g-enumerate.mjs
Diff
commit f78c7a1e61d85b75528b4b8c373e7a67786eb07f
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Sep 10 00:15:48 2026 -0700
gmc-titlefix: migrate remaining v2.1 read tools to _mc-read-v1.js shim + flag spent one-offs (TK10993)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQ7X5JUdRkJVZ7HbNtzw4e
---
build-fresh-canary.mjs | 17 ++++++++---------
diag2.js | 2 ++
enumerate-genuine-rollleaks.mjs | 2 ++
gmc-dwla-peryard-primary.js | 15 ++++++++-------
gmc-price-override.js | 20 ++++++++------------
primary-fix-batch.mjs | 16 +++++++++++-----
primary-fix-canary.mjs | 18 +++++++++++-------
spotcheck500.js | 2 ++
step-c-verify-one.js | 2 ++
step-e-verify-all.js | 2 ++
step-e-verify-retry.js | 2 ++
tk10993-disapproval-snapshot.mjs | 2 ++
tk10993-track-d-executor.mjs | 2 ++
tk10993-track-g-enumerate.mjs | 2 ++
14 files changed, 64 insertions(+), 40 deletions(-)
diff --git a/build-fresh-canary.mjs b/build-fresh-canary.mjs
index 38a7578..fec0afb 100644
--- a/build-fresh-canary.mjs
+++ b/build-fresh-canary.mjs
@@ -6,6 +6,7 @@ import fs from 'fs';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const { token, MERCHANT } = require('./_auth.js');
+const mc = require('./_mc-read-v1.js'); // Merchant API v1 read shim (Content API v2.1 sunset 2026-08-18)
const CSV = '/Users/macstudio3/.claude/skills/google-merchant-agent/out/active-roll-and-sample.csv';
const OUT_FULL = '/Users/macstudio3/.claude/yolo-queue/gmc-fresh-override-full.json';
@@ -31,15 +32,13 @@ function parseCsv(line){ const out=[];let cur='',q=false;for(let i=0;i<line.leng
else { if(c===','){out.push(cur);cur='';} else if(c==='"')q=true; else cur+=c; } } out.push(cur); return out; }
async function listOffers() {
- const tok = await token(); const H = { Authorization: 'Bearer ' + tok };
- const offers = []; let page = null, scanned = 0;
- do {
- const r = await (await fetch(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/products?maxResults=250` + (page ? `&pageToken=${page}` : ''), { headers: H })).json();
- if (r.error) { console.error('MC err', JSON.stringify(r.error).slice(0,150)); break; }
- for (const p of (r.resources || [])) offers.push({ offerId: p.offerId, price: parseFloat(p.price?.value || '0'), feedLabel: p.feedLabel || 'US', contentLanguage: p.contentLanguage || 'en', title: (p.title||'').slice(0,45) });
- page = r.nextPageToken; scanned += (r.resources||[]).length;
- if (scanned % 10000 < 250) process.stderr.write(` ...${scanned} offers read\n`);
- } while (page);
+ const offers = [];
+ // Merchant API v1 paginated list via shim (replaces v2.1 products?maxResults=250 loop).
+ // feedLabel/contentLanguage live in the raw v1 body (_v1), not the v2.1-compat top level.
+ await mc.listProducts(async (p) => {
+ offers.push({ offerId: p.offerId, price: parseFloat(p.price?.value || '0'), feedLabel: p._v1?.feedLabel || 'US', contentLanguage: p._v1?.contentLanguage || 'en', title: (p.title || '').slice(0, 45) });
+ if (offers.length % 10000 === 0) process.stderr.write(` ...${offers.length} offers read\n`);
+ });
return offers;
}
diff --git a/diag2.js b/diag2.js
index 60b9e55..654866e 100644
--- a/diag2.js
+++ b/diag2.js
@@ -1,3 +1,5 @@
+// NOTE 2026-09-10: uses Content API v2.1 (sunset 2026-08-18). Spent one-off; if re-running,
+// migrate reads to _mc-read-v1.js (see verify-canary.mjs for the pattern).
const {token,MERCHANT}=require('./_auth');
const offerId=require('fs').readFileSync('/tmp/gmc-titlefix-canary-offer.txt','utf8').trim();
(async()=>{
diff --git a/enumerate-genuine-rollleaks.mjs b/enumerate-genuine-rollleaks.mjs
index ccebfbe..3021d7b 100644
--- a/enumerate-genuine-rollleaks.mjs
+++ b/enumerate-genuine-rollleaks.mjs
@@ -1,3 +1,5 @@
+// NOTE 2026-09-10: uses Content API v2.1 (sunset 2026-08-18). Spent one-off; if re-running,
+// migrate reads to _mc-read-v1.js (see verify-canary.mjs for the pattern).
// READ-ONLY: enumerate GENUINE roll-variant leaks in the live GMC feed.
// A leak candidate = offer served price <= 4.26. EXCLUDE sample offers
// (title starts "Sample:" OR offerId/title signals sample). Classify variant, not pid.
diff --git a/gmc-dwla-peryard-primary.js b/gmc-dwla-peryard-primary.js
index 375e58e..657d9d2 100644
--- a/gmc-dwla-peryard-primary.js
+++ b/gmc-dwla-peryard-primary.js
@@ -27,6 +27,7 @@
//
// Cost: $0 (Google Merchant API + local feed read; no per-call charge).
const { token, MERCHANT } = require('./_auth.js');
+const mc = require('./_mc-read-v1.js'); // Merchant API v1 read shim (Content API v2.1 sunset 2026-08-18)
const fs = require('fs');
const FEED_TSV = '/Users/macstudio3/Projects/designerwallcoverings/today-viewer/data/google-merchant-feed.tsv';
@@ -144,14 +145,14 @@ async function insertOffers(ds, offers, label) {
// ---- 4) Verify offers are ingested (read back from MC) -----------------------
async function verify(ds, sample) {
- const tok = await token(); const H = { Authorization: 'Bearer ' + tok };
- console.log(`Verifying ${sample.length} per-yard offers on live MC (Content API products.get)...`);
+ console.log(`Verifying ${sample.length} per-yard offers on live MC (Merchant API v1 products.get via shim)...`);
for (const o of sample) {
- const pid = `online:en:US:${o.offerId}`;
- const r = await fetch(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/products/${encodeURIComponent(pid)}`, { headers: H });
- const j = await r.json();
- if (j.error) { console.log(` ${o.offerId} NOT-FOUND-YET (${j.error.code}) — expected within Google's ~24h merge`); continue; }
- console.log(` ${o.offerId} price=${j.price?.value} ${j.price?.currency} | title="${(j.title || '').slice(0, 45)}" | shippingWeight=${JSON.stringify(j.shippingWeight || null)}`);
+ const rid = `online:en:US:${o.offerId}`;
+ let p;
+ try { p = await mc.getProduct(rid); } // v2.1-COMPAT shape; throws on non-200 (e.g. not-merged-yet)
+ catch (e) { console.log(` ${o.offerId} NOT-FOUND-YET (${e.status}) — expected within Google's ~24h merge`); continue; }
+ const sw = p._v1?.productAttributes?.shippingWeight; // shippingWeight only via _v1 raw body
+ console.log(` ${o.offerId} price=${p.price?.value} ${p.price?.currency} | title="${(p.title || '').slice(0, 45)}" | shippingWeight=${JSON.stringify(sw || null)}`);
}
}
diff --git a/gmc-price-override.js b/gmc-price-override.js
index fa07c74..8d7f286 100644
--- a/gmc-price-override.js
+++ b/gmc-price-override.js
@@ -13,6 +13,7 @@
//
// Cost: $0 (Google Content/Merchant API + local feed read; no per-call charge).
const { token, MERCHANT } = require('./_auth.js');
+const mc = require('./_mc-read-v1.js'); // Merchant API v1 read shim (Content API v2.1 sunset 2026-08-18)
const fs = require('fs');
const { execFileSync } = require('child_process');
@@ -122,18 +123,13 @@ async function dryRunDwlaPidJoin() {
// --- end DWLA pid-join path -------------------------------------------------------------------
async function listMcOffers() {
- const tok = await token(); const H = { Authorization: 'Bearer ' + tok };
- const offers = []; let page = null, scanned = 0;
- do {
- const r = await (await fetch(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/products?maxResults=250` + (page ? `&pageToken=${page}` : ''), { headers: H })).json();
- if (r.error) { console.error('MC list err:', JSON.stringify(r.error).slice(0, 200)); break; }
- for (const p of (r.resources || [])) {
- scanned++;
- offers.push({ offerId: p.offerId, mpn: p.mpn || '', price: parseFloat(p.price?.value || '0'), feedLabel: p.feedLabel || 'US', contentLanguage: p.contentLanguage || 'en', title: (p.title || '').slice(0, 50) });
- }
- page = r.nextPageToken;
- if (scanned % 5000 === 0) process.stderr.write(` ...${scanned} MC offers read\n`);
- } while (page);
+ const offers = [];
+ // Merchant API v1 paginated list via shim (replaces v2.1 products?maxResults=250 loop).
+ // mpn/feedLabel/contentLanguage live in the raw v1 body (_v1), not the v2.1-compat top level.
+ await mc.listProducts(async (p) => {
+ offers.push({ offerId: p.offerId, mpn: p._v1?.productAttributes?.mpn || '', price: parseFloat(p.price?.value || '0'), feedLabel: p._v1?.feedLabel || 'US', contentLanguage: p._v1?.contentLanguage || 'en', title: (p.title || '').slice(0, 50) });
+ if (offers.length % 5000 === 0) process.stderr.write(` ...${offers.length} MC offers read\n`);
+ });
return offers;
}
diff --git a/primary-fix-batch.mjs b/primary-fix-batch.mjs
index 02fb0cc..8df5a5d 100644
--- a/primary-fix-batch.mjs
+++ b/primary-fix-batch.mjs
@@ -38,6 +38,7 @@ import crypto from 'crypto';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const { token, MERCHANT } = require('./_auth.js');
+const mc = require('./_mc-read-v1.js'); // Merchant API v1 read shim (Content API v2.1 sunset 2026-08-18)
const HOME = process.env.HOME;
const PRIMARY_DS = `accounts/${MERCHANT}/dataSources/180695450`;
@@ -87,10 +88,16 @@ async function getProcessed(tok, offerId) {
return { status: 429, body: {} };
}
-async function getServedV21(tok, offerId) {
+// Read back the served product via the Merchant API v1 shim (v2.1-COMPAT shape). Throws on
+// non-200 -> mapped to {error:{code}} for the caller. `link` lives in the raw v1 body (_v1).
+async function getServed(offerId) {
const rid = `online:en:US:${offerId}`;
- const r = await fetch(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/products/${encodeURIComponent(rid)}`, { headers: { Authorization: 'Bearer ' + tok } });
- return r.json();
+ try {
+ const p = await mc.getProduct(rid);
+ return { ...p, link: p._v1?.productAttributes?.link }; // link only via _v1 raw body
+ } catch (e) {
+ return { error: { code: e.status, message: e.message } };
+ }
}
async function insertPrimary(tok, body) {
@@ -165,12 +172,11 @@ async function doVerify(n) {
const step = Math.max(1, Math.floor(landed.length / n));
const sample = [];
for (let i = 0; i < landed.length && sample.length < n; i += step) sample.push(landed[i]);
- const tok = await token();
console.log(`=== VERIFY served price for ${sample.length} landed offers (spread across ${new Set(landed.map(x=>x.vendor)).size} vendors) ===`);
const rows = [];
let stillSample = 0;
for (const o of sample) {
- const pr = await getServedV21(tok, o.offerId);
+ const pr = await getServed(o.offerId);
const served = pr.error ? `ERR ${pr.error.code}` : (pr.price ? parseFloat(pr.price.value) : null);
const link = pr.link || '';
const linkOk = /[?&]variant=/.test(link) ? '✓' : '—';
diff --git a/primary-fix-canary.mjs b/primary-fix-canary.mjs
index e60daef..7068560 100644
--- a/primary-fix-canary.mjs
+++ b/primary-fix-canary.mjs
@@ -21,6 +21,7 @@ import fs from 'fs';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const { token, MERCHANT } = require('./_auth.js');
+const mc = require('./_mc-read-v1.js'); // Merchant API v1 read shim (Content API v2.1 sunset 2026-08-18)
const PRIMARY_DS = `accounts/${MERCHANT}/dataSources/180695450`;
const ROLLBACK_DIR = new URL('./data/primary-fix-rollback/', import.meta.url).pathname;
@@ -55,11 +56,15 @@ async function getProcessed(tok, offerId) {
return { status: r.status, body: await r.json() };
}
-async function getServedV21(tok, offerId) {
+// Read back the served product via the Merchant API v1 shim (returns v2.1-COMPAT shape;
+// throws on non-200, so a removed offer surfaces as {error:{code}} for the caller below).
+async function getServed(offerId) {
const rid = `online:en:US:${offerId}`;
- const r = await fetch(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/products/${encodeURIComponent(rid)}`,
- { headers: { Authorization: 'Bearer ' + tok } });
- return r.json();
+ try {
+ return await mc.getProduct(rid); // { price:{value,currency}, title, destinationStatuses, itemLevelIssues, _v1 }
+ } catch (e) {
+ return { error: { code: e.status, message: e.message } };
+ }
}
// Build the FULL roll-variant productAttributes from the current (sample) processed offer + Shopify roll variant.
@@ -153,11 +158,10 @@ async function main() {
}
if (has('--verify')) {
- const tok = await token();
- console.log('=== VERIFY served price (Content API v2.1 products.get) ===');
+ console.log('=== VERIFY served price (Merchant API v1 products.get via shim) ===');
for (const c of CANARY) {
const offerId = `shopify_US_${c.pid}_${c.sample}`;
- const pr = await getServedV21(tok, offerId);
+ const pr = await getServed(offerId);
if (pr.error) { console.log(` ${c.name} ${offerId}: ${pr.error.code} ${(pr.error.message || '').slice(0, 40)}`); continue; }
console.log(` ${c.name} ${offerId}: SERVED $${pr.price?.value} title="${(pr.title || '').slice(0, 45)}"`);
}
diff --git a/spotcheck500.js b/spotcheck500.js
index 1516dc1..90b3409 100644
--- a/spotcheck500.js
+++ b/spotcheck500.js
@@ -1,3 +1,5 @@
+// NOTE 2026-09-10: uses Content API v2.1 (sunset 2026-08-18). Spent one-off; if re-running,
+// migrate reads to _mc-read-v1.js (see verify-canary.mjs for the pattern).
const {token,MERCHANT}=require('./_auth');
const list=require('./gmc_titlefix_list.json');
// sample 8 offers from within the 500 batch (rows 1..499), spread out
diff --git a/step-c-verify-one.js b/step-c-verify-one.js
index f8a7172..964a48e 100644
--- a/step-c-verify-one.js
+++ b/step-c-verify-one.js
@@ -1,3 +1,5 @@
+// NOTE 2026-09-10: uses Content API v2.1 (sunset 2026-08-18). Spent one-off; if re-running,
+// migrate reads to _mc-read-v1.js (see verify-canary.mjs for the pattern).
// READ-ONLY verify — re-pulls the canary offer; confirms effective title == "Sample: ...".
const {token,MERCHANT}=require('./_auth');
const fs=require('fs');
diff --git a/step-e-verify-all.js b/step-e-verify-all.js
index 761d8c9..83e3f9d 100644
--- a/step-e-verify-all.js
+++ b/step-e-verify-all.js
@@ -1,3 +1,5 @@
+// NOTE 2026-09-10: uses Content API v2.1 (sunset 2026-08-18). Spent one-off; if re-running,
+// migrate reads to _mc-read-v1.js (see verify-canary.mjs for the pattern).
// STEP 2 — READ-ONLY full verify. Pages the entire live Content feed once, then
// confirms each of the 29,672 risky offers now has an effective title starting "Sample:".
// Writes /tmp/gmc-verify-result.json + a human summary. Safe to re-run anytime.
diff --git a/step-e-verify-retry.js b/step-e-verify-retry.js
index f9e0cfe..6209689 100644
--- a/step-e-verify-retry.js
+++ b/step-e-verify-retry.js
@@ -1,3 +1,5 @@
+// NOTE 2026-09-10: uses Content API v2.1 (sunset 2026-08-18). Spent one-off; if re-running,
+// migrate reads to _mc-read-v1.js (see verify-canary.mjs for the pattern).
// DTD-B (2026-06-18, 3/3): page the live processed feed, find which of our 29,672
// offers did NOT flip to a "Sample:" title (transient-500 fails + reprocess-lag),
// and re-push ONLY those to the canonical supplemental DS. Doubles as the verify pass.
diff --git a/tk10993-disapproval-snapshot.mjs b/tk10993-disapproval-snapshot.mjs
index 01d7e0a..b8fdddc 100644
--- a/tk10993-disapproval-snapshot.mjs
+++ b/tk10993-disapproval-snapshot.mjs
@@ -1,3 +1,5 @@
+// NOTE 2026-09-10: uses Content API v2.1 (sunset 2026-08-18). Spent one-off; if re-running,
+// migrate reads to _mc-read-v1.js (see verify-canary.mjs for the pattern).
// TK-10993 — READ-ONLY US disapproval snapshot by issue code, straight from productstatuses.
// Used to measure Track F's effect. Writes nothing anywhere; only GETs Merchant Center.
import { createRequire } from 'module'; import fs from 'fs';
diff --git a/tk10993-track-d-executor.mjs b/tk10993-track-d-executor.mjs
index 61e5c93..cf4dd3e 100644
--- a/tk10993-track-d-executor.mjs
+++ b/tk10993-track-d-executor.mjs
@@ -1,4 +1,6 @@
#!/usr/bin/env node
+// NOTE 2026-09-10: uses Content API v2.1 (sunset 2026-08-18). Spent one-off; if re-running,
+// migrate reads to _mc-read-v1.js (see verify-canary.mjs for the pattern).
import fs from 'fs';
import crypto from 'crypto';
diff --git a/tk10993-track-g-enumerate.mjs b/tk10993-track-g-enumerate.mjs
index 3862df7..3ed3839 100644
--- a/tk10993-track-g-enumerate.mjs
+++ b/tk10993-track-g-enumerate.mjs
@@ -1,3 +1,5 @@
+// NOTE 2026-09-10: uses Content API v2.1 (sunset 2026-08-18). Spent one-off; if re-running,
+// migrate reads to _mc-read-v1.js (see verify-canary.mjs for the pattern).
// TK-10993 Track G — enumerate the US policy-violation disapprovals so the
// false-positive claim can be checked per-offer instead of assumed. READ-ONLY.
import { createRequire } from 'module'; import fs from 'fs';
← 3628b9f gmc-titlefix: add Merchant API v1 read shim + migrate verify
·
back to Gmc Titlefix
·
auto-data-snapshot: 2026-09-10T00:28:57 (1 data files) — dat be34cf5 →