[object Object]

← back to Designerwallcoverings

TK-11871: Wallcovering guard default 3.0 -> 2.0 lb (live catalog norm)

3418680f6abdc32dc4b41d95f89ce0e3e0c240ff · 2026-09-17 17:23:14 -0700 · Steve Abrams

TYPE_DEFAULT_LB Wallcovering family (Wallcovering/Wallcoverings/Wallpaper/
Metallic/Commercial) 3.0 -> 2.0 across the guard sync-group (mjs canonical +
py twin) and the guard tests. Ratified by TK-11803 DTD verdict A (2,299-
variant live sample: 2 lb 91.9% / 3 lb 0.7% / 1 lb 0.4%) + Cody red-team
(freight asymmetry: overcharge kills checkout, slight undercharge invisible).
FALLBACK_LB stays 2.0, so unknown-type and Wallcovering now agree.
Samples stay 0.25 lb; all other type defaults unchanged. Go-forward code
default only — no Shopify/dw_unified writes, no historical re-stamp.
Restore map: ticket-system/evidence/TK-11871/restore-map-20260917-1700/.
Tests: py 14/14 OK, mjs 52/52 PASS.

Files touched

Diff

commit 3418680f6abdc32dc4b41d95f89ce0e3e0c240ff
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 17 17:23:14 2026 -0700

    TK-11871: Wallcovering guard default 3.0 -> 2.0 lb (live catalog norm)
    
    TYPE_DEFAULT_LB Wallcovering family (Wallcovering/Wallcoverings/Wallpaper/
    Metallic/Commercial) 3.0 -> 2.0 across the guard sync-group (mjs canonical +
    py twin) and the guard tests. Ratified by TK-11803 DTD verdict A (2,299-
    variant live sample: 2 lb 91.9% / 3 lb 0.7% / 1 lb 0.4%) + Cody red-team
    (freight asymmetry: overcharge kills checkout, slight undercharge invisible).
    FALLBACK_LB stays 2.0, so unknown-type and Wallcovering now agree.
    Samples stay 0.25 lb; all other type defaults unchanged. Go-forward code
    default only — no Shopify/dw_unified writes, no historical re-stamp.
    Restore map: ticket-system/evidence/TK-11871/restore-map-20260917-1700/.
    Tests: py 14/14 OK, mjs 52/52 PASS.
---
 scripts/dwpw-grs-migrate.py       | 3 ++-
 scripts/lib/test_weight_guard.py  | 6 +++---
 scripts/lib/weight-guard.mjs      | 9 +++++++--
 scripts/lib/weight-guard.test.mjs | 7 ++++---
 scripts/lib/weight_guard.py       | 8 +++++---
 5 files changed, 21 insertions(+), 12 deletions(-)

diff --git a/scripts/dwpw-grs-migrate.py b/scripts/dwpw-grs-migrate.py
index 4002f1d..281823b 100644
--- a/scripts/dwpw-grs-migrate.py
+++ b/scripts/dwpw-grs-migrate.py
@@ -17,7 +17,8 @@ PER PRODUCT, in this EXACT order (the ordering IS the safety property):
   2b. WEIGHT GATE (TK-11414 hard rule, wired TK-11471): every variant must carry a
      real weight. Create stamps it; before publish the weights are re-read through a
      query that ACTUALLY returns them, any zero/unset variant is healed to the
-     approved default (Sample 0.25 lb / Wallcovering 3.0 lb) and RE-VERIFIED. Still
+     approved default (Sample 0.25 lb / Wallcovering 2.0 lb — TK-11871 catalog norm,
+     was 3.0) and RE-VERIFIED. Still
      zero -> LEFT_DRAFT_NEEDS_WEIGHT, no publish, no DWPW archive. Zero weight
      collapses the order into the lowest weight tier / free band and mis-costs freight.
   3. PUBLISH the GRS (status ACTIVE) + add to Online Store + Google & YouTube channel.
diff --git a/scripts/lib/test_weight_guard.py b/scripts/lib/test_weight_guard.py
index 941bfed..f8f183b 100644
--- a/scripts/lib/test_weight_guard.py
+++ b/scripts/lib/test_weight_guard.py
@@ -93,7 +93,7 @@ class TestDetector(unittest.TestCase):
 
     def test_resolve_preserves_positive_and_fills_default(self):
         self.assertAlmostEqual(wg.resolve_weight_lb(sellable(weight=7.5, weight_unit="lb"), WALLCOVERING), 7.5)
-        self.assertEqual(wg.resolve_weight_lb(sellable(), WALLCOVERING), 3.0)
+        self.assertEqual(wg.resolve_weight_lb(sellable(), WALLCOVERING), 2.0)  # TK-11871 catalog norm
         self.assertEqual(wg.resolve_weight_lb(sample(), WALLCOVERING), 0.25)
         self.assertEqual(wg.resolve_weight_lb(sellable(), {"product_type": "Mural"}), 4.0)
         self.assertEqual(wg.resolve_weight_lb(sellable(), {"product_type": "Nonesuch"}), 2.0)
@@ -129,7 +129,7 @@ def load_migrate():
 class TestMigrateWeightsAreStamped(unittest.TestCase):
     def test_create_payload_weights_come_from_the_guard(self):
         m = load_migrate()
-        self.assertEqual(m._WEIGHT_SELLABLE_LB, 3.0)     # Wallcovering default
+        self.assertEqual(m._WEIGHT_SELLABLE_LB, 2.0)     # Wallcovering default (TK-11871 catalog norm)
         self.assertEqual(m._WEIGHT_SAMPLE_LB, 0.25)      # sample default
         self.assertGreater(m._WEIGHT_SELLABLE_LB, 0)
         self.assertGreater(m._WEIGHT_SAMPLE_LB, 0)
@@ -220,7 +220,7 @@ class TestMigratePublishGate(unittest.TestCase):
         res = m.process(dict(self.ROW), apply=True)
         self.assertEqual(res["result"], "PUBLISHED_NO_TWIN", res)
         self.assertTrue(state["published"])
-        self.assertEqual(sorted(w for _, w in state["mutations"]), [0.25, 3.0])
+        self.assertEqual(sorted(w for _, w in state["mutations"]), [0.25, 2.0])
 
     def test_product_whose_heal_FAILS_is_NOT_PUBLISHED(self):
         m, state = self._harness(heal_ok=False)
diff --git a/scripts/lib/weight-guard.mjs b/scripts/lib/weight-guard.mjs
index 8d8313d..cb05934 100644
--- a/scripts/lib/weight-guard.mjs
+++ b/scripts/lib/weight-guard.mjs
@@ -21,9 +21,14 @@ export const SAMPLE_WEIGHT_LB = 0.25;
 export const FALLBACK_LB = 2.0;
 
 // product_type -> sellable default weight (POUNDS)
+// TK-11871 (2026-09-17): Wallcovering family 3.0 -> 2.0 to match the live catalog norm —
+// 91.9% of ACTIVE Wallcovering sellable variants carry 2 lb (independent sample 2,299:
+// 2 lb 91.9% / 3 lb 0.7% / 1 lb 0.4%). Ratified by TK-11803 DTD verdict A + Cody red-team:
+// freight asymmetry favors 2 lb (overcharge kills checkout; slight undercharge is invisible),
+// and 2.0 == FALLBACK_LB, so an unknown type and a Wallcovering now agree. Revert = 3.0.
 export const TYPE_DEFAULT_LB = {
-  'Wallcovering': 3.0, 'Wallcoverings': 3.0, 'Wallpaper': 3.0,
-  'Metallic Wallcovering': 3.0, 'Commercial Wallcovering': 3.0,
+  'Wallcovering': 2.0, 'Wallcoverings': 2.0, 'Wallpaper': 2.0,
+  'Metallic Wallcovering': 2.0, 'Commercial Wallcovering': 2.0,
   'Mural': 4.0,
   'Fabric': 1.0, 'Commercial Fabric': 1.0, 'Commercial Drapery': 1.0,
   'Trim': 0.5, 'Acoustic Panel': 6.0, 'Pillow': 1.5,
diff --git a/scripts/lib/weight-guard.test.mjs b/scripts/lib/weight-guard.test.mjs
index c037c95..1227fb8 100644
--- a/scripts/lib/weight-guard.test.mjs
+++ b/scripts/lib/weight-guard.test.mjs
@@ -94,13 +94,14 @@ section('resolveWeightLb (create-side)');
 {
   near('preserves an existing positive weight', resolveWeightLb({ weight: 7.5, weight_unit: 'lb' }, { productType: 'Wallcovering' }), 7.5);
   near('preserves a positive GQL weight', resolveWeightLb(gqlVariant('p', 4.2), { productType: 'Wallcovering' }), 4.2);
-  near('fills the per-type default (Wallcovering=3)', resolveWeightLb({ sku: 'DWX-1', option1: 'Roll' }, { productType: 'Wallcovering' }), TYPE_DEFAULT_LB['Wallcovering']);
+  near('fills the per-type default (Wallcovering=2, TK-11871 catalog norm)', resolveWeightLb({ sku: 'DWX-1', option1: 'Roll' }, { productType: 'Wallcovering' }), TYPE_DEFAULT_LB['Wallcovering']);
+  near('  …and that default is literally 2.0', resolveWeightLb({ sku: 'DWX-1b', option1: 'Roll' }, { productType: 'Wallcovering' }), 2);
   near('fills the per-type default (Mural=4)', resolveWeightLb({ sku: 'DWX-2', option1: 'Roll' }, { productType: 'Mural' }), TYPE_DEFAULT_LB['Mural']);
   near('fills the per-type default (Fabric=1)', resolveWeightLb({ sku: 'DWX-3', option1: 'Yard' }, { productType: 'Fabric' }), TYPE_DEFAULT_LB['Fabric']);
   near('unknown product_type => FALLBACK_LB', resolveWeightLb({ sku: 'DWX-4', option1: 'Roll' }, { productType: 'Nonesuch' }), FALLBACK_LB);
   near('sample => SAMPLE_WEIGHT_LB', resolveWeightLb({ sku: 'DWX-5-Sample', option1: 'Sample' }, { productType: 'Wallcovering' }), SAMPLE_WEIGHT_LB);
   near('$4.25 memo detected as sample', resolveWeightLb({ sku: 'DWX-6', option1: 'Memo', price: '4.25' }, { productType: 'Wallcovering' }), SAMPLE_WEIGHT_LB);
-  near('zero weight => default, not 0', resolveWeightLb({ weight: 0, weight_unit: 'lb', sku: 'DWX-7', option1: 'Roll' }, { productType: 'Wallcovering' }), 3);
+  near('zero weight => default, not 0', resolveWeightLb({ weight: 0, weight_unit: 'lb', sku: 'DWX-7', option1: 'Roll' }, { productType: 'Wallcovering' }), 2);
   ok('isSampleVariant(-Sample sku)', isSampleVariant({ sku: 'DWX-8-Sample' }));
   ok('isSampleVariant(Roll) false', !isSampleVariant({ sku: 'DWX-8', title: 'Roll', price: '199.00' }));
   near('defaultWeightLb honors product_type snake_case too', defaultWeightLb({ sku: 'r', option1: 'Roll' }, { product_type: 'Mural' }), 4);
@@ -135,7 +136,7 @@ function mockGql({ before, after, failMutation = false, reverifyNull = false, th
   const r = await healAndVerifyWeights(m.gql, 'gid://shopify/Product/1', before);
   ok('heal succeeds => ok', r.ok === true, JSON.stringify(r));
   eq('  both variants healed', r.healed.length, 2);
-  near('  sellable healed to the Wallcovering default', r.healed.find(h => h.sku === 'DWTT-2001').lb, 3);
+  near('  sellable healed to the Wallcovering default', r.healed.find(h => h.sku === 'DWTT-2001').lb, 2);
   near('  sample healed to SAMPLE_WEIGHT_LB', r.healed.find(h => h.sku === 'DWTT-2001-Sample').lb, SAMPLE_WEIGHT_LB);
   eq('  stillZero empty', r.stillZero.length, 0);
   eq('  no errors', r.errs.length, 0);
diff --git a/scripts/lib/weight_guard.py b/scripts/lib/weight_guard.py
index da259a8..15c61fa 100644
--- a/scripts/lib/weight_guard.py
+++ b/scripts/lib/weight_guard.py
@@ -27,10 +27,12 @@ import math
 SAMPLE_WEIGHT_LB = 0.25
 FALLBACK_LB = 2.0
 
-# product_type -> sellable default weight (POUNDS). Identical to the .mjs table.
+# product_type -> sellable default weight (POUNDS). Identical to the .mjs twin.
+# TK-11871 (2026-09-17): Wallcovering family 3.0 -> 2.0 = live catalog norm (91.9% of
+# ACTIVE Wallcovering sellable variants; TK-11803 DTD verdict A). Revert = 3.0.
 TYPE_DEFAULT_LB = {
-    'Wallcovering': 3.0, 'Wallcoverings': 3.0, 'Wallpaper': 3.0,
-    'Metallic Wallcovering': 3.0, 'Commercial Wallcovering': 3.0,
+    'Wallcovering': 2.0, 'Wallcoverings': 2.0, 'Wallpaper': 2.0,
+    'Metallic Wallcovering': 2.0, 'Commercial Wallcovering': 2.0,
     'Mural': 4.0,
     'Fabric': 1.0, 'Commercial Fabric': 1.0, 'Commercial Drapery': 1.0,
     'Trim': 0.5, 'Acoustic Panel': 6.0, 'Pillow': 1.5,

← fa74e2a auto-data-snapshot: 2026-09-17T16:20:35 (3 data files) — scr  ·  back to Designerwallcoverings  ·  auto-data-snapshot: 2026-09-17T19:01:39 (25 data files) — ev 5c9b9f2 →