[object Object]

← back to Gmc Titlefix

step-f: bounded verify-gated retry (dry-run default, CONFIRM_RETRY=1 to push); step-e emits full not-flipped list; wrapper auto-dry-runs + drafts gated retry (DTD-unanimous A)

084653fccf8951132dfdb4f48c983b53944df060 · 2026-06-18 17:16:45 -0700 · SteveStudio2

Files touched

Diff

commit 084653fccf8951132dfdb4f48c983b53944df060
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Thu Jun 18 17:16:45 2026 -0700

    step-f: bounded verify-gated retry (dry-run default, CONFIRM_RETRY=1 to push); step-e emits full not-flipped list; wrapper auto-dry-runs + drafts gated retry (DTD-unanimous A)
---
 step-e-verify-all.js  |  8 +++++---
 step-f-retry-fails.js | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 wait-and-verify.sh    | 25 +++++++++++++++++++++++++
 3 files changed, 78 insertions(+), 3 deletions(-)

diff --git a/step-e-verify-all.js b/step-e-verify-all.js
index c7b2488..761d8c9 100644
--- a/step-e-verify-all.js
+++ b/step-e-verify-all.js
@@ -25,13 +25,15 @@ const vendorOf=row=>{ const t=row.currentTitle||''; const m=t.match(/(?:by|\|)\s
   }while(pageToken);
 
   let flipped=0, notyet=0, missing=0;
-  const notFlipped=[], byVendorNot={};
+  const notFlipped=[], byVendorNot={}, notFlippedAll=[];
   for(const [offerId,row] of risky){
-    if(!title.has(offerId)){ missing++; continue; }
+    if(!title.has(offerId)){ missing++; continue; }   // gone from feed → never retry (ghost)
     if(/^Sample:/.test(title.get(offerId))) flipped++;
-    else { notyet++; const v=vendorOf(row); byVendorNot[v]=(byVendorNot[v]||0)+1;
+    else { notyet++; notFlippedAll.push(offerId); const v=vendorOf(row); byVendorNot[v]=(byVendorNot[v]||0)+1;
            if(notFlipped.length<25) notFlipped.push({offerId, title:title.get(offerId).slice(0,80)}); }
   }
+  // Full not-flipped offerId list (present-in-feed only) — drives the bounded retry (step-f). DTD-unanimous 2026-06-18.
+  fs.writeFileSync('/tmp/gmc-notflipped.json', JSON.stringify(notFlippedAll));
   const result={ ts:new Date().toISOString(), totalRisky:risky.size, feedOffersScanned:scanned,
     flipped, notyet, missingFromFeed:missing,
     flipRatePct:+(100*flipped/risky.size).toFixed(2),
diff --git a/step-f-retry-fails.js b/step-f-retry-fails.js
new file mode 100644
index 0000000..6cfc80a
--- /dev/null
+++ b/step-f-retry-fails.js
@@ -0,0 +1,48 @@
+// STEP-F — bounded retry of the title-fix offers the final verify proved still unflipped.
+// DTD verdict 2026-06-18 (unanimous A): retry is idempotent + free; only the verify's
+// not-flipped set (present-in-feed, ghosts already excluded by step-e) is touched.
+//
+// SAFE BY DEFAULT — this is a GATED GMC write, so it DRY-RUNS unless CONFIRM_RETRY=1:
+//   node step-f-retry-fails.js              -> dry-run: prints exactly what it WOULD retry
+//   CONFIRM_RETRY=1 node step-f-retry-fails.js   -> actually re-inserts overrides (Steve's go)
+const {token,MERCHANT}=require('./_auth');
+const fs=require('fs');
+const DS='accounts/146735262/dataSources/10677010255'; // canonical bound supplemental source
+const MAX_ATTEMPTS=parseInt(process.env.MAX_ATTEMPTS||'3',10);
+const CONFIRM=process.env.CONFIRM_RETRY==='1';
+const LISTFILE='/tmp/gmc-notflipped.json';
+
+if(!fs.existsSync(LISTFILE)){ console.error(`No ${LISTFILE} — run step-e-verify-all.js first.`); process.exit(1); }
+const ids=JSON.parse(fs.readFileSync(LISTFILE,'utf8'));
+const rows=new Map(require('./gmc_titlefix_list.json').map(r=>[r.offerId,r]));
+const targets=ids.filter(id=>rows.has(id)); // must have a proposedTitle to re-insert
+
+console.log(`STEP-F retry — ${targets.length} not-flipped offers (present in feed), ≤${MAX_ATTEMPTS} attempts each → ${DS}`);
+if(!CONFIRM){
+  console.log('\nDRY-RUN (no writes). Sample of what would be retried:');
+  for(const id of targets.slice(0,15)) console.log('  ',id,'->',(rows.get(id).proposedTitle||'').slice(0,70));
+  console.log(`\nTo execute the retry: CONFIRM_RETRY=1 node step-f-retry-fails.js   (${targets.length} idempotent re-inserts, $0)`);
+  process.exit(0);
+}
+
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+(async()=>{
+  let tok=await token(), tokAt=Date.now(), recovered=0, stillFail=0;
+  const persist=[];
+  for(let i=0;i<targets.length;i++){
+    if(Date.now()-tokAt>50*60*1000){ tok=await token(); tokAt=Date.now(); }
+    const id=targets[i], row=rows.get(id); let ok=false;
+    for(let a=1;a<=MAX_ATTEMPTS && !ok;a++){
+      const url=`https://merchantapi.googleapis.com/products/v1/accounts/${MERCHANT}/productInputs:insert?dataSource=${encodeURIComponent(DS)}`;
+      const body={ offerId:id, contentLanguage:'en', feedLabel:'US', productAttributes:{ title: row.proposedTitle } };
+      const r=await fetch(url,{method:'POST',headers:{Authorization:'Bearer '+tok,'Content-Type':'application/json'},body:JSON.stringify(body)});
+      if(r.ok){ ok=true; }
+      else { if(r.status===429) await sleep(3000); else await sleep(500*a); }
+    }
+    if(ok) recovered++; else { stillFail++; persist.push(id); }
+    if(i%200===0) console.log(`  ${i}/${targets.length} | recovered ${recovered} | stillFail ${stillFail}`);
+  }
+  fs.writeFileSync('/tmp/gmc-retry-persisted-fails.json', JSON.stringify(persist));
+  console.log(`\nSTEP-F DONE: re-inserted ${recovered}/${targets.length} | persistent failures ${stillFail} (saved to /tmp/gmc-retry-persisted-fails.json)`);
+  console.log('Re-run step-e-verify-all.js after ~15 min to confirm the recovered set flipped.');
+})().catch(e=>{ console.error('ERR',e.message); process.exit(1); });
diff --git a/wait-and-verify.sh b/wait-and-verify.sh
index 472f643..1ec50c5 100755
--- a/wait-and-verify.sh
+++ b/wait-and-verify.sh
@@ -15,3 +15,28 @@ sleep "$BUFFER"
 echo "[$(date)] running step-e-verify-all.js ..."
 node step-e-verify-all.js
 echo "[$(date)] verify done. verdict: $(cat /tmp/gmc-verify-verdict.txt 2>/dev/null) — result: /tmp/gmc-verify-result.json"
+
+# STEP-F retry (DTD-unanimous A) — execution is GATED, so only the DRY-RUN runs automatically.
+NF=$(node -e 'try{console.log(JSON.parse(require("fs").readFileSync("/tmp/gmc-notflipped.json","utf8")).length)}catch(e){console.log(0)}')
+if [ "${NF:-0}" -gt 0 ]; then
+  echo "[$(date)] $NF offers still not flipped — running step-f DRY-RUN (no writes):"
+  node step-f-retry-fails.js
+  DRAFT="$HOME/.claude/yolo-queue/pending-approval/31-gmc-titlefix-retry-fails.md"
+  {
+    echo "# GATED — GMC title-fix retry of not-flipped offers (DTD-unanimous A, 2026-06-18)"
+    echo
+    echo "Final verify left **$NF** offers not flipped to \`Sample:\` (present in feed; ghosts excluded)."
+    echo "DTD panel (3/3) decided: run a bounded, verify-gated, idempotent retry. \$0, harmless re-insert."
+    echo
+    echo "**To execute (Steve's go):**"
+    echo '```bash'
+    echo "cd ~/Projects/gmc-titlefix && CONFIRM_RETRY=1 node step-f-retry-fails.js"
+    echo "# then, ~15 min later, re-verify:  node step-e-verify-all.js"
+    echo '```'
+    echo
+    echo "Dry-run scope + sample logged to /tmp/gmc-verify.log. Not-flipped ids: /tmp/gmc-notflipped.json"
+  } > "$DRAFT"
+  echo "[$(date)] gated retry draft written → $DRAFT"
+else
+  echo "[$(date)] 0 offers not flipped — nothing to retry. ✅"
+fi

← 85cc622 step-e: DTD-B verify-and-retry — page processed feed, re-pus  ·  back to Gmc Titlefix  ·  wrapper: auto-execute authorized retry (Steve go) after fina e79940d →