← 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
M carnegie-split/rollout.mjsM carnegie-split/rollout2.mjs
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 →