← back to Dw Rotation Activator

lib/mfr-gate-resolve.js

141 lines

'use strict';

/*
 * mfr-gate-resolve.js — the thin I/O layer that feeds lib/mfr-gate.js.
 *
 * Keeps mfr-gate.js pure (no DB) by doing the cheap dw_unified lookups here:
 *   - reusedMfrSet(): one query → the SET of mfr codes that appear under >1 vendor
 *     (the internal-counter bug class), loaded ONCE per activator run.
 *   - stagingColorFor(vendor, mfr): resolve the vendor's staging table via
 *     vendor_registry.catalog_table and read the color_name of the row whose
 *     mfr_sku === the resolved code (the fabricated-placeholder-color signal).
 *
 * All reads are host=/tmp local dw_unified. Fail-safe: any query error returns the
 * EMPTY/absent value so the gate fails OPEN on that dimension (never a false BLOCK
 * on a DB hiccup; the gate only BLOCKs on positive evidence).
 */
const { execFileSync } = require('child_process');

function psql(sql) {
  return execFileSync('psql', ['host=/tmp dbname=dw_unified', '-tAF', '\t', '-c', sql],
    { encoding: 'utf8', maxBuffer: 1 << 28 }).trim();
}

// SQL string literal escaper (single-quote doubling). Codes/vendors are alnum+dash in
// practice, but never interpolate raw.
const lit = (s) => `'${String(s == null ? '' : s).replace(/'/g, "''")}'`;

// The set of mfr codes shared across >1 vendor in shopify_products — the reused
// internal-counter signature. Loaded once; O(1) membership after. Fail-safe → empty set.
function reusedMfrSet() {
  try {
    const raw = psql(`
      SELECT rm FROM (
        SELECT COALESCE(
          NULLIF(metafields->'dwc'->'manufacturer_sku'->>'value',''),
          NULLIF(metafields->'custom'->'manufacturer_sku'->>'value',''),
          NULLIF(metafields->'global'->'manufacturer_sku'->>'value',''),
          NULLIF(mfr_sku,'')
        ) AS rm, vendor
        FROM shopify_products
      ) q
      WHERE rm IS NOT NULL
      GROUP BY rm HAVING count(DISTINCT vendor) > 1;`);
    return new Set(raw ? raw.split('\n').map((l) => l.trim()).filter(Boolean) : []);
  } catch (_) {
    return new Set();
  }
}

// Cache vendor→catalog_table so we resolve vendor_registry once per vendor per run.
const _tableCache = new Map();
function catalogTableFor(vendor) {
  const key = String(vendor || '').toLowerCase();
  if (_tableCache.has(key)) return _tableCache.get(key);
  let table = null;
  try {
    // Exact-name first, then a contained match (vendor labels vary slightly). Only
    // accept a real, safe table identifier (letters/digits/underscore) — never a value
    // we'd interpolate blindly into the next query.
    const raw = psql(
      `SELECT catalog_table FROM vendor_registry
       WHERE catalog_table IS NOT NULL AND catalog_table <> ''
         AND (lower(vendor_name) = ${lit(key)} OR lower(vendor_name) LIKE ${lit('%' + key + '%')})
       ORDER BY (lower(vendor_name) = ${lit(key)}) DESC LIMIT 1;`);
    const t = (raw || '').split('\n')[0].trim();
    if (/^[a-z_][a-z0-9_]*$/i.test(t)) table = t;
  } catch (_) { /* fail-open */ }
  _tableCache.set(key, table);
  return table;
}

// The color_name of the staging row whose mfr_sku === the resolved code. Returns null
// when there is no staging table, no matching row, or on any error → gate fails open
// on the fabricated-sequence dimension. Guards that the table actually has the columns.
// Cache table→vendor-scoping clause. A SHARED catalog table (>1 vendor registered to it)
// must be filtered to THIS vendor's rows: `vendor_catalog` alone holds 157 distinct
// vendor_codes, and 11,640 mfr_sku values in it are used by MORE THAN ONE vendor, so an
// unfiltered `WHERE mfr_sku = …` returns whichever row the seq scan reaches first.
// Demonstrated 2026-09-10: mfr 5016840 resolves to schumacher "sage green" AND backdrop
// "Seaglass Wallcovering" — for a Backdrop product the unfiltered query returned
// Schumacher's colour. Returns '' for a private (single-vendor) table, a filter clause
// when it can scope, or null when it CANNOT — and null makes the caller fail OPEN, which
// is this module's standing doctrine (never a false BLOCK on a resolution failure).
const _scopeCache = new Map();
function vendorScopeFor(vendor, table) {
  const key = `${table}::${String(vendor || '').toLowerCase()}`;
  if (_scopeCache.has(key)) return _scopeCache.get(key);
  let clause = '';
  try {
    const sharers = parseInt(
      psql(`SELECT count(*) FROM vendor_registry WHERE catalog_table = ${lit(table)};`), 10) || 0;
    if (sharers > 1) {
      const cols = new Set(
        (psql(`SELECT column_name FROM information_schema.columns
                WHERE table_name = ${lit(table)}
                  AND column_name IN ('vendor_code','brand','vendor');`) || '')
          .split('\n').map((s) => s.trim()).filter(Boolean));
      const col = cols.has('vendor_code') ? 'vendor_code'
        : cols.has('brand') ? 'brand'
        : cols.has('vendor') ? 'vendor' : null;
      if (!col) {
        clause = null; // shared and unscopable → caller fails open
      } else if (col === 'vendor_code') {
        // `vendor` here is a vendor_NAME; map it to its code via the registry.
        const code = (psql(
          `SELECT vendor_code FROM vendor_registry
            WHERE lower(vendor_name) = ${lit(String(vendor || '').toLowerCase())} LIMIT 1;`) || '')
          .split('\n')[0].trim();
        clause = code ? ` AND lower(vendor_code) = lower(${lit(code)})` : null;
      } else {
        clause = ` AND lower(${col}) = lower(${lit(vendor)})`;
      }
    }
  } catch (_) {
    clause = null; // resolution failed → fail open rather than read another vendor's row
  }
  _scopeCache.set(key, clause);
  return clause;
}

function stagingColorFor(vendor, mfr) {
  if (!mfr) return null;
  const table = catalogTableFor(vendor);
  if (!table) return null;
  const scope = vendorScopeFor(vendor, table);
  if (scope === null) return null; // shared table we cannot scope — fail open
  try {
    const raw = psql(
      `SELECT color_name FROM ${table}
       WHERE mfr_sku = ${lit(mfr)} AND color_name IS NOT NULL${scope}
       ORDER BY color_name
       LIMIT 1;`);
    const c = (raw || '').split('\n')[0];
    return c === '' ? null : c;
  } catch (_) {
    return null; // e.g. table lacks color_name/mfr_sku columns — fail open
  }
}

module.exports = { reusedMfrSet, catalogTableFor, stagingColorFor };