[object Object]

← back to Shopify Sample Shipping

TK-11333: fix theme engine per Cody red-team — honest price-over-band messaging, code fires only when it helps, soft-cap hides wallets; add band-truth verification + engine-logic test (9/9)

3963701d224e2e68b177883721aeee1a6b41c978 · 2026-09-09 22:53:14 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019GGVzyUM2B8rVz9TxouGgY

Files touched

Diff

commit 3963701d224e2e68b177883721aeee1a6b41c978
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 9 22:53:14 2026 -0700

    TK-11333: fix theme engine per Cody red-team — honest price-over-band messaging, code fires only when it helps, soft-cap hides wallets; add band-truth verification + engine-logic test (9/9)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_019GGVzyUM2B8rVz9TxouGgY
---
 all-profiles.mjs                         |  19 ++++
 band-source.mjs                          |  15 +++
 create-freeship-codes.mjs                |  25 ++--
 create-trade-segment-undo.mjs            |   9 +-
 create-trade-segment.mjs                 |  75 +++++++-----
 engine-logic-test.mjs                    |  52 +++++++++
 find-band.mjs                            |  24 ++++
 list-profiles.mjs                        |   4 +
 ptype-distinct.mjs                       |  13 +++
 samples-profile.mjs                      |  21 ++++
 theme/sample-shipping-cart-engine.liquid | 189 ++++++++++++++++++-------------
 11 files changed, 322 insertions(+), 124 deletions(-)

diff --git a/all-profiles.mjs b/all-profiles.mjs
new file mode 100644
index 0000000..8b822d5
--- /dev/null
+++ b/all-profiles.mjs
@@ -0,0 +1,19 @@
+import {query} from './query.mjs';
+const q=`{deliveryProfiles(first:20){nodes{id name default productVariantsCount{count}
+  profileLocationGroups{locationGroupZones(first:30){nodes{zone{id name}
+    methodDefinitions(first:40){nodes{id name active rateProvider{__typename
+      ... on DeliveryRateDefinition{price{amount currencyCode}}
+      ... on DeliveryParticipant{carrierService{name}}}
+      methodConditions{operator field conditionCriteria{__typename ... on MoneyV2{amount currencyCode} ... on Weight{value unit}}}}}}}}}}}`;
+const d=await query(q);
+for(const p of d.deliveryProfiles.nodes){
+  console.log(`\n#### PROFILE: ${p.name} ${p.default?'(DEFAULT)':''} — variants:${p.productVariantsCount.count} — ${p.id}`);
+  for(const g of p.profileLocationGroups)for(const z of g.locationGroupZones.nodes){
+    for(const md of z.methodDefinitions.nodes){
+      const rp=md.rateProvider;
+      const price=rp?.price?`$${rp.price.amount}`:(rp?.carrierService?`carrier:${rp.carrierService.name}`:rp?.__typename);
+      const conds=md.methodConditions.map(c=>{const cr=c.conditionCriteria;const v=cr?.amount!==undefined?`$${cr.amount}`:(cr?.value!==undefined?`${cr.value}${cr.unit}`:'?');return `${c.field} ${c.operator} ${v}`;}).join(' AND ')||'(none)';
+      console.log(`  [${z.zone.name}] ${md.name} [${md.active?'on':'OFF'}] ${price} {${conds}}`);
+    }
+  }
+}
diff --git a/band-source.mjs b/band-source.mjs
new file mode 100644
index 0000000..818d526
--- /dev/null
+++ b/band-source.mjs
@@ -0,0 +1,15 @@
+import {query} from './query.mjs';
+const S='gid://shopify/ProductVariant/44090076954675';
+const ROLL='gid://shopify/ProductVariant/40348093055027';
+// 1) what rates + titles does a 6-sample cart see?
+const qq=`query($items:[DraftOrderLineItemInput!]!){draftOrderAvailableDeliveryOptions(input:{lineItems:$items,shippingAddress:{address1:"15442 Ventura Blvd",city:"Sherman Oaks",provinceCode:"CA",countryCode:US,zip:"91403"}}){availableShippingRates{title price{amount}}}}`;
+const r6=(await query(qq,{items:[{variantId:S,quantity:6}]})).draftOrderAvailableDeliveryOptions.availableShippingRates;
+console.log('6-sample cart ($25.50) rates:'); r6.forEach(x=>console.log(`   "${x.title}" = $${x.price.amount}`));
+const r11=(await query(qq,{items:[{variantId:S,quantity:11}]})).draftOrderAvailableDeliveryOptions.availableShippingRates;
+console.log('11-sample cart ($46.75) rates:'); r11.forEach(x=>console.log(`   "${x.title}" = $${x.price.amount}`));
+// 2) which delivery profile does the sample variant belong to?
+const pq=`{productVariant(id:"${S}"){id sku title product{title}
+  deliveryProfile{id name}}}`;
+try{const pv=await query(pq);console.log('\nSAMPLE variant profile:',JSON.stringify(pv.productVariant,null,0));}catch(e){console.log('variant.deliveryProfile query err:',e.message);}
+const pq2=`{productVariant(id:"${ROLL}"){id sku title deliveryProfile{id name}}}`;
+try{const pv2=await query(pq2);console.log('ROLL variant profile:',JSON.stringify(pv2.productVariant,null,0));}catch(e){console.log('roll err:',e.message);}
diff --git a/create-freeship-codes.mjs b/create-freeship-codes.mjs
index cee7a88..3d217b3 100644
--- a/create-freeship-codes.mjs
+++ b/create-freeship-codes.mjs
@@ -22,24 +22,29 @@ const SEG_REC = new URL('./verification/trade-segment-created.json', import.meta
 const OUT = new URL('./verification/freeship-codes-created.json', import.meta.url);
 const LOGX = process.env.HOME + '/.claude/yolo-queue/executed-reversible/log-exec.mjs';
 
-let segId;
-if (fs.existsSync(SEG_REC)) segId = JSON.parse(fs.readFileSync(SEG_REC, 'utf8')).segmentId;
-if (!segId) {
-  if (APPLY) { console.error('MISSING segmentId — run create-trade-segment.mjs --apply FIRST'); process.exit(1); }
-  segId = 'gid://shopify/Segment/PENDING';  // dry-run placeholder so the mutation shape prints
-  console.log('(dry-run: no segment created yet — using placeholder segId; create the segment before --apply)');
+let segIds = [];
+if (fs.existsSync(SEG_REC)) {
+  const rec = JSON.parse(fs.readFileSync(SEG_REC, 'utf8'));
+  segIds = rec.segmentIds || (rec.segmentId ? [rec.segmentId] : []);
+}
+if (!segIds.length) {
+  if (APPLY) { console.error('MISSING segmentIds — run create-trade-segment.mjs --apply FIRST'); process.exit(1); }
+  segIds = ['gid://shopify/Segment/PENDING'];  // dry-run placeholder so the mutation shape prints
+  console.log('(dry-run: no segment created yet — using placeholder; create the segment before --apply)');
 }
 
 const now = new Date().toISOString();
 const combines = { orderDiscounts: true, productDiscounts: true, shippingDiscounts: false };
 const CODES = [
-  { code: 'TRADESHIP', title: 'DW Trade Sample Free Shipping', context: { customerSegments: { add: [segId] } } },
+  { code: 'TRADESHIP', title: 'DW Trade Sample Free Shipping', context: { customerSegments: { add: segIds } } },
   { code: 'SAMPLESHIP', title: 'DW Retail Sample Free Shipping', context: { all: 'ALL' } },
 ];
 const MUT = `mutation($fs:DiscountCodeFreeShippingInput!){discountCodeFreeShippingCreate(freeShippingCodeDiscount:$fs){codeDiscountNode{id codeDiscount{__typename ... on DiscountCodeFreeShipping{title status}}} userErrors{field message}}}`;
 
 function buildInput(c) {
-  return { title: c.title, code: c.code, startsAt: now, appliesOnOneTimePurchase: true, appliesOnSubscription: false,
+  // NOTE: appliesOnOneTimePurchase/appliesOnSubscription omitted — this shop has no subscriptions,
+  // and Shopify rejects those fields unless subscriptions are enabled.
+  return { title: c.title, code: c.code, startsAt: now,
     destination: { all: true }, maximumShippingPrice: MAX_SHIP, combinesWith: combines, context: c.context };
 }
 
@@ -50,10 +55,10 @@ async function codeExists(code) {
 }
 
 console.log('=== create-freeship-codes (' + (APPLY ? 'APPLY' : 'DRY-RUN') + ') ===');
-console.log('segment (designer code) :', segId);
+console.log('segments (designer code):', segIds.join(', '));
 console.log('maximumShippingPrice    : $' + MAX_SHIP);
 
-const result = { at: now, segId, maxShip: MAX_SHIP, created: [] };
+const result = { at: now, segIds, maxShip: MAX_SHIP, created: [] };
 for (const c of CODES) {
   const input = buildInput(c);
   console.log(`\n• ${c.code} — ${c.title}`);
diff --git a/create-trade-segment-undo.mjs b/create-trade-segment-undo.mjs
index 28c5795..ca31c10 100644
--- a/create-trade-segment-undo.mjs
+++ b/create-trade-segment-undo.mjs
@@ -14,21 +14,22 @@ const APPLY = process.argv.includes('--apply');
 const REC = new URL('./verification/trade-segment-created.json', import.meta.url);
 if (!fs.existsSync(REC)) { console.error('no verification/trade-segment-created.json — nothing to undo'); process.exit(1); }
 const rec = JSON.parse(fs.readFileSync(REC, 'utf8'));
+const segIds = rec.segmentIds || (rec.segmentId ? [rec.segmentId] : []);
 console.log('=== create-trade-segment UNDO (' + (APPLY ? 'APPLY' : 'DRY-RUN') + ') ===');
-console.log('would delete segment:', rec.segmentId);
+console.log('would delete segment(s):', segIds.join(', '));
 if (rec.brokenSnapshot) console.log('would recreate broken segment:', rec.brokenSnapshot.name);
 
 // safety: is the segment still referenced by a free-ship code?
 try {
   const nodes = (await query(`{codeDiscountNodes(first:100){nodes{codeDiscount{__typename ... on DiscountCodeFreeShipping{title customerSelection{__typename ... on DiscountCustomerSegments{segments{id}}}}}}}}`)).codeDiscountNodes.nodes;
-  const inUse = nodes.some(n => n.codeDiscount?.customerSelection?.segments?.some(s => s.id === rec.segmentId));
+  const inUse = nodes.some(n => n.codeDiscount?.customerSelection?.segments?.some(s => segIds.includes(s.id)));
   if (inUse) { console.error('\nREFUSING: segment still referenced by a free-ship code. Run create-freeship-codes-undo.mjs --apply first.'); process.exit(2); }
 } catch (e) { console.log('(in-use check skipped:', e.message, ')'); }
 
 if (!APPLY) { console.log('\nDry-run only. Re-run with --apply.'); process.exit(0); }
 
-if (rec.segmentId) {
-  const del = (await query(`mutation($id:ID!){segmentDelete(id:$id){deletedSegmentId userErrors{field message}}}`, { id: rec.segmentId })).segmentDelete;
+for (const id of segIds) {
+  const del = (await query(`mutation($id:ID!){segmentDelete(id:$id){deletedSegmentId userErrors{field message}}}`, { id })).segmentDelete;
   console.log('deleted segment:', del.deletedSegmentId || JSON.stringify(del.userErrors));
 }
 if (rec.brokenSnapshot) {
diff --git a/create-trade-segment.mjs b/create-trade-segment.mjs
index c32c5c4..1e20957 100644
--- a/create-trade-segment.mjs
+++ b/create-trade-segment.mjs
@@ -1,17 +1,15 @@
 #!/usr/bin/env node
-// TK-11333 — create the `DW Trade / Designers` customer segment, and delete the broken
+// TK-11333 — create the `DW Trade / Designers` customer segment(s), and delete the broken
 // `interior-designer-res` segment (which queries a literal that matches 0 customers).
 //
-// GATED (customer-facing config write). DRY-RUN BY DEFAULT — prints the exact segmentCreate
-// query it WOULD run. Pass --apply to actually write. Idempotent: if a segment with the same
-// name already exists it re-uses it and does NOT create a duplicate. Records created ids to
-// verification/trade-segment-created.json for the paired undo (create-trade-segment-undo.mjs).
+// Shopify caps a customer-segment query at 10 filters. Our tag set is 12 (19 with --with-confirm),
+// so we split into ≤10-filter segments ("DW Trade / Designers", "DW Trade / Designers (2)", …).
+// The free-ship discount code scopes to ALL of them (multi-segment) — see create-freeship-codes.mjs.
+// `sample-freeship` (the grandfather vehicle for the 2,663) is ALWAYS included.
 //
-// VEHICLE DECISION (trade-grant-check.mjs verdict, 2026-09-09): the segment ORs the real trade
-// tags PLUS the dedicated `sample-freeship` grandfather tag — NOT mass-`trade` — because `trade`
-// on this store also gates PRICING (TRADECODE/TRADE15 15%-off codes + trade-only-benefits page +
-// trade_approved theme entitlement). Tagging 2,663 grandfathered customers `trade` would leak
-// those perks; `sample-freeship` is single-purpose.
+// GATED (customer-facing config write). DRY-RUN BY DEFAULT — prints the exact segmentCreate
+// queries it WOULD run. Pass --apply to write. Idempotent: reuses same-named segments, no dupes.
+// Records created ids to verification/trade-segment-created.json for the paired undo.
 //
 // Usage:
 //   node create-trade-segment.mjs                 # dry-run
@@ -34,44 +32,58 @@ const CORE = ['trade', 'trade_approved', 'Interior Designer - Residential', 'Int
 const CONFIRM = ['Photography Studio', 'Graphic Designer', 'Illustrator', 'Visual Merchandiser',
   'Production Company', 'Manufacturer', 'Developer'];
 const tags = WITH_CONFIRM ? [...CORE, ...CONFIRM] : CORE;
-const SEG_QUERY = tags.map(t => `customer_tags CONTAINS '${t.replace(/'/g, "\\'")}'`).join(' OR ');
+
+// --- chunk into ≤10-filter segments; guarantee sample-freeship lands in a chunk (it's in CORE) ---
+const CHUNK = 10;
+const chunks = [];
+for (let i = 0; i < tags.length; i += CHUNK) chunks.push(tags.slice(i, i + CHUNK));
+const chunkQuery = c => c.map(t => `customer_tags CONTAINS '${t.replace(/'/g, "\\'")}'`).join(' OR ');
+const segNameFor = i => i === 0 ? SEG_NAME : `${SEG_NAME} (${i + 1})`;
 
 async function existingSegments() {
   return (await query(`{segments(first:200){nodes{id name query}}}`)).segments.nodes;
 }
 
 console.log('=== create-trade-segment  (' + (APPLY ? 'APPLY' : 'DRY-RUN') + ') ===');
-console.log('segment name :', SEG_NAME);
-console.log('tag set      :', WITH_CONFIRM ? 'CORE + confirm-these' : 'CORE only', `(${tags.length} tags)`);
-console.log('segment query:\n  ' + SEG_QUERY);
+console.log('base name    :', SEG_NAME);
+console.log('tag set      :', WITH_CONFIRM ? 'CORE + confirm-these' : 'CORE only', `(${tags.length} tags → ${chunks.length} segment(s), ≤10 filters each)`);
+chunks.forEach((c, i) => console.log(`  [${segNameFor(i)}]  (${c.length}) ${chunkQuery(c)}`));
 
 const segs = await existingSegments();
-const dupe = segs.find(s => s.name === SEG_NAME);
 const broken = segs.find(s => s.name === BROKEN_NAME);
-console.log('\nexisting DW Trade / Designers?', dupe ? `YES ${dupe.id}` : 'no');
+chunks.forEach((c, i) => { const ex = segs.find(s => s.name === segNameFor(i)); console.log(`existing "${segNameFor(i)}"?`, ex ? `YES ${ex.id}` : 'no'); });
 console.log('broken interior-designer-res?', broken ? `YES ${broken.id} (query: ${broken.query})` : 'no');
 
 if (!APPLY) {
-  console.log('\n-- WOULD segmentCreate(name, query) above');
+  chunks.forEach((c, i) => console.log(`-- WOULD segmentCreate("${segNameFor(i)}", <${c.length} filters>)`));
   if (broken) console.log('-- WOULD segmentDelete(' + broken.id + ')  [snapshot recorded first]');
   console.log('\nDry-run only. Re-run with --apply to write.');
   process.exit(0);
 }
 
-const result = { at: new Date().toISOString(), segName: SEG_NAME, segQuery: SEG_QUERY, tags };
+const result = { at: new Date().toISOString(), segName: SEG_NAME, tags, segments: [] };
 
-// 1) create (or reuse) the segment
-let segId = dupe?.id;
-if (!segId) {
-  const r = (await query(`mutation($name:String!,$q:String!){segmentCreate(name:$name,query:$q){segment{id name query} userErrors{field message}}}`,
-    { name: SEG_NAME, q: SEG_QUERY })).segmentCreate;
-  if (r.userErrors?.length) { console.error('segmentCreate ERR', JSON.stringify(r.userErrors)); process.exit(1); }
-  segId = r.segment.id;
-  console.log('created segment', segId);
-} else {
-  console.log('re-using existing segment', segId, '(no duplicate created)');
+// 1) create (or reuse) each chunk segment
+const segmentIds = [];
+for (let i = 0; i < chunks.length; i++) {
+  const nm = segNameFor(i);
+  const q = chunkQuery(chunks[i]);
+  const ex = segs.find(s => s.name === nm);
+  let segId = ex?.id;
+  if (!segId) {
+    const r = (await query(`mutation($name:String!,$q:String!){segmentCreate(name:$name,query:$q){segment{id name query} userErrors{field message}}}`,
+      { name: nm, q })).segmentCreate;
+    if (r.userErrors?.length) { console.error('segmentCreate ERR', nm, JSON.stringify(r.userErrors)); process.exit(1); }
+    segId = r.segment.id;
+    console.log('created segment', nm, segId);
+  } else {
+    console.log('re-using existing segment', nm, segId, '(no duplicate created)');
+  }
+  segmentIds.push(segId);
+  result.segments.push({ name: nm, id: segId, query: q });
 }
-result.segmentId = segId;
+result.segmentIds = segmentIds;
+result.segmentId = segmentIds[0]; // back-compat
 
 // 2) snapshot + delete the broken segment
 if (broken) {
@@ -82,13 +94,14 @@ if (broken) {
 }
 
 fs.writeFileSync(OUT, JSON.stringify(result, null, 2) + '\n');
-console.log('\nrecorded -> verification/trade-segment-created.json');
+console.log('\nsegmentIds:', segmentIds.join(', '));
+console.log('recorded -> verification/trade-segment-created.json');
 
 // 3) ledger (reversible)
 try {
   const { execSync } = await import('node:child_process');
   execSync(`node ${LOGX} --agent vp-dw-commerce --ticket TK-11333 ` +
-    `--action ${JSON.stringify('created segment "' + SEG_NAME + '" + deleted broken interior-designer-res')} --blast 2 ` +
+    `--action ${JSON.stringify('created ' + segmentIds.length + ' segment(s) "' + SEG_NAME + '" + deleted broken interior-designer-res')} --blast ${segmentIds.length + (broken ? 1 : 0)} ` +
     `--undo ${JSON.stringify('cd ~/Projects/shopify-sample-shipping && node create-trade-segment-undo.mjs --apply')} ` +
     `--verify ${JSON.stringify('node list-designer-segments.mjs')}`, { stdio: 'inherit' });
 } catch (e) { console.log('(ledger note skipped:', e.message, ')'); }
diff --git a/engine-logic-test.mjs b/engine-logic-test.mjs
new file mode 100644
index 0000000..09e442c
--- /dev/null
+++ b/engine-logic-test.mjs
@@ -0,0 +1,52 @@
+// Mirrors the theme engine evaluate() exactly, then asserts the TK-11333 policy scenarios.
+const CONFIG={designerCap:12,bandCapCents:4500,retailWC:5,retailFabric:5,softCap:20,
+  designerTags:['trade','sample-freeship','interior designer'],fabricTypes:['fabric','fabrics','textile','textiles']};
+const isSample=l=>(l.variant_title||'').toLowerCase()==='sample'||/-sample$/i.test(l.sku||'');
+const isFabric=l=>CONFIG.fabricTypes.indexOf((l.product_type||'').toLowerCase())!==-1;
+const isDesigner=t=>{const s=String(t||'').toLowerCase().split(',').map(x=>x.trim());return CONFIG.designerTags.some(d=>s.indexOf(d)!==-1);};
+function evaluate(d){
+  const samples=d.lines.filter(isSample);
+  const total=samples.reduce((n,l)=>n+l.quantity,0);
+  const wc=samples.filter(l=>!isFabric(l)).reduce((n,l)=>n+l.quantity,0);
+  const fab=samples.filter(isFabric).reduce((n,l)=>n+l.quantity,0);
+  const hasNonSample=d.lines.some(l=>!isSample(l));
+  const designer=!!d.isLoggedIn&&isDesigner(d.tags);
+  const overSoftCap=total>CONFIG.softCap;
+  const bandCovers=d.cartSubtotalCents<=CONFIG.bandCapCents;
+  const codeCovers=designer&&total<=CONFIG.designerCap&&total>0;
+  const willBeFree=bandCovers||codeCovers;
+  const willBeCharged=total>0&&!willBeFree;
+  const shouldApplyCode=designer&&!bandCovers&&total<=CONFIG.designerCap&&!overSoftCap;
+  return {total,wc,fab,designer,bandCovers,willBeFree,willBeCharged,shouldApplyCode,overSoftCap};
+}
+const S=(n,type='Wallcovering')=>({product_type:type,variant_title:'Sample',sku:'X-'+n+'-Sample',quantity:n});
+const ROLL={product_type:'Wallcovering',variant_title:'Sold Per Roll',sku:'X-123',quantity:1};
+const P=n=>Math.round(n*425); // n samples subtotal in cents
+let pass=0,fail=0;
+function t(name,d,exp){const g=evaluate(d);const ok=Object.keys(exp).every(k=>g[k]===exp[k]);
+  console.log((ok?'PASS':'FAIL')+'  '+name+'  => free:'+g.willBeFree+' charged:'+g.willBeCharged+' applyCode:'+g.shouldApplyCode+(ok?'':'  EXPECTED '+JSON.stringify(exp)));ok?pass++:fail++;}
+
+// RETAIL (not logged in / no trade tag)
+t('retail 6 WC ($25.50) — under band, must NOT claim charged (the old lie)',
+  {isLoggedIn:false,tags:'',cartSubtotalCents:P(6),lines:[S(6)]},{willBeFree:true,willBeCharged:false,shouldApplyCode:false});
+t('retail 5 WC + 5 fabric ($42.50) — free by band, no code',
+  {isLoggedIn:false,tags:'',cartSubtotalCents:P(10),lines:[S(5,'Wallcovering'),S(5,'Fabric')]},{willBeFree:true,willBeCharged:false,shouldApplyCode:false});
+t('retail 11 samples ($46.75) — over band, charged',
+  {isLoggedIn:false,tags:'',cartSubtotalCents:P(11),lines:[S(11)]},{willBeFree:false,willBeCharged:true,shouldApplyCode:false});
+// DESIGNER
+t('designer 10 samples ($42.50) — free by band, NO needless redirect',
+  {isLoggedIn:true,tags:'sample-freeship',cartSubtotalCents:P(10),lines:[S(10)]},{willBeFree:true,willBeCharged:false,shouldApplyCode:false});
+t('designer 12 samples ($51) — over band, code extends -> free + applies TRADESHIP',
+  {isLoggedIn:true,tags:'trade',cartSubtotalCents:P(12),lines:[S(12)]},{willBeFree:true,willBeCharged:false,shouldApplyCode:true});
+t('designer 13 samples ($55.25) — over designer cap, charged, no code',
+  {isLoggedIn:true,tags:'interior designer',cartSubtotalCents:P(13),lines:[S(13)]},{willBeFree:false,willBeCharged:true,shouldApplyCode:false});
+// MIXED + SOFT CAP
+t('retail 5 samples + 1 roll — over band (roll), charged',
+  {isLoggedIn:false,tags:'',cartSubtotalCents:P(5)+16840,lines:[S(5),ROLL]},{willBeFree:false,willBeCharged:true,shouldApplyCode:false});
+t('any 21 samples — over soft cap (blocks checkout)',
+  {isLoggedIn:true,tags:'trade',cartSubtotalCents:P(21),lines:[S(21)]},{overSoftCap:true,shouldApplyCode:false});
+// Type classifier + Default Title sample edge case
+t('Default-Title sample sku (-Sample) still counts as a sample',
+  {isLoggedIn:false,tags:'',cartSubtotalCents:P(6),lines:[{product_type:'Wallcovering',variant_title:'Default Title',sku:'DWFE-1-2-Sample',quantity:6}]},{willBeFree:true,total:6});
+console.log('\n'+pass+' passed, '+fail+' failed');
+process.exit(fail?1:0);
diff --git a/find-band.mjs b/find-band.mjs
new file mode 100644
index 0000000..6d93b6c
--- /dev/null
+++ b/find-band.mjs
@@ -0,0 +1,24 @@
+import {query} from './query.mjs';
+// Read the General profile with ALL location groups + their location sets
+const q=`{deliveryProfile(id:"gid://shopify/DeliveryProfile/29033627699"){name
+  profileLocationGroups{
+    locationGroup{id locations(first:5){nodes{name}}}
+    locationGroupZones(first:30){nodes{zone{id name}
+      methodDefinitions(first:40){nodes{id name active
+        rateProvider{__typename ... on DeliveryRateDefinition{price{amount}}}
+        methodConditions{operator field conditionCriteria{__typename ... on MoneyV2{amount} ... on Weight{value unit}}}}}}}}}}`;
+const d=await query(q);
+let lgN=0;
+for(const g of d.deliveryProfile.profileLocationGroups){
+  lgN++;
+  console.log(`\n== LOCATION GROUP #${lgN} ${g.locationGroup.id.split('/').pop()} locs:[${g.locationGroup.locations.nodes.map(l=>l.name).join(', ')}]`);
+  for(const z of g.locationGroupZones.nodes){
+    console.log(`  ZONE ${z.zone.id.split('/').pop()} ${z.zone.name}:`);
+    for(const md of z.methodDefinitions.nodes){
+      const rp=md.rateProvider;
+      const price=rp?.price?`$${rp.price.amount}`:rp?.__typename;
+      const conds=md.methodConditions.map(c=>{const cr=c.conditionCriteria;const v=cr?.amount!==undefined?`$${cr.amount}`:(cr?.value!==undefined?`${cr.value}${cr.unit}`:'?');return `${c.field} ${c.operator} ${v}`;}).join(' AND ')||'(none)';
+      console.log(`     ${md.id.split('/').pop()} ${md.name} [${md.active?'on':'OFF'}] ${price} {${conds}}`);
+    }
+  }
+}
diff --git a/list-profiles.mjs b/list-profiles.mjs
new file mode 100644
index 0000000..544c2e0
--- /dev/null
+++ b/list-profiles.mjs
@@ -0,0 +1,4 @@
+import {query} from './query.mjs';
+const q=`{deliveryProfiles(first:20){nodes{id name default productVariantsCount{count}}}}`;
+const d=await query(q);
+for(const p of d.deliveryProfiles.nodes) console.log(`${p.default?'*':' '} ${p.id.split('/').pop().padEnd(14)} variants:${String(p.productVariantsCount.count).padStart(7)}  ${p.name}`);
diff --git a/ptype-distinct.mjs b/ptype-distinct.mjs
new file mode 100644
index 0000000..7ddd0d4
--- /dev/null
+++ b/ptype-distinct.mjs
@@ -0,0 +1,13 @@
+import {query} from './query.mjs';
+// Sample a range of products to learn the real product.type vocabulary + sample-variant shape
+const q=`{products(first:60, query:"status:active"){nodes{productType title
+  variants(first:3){nodes{title sku}}}}}`;
+const d=await query(q);
+const types={};
+let sampleShapes=new Set();
+for(const p of d.products.nodes){
+  types[p.productType||'(empty)']=(types[p.productType||'(empty)']||0)+1;
+  for(const v of p.variants.nodes){ if(/sample/i.test(v.title)||/-sample$/i.test(v.sku||'')) sampleShapes.add(`title="${v.title}" sku~="${(v.sku||'').replace(/[0-9]+/g,'#')}"`); }
+}
+console.log('PRODUCT TYPES (60 active products):'); Object.entries(types).sort((a,b)=>b[1]-a[1]).forEach(([t,n])=>console.log(`  ${String(n).padStart(3)}  "${t}"`));
+console.log('\nSAMPLE VARIANT SHAPES seen:'); [...sampleShapes].forEach(s=>console.log('  '+s));
diff --git a/samples-profile.mjs b/samples-profile.mjs
new file mode 100644
index 0000000..8e24bad
--- /dev/null
+++ b/samples-profile.mjs
@@ -0,0 +1,21 @@
+import {query} from './query.mjs';
+const q=`{deliveryProfile(id:"gid://shopify/DeliveryProfile/96764067891"){name default productVariantsCount{count}
+  profileLocationGroups{locationGroup{id locations(first:5){nodes{name}}}
+    locationGroupZones(first:30){nodes{zone{id name countries{code{countryCode}}}
+      methodDefinitions(first:40){nodes{id name active
+        rateProvider{__typename ... on DeliveryRateDefinition{price{amount currencyCode}}}
+        methodConditions{operator field conditionCriteria{__typename ... on MoneyV2{amount} ... on Weight{value unit}}}}}}}}}}`;
+const d=await query(q);
+const p=d.deliveryProfile;
+console.log(`PROFILE "${p.name}" default:${p.default} variants:${p.productVariantsCount.count}`);
+for(const g of p.profileLocationGroups){
+  console.log(` LG ${g.locationGroup.id.split('/').pop()} locs:[${g.locationGroup.locations.nodes.map(l=>l.name).join(', ')}]`);
+  for(const z of g.locationGroupZones.nodes){
+    console.log(`  ZONE ${z.zone.id.split('/').pop()} ${z.zone.name} [${z.zone.countries.map(c=>c.code.countryCode).join(',')}]`);
+    for(const md of z.methodDefinitions.nodes){
+      const rp=md.rateProvider;const price=rp?.price?`$${rp.price.amount}`:rp?.__typename;
+      const conds=md.methodConditions.map(c=>{const cr=c.conditionCriteria;const v=cr?.amount!==undefined?`$${cr.amount}`:(cr?.value!==undefined?`${cr.value}${cr.unit}`:'?');return `${c.field} ${c.operator} ${v}`;}).join(' AND ')||'(none)';
+      console.log(`    METHOD ${md.id.split('/').pop()} "${md.name}" [${md.active?'on':'OFF'}] ${price} {${conds}}`);
+    }
+  }
+}
diff --git a/theme/sample-shipping-cart-engine.liquid b/theme/sample-shipping-cart-engine.liquid
index 6a3d224..ba7f7ad 100644
--- a/theme/sample-shipping-cart-engine.liquid
+++ b/theme/sample-shipping-cart-engine.liquid
@@ -1,39 +1,48 @@
 {%- comment -%}
   ============================================================================
-  TK-11333 — SAMPLE-SHIPPING CART POLICY ENGINE  (starter, NEEDS DEV THEME)
+  TK-11333 — SAMPLE-SHIPPING CART POLICY ENGINE  (Option A — DTD-committed)
   ============================================================================
-  STATUS: STARTER IMPLEMENTATION. Do NOT paste into the LIVE theme. Install on a
-  DEV/duplicate theme, QA with real carts, then Steve publishes. Go-live is gated
-  on proto-autoapply.mjs returning PASS (does the /discount/CODE permalink make free
-  shipping persist to checkout for an eligible logged-in customer?).
-
-  WHY THIS EXISTS (platform reality): the store is Advanced, NO Shopify Functions,
-  so a shipping RATE cannot be gated by customer tag nor count samples by type. The
-  ONLY place counts/types/tag-logic can live is theme JS on the CART/product pages.
-  The SERVER-ENFORCED gate is the segment-scoped free-ship CODE (TRADESHIP); this
-  engine only AUTO-APPLIES the correct code when thresholds pass and MESSAGES when not.
-
-  SPEC (locked, memo §1/§3):
-    • Eligibility: DESIGNER if customer.tags includes any trade tag OR 'sample-freeship';
-      otherwise RETAIL.
-    • Count SAMPLE line items only, split by product.type: Wallcovering vs Fabric.
-      (A line is a "sample" if variant.title == 'Sample' or sku ends in '-Sample'.)
-    • DESIGNER free rule: total samples <= 12  -> apply TRADESHIP (free ship).
-      13+ -> do NOT apply; show the "why you're charged" message.
-    • RETAIL free rule: WC samples <= 5 AND Fabric samples <= 5 -> apply SAMPLESHIP.
-      Over EITHER type -> show the "why you're charged" message (>5 of that type).
-    • SOFT CAP 20 samples/order: at 21+ disable the checkout button + message
-      (theme-soft; a hard cap needs Plus/Functions).
-    • Removal note: on Advanced there is no clean in-cart "remove code" API. This engine
-      APPLIES when eligible and MESSAGES when not; a stale code is dropped at checkout
-      (maximumShippingPrice '30' on both codes stops a freight roll ever riding free).
+  STATUS: install on a DUPLICATE/DEV theme only. Steve publishes. Do NOT paste
+  into the LIVE theme. Go-live gated on proto-autoapply proving the /discount
+  permalink makes free shipping persist to checkout for an eligible customer.
+
+  ARCHITECTURE (verified live 2026-09-09 — see verification/band-truth-2026-09-09.json):
+    • The FREE-SAMPLE floor is a delivery-profile RATE, not this engine: a $0
+      "Free Shipping (No Tracking)" method on the dedicated profile
+      "Samples — Free Shipping (No Tracking)" (gid 96764067891), gated by
+      cart TOTAL_PRICE <= $45. Samples are $4.25, so ~10 samples ship free with
+      NO code and NO login. VERIFIED the band evaluates WHOLE-CART subtotal:
+      any roll (General/carrier profile) pushes the total over $45 and the band
+      switches off for the whole cart.
+    • Store is Advanced Shopify — NO Shopify Functions — so a shipping RATE
+      CANNOT be gated by customer tag, sample count, or product type.
+    • PROVEN LIMIT: a TOTAL_PRICE band cannot tell 6 wallcovering samples
+      ($25.50) from 3 WC + 3 fabric ($25.50) — identical price. So the retail
+      per-type "5 and 5" rule CANNOT be a shipping charge below the cap. Below
+      $45 everyone is free regardless of type. Per-type "5 and 5" is therefore
+      GUIDANCE ONLY here (a true per-type charge needs Plus + Functions).
+
+  WHAT THIS ENGINE DOES (and only this):
+    1. DESIGNER EXTENSION: a logged-in designer (tag match) whose sample cart is
+       OVER the band cap (11–12 samples, subtotal > $45) gets TRADESHIP
+       auto-applied so their free shipping extends to 12. This is the ONLY case
+       a code is auto-applied — it never fires when the band already gives free
+       (no needless redirect) and SAMPLESHIP is NOT auto-applied at all (the band
+       already covers the retail free range; SAMPLESHIP stays a manual code).
+    2. HONEST MESSAGING: it shows a "why you're charged" message ONLY when a
+       charge will ACTUALLY be incurred (cart subtotal is over the band cap AND
+       no covering code applies). It NEVER claims a charge the live band won't
+       impose. When free, it says so; it never nags "charged" on a free cart.
+    3. SOFT CAP 20: at 21+ samples it disables the standard checkout button AND
+       hides accelerated wallet buttons + shows a reduce-to-20 message. Honestly
+       SOFT — the direct /checkout URL and some wallet flows can still slip past;
+       a hard cap needs Plus/Functions.
 
   INSTALL:
-    1. Save this file as snippets/sample-shipping-cart-engine.liquid on a DEV theme.
-    2. In the cart template/section, render it near the checkout button:
-         {%- render 'sample-shipping-cart-engine' -%}
-    3. Confirm the CODES exist (create-freeship-codes.mjs) before enabling apply.
-    4. Adjust CONFIG below (codes, caps, product-type labels, selectors) to the theme.
+    1. Save as snippets/sample-shipping-cart-engine.liquid on a DEV theme.
+    2. Render near the cart checkout button:  {%- render 'sample-shipping-cart-engine' -%}
+    3. Confirm TRADESHIP exists (create-freeship-codes.mjs --apply) before relying on the extension.
+    4. Tune CONFIG (bandCap, codes, caps, checkout selectors) to the theme.
   ============================================================================
 {%- endcomment -%}
 
@@ -42,13 +51,15 @@
 {
   "isLoggedIn": {{ customer | default: false | json }},
   "tags": {{ customer.tags | default: '' | json }},
+  "cartSubtotalCents": {{ cart.total_price | default: 0 }},
   "lines": [
     {%- for item in cart.items -%}
     {
       "product_type": {{ item.product.type | default: '' | json }},
       "variant_title": {{ item.variant.title | default: '' | json }},
       "sku": {{ item.sku | default: '' | json }},
-      "quantity": {{ item.quantity }}
+      "quantity": {{ item.quantity }},
+      "line_price_cents": {{ item.final_line_price | default: 0 }}
     }{%- unless forloop.last -%},{%- endunless -%}
     {%- endfor -%}
   ]
@@ -59,33 +70,35 @@
 (function () {
   // ---------------- CONFIG (tune to the store) ----------------
   var CONFIG = {
-    designerCode: 'TRADESHIP',
-    retailCode:   'SAMPLESHIP',
-    designerCap:  12,   // total samples free for designers
-    retailWC:     5,    // free wallcovering samples for retail
-    retailFabric: 5,    // free fabric samples for retail
-    softCap:      20,   // block checkout above this many samples
-    // customer tags that mean "gets designer free shipping" (mirror the DW Trade/Designers segment)
+    designerCode: 'TRADESHIP',       // segment-scoped free-ship code, maxShip $30
+    bandCapCents: 4500,              // live band: free while whole-cart subtotal <= $45
+    designerCap:  12,                // total samples free for designers (via code)
+    retailWC:     5,                 // per-type GUIDANCE only (cannot be a charge below the band)
+    retailFabric: 5,                 // per-type GUIDANCE only
+    softCap:      20,                // block standard checkout above this many samples
+    // customer tags that mean "gets designer free shipping" — mirror the DW Trade/Designers segment
     designerTags: ['trade','trade_approved','sample-freeship','interior designer',
       'interior design','interior','architect','contractor','commercial property owner',
       'wallcovering installer','interior designer - residential','interior designer - commercial'],
-    // product.type values that count as Fabric; everything else with a sample counts as Wallcovering
+    // product.type values that count as Fabric (VERIFIED live: catalog uses "Fabric" and
+    // "Wallcovering"; toLowerCase makes "Fabric" -> "fabric"). Extra synonyms are harmless.
     fabricTypes: ['fabric','fabrics','textile','textiles'],
-    checkoutBtnSelector: '[name="checkout"], button[name="checkout"], .cart__checkout, #checkout',
+    checkoutBtnSelector: '[name="checkout"], button[name="checkout"], .cart__checkout, #checkout, #cart-checkout',
+    // accelerated wallet / dynamic checkout buttons to hide when over the soft cap
+    walletSelector: '.additional-checkout-buttons, .shopify-payment-button, [data-shopify="payment-button"], .dynamic-checkout__content',
     messageMountSelector: '#dw-ship-policy'
   };
   var APPLIED_KEY = 'dw_ship_code_applied'; // sessionStorage guard against redirect loops
 
   function readData() {
     try { return JSON.parse(document.getElementById('dw-ship-cart-data').textContent); }
-    catch (e) { return { isLoggedIn: false, tags: '', lines: [] }; }
+    catch (e) { return { isLoggedIn: false, tags: '', cartSubtotalCents: 0, lines: [] }; }
   }
   function isSample(l) {
     return (l.variant_title || '').toLowerCase() === 'sample' || /-sample$/i.test(l.sku || '');
   }
   function isFabric(l) {
-    var t = (l.product_type || '').toLowerCase();
-    return CONFIG.fabricTypes.indexOf(t) !== -1;
+    return CONFIG.fabricTypes.indexOf((l.product_type || '').toLowerCase()) !== -1;
   }
   function isDesigner(tags) {
     var set = String(tags || '').toLowerCase().split(',').map(function (s) { return s.trim(); });
@@ -93,35 +106,53 @@
   }
 
   function evaluate(d) {
-    var samples = d.lines.filter(isSample);
-    var total = samples.reduce(function (n, l) { return n + l.quantity; }, 0);
-    var wc = samples.filter(function (l) { return !isFabric(l); }).reduce(function (n, l) { return n + l.quantity; }, 0);
-    var fab = samples.filter(isFabric).reduce(function (n, l) { return n + l.quantity; }, 0);
-    var designer = d.isLoggedIn && isDesigner(d.tags);
+    var samples   = d.lines.filter(isSample);
+    var total     = samples.reduce(function (n, l) { return n + l.quantity; }, 0);
+    var wc        = samples.filter(function (l) { return !isFabric(l); }).reduce(function (n, l) { return n + l.quantity; }, 0);
+    var fab       = samples.filter(isFabric).reduce(function (n, l) { return n + l.quantity; }, 0);
+    var hasNonSample = d.lines.some(function (l) { return !isSample(l); }); // e.g. a roll
+    var designer  = !!d.isLoggedIn && isDesigner(d.tags);
     var overSoftCap = total > CONFIG.softCap;
-    var eligibleFree, code, reason;
-    if (designer) {
-      eligibleFree = total <= CONFIG.designerCap;
-      code = CONFIG.designerCode;
-      reason = eligibleFree ? '' : ('Free sample shipping covers up to ' + CONFIG.designerCap +
-        ' samples for trade accounts. You have ' + total + ' — shipping on the rest is charged at cost.');
-    } else {
-      eligibleFree = wc <= CONFIG.retailWC && fab <= CONFIG.retailFabric;
-      code = CONFIG.retailCode;
-      if (!eligibleFree) {
-        var bits = [];
-        if (wc > CONFIG.retailWC) bits.push(wc + ' wallcovering (free up to ' + CONFIG.retailWC + ')');
-        if (fab > CONFIG.retailFabric) bits.push(fab + ' fabric (free up to ' + CONFIG.retailFabric + ')');
-        reason = 'Free sample shipping covers ' + CONFIG.retailWC + ' wallcovering and ' + CONFIG.retailFabric +
-          ' fabric samples. You have ' + bits.join(' and ') + ' — shipping on the extras is charged.';
-      } else reason = '';
+
+    // The LIVE band already gives free shipping while whole-cart subtotal <= $45.
+    var bandCovers = d.cartSubtotalCents <= CONFIG.bandCapCents;
+
+    // A covering code closes the gap ONLY for a logged-in designer within the 12-sample cap.
+    var codeCovers = designer && total <= CONFIG.designerCap && total > 0;
+
+    // Will the customer ACTUALLY be charged shipping? (this is what the message must reflect)
+    var willBeFree   = bandCovers || codeCovers;
+    var willBeCharged = total > 0 && !willBeFree;
+
+    // Auto-apply the designer code ONLY when it changes the outcome:
+    // designer, over the band cap, still within the 12 cap, not over the soft cap.
+    var shouldApplyCode = designer && !bandCovers && total <= CONFIG.designerCap && !overSoftCap;
+
+    // Honest reason (only populated when a charge is real, or as a soft note).
+    var reason = '';
+    if (overSoftCap) {
+      reason = 'Sample orders are limited to ' + CONFIG.softCap + ' per order. Please reduce to ' +
+        CONFIG.softCap + ' or fewer to check out.';
+    } else if (willBeCharged) {
+      if (designer && total > CONFIG.designerCap) {
+        reason = 'Free sample shipping covers up to ' + CONFIG.designerCap + ' samples for trade accounts. ' +
+          'You have ' + total + ' — shipping on the rest is at carrier cost.';
+      } else if (hasNonSample) {
+        reason = 'Free sample shipping covers sample-only orders up to $' + (CONFIG.bandCapCents/100).toFixed(0) +
+          '. Rolls and larger orders ship at standard carrier rates.';
+      } else {
+        reason = 'Free sample shipping covers orders up to $' + (CONFIG.bandCapCents/100).toFixed(0) +
+          ' (about 10 samples). Your order is over that — shipping is at carrier cost.';
+      }
     }
-    return { total: total, wc: wc, fab: fab, designer: designer, eligibleFree: eligibleFree, code: code, reason: reason, overSoftCap: overSoftCap };
+    return { total: total, wc: wc, fab: fab, designer: designer, bandCovers: bandCovers,
+      willBeFree: willBeFree, willBeCharged: willBeCharged, shouldApplyCode: shouldApplyCode,
+      overSoftCap: overSoftCap, reason: reason };
   }
 
   function applyCode(code) {
-    // /discount/CODE?redirect=/cart sets the session discount cookie. Guard against loops:
-    // only redirect once per cart state (keyed on code+item signature).
+    // /discount/CODE?redirect=/cart sets the session discount cookie (this is a full-page nav).
+    // Guarded so we redirect at most once per cart signature (no loops).
     var sig = code + ':' + (document.getElementById('dw-ship-cart-data').textContent.length);
     if (sessionStorage.getItem(APPLIED_KEY) === sig) return;
     sessionStorage.setItem(APPLIED_KEY, sig);
@@ -134,29 +165,29 @@
       mount.hidden = false;
       if (state.overSoftCap) {
         mount.innerHTML = '<div class="dw-ship-msg dw-ship-msg--block" role="alert" style="padding:.75rem 1rem;border:1px solid #b00;border-radius:8px;margin:.5rem 0;color:#b00;">' +
-          'Sample orders are limited to ' + CONFIG.softCap + ' per order. Please reduce to ' + CONFIG.softCap + ' or fewer to check out.</div>';
-      } else if (state.reason) {
+          state.reason + '</div>';
+      } else if (state.reason) { // real charge -> honest "why charged"
         mount.innerHTML = '<div class="dw-ship-msg" role="status" style="padding:.75rem 1rem;border:1px solid #d8c9a8;border-radius:8px;margin:.5rem 0;background:#faf6ee;">' +
           state.reason + '</div>';
-      } else if (state.eligibleFree) {
+      } else if (state.total > 0 && state.willBeFree) {
         mount.innerHTML = '<div class="dw-ship-msg dw-ship-msg--ok" role="status" style="padding:.5rem 1rem;color:#2e6b2e;">Free sample shipping applied.</div>';
       } else { mount.innerHTML = ''; }
     }
-    // soft cap: disable checkout
-    var btns = document.querySelectorAll(CONFIG.checkoutBtnSelector);
-    btns.forEach(function (b) {
-      if (state.overSoftCap) { b.setAttribute('disabled', 'disabled'); b.setAttribute('aria-disabled', 'true'); b.style.opacity = '0.5'; b.style.pointerEvents = 'none'; }
-      else { b.removeAttribute('disabled'); b.removeAttribute('aria-disabled'); b.style.opacity = ''; b.style.pointerEvents = ''; }
+    // soft cap: disable standard checkout + hide accelerated wallet buttons (still SOFT — /checkout URL can slip past)
+    var over = state.overSoftCap;
+    document.querySelectorAll(CONFIG.checkoutBtnSelector).forEach(function (b) {
+      if (over) { b.setAttribute('disabled','disabled'); b.setAttribute('aria-disabled','true'); b.style.opacity='0.5'; b.style.pointerEvents='none'; }
+      else { b.removeAttribute('disabled'); b.removeAttribute('aria-disabled'); b.style.opacity=''; b.style.pointerEvents=''; }
     });
+    document.querySelectorAll(CONFIG.walletSelector).forEach(function (w) { w.style.display = over ? 'none' : ''; });
   }
 
   function run() {
     var d = readData();
     var state = evaluate(d);
     render(state);
-    // auto-apply the correct code only when the cart is all-eligible-free and NOT over soft cap
-    if (state.total > 0 && state.eligibleFree && !state.overSoftCap) applyCode(state.code);
-    // (removal is checkout-side on Advanced; maximumShippingPrice '30' guards a stale code)
+    // auto-apply the designer code ONLY when it actually changes the outcome (11-12 designer samples)
+    if (state.shouldApplyCode) applyCode(CONFIG.designerCode);
   }
 
   if (document.readyState !== 'loading') run();

← 30edcd5 auto-data-snapshot: 2026-09-09T22:50:09 (4 data files) — ver  ·  back to Shopify Sample Shipping  ·  TK-11333: correct band-truth (band is LIVE on dedicated Samp ffc2043 →