[object Object]

← back to Gmc 425 Supplemental Feed

canary: 10-product reorder applied (reversible, ledger); feed-upload probe found 0 datafeeds (classic supplemental path unavailable — Shopify API-pushes, needs Merchant API dataSource)

8c108c63a17698fb4859334cf6a6c76a81a95e52 · 2026-08-10 09:52:24 -0700 · Steve Abrams

Files touched

Diff

commit 8c108c63a17698fb4859334cf6a6c76a81a95e52
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 10 09:52:24 2026 -0700

    canary: 10-product reorder applied (reversible, ledger); feed-upload probe found 0 datafeeds (classic supplemental path unavailable — Shopify API-pushes, needs Merchant API dataSource)
---
 probe-datafeeds.mjs      | 14 ++++++++++++++
 reorder-canary-apply.mjs | 28 ++++++++++++++++++++++++++++
 2 files changed, 42 insertions(+)

diff --git a/probe-datafeeds.mjs b/probe-datafeeds.mjs
new file mode 100644
index 0000000..64be314
--- /dev/null
+++ b/probe-datafeeds.mjs
@@ -0,0 +1,14 @@
+import fs from 'node:fs'; import crypto from 'node:crypto';
+const HOME=process.env.HOME, MERCHANT='146735262';
+const SA_PATH=HOME+'/Projects/secrets-manager/gmc-sa-146735262.json';
+const b64=b=>Buffer.from(b).toString('base64').replace(/=/g,'').replace(/\+/g,'-').replace(/\//g,'_');
+const SA=JSON.parse(fs.readFileSync(SA_PATH,'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 s=crypto.createSign('RSA-SHA256');s.update(si);const jwt=si+'.'+b64(s.sign(SA.private_key));
+const tr=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 tok=(await tr.json()).access_token;
+const H={Authorization:`Bearer ${tok}`};
+const df=await (await fetch(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/datafeeds`,{headers:H})).json();
+console.log('EXISTING DATAFEEDS:');
+for(const d of (df.resources||[])){console.log(`  id=${d.id} name="${d.name}" contentType=${d.contentType} targets=${JSON.stringify((d.targets||[]).map(t=>t.country+'/'+t.language))} fetch=${d.fetchSchedule?.fetchUrl?'URL':'manual/other'}`);}
+console.log(`  (${(df.resources||[]).length} datafeeds total)`);
diff --git a/reorder-canary-apply.mjs b/reorder-canary-apply.mjs
new file mode 100644
index 0000000..887a4e3
--- /dev/null
+++ b/reorder-canary-apply.mjs
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+/** reorder-canary-apply.mjs — LIVE (Steve-approved) but CANARY-scoped + reversible.
+ * Reorders Roll->pos1 for the first N SUPP_CANT_HELP products, recording a rollback ledger FIRST. */
+import fs from 'node:fs';
+const HOME=process.env.HOME, SHOP='designer-laboratory-sandbox';
+const API=`https://${SHOP}.myshopify.com/admin/api/2024-10/graphql.json`;
+const T=(fs.readFileSync(HOME+'/Projects/secrets-manager/.env','utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1].replace(/['"]/g,'').trim();
+const N=parseInt(process.argv[2]||'10',10);
+const LEDGER=HOME+'/.claude/yolo-queue/restore-maps/reorder-canary-rollback.jsonl';
+async function gql(q,v){const r=await fetch(API,{method:'POST',headers:{'X-Shopify-Access-Token':T,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v||{}})});return r.json();}
+const rows=JSON.parse(fs.readFileSync(new URL('./out/supp-cant-help.json',import.meta.url),'utf8')).slice(0,N);
+const CUR=`query($id:ID!){product(id:$id){title variants(first:20){nodes{id title position price}}}}`;
+const MUT=`mutation($productId:ID!,$positions:[ProductVariantPositionInput!]!){productVariantsBulkReorder(productId:$productId,positions:$positions){userErrors{field message}}}`;
+let ok=0,fail=0;
+for(const r of rows){
+  const pid=`gid://shopify/Product/${r.pid}`;
+  const cj=await gql(CUR,{id:pid}); const p=cj.data?.product; if(!p){console.log(`SKIP ${r.pid} (not found)`);continue;}
+  const before=p.variants.nodes.map(v=>({id:v.id,title:v.title,position:v.position,price:v.price}));
+  fs.appendFileSync(LEDGER,JSON.stringify({ts:new Date().toISOString(),pid:r.pid,title:p.title,before})+'\n');
+  const positions=[{id:`gid://shopify/ProductVariant/${r.rollVid}`,position:1},{id:`gid://shopify/ProductVariant/${r.sampleVid}`,position:2}];
+  const mj=await gql(MUT,{productId:pid,positions});
+  const errs=mj.data?.productVariantsBulkReorder?.userErrors||[];
+  if(errs.length){fail++;console.log(`ERR  ${r.title||p.title}: ${JSON.stringify(errs)}`);continue;}
+  const vj=await gql(CUR,{id:pid}); const roll=vj.data.product.variants.nodes.find(v=>v.id.endsWith(r.rollVid));
+  console.log(`OK   ${p.title.slice(0,40).padEnd(40)} Roll now pos${roll?.position} ($${roll?.price})`); ok++;
+}
+console.log(`\nCANARY DONE: ${ok} reordered OK, ${fail} failed. Rollback ledger: ${LEDGER}`);
+console.log(`Next: Google re-sync (hours) -> re-run classify on these ${ok} to confirm the Roll offer appears + $4.25 drops.`);

← 92213f9 add full-population classifier (MC-feed-set based, bounded)  ·  back to Gmc 425 Supplemental Feed  ·  add reorder-canary re-sync recheck script (read-only efficac b93ed3f →