← back to Designerwallcoverings
TK-11471: every create payload carries a guard-sourced weight
231a90eadf9e03a2ec9c8d636ee1f8efb667f617 · 2026-09-11 11:30:32 -0700 · Steve Abrams
(B) four sites shipped variants with NO weight field at all — a product created there was born
at zero weight and could only be saved by a downstream heal:
artmura/push-artmura-live.js, fallingstar/push-fallingstar-live.js,
tres-tintas/push-tres-tintas-live.js, zoffany/push-zoffany-live.js
(C) eight build-payloads.mjs carried the literals weight: 3 / weight: 0.25 (2be0da4). Same
numbers, but a magic number cannot follow TYPE_DEFAULT_LB when it changes:
greenland, knoll, maharam, muralsource, osborne, rigo, stout, stroheim
All twelve now emit weight: resolveWeightLb(variant, { productType }), weight_unit: 'lb' from
scripts/lib/weight-guard.mjs. Values are unchanged at 3.0 lb sellable / 0.25 lb sample — this is
a source-of-truth change, not a repricing.
verification/tk11471/create-weight-wiring.test.mjs proves it three ways (static wiring, a drift
check that the productType handed to resolveWeightLb matches each payload's own product_type
literal, and BEHAVIOURAL evaluation of all 20 extracted expressions against the real module),
plus negative tests for a dropped weight, a returning magic number and a dropped import.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJHxAzaEMMxado57mFjiCk
Files touched
M scripts/artmura-onboard/push-artmura-live.jsM scripts/fallingstar-onboard/push-fallingstar-live.jsM scripts/greenland-onboard/build-payloads.mjsM scripts/knoll-onboard/build-payloads.mjsM scripts/maharam-onboard/build-payloads.mjsM scripts/muralsource-onboard/build-payloads.mjsM scripts/osborne-onboard/build-payloads.mjsM scripts/rigo-onboard/build-payloads.mjsM scripts/stout-onboard/build-payloads.mjsM scripts/stroheim-onboard/build-payloads.mjsM scripts/tres-tintas-onboard/push-tres-tintas-live.jsM scripts/zoffany-onboard/push-zoffany-live.jsA verification/tk11471/create-weight-wiring.test.mjs
Diff
commit 231a90eadf9e03a2ec9c8d636ee1f8efb667f617
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 11 11:30:32 2026 -0700
TK-11471: every create payload carries a guard-sourced weight
(B) four sites shipped variants with NO weight field at all — a product created there was born
at zero weight and could only be saved by a downstream heal:
artmura/push-artmura-live.js, fallingstar/push-fallingstar-live.js,
tres-tintas/push-tres-tintas-live.js, zoffany/push-zoffany-live.js
(C) eight build-payloads.mjs carried the literals weight: 3 / weight: 0.25 (2be0da4). Same
numbers, but a magic number cannot follow TYPE_DEFAULT_LB when it changes:
greenland, knoll, maharam, muralsource, osborne, rigo, stout, stroheim
All twelve now emit weight: resolveWeightLb(variant, { productType }), weight_unit: 'lb' from
scripts/lib/weight-guard.mjs. Values are unchanged at 3.0 lb sellable / 0.25 lb sample — this is
a source-of-truth change, not a repricing.
verification/tk11471/create-weight-wiring.test.mjs proves it three ways (static wiring, a drift
check that the productType handed to resolveWeightLb matches each payload's own product_type
literal, and BEHAVIOURAL evaluation of all 20 extracted expressions against the real module),
plus negative tests for a dropped weight, a returning magic number and a dropped import.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJHxAzaEMMxado57mFjiCk
---
scripts/artmura-onboard/push-artmura-live.js | 5 +-
.../fallingstar-onboard/push-fallingstar-live.js | 5 +-
scripts/greenland-onboard/build-payloads.mjs | 3 +-
scripts/knoll-onboard/build-payloads.mjs | 5 +-
scripts/maharam-onboard/build-payloads.mjs | 3 +-
scripts/muralsource-onboard/build-payloads.mjs | 5 +-
scripts/osborne-onboard/build-payloads.mjs | 5 +-
scripts/rigo-onboard/build-payloads.mjs | 3 +-
scripts/stout-onboard/build-payloads.mjs | 5 +-
scripts/stroheim-onboard/build-payloads.mjs | 5 +-
.../tres-tintas-onboard/push-tres-tintas-live.js | 5 +-
scripts/zoffany-onboard/push-zoffany-live.js | 2 +
verification/tk11471/create-weight-wiring.test.mjs | 155 +++++++++++++++++++++
13 files changed, 187 insertions(+), 19 deletions(-)
diff --git a/scripts/artmura-onboard/push-artmura-live.js b/scripts/artmura-onboard/push-artmura-live.js
index 2894a8d..020bec3 100644
--- a/scripts/artmura-onboard/push-artmura-live.js
+++ b/scripts/artmura-onboard/push-artmura-live.js
@@ -17,6 +17,7 @@ const https = require('https');
const fs = require('fs');
const path = require('path');
const { Pool } = require('pg');
+const { resolveWeightLb } = require('../lib/weight-guard.mjs'); // GUARD TK-11471 (create-side weight: no magic numbers)
const STORE = process.env.SHOPIFY_STORE || 'designer-laboratory-sandbox.myshopify.com';
const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_PRODUCT_TOKEN;
@@ -172,8 +173,8 @@ function buildPayload(r, dwSku) {
...(metafields.length ? { metafields } : {}),
options: [{ name: 'Size' }],
variants: [
- { sku: dwSku, price: String(r.price_newwall_retail), option1: 'Yard', taxable: true, requires_shipping: true },
- { sku: `${dwSku}-Sample`, price: String(r.sample_price || 5), option1: 'Sample', taxable: true, requires_shipping: true },
+ { sku: dwSku, price: String(r.price_newwall_retail), option1: 'Yard', taxable: true, requires_shipping: true, weight: resolveWeightLb({ sku: dwSku, option1: 'Yard' }, { productType: r.product_type || 'Wallcoverings' }), weight_unit: 'lb' /* TK-11471 */ },
+ { sku: `${dwSku}-Sample`, price: String(r.sample_price || 5), option1: 'Sample', taxable: true, requires_shipping: true, weight: resolveWeightLb({ sku: `${dwSku}-Sample`, option1: 'Sample' }, { productType: r.product_type || 'Wallcoverings' }), weight_unit: 'lb' /* TK-11471 */ },
],
} };
}
diff --git a/scripts/fallingstar-onboard/push-fallingstar-live.js b/scripts/fallingstar-onboard/push-fallingstar-live.js
index 1ee8c5b..e0bfb70 100644
--- a/scripts/fallingstar-onboard/push-fallingstar-live.js
+++ b/scripts/fallingstar-onboard/push-fallingstar-live.js
@@ -16,6 +16,7 @@
const https = require('https');
const fs = require('fs');
const path = require('path');
+const { resolveWeightLb } = require('../lib/weight-guard.mjs'); // GUARD TK-11471 (create-side weight: no magic numbers)
const STORE = process.env.SHOPIFY_STORE || 'designer-laboratory-sandbox.myshopify.com';
const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_PRODUCT_TOKEN;
@@ -128,8 +129,8 @@ function buildPayload(r) {
status: STATUS, tags: r.tags.join(', '), images, body_html: specBody(r),
options: [{ name: 'Size' }],
variants: [
- { sku: r.dw_sku, price: String(r.price_retail), option1: 'Roll', taxable: true, requires_shipping: true },
- { sku: `${r.dw_sku}-Sample`, price: String(r.sample_price || 5), option1: 'Sample', taxable: true, requires_shipping: true },
+ { sku: r.dw_sku, price: String(r.price_retail), option1: 'Roll', taxable: true, requires_shipping: true, weight: resolveWeightLb({ sku: r.dw_sku, option1: 'Roll' }, { productType: 'Wallcoverings' }), weight_unit: 'lb' /* TK-11471 */ },
+ { sku: `${r.dw_sku}-Sample`, price: String(r.sample_price || 5), option1: 'Sample', taxable: true, requires_shipping: true, weight: resolveWeightLb({ sku: `${r.dw_sku}-Sample`, option1: 'Sample' }, { productType: 'Wallcoverings' }), weight_unit: 'lb' /* TK-11471 */ },
],
metafields,
} };
diff --git a/scripts/greenland-onboard/build-payloads.mjs b/scripts/greenland-onboard/build-payloads.mjs
index 58e0c6c..5303ce0 100644
--- a/scripts/greenland-onboard/build-payloads.mjs
+++ b/scripts/greenland-onboard/build-payloads.mjs
@@ -13,6 +13,7 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { execSync } from 'node:child_process';
import { specMetafields } from '../_spec-to-metafields.mjs';
+import { resolveWeightLb } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (create-side weight: no magic numbers)
const HERE = path.dirname(fileURLToPath(import.meta.url));
const OUT = path.join(HERE, 'out');
@@ -86,7 +87,7 @@ function main() {
// Image is downloaded + base64-attached post-create by create-drafts.mjs.
variants: [
// TK-11357 Fix A: sample-only until a real cost list lands (launch-pricing ADDs the priced Roll).
- { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_management: null, weight: 0.25, weight_unit: 'lb' /* TK-11414 */ },
+ { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_management: null, weight: resolveWeightLb({ sku: `${sku}-Sample`, option1: 'Sample' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
],
},
metafields: metafields(r),
diff --git a/scripts/knoll-onboard/build-payloads.mjs b/scripts/knoll-onboard/build-payloads.mjs
index ae0cf34..3a85ce1 100644
--- a/scripts/knoll-onboard/build-payloads.mjs
+++ b/scripts/knoll-onboard/build-payloads.mjs
@@ -15,6 +15,7 @@ import { fileURLToPath } from 'node:url';
import { execSync } from 'node:child_process';
import { specMetafields } from '../_spec-to-metafields.mjs';
import { keepOwnImages } from '../_own-image-filter.mjs'; // TK-11073 Wave 3 defensive image filter
+import { resolveWeightLb } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (create-side weight: no magic numbers)
const HERE = path.dirname(fileURLToPath(import.meta.url));
const OUT = path.join(HERE, 'out');
@@ -110,8 +111,8 @@ function main() {
status: 'draft', tags: tags(r) + (priced ? '' : ', quotes, Needs-Price'), body_html: bodyHtml(r),
options: [{ name: 'Format' }],
variants: [
- { sku, price: rollPrice, option1: 'Roll', taxable: true, requires_shipping: true, inventory_management: null, weight: 3, weight_unit: 'lb' /* TK-11414 */ },
- { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_management: null, weight: 0.25, weight_unit: 'lb' /* TK-11414 */ },
+ { sku, price: rollPrice, option1: 'Roll', taxable: true, requires_shipping: true, inventory_management: null, weight: resolveWeightLb({ sku: sku, option1: 'Roll' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
+ { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_management: null, weight: resolveWeightLb({ sku: `${sku}-Sample`, option1: 'Sample' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
],
},
metafields: metafields(r),
diff --git a/scripts/maharam-onboard/build-payloads.mjs b/scripts/maharam-onboard/build-payloads.mjs
index 05f8d96..4ef182a 100644
--- a/scripts/maharam-onboard/build-payloads.mjs
+++ b/scripts/maharam-onboard/build-payloads.mjs
@@ -24,6 +24,7 @@ import { fileURLToPath } from 'node:url';
import { execSync } from 'node:child_process';
import { specMetafields } from '../_spec-to-metafields.mjs';
import { keepOwnImages } from '../_own-image-filter.mjs'; // TK-11073 Wave 3 defensive image filter
+import { resolveWeightLb } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (create-side weight: no magic numbers)
const HERE = path.dirname(fileURLToPath(import.meta.url));
const OUT = path.join(HERE, 'out');
@@ -132,7 +133,7 @@ function main() {
variants: [
// SAMPLE-ONLY: the sole sellable variant is the $4.25 memo. No inventory at
// draft time (inventory_management:null) — go-live enables tracking + qty.
- { sku: `${sku}-Sample`, price: samplePrice, option1: 'Sample', taxable: true, requires_shipping: true, inventory_management: null, weight: 0.25, weight_unit: 'lb' /* TK-11414 */ },
+ { sku: `${sku}-Sample`, price: samplePrice, option1: 'Sample', taxable: true, requires_shipping: true, inventory_management: null, weight: resolveWeightLb({ sku: `${sku}-Sample`, option1: 'Sample' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
],
},
metafields: metafields(r),
diff --git a/scripts/muralsource-onboard/build-payloads.mjs b/scripts/muralsource-onboard/build-payloads.mjs
index c46652d..38b8c33 100644
--- a/scripts/muralsource-onboard/build-payloads.mjs
+++ b/scripts/muralsource-onboard/build-payloads.mjs
@@ -25,6 +25,7 @@ import { fileURLToPath } from 'node:url';
import { execSync } from 'node:child_process';
import { specMetafields } from '../_spec-to-metafields.mjs';
import { keepOwnImages } from '../_own-image-filter.mjs'; // TK-11073 Wave 3 defensive image filter
+import { resolveWeightLb } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (create-side weight: no magic numbers)
const HERE = path.dirname(fileURLToPath(import.meta.url));
const OUT = path.join(HERE, 'out');
@@ -157,8 +158,8 @@ function main() {
variants: [
// Sellable roll variant at price_retail + $4.25 memo Sample. inventory_management:null
// at draft time; go-live enables tracking + on_hand qty on BOTH variants.
- { sku, price, option1: 'Priced Per Single Roll', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: 3, weight_unit: 'lb' /* TK-11414 */ },
- { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: 0.25, weight_unit: 'lb' /* TK-11414 */ },
+ { sku, price, option1: 'Priced Per Single Roll', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: resolveWeightLb({ sku: sku, option1: 'Priced Per Single Roll' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
+ { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: resolveWeightLb({ sku: `${sku}-Sample`, option1: 'Sample' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
],
},
metafields: metafields(r),
diff --git a/scripts/osborne-onboard/build-payloads.mjs b/scripts/osborne-onboard/build-payloads.mjs
index c90d058..7876aa3 100644
--- a/scripts/osborne-onboard/build-payloads.mjs
+++ b/scripts/osborne-onboard/build-payloads.mjs
@@ -21,6 +21,7 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { execSync } from 'node:child_process';
import { specMetafields } from '../_spec-to-metafields.mjs';
+import { resolveWeightLb } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (create-side weight: no magic numbers)
const DB = 'postgresql:///dw_unified?host=/tmp';
@@ -151,8 +152,8 @@ function main() {
images: r.image_url ? [{ src: r.image_url, alt: esc(r.title_core) }] : [],
options: [{ name: 'Size' }],
variants: [
- { sku, price: String(r.retail), option1: 'Roll', taxable: true, requires_shipping: true, weight: 3, weight_unit: 'lb' /* TK-11414 */ },
- { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, weight: 0.25, weight_unit: 'lb' /* TK-11414 */ },
+ { sku, price: String(r.retail), option1: 'Roll', taxable: true, requires_shipping: true, weight: resolveWeightLb({ sku: sku, option1: 'Roll' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
+ { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, weight: resolveWeightLb({ sku: `${sku}-Sample`, option1: 'Sample' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
],
},
metafields: metafields(r, e),
diff --git a/scripts/rigo-onboard/build-payloads.mjs b/scripts/rigo-onboard/build-payloads.mjs
index b116aa0..8d1a14a 100644
--- a/scripts/rigo-onboard/build-payloads.mjs
+++ b/scripts/rigo-onboard/build-payloads.mjs
@@ -20,6 +20,7 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFileSync } from 'node:child_process';
import { specMetafields } from '../_spec-to-metafields.mjs';
+import { resolveWeightLb } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (create-side weight: no magic numbers)
const HERE = path.dirname(fileURLToPath(import.meta.url));
const OUT = path.join(HERE, 'out');
@@ -119,7 +120,7 @@ for (const r of rows) {
options: [{ name: 'Format' }],
variants: [
// TK-11357 Fix A: sample-only until a real cost list lands (launch-pricing ADDs the priced Yard).
- { sku: `${r.dw_sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_management: null, weight: 0.25, weight_unit: 'lb' /* TK-11414 */ },
+ { sku: `${r.dw_sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_management: null, weight: resolveWeightLb({ sku: `${r.dw_sku}-Sample`, option1: 'Sample' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
],
},
metafields: metafields(r),
diff --git a/scripts/stout-onboard/build-payloads.mjs b/scripts/stout-onboard/build-payloads.mjs
index 21954dd..f2afbbe 100644
--- a/scripts/stout-onboard/build-payloads.mjs
+++ b/scripts/stout-onboard/build-payloads.mjs
@@ -28,6 +28,7 @@ import { fileURLToPath } from 'node:url';
import { execSync } from 'node:child_process';
import { specMetafields } from '../_spec-to-metafields.mjs';
import { keepOwnImages } from '../_own-image-filter.mjs'; // TK-11073 Wave 3 defensive image filter
+import { resolveWeightLb } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (create-side weight: no magic numbers)
const HERE = path.dirname(fileURLToPath(import.meta.url));
const OUT = path.join(HERE, 'out');
@@ -164,8 +165,8 @@ function main() {
variants: [
// Sellable roll variant at price_retail + $4.25 memo Sample. inventory_management:null
// at draft time; go-live enables tracking + on_hand qty on BOTH variants.
- { sku, price, option1: 'Double Roll', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: 3, weight_unit: 'lb' /* TK-11414 */ },
- { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: 0.25, weight_unit: 'lb' /* TK-11414 */ },
+ { sku, price, option1: 'Double Roll', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: resolveWeightLb({ sku: sku, option1: 'Double Roll' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
+ { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: resolveWeightLb({ sku: `${sku}-Sample`, option1: 'Sample' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
],
},
metafields: metafields(r),
diff --git a/scripts/stroheim-onboard/build-payloads.mjs b/scripts/stroheim-onboard/build-payloads.mjs
index 19e64e3..82e8f36 100644
--- a/scripts/stroheim-onboard/build-payloads.mjs
+++ b/scripts/stroheim-onboard/build-payloads.mjs
@@ -23,6 +23,7 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFileSync } from 'node:child_process';
import { specMetafields } from '../_spec-to-metafields.mjs';
+import { resolveWeightLb } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (create-side weight: no magic numbers)
const HERE = path.dirname(fileURLToPath(import.meta.url));
const OUT = path.join(HERE, 'out');
@@ -132,8 +133,8 @@ function main() {
status: 'draft', tags: tags(r), body_html: bodyHtml(r),
options: [{ name: 'Size' }],
variants: [
- { sku, price, option1: 'Priced Per Single Roll', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: 3, weight_unit: 'lb' /* TK-11414 */ },
- { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: 0.25, weight_unit: 'lb' /* TK-11414 */ },
+ { sku, price, option1: 'Priced Per Single Roll', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: resolveWeightLb({ sku: sku, option1: 'Priced Per Single Roll' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
+ { sku: `${sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, inventory_policy: 'deny', inventory_management: null, weight: resolveWeightLb({ sku: `${sku}-Sample`, option1: 'Sample' }, { productType: 'Wallcovering' }), weight_unit: 'lb' /* TK-11471: shared weight-guard, no magic number */ },
],
},
metafields: metafields(r),
diff --git a/scripts/tres-tintas-onboard/push-tres-tintas-live.js b/scripts/tres-tintas-onboard/push-tres-tintas-live.js
index 81af3e1..4c20768 100644
--- a/scripts/tres-tintas-onboard/push-tres-tintas-live.js
+++ b/scripts/tres-tintas-onboard/push-tres-tintas-live.js
@@ -15,6 +15,7 @@ const https = require('https');
const fs = require('fs');
const path = require('path');
const { Client } = require('pg');
+const { resolveWeightLb } = require('../lib/weight-guard.mjs'); // GUARD TK-11471 (create-side weight: no magic numbers)
const STORE = process.env.SHOPIFY_STORE;
const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
@@ -124,8 +125,8 @@ function buildPayload(r) {
options: [{ name: 'Size' }],
variants: [
{ sku: r.dw_sku, price: String(r.dw_retail), option1: 'Roll', taxable: true, requires_shipping: true,
- inventory_management: 'shopify', cost: String(r.cost_newwall) },
- { sku: `${r.dw_sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true },
+ inventory_management: 'shopify', cost: String(r.cost_newwall), weight: resolveWeightLb({ sku: r.dw_sku, option1: 'Roll' }, { productType: 'Wallcoverings' }), weight_unit: 'lb' /* TK-11471 */ },
+ { sku: `${r.dw_sku}-Sample`, price: SAMPLE_PRICE, option1: 'Sample', taxable: true, requires_shipping: true, weight: resolveWeightLb({ sku: `${r.dw_sku}-Sample`, option1: 'Sample' }, { productType: 'Wallcoverings' }), weight_unit: 'lb' /* TK-11471 */ },
],
} };
}
diff --git a/scripts/zoffany-onboard/push-zoffany-live.js b/scripts/zoffany-onboard/push-zoffany-live.js
index ff673a9..2bba2b5 100644
--- a/scripts/zoffany-onboard/push-zoffany-live.js
+++ b/scripts/zoffany-onboard/push-zoffany-live.js
@@ -19,6 +19,7 @@
*/
const https = require('https');
const { Pool } = require('pg');
+const { resolveWeightLb } = require('../lib/weight-guard.mjs'); // GUARD TK-11471 (create-side weight: no magic numbers)
const STORE = process.env.SHOPIFY_STORE || 'designer-laboratory-sandbox.myshopify.com';
const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_PRODUCT_TOKEN;
@@ -166,6 +167,7 @@ function buildPayload(r) {
sku: r.dw_sku, price: String(r.price_retail), option1: 'Default Title',
taxable: true, requires_shipping: true,
inventory_management: null, inventory_policy: 'continue',
+ weight: resolveWeightLb({ sku: r.dw_sku, option1: 'Default Title' }, { productType: r.is_fabric ? 'Fabric' : 'Wallcovering' }), weight_unit: 'lb' /* TK-11471 */,
}],
};
// Spec metafields (TK-10039): preserve any already on the payload, no duplicate namespace.key.
diff --git a/verification/tk11471/create-weight-wiring.test.mjs b/verification/tk11471/create-weight-wiring.test.mjs
new file mode 100644
index 0000000..7e0e38a
--- /dev/null
+++ b/verification/tk11471/create-weight-wiring.test.mjs
@@ -0,0 +1,155 @@
+#!/usr/bin/env node
+/**
+ * create-weight-wiring.test.mjs — TK-11471. Offline proof that every create-payload site emits a
+ * real positive weight from the SHARED module. node verification/tk11471/create-weight-wiring.test.mjs
+ *
+ * Three layers, because a static grep alone would have accepted `weight: 0`:
+ * 1. STATIC — every variant literal carries weight + weight_unit:'lb', sourced from
+ * weight-guard (no magic numbers left).
+ * 2. DRIFT — the productType handed to resolveWeightLb matches the payload's own product_type
+ * literal, so the two cannot silently diverge.
+ * 3. BEHAVIOUR — each resolveWeightLb(...) expression is lifted out of the source and EVALUATED
+ * against the real module with realistic bindings; the result must be > 0 and equal
+ * the approved default for that product type / sample.
+ * No network, no DB, no Shopify.
+ */
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import * as WG from '../../scripts/lib/weight-guard.mjs';
+
+const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../scripts');
+
+export const CREATE_SITES = [
+ // (B) had NO weight at all before TK-11471
+ 'artmura-onboard/push-artmura-live.js',
+ 'fallingstar-onboard/push-fallingstar-live.js',
+ 'tres-tintas-onboard/push-tres-tintas-live.js',
+ 'zoffany-onboard/push-zoffany-live.js',
+ // (C) carried hardcoded literal weights (3 / 0.25), now the shared module
+ 'greenland-onboard/build-payloads.mjs',
+ 'knoll-onboard/build-payloads.mjs',
+ 'maharam-onboard/build-payloads.mjs',
+ 'muralsource-onboard/build-payloads.mjs',
+ 'osborne-onboard/build-payloads.mjs',
+ 'rigo-onboard/build-payloads.mjs',
+ 'stout-onboard/build-payloads.mjs',
+ 'stroheim-onboard/build-payloads.mjs',
+];
+
+let pass = 0, fail = 0;
+const ok = (n, c, d = '') => { if (c) { pass++; console.log(` ok ${n}`); } else { fail++; console.error(` FAIL ${n}${d ? ' — ' + d : ''}`); } };
+
+/** Pull every `resolveWeightLb( ... )` call out of source, balanced-paren safe. */
+export function extractCalls(src) {
+ const out = [];
+ let i = 0;
+ while ((i = src.indexOf('resolveWeightLb(', i)) !== -1) {
+ const start = i + 'resolveWeightLb'.length;
+ let depth = 0, j = start;
+ for (; j < src.length; j++) {
+ if (src[j] === '(') depth++;
+ else if (src[j] === ')') { depth--; if (depth === 0) { j++; break; } }
+ }
+ out.push(src.slice(i, j));
+ i = j;
+ }
+ return out;
+}
+
+/** Balanced-brace extraction of every `{ sku ... }` variant literal (multi-line safe). */
+export function extractVariantLiterals(src) {
+ const out = [];
+ const re = /\{\s*sku[\s:,]/g;
+ let m;
+ while ((m = re.exec(src))) {
+ let depth = 0, j = m.index;
+ for (; j < src.length; j++) {
+ if (src[j] === '{') depth++;
+ else if (src[j] === '}') { depth--; if (depth === 0) { j++; break; } }
+ }
+ out.push(src.slice(m.index, j));
+ re.lastIndex = j;
+ }
+ return out;
+}
+
+/** Does this variant literal carry a weight sourced from the guard? */
+export function auditCreateSource(src) {
+ const missing = [];
+ const importsGuard = /weight-guard\.mjs/.test(src);
+ if (!importsGuard) missing.push('imports weight-guard');
+ // Any variant OBJECT LITERAL that sets a sku must also set weight + weight_unit. Scan balanced
+ // braces, not lines — tres-tintas' sellable variant spans two lines, and a line-based check
+ // reported it as weightless when it was fine (caught by this suite on the first run).
+ const variantLines = extractVariantLiterals(src).filter(v => /option1:/.test(v));
+ for (const l of variantLines) {
+ if (!/weight:/.test(l)) missing.push('variant literal with no weight');
+ else if (!/weight_unit: 'lb'/.test(l)) missing.push('variant weight with no lb unit');
+ else if (/weight:\s*[\d.]+\s*,/.test(l)) missing.push('magic-number weight');
+ }
+ if (!variantLines.length && !/resolveWeightLb\(/.test(src)) missing.push('no weighted variant found');
+ return { ok: missing.length === 0, missing: [...new Set(missing)], variantLines: variantLines.length };
+}
+
+console.log('── (1) STATIC: every create site emits a guard-sourced weight');
+for (const rel of CREATE_SITES) {
+ const p = path.join(ROOT, rel);
+ if (!fs.existsSync(p)) { fail++; console.error(` FAIL ${rel} — missing`); continue; }
+ const r = auditCreateSource(fs.readFileSync(p, 'utf8'));
+ ok(rel, r.ok, r.missing.join(', '));
+}
+
+console.log('\n── (2) DRIFT: resolveWeightLb productType matches the payload product_type literal');
+for (const rel of CREATE_SITES.filter(f => f.endsWith('build-payloads.mjs'))) {
+ const src = fs.readFileSync(path.join(ROOT, rel), 'utf8');
+ const declared = (src.match(/product_type: '([^']+)'/) || [])[1];
+ const used = [...new Set(extractCalls(src).map(c => (c.match(/productType: '([^']+)'/) || [])[1]))];
+ ok(`${rel} (${declared})`, used.length === 1 && used[0] === declared, `declared=${declared} used=${JSON.stringify(used)}`);
+}
+
+console.log('\n── (3) BEHAVIOUR: each expression really evaluates to a positive, correct weight');
+{
+ // Realistic bindings for the identifiers the extracted expressions close over.
+ const bind = {
+ sku: 'DWX-1001', dwSku: 'DWX-1001',
+ r: { dw_sku: 'DWX-1001', product_type: null, is_fabric: false },
+ };
+ let evaluated = 0;
+ for (const rel of CREATE_SITES) {
+ const src = fs.readFileSync(path.join(ROOT, rel), 'utf8');
+ for (const call of extractCalls(src)) {
+ const fn = new Function('resolveWeightLb', 'sku', 'dwSku', 'r', `return ${call};`);
+ const lb = fn(WG.resolveWeightLb, bind.sku, bind.dwSku, bind.r);
+ const isSample = /-Sample`|option1: 'Sample'/.test(call);
+ const pt = (call.match(/productType: '([^']+)'/) || [])[1]
+ || (/Wallcoverings'/.test(call) ? 'Wallcoverings' : 'Wallcovering');
+ const want = isSample ? WG.SAMPLE_WEIGHT_LB : (WG.TYPE_DEFAULT_LB[pt] ?? WG.FALLBACK_LB);
+ ok(`${rel} :: ${isSample ? 'sample' : 'sellable'} => ${lb} lb`, lb > 0 && Math.abs(lb - want) < 1e-9, `want ${want}`);
+ evaluated++;
+ }
+ }
+ ok(`evaluated ${evaluated} expressions across ${CREATE_SITES.length} sites`, evaluated >= 20, `only ${evaluated}`);
+}
+
+console.log('\n── (4) NEGATIVE: the detector goes red on each injected fault');
+{
+ const good = fs.readFileSync(path.join(ROOT, 'knoll-onboard/build-payloads.mjs'), 'utf8');
+ ok('control: the real file passes', auditCreateSource(good).ok, JSON.stringify(auditCreateSource(good).missing));
+
+ const f1 = good.replace(/, weight: resolveWeightLb\([^\n]*?weight_unit: 'lb'[^\n]*?\*\//, '');
+ ok('a variant loses its weight => red', !auditCreateSource(f1).ok && auditCreateSource(f1).missing.includes('variant literal with no weight'));
+
+ const f2 = good.replace(/weight: resolveWeightLb\(\{ sku: sku[^\n]*?\}\), /, 'weight: 3, ');
+ ok('a magic-number weight returns => red', !auditCreateSource(f2).ok && auditCreateSource(f2).missing.includes('magic-number weight'), JSON.stringify(auditCreateSource(f2).missing));
+
+ const f3 = good.replace(/^import \{ resolveWeightLb \}.*$/m, '');
+ ok('the guard import is dropped => red', !auditCreateSource(f3).ok && auditCreateSource(f3).missing.includes('imports weight-guard'));
+
+ // BEHAVIOURAL negative: a zero weight must never be accepted as "wired"
+ ok('resolveWeightLb can never return 0 for a sellable', WG.resolveWeightLb({ sku: 'X', option1: 'Roll', weight: 0 }, { productType: 'Wallcovering' }) > 0);
+ ok('resolveWeightLb can never return 0 for a sample', WG.resolveWeightLb({ sku: 'X-Sample', option1: 'Sample' }, { productType: 'Wallcovering' }) > 0);
+}
+
+console.log(`\n${fail === 0 ? 'PASS' : 'FAIL'} — ${pass} passed, ${fail} failed`);
+process.exit(fail === 0 ? 0 : 1);
← 6a9e28e TK-11471: weight go-live gate on the 9 unguarded ACTIVATE si
·
back to Designerwallcoverings
·
TK-11471: Python twin of the weight guard (lib/weight_guard. e5b2795 →