← back to Dw Yolo Loop

scripts/collection-leak-sweep/collection-leak-sweep.mjs

36 lines

// collection-leak-sweep (c40) — READ-ONLY. The existing leak scanners check
// PRODUCT pages; c39 proved a live collection (wallquest-wallcoverings) leaks an
// upstream private-label name there too. This sweeps ALL live collection handles
// + titles against the private-label denylist — a collection-level surface no
// canary covers. Live read of /collections.json. $0.
import fs from 'node:fs';
const BASE='https://www.designerwallcoverings.com';
const OUT=`${process.env.HOME}/.claude/yolo-queue/collection-leak-sweep-2026-06-16.json`;
const MD =`${process.env.HOME}/.claude/yolo-queue/collection-leak-sweep-2026-06-16.md`;
// private-label / upstream names that must NEVER be customer-facing (word-ish match)
const DENY = [
  ['wallquest','→ Malibu'],['chesapeake','→ Malibu'],['nextwall','→ Malibu'],['seabrook','→ Malibu'],
  ['brewster','→ Malibu (note: Brewster&York src)'],['command54','→ Phillipe Romano'],['command 54','→ Phillipe Romano'],
  ['nicolette mayer','hard-archived, stay off'],['nicolette-mayer','hard-archived'],
  ['desima','→ archived'],['carlsten','→ archived'],
];
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
async function page(p){ for(let a=0;a<4;a++){ try{ const r=await fetch(`${BASE}/collections.json?limit=250&page=${p}`,{signal:AbortSignal.timeout(15000)}); if(r.status===200) return (await r.json()).collections||[]; if(r.status===429){await sleep(2000*(a+1));continue;} return null;}catch(e){await sleep(1500*(a+1));} } return null; }
const all=[]; for(let p=1;p<=20;p++){ const c=await page(p); if(c===null)break; if(!c.length)break; all.push(...c); await sleep(200); }
const hits=[];
for(const c of all){
  const hay=`${c.handle||''} ${c.title||''}`.toLowerCase();
  for(const [term,why] of DENY){ if(hay.includes(term)){ hits.push({handle:c.handle, title:c.title, products_count:c.products_count, matched:term, why}); break; } }
}
const report={generated_at:new Date().toISOString(), scanned:all.length, leak_hits:hits.length, hits};
fs.writeFileSync(OUT,JSON.stringify(report,null,2));
let md=`# Collection-level leak sweep (live) — ${new Date().toISOString().slice(0,16)}\n\n`;
md+=`**READ-ONLY (live /collections.json), \$0.** ${all.length} live collections swept for private-label/upstream names in handle+title (a surface the product-page leak scanners DON'T cover, per c39).\n\n`;
md+=`## ${hits.length} LEAK hit(s)\n`;
if(hits.length){ md+=`| handle | title | products | matched | maps to |\n|---|---|--:|---|---|\n`; for(const h of hits) md+=`| ${h.handle} | ${(h.title||'').slice(0,40)} | ${h.products_count} | **${h.matched}** | ${h.why} |\n`; }
else md+=`None — no private-label/upstream name found in any live collection handle/title. 🟢\n`;
fs.writeFileSync(MD,md);
console.log(`[collection-leak-sweep] scanned=${all.length} leak_hits=${hits.length}`);
for(const h of hits) console.log(`  ⛔ ${h.handle} (matched "${h.matched}", ${h.products_count} products) ${h.why}`);
console.log(`Report: ${MD}`);