← back to Gmc Titlefix
TK-11449: read-only diagnosis — tobacco_policy_violation + the 3 real price_mismatch
719c07f0394490bed5b941a07b4dcb631e065042 · 2026-09-10 18:17:04 -0700 · Steve Abrams
Task A: the Fentucci Naturals 6 are UNDETERMINED with a documented rule-out list
(title/GPC/price/availability/shipping/feed-description/landing-page-text/image content
all eliminated; the image hypothesis refuted by direct inspection). The Pipa title
claim survives (7 discordant pairs, 4 byte-identical images re-verified by sha256) but
is necessary-not-sufficient, and the 'clean sibling' turns out to be a DUPLICATE product
(<handle>-1, 48 of 94 in the family), which invalidates the title-append remedy for 7 of 9.
Write path: feed title == Shopify title; the DW Policy Title Overrides datasource is
ORPHANED (not in the primary's defaultRule.takeFromDataSources).
Task B: 0 of the 3 real price_mismatch cases are TK-11404's JSON-LD theme leak — tested
live. Mayenne emits 1335.60 in JSON-LD/microdata (position 1 is the roll); Batik and
Glambeads emit 4.25 only because their own offer links pin ?variant=<sample>. Mayenne is
NOT orderable at the advertised $63.53, so no money exposure in the under-charge
direction; the open question runs the other way (1335.60 vs an 8.76-yard roll).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yzBkdi8E67XZz1TJNmvca
Files touched
A tk11449-shopify-pdp-read.mjsA tk11449-tobacco-attrs.mjs
Diff
commit 719c07f0394490bed5b941a07b4dcb631e065042
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 10 18:17:04 2026 -0700
TK-11449: read-only diagnosis — tobacco_policy_violation + the 3 real price_mismatch
Task A: the Fentucci Naturals 6 are UNDETERMINED with a documented rule-out list
(title/GPC/price/availability/shipping/feed-description/landing-page-text/image content
all eliminated; the image hypothesis refuted by direct inspection). The Pipa title
claim survives (7 discordant pairs, 4 byte-identical images re-verified by sha256) but
is necessary-not-sufficient, and the 'clean sibling' turns out to be a DUPLICATE product
(<handle>-1, 48 of 94 in the family), which invalidates the title-append remedy for 7 of 9.
Write path: feed title == Shopify title; the DW Policy Title Overrides datasource is
ORPHANED (not in the primary's defaultRule.takeFromDataSources).
Task B: 0 of the 3 real price_mismatch cases are TK-11404's JSON-LD theme leak — tested
live. Mayenne emits 1335.60 in JSON-LD/microdata (position 1 is the roll); Batik and
Glambeads emit 4.25 only because their own offer links pin ?variant=<sample>. Mayenne is
NOT orderable at the advertised $63.53, so no money exposure in the under-charge
direction; the open question runs the other way (1335.60 vs an 8.76-yard roll).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yzBkdi8E67XZz1TJNmvca
---
tk11449-shopify-pdp-read.mjs | 30 ++++++++++++++++++++++++++++++
tk11449-tobacco-attrs.mjs | 44 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 74 insertions(+)
diff --git a/tk11449-shopify-pdp-read.mjs b/tk11449-shopify-pdp-read.mjs
new file mode 100644
index 0000000..7db1815
--- /dev/null
+++ b/tk11449-shopify-pdp-read.mjs
@@ -0,0 +1,30 @@
+#!/usr/bin/env node
+/** TK-11449 READ-ONLY. Shopify Admin GraphQL read of PDP-bearing fields (descriptionHtml, tags,
+ * productType, seo, metafields) for a handle list, so flagged-vs-clean landing-page TEXT can be
+ * diffed WITHOUT hitting the live storefront. Admin API only. No mutations. $0. */
+import fs from 'fs';
+const env = Object.fromEntries(fs.readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8')
+ .split('\n').filter(l=>l.includes('=')&&!l.trim().startsWith('#')).map(l=>[l.slice(0,l.indexOf('=')).trim(), l.slice(l.indexOf('=')+1).trim().replace(/^["']|["']$/g,'')]));
+const STORE = 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = env.SHOPIFY_ADMIN_TOKEN;
+const items = JSON.parse(fs.readFileSync(process.argv[2],'utf8'));
+const OUT = process.argv[3];
+const Q = `query($h:String!){ productByHandle(handle:$h){ id title handle status productType vendor tags descriptionHtml
+ seo{title description} onlineStoreUrl
+ metafields(first:40){edges{node{namespace key value}}}
+ variants(first:20){edges{node{id title price sku position}}} } }`;
+(async()=>{
+ const rows=[];
+ for (const it of items){
+ const r = await fetch(`https://${STORE}/admin/api/2024-10/graphql.json`, {method:'POST',
+ headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},
+ body: JSON.stringify({query:Q, variables:{h: it.handle}})});
+ const j = await r.json();
+ const p = j?.data?.productByHandle;
+ rows.push({ ...it, http:r.status, found: !!p, product: p || null, errors: j.errors||null });
+ process.stderr.write(p?'.':'x');
+ await new Promise(z=>setTimeout(z,350));
+ }
+ fs.writeFileSync(OUT, JSON.stringify({ticket:'TK-11449',read_only:true,source:'shopify admin graphql 2024-10',fetched_at:new Date().toISOString(),n:rows.length,rows},null,1));
+ console.error(`\nwrote ${OUT}`);
+})();
diff --git a/tk11449-tobacco-attrs.mjs b/tk11449-tobacco-attrs.mjs
new file mode 100644
index 0000000..387377e
--- /dev/null
+++ b/tk11449-tobacco-attrs.mjs
@@ -0,0 +1,44 @@
+#!/usr/bin/env node
+/**
+ * TK-11449 — READ-ONLY. Fetch FULL productAttributes for the tobacco cohort + controlled
+ * clean siblings, so every feed attribute (not just title) can be diffed flagged-vs-clean.
+ * Uses Merchant API v1 products.get (Content API v2.1 is sunsetting). Fires nothing. $0.
+ */
+import { createRequire } from 'module';
+const require = createRequire(import.meta.url);
+const { token, MERCHANT } = require('/Users/macstudio3/Projects/gmc-titlefix/_auth.js');
+import fs from 'fs';
+
+const IDS = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); // [{o,t,tob}]
+const OUT = process.argv[3];
+const BASE = 'https://merchantapi.googleapis.com';
+
+(async () => {
+ const H = { Authorization: 'Bearer ' + (await token()) };
+ const rows = []; let err = 0;
+ for (const it of IDS) {
+ const name = `accounts/${MERCHANT}/products/online~en~US~${it.o}`;
+ let j = null, status = null;
+ for (let a = 0; a < 4; a++) {
+ try {
+ const r = await fetch(`${BASE}/products/v1/${name}`, { headers: H, signal: AbortSignal.timeout(45000) });
+ status = r.status; j = await r.json();
+ if (r.ok) break;
+ await new Promise(z => setTimeout(z, 900 * (a + 1)));
+ } catch (e) { status = String(e).slice(0, 80); await new Promise(z => setTimeout(z, 900 * (a + 1))); }
+ }
+ if (!j || status !== 200) { err++; rows.push({ o: it.o, t: it.t, tob: it.tob, _error: status }); continue; }
+ const ps = j.productStatus || {};
+ rows.push({
+ o: it.o, t: it.t, tob: it.tob,
+ attrs: j.productAttributes || {},
+ feedLabel: j.feedLabel, dataSource: (j.dataSource||'').split('/').pop(),
+ creationDate: ps.creationDate, lastUpdateDate: ps.lastUpdateDate,
+ issueCodes: [...new Set((ps.itemLevelIssues||[]).map(i=>i.severity+'|'+i.code))],
+ _versionNumber: j.versionNumber,
+ });
+ process.stderr.write('.');
+ }
+ fs.writeFileSync(OUT, JSON.stringify({ ticket:'TK-11449', read_only:true, fetched_at:new Date().toISOString(), n: rows.length, errors: err, rows }, null, 1));
+ console.error(`\nwrote ${OUT} n=${rows.length} errors=${err}`);
+})();
← 952fb7d TK-11448: 631 live offers advertise a 2019 screenshot, and t
·
back to Gmc Titlefix
·
TK-11449: fold codex adversarial pass — downgrade 'dominant aa7a9f6 →