← 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
M scripts/dwpw-grs-migrate.pyM scripts/lib/test_weight_guard.pyM scripts/lib/weight-guard.mjsM scripts/lib/weight-guard.test.mjsM scripts/lib/weight_guard.py
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 →