← back to Tk10630 Sku Suffix Canary

canary-scan.mjs

124 lines

// dw-hollywood-sku-canary — READ-ONLY guard for fabricated DWHD/DWHW SKU codes
// on the live Hollywood Wallcoverings line (a DW private-label BRAND, not a vendor).
//
// Flags four violation classes on the live store:
//   1) fabricated_metafield  — a dw_sku metafield holding a DWHD-/DWHW- code
//   2) fabricated_variant    — a variant SKU whose base is a DWHD-/DWHW- code
//   3) sample_suffix         — a "Sample" variant whose SKU does not end in -sample
//   4) fabricated_identity   — product handle/vendor/title carrying a DWHD/DWHW code
//
// Emits data/latest.json with a top-level verdict PASS|WARN|FAIL (fleet-health
// vocabulary). Baseline-aware: alerts only on a WORSENING transition. READ-ONLY —
// never writes to Shopify. --alert posts a CNCP parking-lot card on worsening.
import { gql } from './shopify.mjs';
import { FABRICATED, deriveBase, suffixFor } from './lib.mjs';
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';

const VENDOR = 'Hollywood Wallcoverings';
const ALERT = process.argv.includes('--alert');
const LATEST = new URL('./data/latest.json', import.meta.url).pathname;

const PAGE = `query($cursor:String){
  products(first:50, query:"vendor:\\"${VENDOR}\\"", after:$cursor){
    pageInfo{ hasNextPage endCursor }
    nodes{
      id handle title status vendor
      mfG: metafield(namespace:"global", key:"dw_sku"){ value }
      mfD: metafield(namespace:"dwc",    key:"dw_sku"){ value }
      mfC: metafield(namespace:"custom", key:"dw_sku"){ value }
      variants(first:10){ nodes{ id title sku selectedOptions{ name value } } }
    }
  }
}`;

const sleep = ms => new Promise(r => setTimeout(r, ms));
const isFab = s => !!s && FABRICATED.test(s);
// bare_number_leak (2026-08-17, Cody/DTD): a customer-facing SKU that is a RAW Momentum
// number (all digits, no Hollywood private-label prefix) — e.g. "09620400". "Always pl
// momentum": the real supplier number belongs ONLY in manufacturer_sku, never as the
// visible SKU/dw_sku. Guards the Option-C leak class the generator must never mint.
const isBareNumber = s => { const b = (s || '').replace(/-(sample|yard|roll)$/i, ''); return /^\d{5,}$/.test(b); };

const v = { fabricated_metafield: 0, fabricated_variant: 0, sample_suffix: 0, fabricated_identity: 0, bare_number_leak: 0 };
const examples = { fabricated_metafield: [], fabricated_variant: [], sample_suffix: [], fabricated_identity: [], bare_number_leak: [] };
const affected = new Set();
let scanned = 0, cursor = null, fatal = null;

function note(cls, handle, detail) {
  v[cls]++; affected.add(handle);
  if (examples[cls].length < 5) examples[cls].push({ handle, detail });
}

while (true) {
  let res;
  try {
    for (let a = 0; ; a++) {
      try { res = await gql(PAGE, { cursor }); break; }
      catch (e) { if (/THROTTLED|throttle/i.test(e.message) && a < 8) { await sleep(2000 * (a + 1)); continue; } throw e; }
    }
  } catch (e) { fatal = e.message; break; }

  for (const p of res.data.products.nodes) {
    scanned++;
    // 1) fabricated dw_sku metafields (all three namespaces: global, dwc, custom)
    if (isFab(p.mfG?.value)) note('fabricated_metafield', p.handle, `global.dw_sku=${p.mfG.value}`);
    if (isFab(p.mfD?.value)) note('fabricated_metafield', p.handle, `dwc.dw_sku=${p.mfD.value}`);
    if (isFab(p.mfC?.value)) note('fabricated_metafield', p.handle, `custom.dw_sku=${p.mfC.value}`);
    // 1b) bare-number Momentum leak in a dw_sku metafield (raw supplier number, no prefix)
    if (isBareNumber(p.mfG?.value) || isBareNumber(p.mfD?.value) || isBareNumber(p.mfC?.value))
      note('bare_number_leak', p.handle, `dw_sku=${p.mfG?.value || p.mfD?.value || p.mfC?.value}`);
    // 4) fabricated identity (handle / vendor / title)
    if (/dwh[dw]/i.test(p.handle) || FABRICATED.test((p.vendor || '') + '-') || /\bDWH[DW]\b/i.test(p.title || ''))
      note('fabricated_identity', p.handle, `vendor=${p.vendor}`);
    // 2/3) variant-level
    for (const vr of p.variants.nodes) {
      const base = (vr.sku || '').replace(/-(sample|yard|roll)$/i, '');
      if (isFab(base)) note('fabricated_variant', p.handle, `${vr.title}=${vr.sku}`);
      if (isBareNumber(vr.sku)) note('bare_number_leak', p.handle, `${vr.title}=${vr.sku}`);
      if (suffixFor(vr) === 'sample' && vr.sku && !/-sample$/i.test(vr.sku))
        note('sample_suffix', p.handle, `sample sku=${vr.sku}`);
    }
  }
  if (!res.data.products.pageInfo.hasNextPage) break;
  cursor = res.data.products.pageInfo.endCursor;
}

const total = Object.values(v).reduce((a, b) => a + b, 0);

// Baseline compare
let prev = null;
try { prev = JSON.parse(readFileSync(LATEST, 'utf8')); } catch {}
const prevTotal = prev?.total_violations ?? null;
const worsening = prevTotal != null && total > prevTotal;
const firstRun = prevTotal == null;

let verdict;
if (fatal) verdict = 'WARN';           // couldn't complete — no-signal, don't cry wolf
else if (total === 0) verdict = 'PASS';
else if (worsening) verdict = 'FAIL';  // regression (e.g. daily generator re-minted codes)
else verdict = 'WARN';                 // steady-state backlog under remediation

const out = {
  verdict, generated_at: new Date().toISOString(), vendor: VENDOR,
  scanned, total_violations: total, by_class: v,
  affected_products: affected.size,
  baseline: { prev_total: prevTotal, worsening, first_run: firstRun },
  examples, fatal,
};
mkdirSync(new URL('./data/', import.meta.url).pathname, { recursive: true });
writeFileSync(LATEST, JSON.stringify(out, null, 2));

console.log(`[hollywood-sku-canary] ${verdict} — scanned=${scanned} violations=${total} (mf=${v.fabricated_metafield} var=${v.fabricated_variant} sampleSfx=${v.sample_suffix} ident=${v.fabricated_identity}) affected=${affected.size}` + (fatal ? ` FATAL=${fatal}` : ''));

// Alert ONLY on worsening transition (regression), and only when asked to.
if (ALERT && verdict === 'FAIL' && worsening) {
  const body = `Hollywood SKU canary FAIL: fabricated DWHD/DWHW codes rose ${prevTotal}→${total} (${affected.size} products). Likely the DWHD generator re-minted — check ~/.dw-fixer-stop + com.steve.hollywood-create-resume. TK-10630.`;
  try {
    await fetch('http://127.0.0.1:3333/api/parking-lot', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title: 'Hollywood SKU regression', summary: body, project: 'dw-unified', severity: 'high' }),
    });
    console.log('[hollywood-sku-canary] posted CNCP parking-lot card');
  } catch (e) { console.log('[hollywood-sku-canary] CNCP post failed:', e.message); }
}