← back to Tk10640 Redirect Prune
canary-verify-map.mjs
59 lines
// TK-10653 — CANARY: verify the remediation map is sound BEFORE any write.
// Read-only. Samples the map and proves each proposed action is correct on the LIVE storefront:
// REPOINT rows -> HEAD https://www.designerwallcoverings.com/products/<new> must be 200 (live landing)
// AND the old dead target must still 404 (so the repoint is a real improvement)
// PRUNE rows -> the dead target must 404 (deleting the redirect => clean 404, no live loss)
// Usage: node canary-verify-map.mjs [--n=50] [--repoint-only]
import { readFileSync, writeFileSync } from 'node:fs';
const HOME=process.env.HOME;
const BASE='https://www.designerwallcoverings.com';
const N=Number((process.argv.find(a=>a.startsWith('--n='))||'').split('=')[1]||50);
const REPOINT_ONLY=process.argv.includes('--repoint-only');
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
// Rate-limit-SAFE probe: HEAD, follow redirects, back off + RETRY on 429 (the storefront
// throttles rapid GETs -> a 429/soft-200 is a canary artifact, NOT the real status).
async function head(path){
const url=BASE+path;
for(let a=0;a<6;a++){
try{
const res=await fetch(url,{method:'HEAD',redirect:'follow',headers:{'User-Agent':'Mozilla/5.0 DW-canary'}});
if(res.status===429){ await sleep(3000*(a+1)); continue; }
return res.status;
}catch(e){ await sleep(1500*(a+1)); }
}
return 429; // never resolved past throttle
}
const map=readFileSync(`${HOME}/Projects/tk10640-redirect-prune/data/full-scan/remediation-map.jsonl`,'utf8')
.trim().split('\n').filter(Boolean).map(JSON.parse);
const repoints=map.filter(r=>r.action==='REPOINT');
const prunes=map.filter(r=>r.action==='PRUNE');
// deterministic even sample across both classes
function sample(arr,k){ if(arr.length<=k) return arr.slice(); const step=arr.length/k; const out=[]; for(let i=0;i<k;i++) out.push(arr[Math.floor(i*step)]); return out; }
const halfR = REPOINT_ONLY ? N : Math.ceil(N*0.6);
const halfP = REPOINT_ONLY ? 0 : Math.floor(N*0.4);
const sR=sample(repoints,halfR), sP=sample(prunes,halfP);
const results={repoint:[],prune:[]};
let repoint_ok=0, repoint_bad=0, prune_ok=0, prune_bad=0;
for(const r of sR){
const newStatus=await head(r.new_target);
const oldStatus=await head(r.old_target);
const good = newStatus===200 && oldStatus===404;
results.repoint.push({path:r.path,old:r.old_target,old_status:oldStatus,new:r.new_target,new_status:newStatus,good});
good?repoint_ok++:repoint_bad++;
await sleep(1500);
}
for(const r of sP){
const oldStatus=await head(r.old_target);
const good = oldStatus===404; // pruning a dead target => nothing live lost
results.prune.push({path:r.path,old:r.old_target,old_status:oldStatus,good});
good?prune_ok++:prune_bad++;
await sleep(1500);
}
const summary={
sampled_repoint:sR.length, repoint_ok, repoint_bad,
sampled_prune:sP.length, prune_ok, prune_bad,
map_total:map.length, map_repoint:repoints.length, map_prune:prunes.length
};
writeFileSync(`${HOME}/Projects/tk10640-redirect-prune/data/full-scan/canary-result.json`, JSON.stringify({summary,results},null,2));
console.log(JSON.stringify(summary,null,2));