← back to Carnegie Reprice
carnegie-mfr-gate.mjs
138 lines
#!/usr/bin/env node
// carnegie-mfr-gate.mjs — TK-10792 stop-the-bleed guard
//
// Validates that a Carnegie product row has a REAL vendor mfr_sku before
// any activator is allowed to set status='active'. Products that fail are
// kept as draft and tagged Needs-Mfr-SKU.
//
// Usage as a library:
// import { mfrSkuValid, assertActivatable, SKIP_TAG } from './carnegie-mfr-gate.mjs';
// const { ok, reason } = assertActivatable(row);
// if (!ok) { /* keep draft, add SKIP_TAG to tags */ }
//
// Usage as a CLI audit:
// node carnegie-mfr-gate.mjs # audit carnegie_catalog, print skip counts
// node carnegie-mfr-gate.mjs --verbose # print every skipped SKU
import { execFileSync } from 'node:child_process';
// ---- Gate logic ----------------------------------------------------------------
/** Tag applied to products that fail the gate */
export const SKIP_TAG = 'Needs-Mfr-SKU';
/**
* Returns true iff `mfr` is a real vendor mfr_sku:
* - not null / empty / whitespace-only
* - does NOT match /^DWAG-/i (DW-internal placeholder codes)
* - does NOT match /^DW[A-Z]{2}-/i in general (all DW-prefix internal codes)
* NOTE: only DWAG is confirmed as the bleed source; the regex is intentionally
* tight so legitimate vendor codes (DWSC-*, etc.) that happen to be known are
* not blocked. Adjust the regex if new internal prefixes are discovered.
*/
export function mfrSkuValid(mfr) {
if (mfr === null || mfr === undefined) return false;
const s = String(mfr).trim();
if (!s) return false;
if (/^DWAG-/i.test(s)) return false;
return true;
}
/** Tag applied to products that fail the body_html gate */
export const NEEDS_DESC_TAG = 'Needs-Description';
/**
* Returns true iff `body` is a real, non-blank product description.
* Strips HTML tags, , and whitespace before checking, so an empty
* <p></p> shell or a bare wrapper counts as blank. This is the guard the
* TK-10686 batch was missing — it shipped 57 products ACTIVE with blank
* body_html because the activator gated only on mfr_sku.
*/
export function bodyHtmlValid(body) {
if (body === null || body === undefined) return false;
const text = String(body)
.replace(/<[^>]*>/g, '') // strip tags
.replace(/&(nbsp|thinsp|ensp|emsp|zwnj|zwj|#x?0*(a0|160|8203);?|#160|#8203);/gi, ' ') // whitespace-like entities
.replace(/&[a-z]+;|&#x?[0-9a-f]+;/gi, '') // any remaining entity → drop (mdash/amp/etc. add no real words)
.trim();
return text.length > 0;
}
/**
* Returns { ok: true } if the row is safe to activate, or
* { ok: false, reason: string } if it must stay draft.
* Fails on either a missing mfr_sku OR a blank body_html/description.
*
* @param {object} row - object with `mfr_sku` and (`body_html`|`description_text`)
*/
export function assertActivatable(row) {
const mfr = row?.mfr_sku;
if (!mfrSkuValid(mfr)) {
const display = (mfr === null || mfr === undefined) ? 'null' : `"${String(mfr)}"`;
return {
ok: false,
reason: `mfr_sku ${display} is null, empty, or a DWAG-* internal placeholder — product stays DRAFT (${SKIP_TAG})`,
};
}
const body = row?.body_html ?? row?.description_text;
if (!bodyHtmlValid(body)) {
return {
ok: false,
reason: `body_html/description is blank — product stays DRAFT (${NEEDS_DESC_TAG})`,
};
}
return { ok: true };
}
/**
* Given a comma-separated tags string, ensure SKIP_TAG is present.
* Returns the updated tags string.
*/
export function addSkipTag(tags) {
const arr = (tags || '').split(',').map(t => t.trim()).filter(Boolean);
if (!arr.includes(SKIP_TAG)) arr.push(SKIP_TAG);
return arr.join(', ');
}
// ---- CLI audit (when run directly) --------------------------------------------
if (import.meta.url === `file://${process.argv[1]}`) {
const VERBOSE = process.argv.includes('--verbose');
const DB = 'postgresql:///dw_unified?host=/tmp';
const PSQL = [
'/opt/homebrew/opt/postgresql@14/bin/psql',
'/usr/local/opt/postgresql@14/bin/psql',
'psql',
].find(p => { try { execFileSync(p, ['--version'], { stdio: 'ignore' }); return true; } catch { return false; } }) || 'psql';
const q = sql => execFileSync(PSQL, [DB, '-At', '-c', sql], { encoding: 'utf8' }).trim();
console.log('=== Carnegie mfr_sku gate audit (carnegie_catalog) ===\n');
const total = +q('SELECT COUNT(*) FROM carnegie_catalog');
const nullCount = +q("SELECT COUNT(*) FROM carnegie_catalog WHERE mfr_sku IS NULL OR mfr_sku = ''");
const dwagCount = +q("SELECT COUNT(*) FROM carnegie_catalog WHERE mfr_sku ILIKE 'DWAG-%'");
const realCount = +q("SELECT COUNT(*) FROM carnegie_catalog WHERE mfr_sku IS NOT NULL AND mfr_sku <> '' AND mfr_sku NOT ILIKE 'DWAG-%'");
console.log(`Total rows : ${total}`);
console.log(`Real mfr_sku (PASS) : ${realCount} (${((realCount/total)*100).toFixed(1)}%)`);
console.log(`DWAG-* codes (SKIP) : ${dwagCount} (${((dwagCount/total)*100).toFixed(1)}%)`);
console.log(`null/empty (SKIP) : ${nullCount} (${((nullCount/total)*100).toFixed(1)}%)`);
console.log(`Total would SKIP : ${dwagCount + nullCount}`);
if (VERBOSE) {
console.log('\n--- Skipped SKUs (sample up to 50) ---');
const rows = q(
"SELECT dw_sku, mfr_sku, pattern_name FROM carnegie_catalog " +
"WHERE mfr_sku IS NULL OR mfr_sku = '' OR mfr_sku ILIKE 'DWAG-%' " +
"ORDER BY pattern_name, mfr_sku LIMIT 50"
);
for (const line of rows.split('\n').filter(Boolean)) {
console.log(' ', line);
}
}
console.log('\nProducts in this set would be created as DRAFT with tag "Needs-Mfr-SKU".');
console.log('They will NOT be activated until Steve provides the real vendor code.\n');
}