← 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
M step-e-verify-all.jsA step-f-retry-fails.jsM wait-and-verify.sh
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 →