[object Object]

← 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

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 →