← back to Gmc Titlefix
step-f-retry-fails.js
49 lines
// 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); });