[object Object]

← back to Japan Enrich

Carnegie importer: gate ACTIVE-publish on non-blank body_html (TK-10820)

959c778c9db44345362c69cceb3dbde4bee6a126 · 2026-08-24 19:12:38 -0700 · Steve Abrams

Files touched

Diff

commit 959c778c9db44345362c69cceb3dbde4bee6a126
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 24 19:12:38 2026 -0700

    Carnegie importer: gate ACTIVE-publish on non-blank body_html (TK-10820)
---
 carnegie-split/rollout.mjs  | 17 ++++++++++-------
 carnegie-split/rollout2.mjs | 17 ++++++++++-------
 2 files changed, 20 insertions(+), 14 deletions(-)

diff --git a/carnegie-split/rollout.mjs b/carnegie-split/rollout.mjs
index c501054..e7efbaa 100644
--- a/carnegie-split/rollout.mjs
+++ b/carnegie-split/rollout.mjs
@@ -13,14 +13,14 @@
 import { execSync } from 'node:child_process';
 import fs from 'node:fs';
 // TK-10792: canonical mfr_sku gate — one source of truth, no inline copy
-import { mfrSkuValid, SKIP_TAG as NEEDS_MFR_TAG } from '../carnegie-reprice/carnegie-mfr-gate.mjs';
+import { mfrSkuValid, bodyHtmlValid, SKIP_TAG as NEEDS_MFR_TAG, NEEDS_DESC_TAG } from '../carnegie-reprice/carnegie-mfr-gate.mjs';
 const HOME=process.env.HOME, SHOP='designer-laboratory-sandbox.myshopify.com', API='2024-10';
 const TOK=execSync(`grep -m1 '^SHOPIFY_ADMIN_TOKEN=' ${HOME}/Projects/secrets-manager/.env|cut -d= -f2-|tr -d '"'\\'' '`).toString().trim();
 const args=process.argv.slice(2), aV=k=>{const i=args.indexOf(k);return i>=0?args[i+1]:null;};
 const ONLY_PAT=aV('--pattern'), LIMIT=Number(aV('--limit')||0), DRY=args.includes('--dry'), NO_ARCH=args.includes('--no-archive');
 const LEDGER=`${HOME}/Projects/carnegie-split/rollout-ledger.jsonl`;
 // TK-10792: mfrSkuValid + NEEDS_MFR_TAG imported from canonical gate above
-let mfrSkipCount=0;
+let mfrSkipCount=0, descSkipCount=0;
 const sleep=ms=>new Promise(r=>setTimeout(r,ms));
 const ANCHORS={White:[245,244,240],Alabaster:[237,234,224],Ivory:[240,234,214],Cream:[238,228,200],Bone:[226,220,205],Oatmeal:[214,205,185],Sand:[206,190,160],Fawn:[190,170,140],Taupe:[160,146,128],Greige:[176,168,152],Pewter:[140,138,132],Silver:[190,190,190],"Dove Gray":[168,166,162],Gray:[128,128,128],Slate:[96,102,108],Charcoal:[64,64,66],Graphite:[48,48,50],Black:[24,22,22],Camel:[176,140,98],Tan:[190,155,110],Wheat:[198,178,130],Honey:[196,150,80],Caramel:[168,118,66],Cognac:[140,84,48],Saddle:[120,78,48],Chestnut:[108,68,44],Chocolate:[78,54,40],Espresso:[56,42,34],Oxblood:[96,40,38],Brick:[150,66,50],Rust:[168,86,52],Terracotta:[186,102,72],Clay:[170,120,96],Blush:[214,176,168],Rose:[196,128,128],Wine:[96,44,56],Burgundy:[110,40,52],Navy:[40,48,84],Denim:[72,96,132],"Steel Blue":[96,120,146],Teal:[48,110,112],Sage:[140,150,120],Olive:[110,110,64],Moss:[84,96,58],Forest:[52,80,56],Mustard:[190,150,54],Gold:[196,160,80],Bronze:[140,116,70],Putty:[196,186,168],Stone:[180,172,158],Mushroom:[168,156,140],Flannel:[120,120,124]};
 const hex2rgb=h=>{h=String(h).replace('#','');if(h.length!==6)return null;const n=parseInt(h,16);return[(n>>16)&255,(n>>8)&255,n&255];};
@@ -71,14 +71,17 @@ async function makeRedirect(from,to){ // needs write_content; returns true/false
     console.log(`[${pi}/${patterns.length}] ${pat}: ${pr.length} colorways, ${todo.length} to create`);
     for(const r of todo){ if(LIMIT&&created>=LIMIT)break;
       const title=`Carnegie ${pat} - Color ${r.color_number}`, mfr=cleanMfr(r.mfr_sku);
+      const body=r.body_html||(r.description_text?`<p>${r.description_text}</p>`:'');
       // TK-10792: gate — DWAG-* or missing mfr_sku → DRAFT+Needs-Mfr-SKU
       const hasMfr=mfrSkuValid(r.mfr_sku);
-      const productStatus=hasMfr?'active':'draft';
+      // TK-10820: gate — blank body_html/description must never go ACTIVE (TK-10686 shipped 57 blank-desc live)
+      const bodyValid=bodyHtmlValid(body);
+      const productStatus=(hasMfr&&bodyValid)?'active':'draft';
       if(!hasMfr){mfrSkipCount++;console.warn(`  [MFR-GATE] SKIP-ACTIVE ${r.dw_sku} mfr=${JSON.stringify(r.mfr_sku)} → DRAFT+${NEEDS_MFR_TAG}`);}
-      const tagArr=[...new Set(['Carnegie',pat,'split-batch:carnegie-v2',r._d,r.primary_hex,...(!hasMfr?[NEEDS_MFR_TAG]:[])].filter(Boolean))];
+      if(!bodyValid){descSkipCount++;console.warn(`  [DESC-GATE] SKIP-ACTIVE ${r.dw_sku} blank body_html → DRAFT+${NEEDS_DESC_TAG}`);}
+      const tagArr=[...new Set(['Carnegie',pat,'split-batch:carnegie-v2',r._d,r.primary_hex,...(!hasMfr?[NEEDS_MFR_TAG]:[]),...(!bodyValid?[NEEDS_DESC_TAG]:[])].filter(Boolean))];
       const tags=tagArr.join(', ');
-      const body=r.body_html||(r.description_text?`<p>${r.description_text}</p>`:'');
-      if(DRY){console.log(`  WOULD ${productStatus.toUpperCase()} ${r.dw_sku} ${title} | mfr=${r.mfr_sku||'null'} | +tags ${r._d} ${r.primary_hex}${!hasMfr?' [MFR-GATE: DRAFT]':''}`);created++;continue;}
+      if(DRY){console.log(`  WOULD ${productStatus.toUpperCase()} ${r.dw_sku} ${title} | mfr=${r.mfr_sku||'null'} | +tags ${r._d} ${r.primary_hex}${!hasMfr?' [MFR-GATE: DRAFT]':''}${!bodyValid?' [DESC-GATE: DRAFT]':''}`);created++;continue;}
       const cr=await rest('products.json',{method:'POST',body:JSON.stringify({product:{title,handle:slug(title),vendor:'Carnegie',product_type:r.product_type||'Upholstery',status:productStatus,body_html:body,tags,options:[{name:'Title'}],variants:[{option1:'Fabric',sku:r.dw_sku,price:r.price||'0.00'},{option1:'Memo Sample',sku:`${r.dw_sku}-sample`,price:'4.25'}],images:[{src:r.image_url}]}})});
       if(!cr.ok){const t=await cr.text();console.log('  CREATE FAIL',r.dw_sku,cr.status,t.slice(0,80));ledger({t:Date.now(),dw:r.dw_sku,pat,ok:false,err:t.slice(0,120)});fail++;await sleep(700);continue;}
       const np=(await cr.json()).product,oid=`gid://shopify/Product/${np.id}`; await sleep(450);
@@ -100,5 +103,5 @@ async function makeRedirect(from,to){ // needs write_content; returns true/false
       } else console.log(`  (no old Color-variant product found for ${pat})`);
     }
   }
-  console.log(JSON.stringify({created,archived,fail,mfrDraftSkipped:mfrSkipCount,ledger:LEDGER}));
+  console.log(JSON.stringify({created,archived,fail,mfrDraftSkipped:mfrSkipCount,descDraftSkipped:descSkipCount,ledger:LEDGER}));
 })();
diff --git a/carnegie-split/rollout2.mjs b/carnegie-split/rollout2.mjs
index f9d328a..555fc16 100644
--- a/carnegie-split/rollout2.mjs
+++ b/carnegie-split/rollout2.mjs
@@ -11,7 +11,7 @@
 import { execSync } from 'node:child_process';
 import fs from 'node:fs';
 // TK-10792: canonical mfr_sku gate — one source of truth, no inline copy
-import { mfrSkuValid, SKIP_TAG as NEEDS_MFR_TAG } from '../carnegie-reprice/carnegie-mfr-gate.mjs';
+import { mfrSkuValid, bodyHtmlValid, SKIP_TAG as NEEDS_MFR_TAG, NEEDS_DESC_TAG } from '../carnegie-reprice/carnegie-mfr-gate.mjs';
 const HOME=process.env.HOME, SHOP='designer-laboratory-sandbox.myshopify.com', API='2024-10';
 const TOK=execSync(`grep -m1 '^SHOPIFY_ADMIN_TOKEN=' ${HOME}/Projects/secrets-manager/.env|cut -d= -f2-|tr -d '"'\\'' '`).toString().trim();
 const args=process.argv.slice(2), aV=k=>{const i=args.indexOf(k);return i>=0?args[i+1]:null;};
@@ -19,7 +19,7 @@ const ONLY_PAT=aV('--pattern'), LG=Number(aV('--limit-groups')||0), DRY=args.inc
 const LEDGER=`${HOME}/Projects/carnegie-split/rollout-ledger.jsonl`;
 const sleep=ms=>new Promise(r=>setTimeout(r,ms));
 // TK-10792: mfrSkuValid + NEEDS_MFR_TAG imported from canonical gate above
-let mfrSkipCount=0;
+let mfrSkipCount=0, descSkipCount=0;
 const ANCHORS={White:[245,244,240],Alabaster:[237,234,224],Ivory:[240,234,214],Cream:[238,228,200],Bone:[226,220,205],Oatmeal:[214,205,185],Sand:[206,190,160],Fawn:[190,170,140],Taupe:[160,146,128],Greige:[176,168,152],Pewter:[140,138,132],Silver:[190,190,190],"Dove Gray":[168,166,162],Gray:[128,128,128],Slate:[96,102,108],Charcoal:[64,64,66],Graphite:[48,48,50],Black:[24,22,22],Camel:[176,140,98],Tan:[190,155,110],Wheat:[198,178,130],Honey:[196,150,80],Caramel:[168,118,66],Cognac:[140,84,48],Saddle:[120,78,48],Chestnut:[108,68,44],Chocolate:[78,54,40],Espresso:[56,42,34],Oxblood:[96,40,38],Brick:[150,66,50],Rust:[168,86,52],Terracotta:[186,102,72],Clay:[170,120,96],Blush:[214,176,168],Rose:[196,128,128],Wine:[96,44,56],Burgundy:[110,40,52],Navy:[40,48,84],Denim:[72,96,132],"Steel Blue":[96,120,146],Teal:[48,110,112],Sage:[140,150,120],Olive:[110,110,64],Moss:[84,96,58],Forest:[52,80,56],Mustard:[190,150,54],Gold:[196,160,80],Bronze:[140,116,70],Putty:[196,186,168],Stone:[180,172,158],Mushroom:[168,156,140],Flannel:[120,120,124]};
 const hex2rgb=h=>{h=String(h).replace('#','');if(h.length!==6)return null;const n=parseInt(h,16);return[(n>>16)&255,(n>>8)&255,n&255];};
 const nearest=rgb=>{let b=null,bd=1e18;for(const[n,[ar,ag,ab]]of Object.entries(ANCHORS)){const d=(rgb[0]-ar)**2*.3+(rgb[1]-ag)**2*.59+(rgb[2]-ab)**2*.11;if(d<bd){bd=d;b=n;}}return b;};
@@ -54,18 +54,21 @@ async function makeRedirect(from,to){const j=await gql(`mutation($i:UrlRedirectI
     const [pat,cnum,cls]=k.split('§');
     if(g.some(r=>built.has(r.dw_sku))) continue; // resume: this color/class already built
     const title=`${pat} ${cls} - Color ${cnum} | Carnegie`;
+    const body=rep.body_html||(rep.description_text?`<p>${rep.description_text}</p>`:'');
     // TK-10792: gate — any row in the group has DWAG-* or missing mfr_sku → DRAFT
     const allMfrsValid=g.every(r=>mfrSkuValid(r.mfr_sku));
-    const productStatus=allMfrsValid?'active':'draft';
+    // TK-10820: gate — blank body_html/description must never go ACTIVE (TK-10686 shipped 57 blank-desc live)
+    const bodyValid=bodyHtmlValid(body);
+    const productStatus=(allMfrsValid&&bodyValid)?'active':'draft';
     if(!allMfrsValid){mfrSkipCount++;console.warn(`  [MFR-GATE] SKIP-ACTIVE ${k} mfrs=${g.map(r=>r.mfr_sku||'null').join(',')} → DRAFT+${NEEDS_MFR_TAG}`);}
-    const tagSet=[...new Set(['Carnegie',pat,cls,'split-batch:carnegie-v2',rep._d,rep.primary_hex,...(!allMfrsValid?[NEEDS_MFR_TAG]:[])].filter(Boolean))];
+    if(!bodyValid){descSkipCount++;console.warn(`  [DESC-GATE] SKIP-ACTIVE ${k} blank body_html → DRAFT+${NEEDS_DESC_TAG}`);}
+    const tagSet=[...new Set(['Carnegie',pat,cls,'split-batch:carnegie-v2',rep._d,rep.primary_hex,...(!allMfrsValid?[NEEDS_MFR_TAG]:[]),...(!bodyValid?[NEEDS_DESC_TAG]:[])].filter(Boolean))];
     const tags=tagSet.join(', ');
-    const body=rep.body_html||(rep.description_text?`<p>${rep.description_text}</p>`:'');
     // variants: one per construction (unique label) + Memo Sample
     const seen=new Set(); const vars=[];
     for(const r of g){ let lab=r._lab; while(seen.has(lab)) lab+=' '; seen.add(lab); vars.push({option1:lab,sku:r.dw_sku,price:r.price||'0.00'}); }
     vars.push({option1:'Memo Sample',sku:`${rep.dw_sku}-sample`,price:'4.25'});
-    if(DRY){ console.log(`  WOULD ${productStatus.toUpperCase()} ${title} | variants: ${vars.map(v=>v.option1+'@'+v.price).join(', ')} | +tags ${rep._d} ${rep.primary_hex}${!allMfrsValid?' [MFR-GATE: DRAFT]':''}`); created++; continue; }
+    if(DRY){ console.log(`  WOULD ${productStatus.toUpperCase()} ${title} | variants: ${vars.map(v=>v.option1+'@'+v.price).join(', ')} | +tags ${rep._d} ${rep.primary_hex}${!allMfrsValid?' [MFR-GATE: DRAFT]':''}${!bodyValid?' [DESC-GATE: DRAFT]':''}`); created++; continue; }
     const payload={product:{title,handle:slug(`${pat} ${cls} color ${cnum}`),vendor:'Carnegie',product_type:cls,status:productStatus,body_html:body,tags,options:[{name:'Type'}],variants:vars,images:[{src:rep.image_url}]}};
     const cr=await rest('products.json',{method:'POST',body:JSON.stringify(payload)});
     if(!cr.ok){const t=await cr.text();console.log('  CREATE FAIL',k,cr.status,t.slice(0,90));ledger({t:Date.now(),k,ok:false,err:t.slice(0,120)});fail++;await sleep(700);continue;}
@@ -87,5 +90,5 @@ async function makeRedirect(from,to){const j=await gql(`mutation($i:UrlRedirectI
       ledger({t:Date.now(),pat,archived_old:oldId,handle:old.handle,redirect:rok}); if(a.ok){archived++;console.log(`  archived old "${old.title}" redirect:${rok?'ok':'no-write_content'}`);} await sleep(500);
     }
   }}
-  console.log(JSON.stringify({created,archived,fail,mfrDraftSkipped:mfrSkipCount}));
+  console.log(JSON.stringify({created,archived,fail,mfrDraftSkipped:mfrSkipCount,descDraftSkipped:descSkipCount}));
 })();

← ff6075c fix(carnegie): harden terrain-arctic onboarder — stamp mfr+d  ·  back to Japan Enrich  ·  TK-10820: gate Carnegie importers on non-blank body_html bef 169db8c →