[object Object]

← back to Dw Daily Catchup 20260909

feat(golive-gate): mfr_sku provenance gate blocks blank/fabricated codes at DRAFT->ACTIVE

99013ea3e4c28d90e9bacc134343f997a00e927b · 2026-09-01 08:02:16 -0700 · Steve Abrams

Adds lib/mfr-gate.js (pure, tested) + lib/mfr-gate-resolve.js (dw_unified I/O),
wired into rotate-activate.js / mdc-activate.js / cmo-activate.js before the flip.
Blocks: blank/null/unknown mfr, cross-vendor reused counters, and fabricated
<base>+<seq> runs (Carnegie Grain 6300->63001.. via placeholder 'Color N' staging).
Blocked drafts tag Needs-MfrSKU + hold; by-colour lines (Novasuede) exempt. TK-11063.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 99013ea3e4c28d90e9bacc134343f997a00e927b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 1 08:02:16 2026 -0700

    feat(golive-gate): mfr_sku provenance gate blocks blank/fabricated codes at DRAFT->ACTIVE
    
    Adds lib/mfr-gate.js (pure, tested) + lib/mfr-gate-resolve.js (dw_unified I/O),
    wired into rotate-activate.js / mdc-activate.js / cmo-activate.js before the flip.
    Blocks: blank/null/unknown mfr, cross-vendor reused counters, and fabricated
    <base>+<seq> runs (Carnegie Grain 6300->63001.. via placeholder 'Color N' staging).
    Blocked drafts tag Needs-MfrSKU + hold; by-colour lines (Novasuede) exempt. TK-11063.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 cmo-activate.js         | 21 ++++++++----
 lib/mfr-gate-resolve.js | 91 +++++++++++++++++++++++++++++++++++++++++++++++++
 lib/mfr-gate.js         | 87 ++++++++++++++++++++++++++++++++++++++++++++++
 mdc-activate.js         | 18 +++++++---
 rotate-activate.js      | 42 ++++++++++++++++++++---
 test/mfr-gate.test.js   | 69 +++++++++++++++++++++++++++++++++++++
 6 files changed, 314 insertions(+), 14 deletions(-)

diff --git a/cmo-activate.js b/cmo-activate.js
index 6409933..462b48b 100644
--- a/cmo-activate.js
+++ b/cmo-activate.js
@@ -3,6 +3,8 @@
 const fs=require('fs'), os=require('os'), path=require('path');
 const { SettlementGate } = require('./lib/settlement-gate.js');
 const { fiveFieldExtra } = require('./lib/five-field-extra.js');
+const { mfrGate } = require('./lib/mfr-gate.js');
+const { reusedMfrSet } = require('./lib/mfr-gate-resolve.js');
 const { validateBeforeActivate } = require(path.join(os.homedir(),'Projects/Designer-Wallcoverings/shopify/scripts/lib/validate-before-activate.js'));
 const { execSync } = require('child_process');
 const TOK=fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8').split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN=')).split('=')[1];
@@ -14,11 +16,14 @@ async function gql(q,v){ for(let a=0;a<4;a++){ try{ const r=await fetch(`https:/
 const L=fs.createWriteStream('/tmp/cmo_activate.log',{flags:'w'});
 const P=m=>{L.write(m+'\n');console.log(m);};
 
-// material map from staging (helps settlement auto-pass textures)
-const matMap={};
-for(const line of execSync(`psql "host=/tmp dbname=dw_unified" -tAF '\t' -c "SELECT mfr_sku,material FROM cmo_paris_catalog;"`).toString().trim().split('\n')){
-  const [s,m]=line.split('\t'); if(s) matMap[s]=m||'';
+// material + color map from staging (material helps settlement auto-pass textures;
+// color_name is the fabricated-placeholder signal for the mfr-provenance gate, TK-11063)
+const matMap={}, colorMap={};
+for(const line of execSync(`psql "host=/tmp dbname=dw_unified" -tAF '\t' -c "SELECT mfr_sku,coalesce(material,''),coalesce(color_name,'') FROM cmo_paris_catalog;"`).toString().trim().split('\n')){
+  const [s,m,c]=line.split('\t'); if(s){ matMap[s]=m||''; colorMap[s]=c||''; }
 }
+// cross-vendor reused-code set (internal-counter bug class), loaded once.
+const REUSED_MFRS=reusedMfrSet();
 const mfVal=(mfs,ns,k)=>{const x=mfs.find(m=>m.namespace===ns&&m.key===k);return x?x.value:'';};
 
 async function loadPubs(){ const r=await gql(`{publications(first:50){edges{node{id name}}}}`,{}); return (r.data?.publications?.edges||[]).map(e=>e.node).filter(p=>p.id!==GOOGLE); }
@@ -28,7 +33,7 @@ async function setHold(id,verdict,reason){ await gql(`mutation($mfs:[MetafieldsS
   const gate=new SettlementGate(); P(`settlement lock: ${JSON.stringify(gate.lockStatus())}  mode=${DRY?'DRY':'COMMIT'}`);
   const pubs=DRY?[]:await loadPubs(); if(!DRY)P(`publications (ex-Google): ${pubs.map(p=>p.name).join(', ')}`);
   const Q=`query($cur:String){products(first:40,query:"vendor:'CMO Paris' status:draft",after:$cur){pageInfo{hasNextPage endCursor} nodes{id title tags descriptionHtml featuredImage{url} images(first:6){nodes{url}} variants(first:5){nodes{sku price}} metafields(first:40){nodes{namespace key value}}}}}`;
-  let cur=null, scanned=0, activated=0, publishedN=0, heldSettle=0, heldValidate=0, blocked=0, cost=0;
+  let cur=null, scanned=0, activated=0, publishedN=0, heldSettle=0, heldValidate=0, blocked=0, heldMfr=0, cost=0;
   do{
     const r=await gql(Q,{cur});
     const pr=r.data?.products; if(!pr){P('load error: '+JSON.stringify(r).slice(0,200));break;}
@@ -39,6 +44,10 @@ async function setHold(id,verdict,reason){ await gql(`mutation($mfs:[MetafieldsS
       const mfr=mfVal(mfs,'custom','manufacturer_sku');
       const imgs=(n.images.nodes||[]).map(i=>i.url);
       const width=mfVal(mfs,'global','width');
+      // MFR-PROVENANCE gate (TK-11063) — BLOCK blank/reused/fabricated mfr before customer-facing.
+      const mfrCode=(mfVal(mfs,'dwc','manufacturer_sku')||mfr||mfVal(mfs,'global','manufacturer_sku')||'').trim();
+      const mg=mfrGate({vendor:'CMO Paris',mfr:mfrCode,reusedAcrossVendors:!!mfrCode&&REUSED_MFRS.has(mfrCode),stagingColorName:colorMap[mfrCode]||null});
+      if(!mg.ok){ heldMfr++; P(`HOLD(mfr:${mg.reasons.join(',')}) ${dwSku} ${mfrCode||'(blank)'}`); if(!DRY) await gql(`mutation($id:ID!,$t:[String!]!){tagsAdd(id:$id,tags:$t){userErrors{message}}}`,{id:n.id,t:['Needs-MfrSKU']}); continue; }
       // 5-field / specs gate (real validator)
       const val=validateBeforeActivate({title:n.title,dwSku,vendor:'CMO Paris',tags:n.tags||[],
         descriptionHtml:n.descriptionHtml,variants:n.variants.nodes,
@@ -69,5 +78,5 @@ async function setHold(id,verdict,reason){ await gql(`mutation($mfs:[MetafieldsS
     }
     cur=pr.pageInfo.hasNextPage?pr.pageInfo.endCursor:null;
   }while(cur);
-  P(`\nDONE ${DRY?'(DRY)':''}: scanned=${scanned} activated=${activated} published=${publishedN} heldSettlement=${heldSettle} blocked=${blocked} heldValidate=${heldValidate}  settlement_cost=$${cost.toFixed(4)}`);
+  P(`\nDONE ${DRY?'(DRY)':''}: scanned=${scanned} activated=${activated} published=${publishedN} heldSettlement=${heldSettle} blocked=${blocked} heldValidate=${heldValidate} heldMfr=${heldMfr}  settlement_cost=$${cost.toFixed(4)}`);
 })();
diff --git a/lib/mfr-gate-resolve.js b/lib/mfr-gate-resolve.js
new file mode 100644
index 0000000..22bc88b
--- /dev/null
+++ b/lib/mfr-gate-resolve.js
@@ -0,0 +1,91 @@
+'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.
+function stagingColorFor(vendor, mfr) {
+  if (!mfr) return null;
+  const table = catalogTableFor(vendor);
+  if (!table) return null;
+  try {
+    const raw = psql(
+      `SELECT color_name FROM ${table}
+       WHERE mfr_sku = ${lit(mfr)} AND color_name IS NOT NULL
+       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 };
diff --git a/lib/mfr-gate.js b/lib/mfr-gate.js
new file mode 100644
index 0000000..92d4c82
--- /dev/null
+++ b/lib/mfr-gate.js
@@ -0,0 +1,87 @@
+'use strict';
+
+/*
+ * mfr-gate.js — manufacturer-SKU PROVENANCE gate for the DRAFT→ACTIVE flip.
+ *
+ * Steve's HARD go-live rule (memory new-sku-vendor-mfr-gate, 2026-07-06): any SKU
+ * that goes live must carry a REAL manufacturer SKU number — never blank, never a
+ * reused internal counter, never an AI/importer-fabricated sequence. The
+ * dw-golive-gate-canary MONITORS this after the fact; this gate ENFORCES it BEFORE
+ * activation so the activator stops flipping non-compliant products live (TK-11063).
+ *
+ * Mirrors the canary's predicate (~/.claude/skills/dw-golive-gate-canary/auditor.mjs)
+ * and extends it with the single-vendor FABRICATED-sequence class the canary's
+ * cross-vendor-reuse test can't see (the Carnegie "Grain 6300 → 63001..630026" bug,
+ * where the per-color join invented a 1..N counter and never captured a real color).
+ *
+ * DESIGN — pure + testable like five-field-extra.js. `mfrGate(ctx)` takes the
+ * already-resolved facts (no I/O) so it unit-tests cleanly; the activator does the
+ * cheap DB/metafield resolution and hands the result in. Fail-safe: this can only
+ * BLOCK an activation, never cause one. Fail-OPEN on unknowns (blank/reuse are the
+ * high-confidence classes and are covered explicitly; the fabricated-sequence class
+ * only blocks on POSITIVE evidence from staging, never on absence of it).
+ */
+
+// A staging color_name that is just a placeholder ("Color 1", "Color 12", "colour 3")
+// = DW never captured a real color for this SKU. Per the title rule (color from a real
+// scrape only, never AI) this is itself a provenance failure, and it is the signature
+// of the fabricated <base>+<seq> family (each fabricated member got a "Color N" stub).
+const PLACEHOLDER_COLOR_RE = /^colou?r\s*\d+$/i;
+
+// By-COLOUR private lines that legitimately have NO vendor mfr# (Steve, 2026-08-12) —
+// they should stay LIVE, not be blocked. Kept in sync with the canary's
+// NOMFR_EXEMPT_VENDORS. Extend for future by-colour lines.
+const NOMFR_EXEMPT_VENDORS = ['novasuede'];
+
+function isExemptVendor(vendor) {
+  const v = String(vendor || '').toLowerCase();
+  return NOMFR_EXEMPT_VENDORS.some((e) => v.includes(e));
+}
+
+/*
+ * mfrGate(ctx) → { ok, reasons }
+ *
+ * ctx:
+ *   vendor            {string}  the product's vendor (for the by-colour exemption)
+ *   mfr               {string}  the RESOLVED mfr code (metafield dwc/custom/global
+ *                               .manufacturer_sku, else the mfr_sku column). '' / null
+ *                               / 'unknown' all count as blank.
+ *   reusedAcrossVendors {bool}  true iff this exact code appears under >1 vendor in
+ *                               shopify_products (the internal-counter bug class).
+ *   stagingColorName  {string|null|undefined}  the color_name of the staging row whose
+ *                               mfr_sku === this code, or null/undefined if there is no
+ *                               such row / staging was not consulted. A PLACEHOLDER value
+ *                               ("Color N") = fabricated provenance → BLOCK. A real color
+ *                               name, or no staging row at all, does NOT block here.
+ */
+function mfrGate(ctx) {
+  const ctxObj = ctx && typeof ctx === 'object' ? ctx : {};
+  const vendor = ctxObj.vendor;
+  const reasons = [];
+
+  // By-colour private lines are a legitimate exception — never a mfr FAIL.
+  if (isExemptVendor(vendor)) return { ok: true, reasons };
+
+  const mfr = String(ctxObj.mfr == null ? '' : ctxObj.mfr).trim();
+
+  // (a) blank / null / literal "unknown" (never allowed in a title either).
+  if (!mfr || /^unknown$/i.test(mfr)) {
+    reasons.push('mfr-sku-blank');
+    return { ok: false, reasons }; // nothing else to test on a blank code
+  }
+
+  // (b) reused internal counter — the same code under >1 vendor.
+  if (ctxObj.reusedAcrossVendors === true) reasons.push('mfr-sku-reused-across-vendors');
+
+  // (c) fabricated single-vendor sequence — POSITIVE evidence only: the staging row
+  // for this exact code carries a placeholder "Color N" name (the invented-counter
+  // signature). Absence of a staging row, or a real color name, does NOT block.
+  const staging = ctxObj.stagingColorName;
+  if (typeof staging === 'string' && PLACEHOLDER_COLOR_RE.test(staging.trim())) {
+    reasons.push('mfr-sku-fabricated-placeholder-color');
+  }
+
+  return { ok: reasons.length === 0, reasons };
+}
+
+module.exports = { mfrGate, isExemptVendor, NOMFR_EXEMPT_VENDORS, PLACEHOLDER_COLOR_RE };
diff --git a/mdc-activate.js b/mdc-activate.js
index 6fe7614..3a4885e 100644
--- a/mdc-activate.js
+++ b/mdc-activate.js
@@ -27,6 +27,8 @@
 const fs=require('fs'), os=require('os'), path=require('path');
 const { SettlementGate } = require('./lib/settlement-gate.js');
 const { fiveFieldExtra } = require('./lib/five-field-extra.js');
+const { mfrGate } = require('./lib/mfr-gate.js');
+const { reusedMfrSet, stagingColorFor } = require('./lib/mfr-gate-resolve.js');
 const { validateBeforeActivate } = require(path.join(os.homedir(),'Projects/Designer-Wallcoverings/shopify/scripts/lib/validate-before-activate.js'));
 const { execSync } = require('child_process');
 const TOK=fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8').split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN=')).split('=')[1];
@@ -49,13 +51,15 @@ async function loadPubs(){ const r=await gql(`{publications(first:50){edges{node
 async function setHold(id,verdict,reason){ await gql(`mutation($mfs:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mfs){userErrors{message}}}`,{mfs:[{ownerId:id,namespace:'internal',key:'settlement_hold',type:'single_line_text_field',value:`${verdict}:${reason}`.slice(0,250)}]}); }
 
 // ---- MDC set from staging (authoritative). PID -> {mfr,material,width,dw_sku,vital}
-const rows=execSync(`psql "host=/tmp dbname=dw_unified" -tAF '\t' -c "SELECT shopify_product_id,mfr_sku,coalesce(material,''),coalesce(width,''),dw_sku,(private_label_collection='Dublin' AND pattern_name ~* 'vital') FROM mdc_catalog WHERE shopify_product_id IS NOT NULL ORDER BY dw_sku;"`).toString().trim().split('\n');
-const SET=rows.map(l=>{const[pid,mfr,mat,width,dw,vital]=l.split('\t');return{pid,mfr,mat,width,dw,vital:vital==='t'};}).filter(r=>r.pid);
+const rows=execSync(`psql "host=/tmp dbname=dw_unified" -tAF '\t' -c "SELECT shopify_product_id,mfr_sku,coalesce(material,''),coalesce(width,''),dw_sku,(private_label_collection='Dublin' AND pattern_name ~* 'vital'),coalesce(color_name,'') FROM mdc_catalog WHERE shopify_product_id IS NOT NULL ORDER BY dw_sku;"`).toString().trim().split('\n');
+const SET=rows.map(l=>{const[pid,mfr,mat,width,dw,vital,color]=l.split('\t');return{pid,mfr,mat,width,dw,vital:vital==='t',color};}).filter(r=>r.pid);
+// MFR-PROVENANCE gate (TK-11063) — load the cross-vendor reused-code set once.
+const REUSED_MFRS=reusedMfrSet();
 
 (async()=>{
   const gate=new SettlementGate(); P(`settlement lock: ${JSON.stringify(gate.lockStatus())}  mode=${DRY?'DRY':'COMMIT'}  mdc_set=${SET.length}  allowVital=${ALLOW_VITAL}`);
   const pubs=DRY?[]:await loadPubs(); if(!DRY)P(`publications (ex-Google): ${pubs.map(p=>p.name).join(', ')}`);
-  let scanned=0,activated=0,publishedN=0,heldSettle=0,heldValidate=0,blocked=0,heldLeak=0,heldVital=0,cost=0;
+  let scanned=0,activated=0,publishedN=0,heldSettle=0,heldValidate=0,blocked=0,heldLeak=0,heldVital=0,heldMfr=0,cost=0;
   const todo=SET.slice(0,LIMIT===Infinity?SET.length:LIMIT);
   for(const row of todo){
     scanned++;
@@ -70,6 +74,12 @@ const SET=rows.map(l=>{const[pid,mfr,mat,width,dw,vital]=l.split('\t');return{pi
     if(HARD_LEAK.test(surface)){ heldLeak++; P(`HOLD(leak) ${row.dw} ${row.mfr}`); if(!DRY) await gql(`mutation($id:ID!,$t:[String!]!){tagsAdd(id:$id,tags:$t){userErrors{message}}}`,{id:n.id,t:['Hold-Leak-Review']}); continue; }
     // (0b) Dublin "Vital" generic-word hold — needs human OK before customer-facing
     if(row.vital && !ALLOW_VITAL){ heldVital++; P(`HOLD(vital-review) ${row.dw} ${n.title}`); if(!DRY) await gql(`mutation($id:ID!,$t:[String!]!){tagsAdd(id:$id,tags:$t){userErrors{message}}}`,{id:n.id,t:['Hold-Review']}); continue; }
+    // (0c) MFR-PROVENANCE gate (TK-11063) — BLOCK blank/reused/fabricated mfr codes before customer-facing.
+    // Resolve mfr from the live metafield first (custom.manufacturer_sku), fall back to the staging mfr_sku;
+    // staging color_name from mdc_catalog is the fabricated-placeholder signal (mdc_catalog isn't vendor_registry-mapped).
+    const mfrCode=(mfVal(mfs,'dwc','manufacturer_sku')||mfVal(mfs,'custom','manufacturer_sku')||mfVal(mfs,'global','manufacturer_sku')||row.mfr||'').trim();
+    const mg=mfrGate({vendor:'Phillipe Romano',mfr:mfrCode,reusedAcrossVendors:!!mfrCode&&REUSED_MFRS.has(mfrCode),stagingColorName:row.color||null});
+    if(!mg.ok){ heldMfr++; P(`HOLD(mfr:${mg.reasons.join(',')}) ${row.dw} ${mfrCode||'(blank)'}`); if(!DRY) await gql(`mutation($id:ID!,$t:[String!]!){tagsAdd(id:$id,tags:$t){userErrors{message}}}`,{id:n.id,t:['Needs-MfrSKU']}); continue; }
     // (1) 5-field / specs gate (real validator)
     const val=validateBeforeActivate({title:n.title,dwSku:row.dw,vendor:'Phillipe Romano',tags:n.tags||[],
       descriptionHtml:n.descriptionHtml,variants:n.variants.nodes,
@@ -97,5 +107,5 @@ const SET=rows.map(l=>{const[pid,mfr,mat,width,dw,vital]=l.split('\t');return{pi
     if(activated%25===0)P(`  ...activated ${activated}`);
     await sleep(300);
   }
-  P(`\nDONE ${DRY?'(DRY)':''}: scanned=${scanned} ${DRY?'wouldActivate':'activated'}=${activated} published=${publishedN} heldSettlement=${heldSettle} blocked=${blocked} heldValidate=${heldValidate} heldLeak=${heldLeak} heldVital=${heldVital}  settlement_cost=$${cost.toFixed(4)}`);
+  P(`\nDONE ${DRY?'(DRY)':''}: scanned=${scanned} ${DRY?'wouldActivate':'activated'}=${activated} published=${publishedN} heldSettlement=${heldSettle} blocked=${blocked} heldValidate=${heldValidate} heldLeak=${heldLeak} heldVital=${heldVital} heldMfr=${heldMfr}  settlement_cost=$${cost.toFixed(4)}`);
 })();
diff --git a/rotate-activate.js b/rotate-activate.js
index 9b41ac8..e9102af 100644
--- a/rotate-activate.js
+++ b/rotate-activate.js
@@ -44,6 +44,8 @@ const { execFileSync } = require('child_process');
 const { ROTATION_ORDER_SQL } = require('./lib/rotation-order.js');
 const { SettlementGate } = require('./lib/settlement-gate.js');
 const { fiveFieldExtra } = require('./lib/five-field-extra.js');
+const { mfrGate } = require('./lib/mfr-gate.js');
+const { reusedMfrSet, stagingColorFor } = require('./lib/mfr-gate-resolve.js');
 const { validateBeforeActivate, toImageList } =
   require(path.join(os.homedir(), 'Projects/Designer-Wallcoverings/shopify/scripts/lib/validate-before-activate.js'));
 
@@ -172,6 +174,14 @@ function materialFromNode(n) {
   const mfs = n.metafields?.nodes || [];
   return mfVal(mfs, 'global', 'material') || mfVal(mfs, 'custom', 'material') || mfVal(mfs, 'specs', 'material') || '';
 }
+// Resolve the manufacturer SKU the same way the go-live-gate canary does: prefer the
+// metafield (dwc/custom/global.manufacturer_sku), fall back to nothing (the mirror's
+// mfr_sku column isn't on the live node — the metafield IS the live truth).
+function resolvedMfrFromNode(n) {
+  const mfs = n.metafields?.nodes || [];
+  return (mfVal(mfs, 'dwc', 'manufacturer_sku') || mfVal(mfs, 'custom', 'manufacturer_sku') ||
+    mfVal(mfs, 'global', 'manufacturer_sku') || '').trim();
+}
 
 function pidToGid(shopify_id) {
   // shopify_id already looks like gid://shopify/Product/NNN in this table.
@@ -249,6 +259,14 @@ function leakGuard(title, vendor) {
 
   // SETTLEMENT GATE — construct once (runs lock.sh fail-closed). Plain textures
   // auto-pass at $0; motif/print/botanical products are Gemini-vision-gated.
+  // MFR-PROVENANCE GATE — load the cross-vendor reused-code set ONCE (the internal-
+  // counter bug class), then per product resolve the live mfr metafield + its staging
+  // color name and BLOCK any DRAFT→ACTIVE whose mfr is blank / reused / fabricated
+  // (Carnegie "Grain 6300 → 63001…" placeholder-color class). TK-11063.
+  const reusedMfrs = reusedMfrSet();
+  console.log(`mfr-gate: ${reusedMfrs.size} mfr code(s) reused across >1 vendor (internal-counter watch)`);
+  let mfrBlocked = 0;
+
   const settlement = new SettlementGate();
   const ls = settlement.lockStatus();
   console.log(`settlement gate: lock=${ls.ok ? 'PASS' : 'FAIL (' + ls.msg + ')'} — ` +
@@ -293,17 +311,32 @@ function leakGuard(title, vendor) {
       const gate = gateFromLive(n, q && q.dw_sku, q && q.vendor);
       const extra = fiveFieldExtra(n);
       const leak = leakGuard(n.title, q && q.vendor);
-      const passes = gate.ok && extra.ok && leak.ok;
+      // MFR-PROVENANCE gate (TK-11063). Resolve the live mfr metafield, its cross-vendor
+      // reuse flag, and its staging color name, then run the pure gate. Staging lookup is
+      // only worth doing when the code is otherwise present + not reused (the fabricated
+      // class is unique-and-present), so we short-circuit the DB read on blank/reused.
+      const mfrCode = resolvedMfrFromNode(n);
+      const reused = !!mfrCode && reusedMfrs.has(mfrCode);
+      const stagingColor = (mfrCode && !reused) ? stagingColorFor(q && q.vendor, mfrCode) : null;
+      const mfr = mfrGate({ vendor: q && q.vendor, mfr: mfrCode,
+        reusedAcrossVendors: reused, stagingColorName: stagingColor });
+      const passes = gate.ok && extra.ok && leak.ok && mfr.ok;
+      if (!mfr.ok) mfrBlocked++;
       const rec = { ts: new Date().toISOString(), shopify_id: n.id, vendor: q && q.vendor,
         dw_sku: q && q.dw_sku, mat_tier: q && q.mat_tier, rr: q && q.rr,
-        title: n.title, passes,
+        title: n.title, passes, mfr_code: mfrCode,
         reasons: [...(gate.ok ? [] : gate.reasons), ...(extra.ok ? [] : extra.reasons),
-                  ...(leak.ok ? [] : [leak.reason])] };
+                  ...(leak.ok ? [] : [leak.reason]), ...(mfr.ok ? [] : mfr.reasons)] };
 
       if (!passes) {
         skipped++;
         // NEVER activate a broken product — log + skip. It flows through the
-        // field-fix drain and is re-tried on the next rotation pass.
+        // field-fix drain and is re-tried on the next rotation pass. If the ONLY
+        // failure is the mfr-provenance gate, tag Needs-MfrSKU so the drain/human
+        // can restore the real code (COMMIT mode only; the tag itself is reversible).
+        if (!DRY && !mfr.ok && gate.ok && extra.ok && leak.ok) {
+          try { await gqlRetry(TAGS_ADD, { id: n.id, tags: ['Needs-MfrSKU'] }); } catch (_) {}
+        }
         fs.appendFileSync(AUDIT, JSON.stringify({ ...rec, action: 'skip-gate' }) + '\n');
         continue;
       }
@@ -363,6 +396,7 @@ function leakGuard(title, vendor) {
   const sstat = settlement.stats();
   console.log(`\n=== ROTATION ACTIVATOR ${DRY ? 'DRY-RUN' : 'RUN'} DONE ===`);
   console.log(`scanned=${scanned}  ${DRY ? 'would-activate' : 'activated'}=${activated}  published=${published}  skipped(gate-fail)=${skipped}  alreadyActive=${alreadyActive}`);
+  console.log(`mfr-gate: blocked ${mfrBlocked} product(s) for blank/reused/fabricated mfr provenance`);
   console.log(`settlement: auto-pass-textures=${sstat.autoPassCount}  vision-calls=${sstat.visionCalls}  blocked=${settlementBlocked}  held=${settlementHeld}`);
   console.log(`settlement cost: textures $0 (local) + vision ${sstat.visionCalls} calls = $${sstat.visionCostTotal.toFixed(5)} (Gemini 2.5-flash; ~$0.0006/img)`);
   if (!DRY) console.log(`daily activation ledger: ${ledgerUsed()}/${DAILY_ACTIVATION_CAP}`);
diff --git a/test/mfr-gate.test.js b/test/mfr-gate.test.js
new file mode 100644
index 0000000..badff02
--- /dev/null
+++ b/test/mfr-gate.test.js
@@ -0,0 +1,69 @@
+'use strict';
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const { mfrGate } = require('../lib/mfr-gate.js');
+
+// KNOWN-GOOD: a real Carnegie code with a real color name in staging → PASS.
+test('passes a real mfr code with a real staging color name', () => {
+  assert.deepEqual(
+    mfrGate({ vendor: 'Carnegie', mfr: '24121-windows', reusedAcrossVendors: false, stagingColorName: 'Oatmeal' }),
+    { ok: true, reasons: [] });
+});
+
+// A real code with no staging row consulted still PASSes (fail-open on the fabricated dim).
+test('passes a present code when no staging row is available', () => {
+  assert.deepEqual(
+    mfrGate({ vendor: 'Thibaut', mfr: 'T10958', reusedAcrossVendors: false, stagingColorName: null }),
+    { ok: true, reasons: [] });
+});
+
+// KNOWN-FABRICATED: the Carnegie "Grain 6300 → 63002" member with a placeholder "Color 2".
+test('BLOCKS the Carnegie fabricated <base>+<seq> code (placeholder color)', () => {
+  const r = mfrGate({ vendor: 'Carnegie', mfr: '63002', reusedAcrossVendors: false, stagingColorName: 'Color 2' });
+  assert.equal(r.ok, false);
+  assert.ok(r.reasons.includes('mfr-sku-fabricated-placeholder-color'));
+});
+
+test('placeholder color regex tolerates casing / spacing / British spelling', () => {
+  for (const c of ['Color 1', 'color 10', 'COLOR 12', 'Colour 3', 'colour10']) {
+    const r = mfrGate({ vendor: 'Carnegie', mfr: '6300' + c.replace(/\D/g, ''), reusedAcrossVendors: false, stagingColorName: c });
+    assert.equal(r.ok, false, `${c} should be treated as placeholder`);
+  }
+});
+
+// (a) blank / null / 'unknown' → BLOCK.
+test('BLOCKS a blank / null / unknown mfr code', () => {
+  for (const mfr of ['', '   ', null, undefined, 'Unknown', 'UNKNOWN']) {
+    const r = mfrGate({ vendor: 'Elitis', mfr, reusedAcrossVendors: false, stagingColorName: 'Chartreuse' });
+    assert.equal(r.ok, false, `${String(mfr)} should block`);
+    assert.ok(r.reasons.includes('mfr-sku-blank'));
+  }
+});
+
+// (b) reused across vendors → BLOCK (internal-counter bug class).
+test('BLOCKS a code reused across >1 vendor', () => {
+  const r = mfrGate({ vendor: 'IKSEL', mfr: '800010', reusedAcrossVendors: true, stagingColorName: 'Ivory' });
+  assert.equal(r.ok, false);
+  assert.ok(r.reasons.includes('mfr-sku-reused-across-vendors'));
+});
+
+// by-colour exempt line (Novasuede) is never a mfr FAIL even with a blank code.
+test('exempts by-colour lines (Novasuede) from the blank-code block', () => {
+  assert.deepEqual(
+    mfrGate({ vendor: 'Novasuede', mfr: '', reusedAcrossVendors: false, stagingColorName: null }),
+    { ok: true, reasons: [] });
+});
+
+// a real color name on a present unique code → PASS even for Carnegie.
+test('passes a Carnegie code whose staging color is a real name', () => {
+  assert.deepEqual(
+    mfrGate({ vendor: 'Carnegie', mfr: '62901-upholstery', reusedAcrossVendors: false, stagingColorName: 'Espresso' }),
+    { ok: true, reasons: [] });
+});
+
+// defensive: garbage ctx never throws, fails closed on a blank.
+test('handles a non-object ctx without throwing (fails closed)', () => {
+  const r = mfrGate(null);
+  assert.equal(r.ok, false);
+  assert.ok(r.reasons.includes('mfr-sku-blank'));
+});

← d9e84e1 rotation-activator: block $4.25 sample-leak price from passi  ·  back to Dw Daily Catchup 20260909  ·  TK-11186 Step D prep: rotation-activator skips showroom vend 37bb28a →