[object Object]

← back to Designerwallcoverings

TK-11471: weight go-live gate on the 9 unguarded ACTIVATE sites

6a9e28e2f8e0fc14f8b04ec2d1027649d0c9a905 · 2026-09-11 11:28:12 -0700 · Steve Abrams

artmura, fallingstar, knoll, maharam, muralsource, osborne, stout, stroheim, tres-tintas.
sanderson was already wired and is the reference.

Per site, immediately before productUpdate(status:ACTIVE):
  - the product query now selects inventoryItem{id measurement{weight{value unit}}} and
    productType. NONE of the nine selected weight before, so a gate dropped in without this
    would have measured nothing and silently passed — the CLAUDE.md false-green class.
  - healAndVerifyWeights() SELF-HEALS every zero-weight variant to the approved default, then
    RE-VERIFIES. Still zero (or unhealable) => the product is HELD as draft with 'weight>0',
    never activated. Idempotent: a fully weighted product makes zero extra API calls.

Each site keeps its own style (.js/.mjs, its own gql, its own result shape — fallingstar
returns the errs array its caller reads, the rest return { heldZeroWeight, errs }). No
inventory, price or publication logic touched; the TK-11357 /bin/zsh guard is untouched.

verification/tk11471/golive-wiring.test.mjs asserts all ten sites are wired AND carries five
negative tests (drop the import / drop measurement.weight / drop productType / delete the gate /
move it after the mutation) that each prove the detector goes red.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJHxAzaEMMxado57mFjiCk

Files touched

Diff

commit 6a9e28e2f8e0fc14f8b04ec2d1027649d0c9a905
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Sep 11 11:28:12 2026 -0700

    TK-11471: weight go-live gate on the 9 unguarded ACTIVATE sites
    
    artmura, fallingstar, knoll, maharam, muralsource, osborne, stout, stroheim, tres-tintas.
    sanderson was already wired and is the reference.
    
    Per site, immediately before productUpdate(status:ACTIVE):
      - the product query now selects inventoryItem{id measurement{weight{value unit}}} and
        productType. NONE of the nine selected weight before, so a gate dropped in without this
        would have measured nothing and silently passed — the CLAUDE.md false-green class.
      - healAndVerifyWeights() SELF-HEALS every zero-weight variant to the approved default, then
        RE-VERIFIES. Still zero (or unhealable) => the product is HELD as draft with 'weight>0',
        never activated. Idempotent: a fully weighted product makes zero extra API calls.
    
    Each site keeps its own style (.js/.mjs, its own gql, its own result shape — fallingstar
    returns the errs array its caller reads, the rest return { heldZeroWeight, errs }). No
    inventory, price or publication logic touched; the TK-11357 /bin/zsh guard is untouched.
    
    verification/tk11471/golive-wiring.test.mjs asserts all ten sites are wired AND carries five
    negative tests (drop the import / drop measurement.weight / drop productType / delete the gate /
    move it after the mutation) that each prove the detector goes red.
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01FJHxAzaEMMxado57mFjiCk
---
 scripts/artmura-onboard/go-live-artmura.js         |  12 ++-
 scripts/fallingstar-onboard/go-live-fallingstar.js |  12 ++-
 scripts/knoll-onboard/go-live.mjs                  |  12 ++-
 scripts/maharam-onboard/go-live.mjs                |  12 ++-
 scripts/muralsource-onboard/go-live.mjs            |  12 ++-
 scripts/osborne-onboard/go-live.mjs                |  12 ++-
 scripts/stout-onboard/go-live.mjs                  |  12 ++-
 scripts/stroheim-onboard/go-live.mjs               |  12 ++-
 scripts/tres-tintas-onboard/go-live-tres-tintas.js |  12 ++-
 verification/tk11471/golive-wiring.test.mjs        | 103 +++++++++++++++++++++
 10 files changed, 202 insertions(+), 9 deletions(-)

diff --git a/scripts/artmura-onboard/go-live-artmura.js b/scripts/artmura-onboard/go-live-artmura.js
index ad007fe..0d9ed11 100644
--- a/scripts/artmura-onboard/go-live-artmura.js
+++ b/scripts/artmura-onboard/go-live-artmura.js
@@ -62,8 +62,9 @@ async function gql(query, variables = {}) {
   }
 }
 
-const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status vendor tags variants(first:10){edges{node{id sku title price inventoryItem{id}}}}}}`;
+const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status productType vendor tags variants(first:10){edges{node{id sku title price inventoryItem{id measurement{weight{value unit}}}}}}}}`;
 const { safeStampQuantity } = require('../lib/inventory-stamp-guard.mjs');  // GUARD TK-11357 (shared guard)
+const { healAndVerifyWeights } = require('../lib/weight-guard.mjs');  // GUARD TK-11471 (weight go-live: self-heal then verify)
 // ── GUARD TK-11357 BEGIN ─ do not edit without re-running the fixture proof ──────────
 // A $0 / quote-only sellable variant must NEVER receive positive stock: positive stock is what
 // flips availableForSale=true, making it checkout-orderable at $0 (lineage TK-10825 -> 10965 ->
@@ -127,6 +128,15 @@ async function goLive(pid) {
     if (!((_dh?.product?.descriptionHtml || '').replace(/<[^>]*>/g, '').trim())) {
       console.error('  ⛔ HELD (empty description — TK-11237 go-live gate): ' + gidP);
       return { heldNoDesc: true, errs: ['heldNoDesc: empty description'] }; } }
+  // TK-11471 (TK-11414 follow-on) WEIGHT go-live gate: never flip a product ACTIVE at zero weight —
+  // zero collapses the order into the lowest weight tier / free-shipping band and mis-costs DW
+  // freight. SELF-HEAL first (assign the already-approved default rather than strand the product,
+  // per sanderson-onboard create_sdg.mjs 8d09eed), then RE-VERIFY; still zero ⇒ HOLD as draft.
+  { const _w = await healAndVerifyWeights(gql, gidP, d.product);
+    _w.errs.forEach(e => errs.push(e));
+    if (!_w.ok) {
+      console.error('  ⛔ HELD (weight>0 — TK-11414 weight go-live gate): ' + gidP + (_w.stillZero.length ? ' [' + _w.stillZero.join(',') + ']' : ''));
+      return { heldZeroWeight: true, errs: [...errs, 'heldZeroWeight: weight>0'] }; } }
   const r4 = await gql(M_ACTIVE, { id: gidP });
   (r4.productUpdate?.userErrors || []).forEach(e => errs.push('active:' + e.message));
   return { status: r4.productUpdate?.product?.status, errs };
diff --git a/scripts/fallingstar-onboard/go-live-fallingstar.js b/scripts/fallingstar-onboard/go-live-fallingstar.js
index aae60e3..022e344 100644
--- a/scripts/fallingstar-onboard/go-live-fallingstar.js
+++ b/scripts/fallingstar-onboard/go-live-fallingstar.js
@@ -56,8 +56,9 @@ async function gql(query, variables = {}) {
     } catch (e) { if (a === 3) throw e; await sleep(900 * (a + 1)); }
   }
 }
-const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status vendor tags variants(first:10){edges{node{id sku title price inventoryItem{id}}}}}}`;
+const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status productType vendor tags variants(first:10){edges{node{id sku title price inventoryItem{id measurement{weight{value unit}}}}}}}}`;
 const { safeStampQuantity } = require('../lib/inventory-stamp-guard.mjs');  // GUARD TK-11357 (shared guard)
+const { healAndVerifyWeights } = require('../lib/weight-guard.mjs');  // GUARD TK-11471 (weight go-live: self-heal then verify)
 // ── GUARD TK-11357 BEGIN ─ do not edit without re-running the fixture proof ──────────
 // A $0 / quote-only sellable variant must NEVER receive positive stock: positive stock is what
 // flips availableForSale=true, making it checkout-orderable at $0 (lineage TK-10825 -> 10965 ->
@@ -118,6 +119,15 @@ async function goLive(pid) {
     if (!((_dh?.product?.descriptionHtml || '').replace(/<[^>]*>/g, '').trim())) {
       console.error('  ⛔ HELD (empty description — TK-11237 go-live gate): ' + gidP);
       return { heldNoDesc: true, errs: ['heldNoDesc: empty description'] }; } }
+  // TK-11471 (TK-11414 follow-on) WEIGHT go-live gate: never flip a product ACTIVE at zero weight —
+  // zero collapses the order into the lowest weight tier / free-shipping band and mis-costs DW
+  // freight. SELF-HEAL first (assign the already-approved default rather than strand the product,
+  // per sanderson-onboard create_sdg.mjs 8d09eed), then RE-VERIFY; still zero ⇒ HOLD as draft.
+  { const _w = await healAndVerifyWeights(gql, gidP, d.product);
+    _w.errs.forEach(e => errs.push(e));
+    if (!_w.ok) {
+      console.error('  ⛔ HELD (weight>0 — TK-11414 weight go-live gate): ' + gidP + (_w.stillZero.length ? ' [' + _w.stillZero.join(',') + ']' : ''));
+      return [...errs, 'heldZeroWeight: weight>0']; } }
   const r4 = await gql(M_ACTIVE, { id: gidP });
   (r4.productUpdate?.userErrors || []).forEach(e => errs.push('active:' + e.message));
   return errs;
diff --git a/scripts/knoll-onboard/go-live.mjs b/scripts/knoll-onboard/go-live.mjs
index aac6e05..7b76147 100644
--- a/scripts/knoll-onboard/go-live.mjs
+++ b/scripts/knoll-onboard/go-live.mjs
@@ -37,8 +37,9 @@ const PUBLICATIONS = [
 ].map(n => `gid://shopify/Publication/${n}`);
 const sleep = ms => new Promise(r => setTimeout(r, ms));
 
-const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status handle vendor tags variants(first:10){edges{node{title price inventoryItem{id}}}}}}`;
+const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status productType handle vendor tags variants(first:10){edges{node{sku title price inventoryItem{id measurement{weight{value unit}}}}}}}}`;
 import { safeStampQuantity } from '../lib/inventory-stamp-guard.mjs';  // GUARD TK-11357 (shared guard)
+import { healAndVerifyWeights } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (weight go-live: self-heal then verify)
 // ── GUARD TK-11357 BEGIN ─ do not edit without re-running the fixture proof ──────────
 // A $0 / quote-only sellable variant must NEVER receive positive stock: positive stock is what
 // flips availableForSale=true, making it checkout-orderable at $0 (lineage TK-10825 -> 10965 ->
@@ -116,6 +117,15 @@ async function goLive(pid, sku) {
     if (!((_dh?.product?.descriptionHtml || '').replace(/<[^>]*>/g, '').trim())) {
       console.error('  ⛔ HELD (empty description — TK-11237 go-live gate): ' + gidP);
       return { heldNoDesc: true, errs: ['heldNoDesc: empty description'] }; } }
+  // TK-11471 (TK-11414 follow-on) WEIGHT go-live gate: never flip a product ACTIVE at zero weight —
+  // zero collapses the order into the lowest weight tier / free-shipping band and mis-costs DW
+  // freight. SELF-HEAL first (assign the already-approved default rather than strand the product,
+  // per sanderson-onboard create_sdg.mjs 8d09eed), then RE-VERIFY; still zero ⇒ HOLD as draft.
+  { const _w = await healAndVerifyWeights(gql, gidP, d.product);
+    _w.errs.forEach(e => errs.push(e));
+    if (!_w.ok) {
+      console.error('  ⛔ HELD (weight>0 — TK-11414 weight go-live gate): ' + gidP + (_w.stillZero.length ? ' [' + _w.stillZero.join(',') + ']' : ''));
+      return { heldZeroWeight: true, errs: [...errs, 'heldZeroWeight: weight>0'] }; } }
   const r4 = await gql(M_ACTIVE, { id: gidP });
   (r4.productUpdate?.userErrors || []).forEach(e => errs.push('active:' + e.message));
   const status = r4.productUpdate?.product?.status;
diff --git a/scripts/maharam-onboard/go-live.mjs b/scripts/maharam-onboard/go-live.mjs
index dd989c7..28d8314 100644
--- a/scripts/maharam-onboard/go-live.mjs
+++ b/scripts/maharam-onboard/go-live.mjs
@@ -40,8 +40,9 @@ const PUBLICATIONS = [
 ].map(n => `gid://shopify/Publication/${n}`);
 const sleep = ms => new Promise(r => setTimeout(r, ms));
 
-const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status handle vendor tags variants(first:10){edges{node{title price inventoryItem{id}}}}}}`;
+const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status productType handle vendor tags variants(first:10){edges{node{sku title price inventoryItem{id measurement{weight{value unit}}}}}}}}`;
 import { safeStampQuantity } from '../lib/inventory-stamp-guard.mjs';  // GUARD TK-11357 (shared guard)
+import { healAndVerifyWeights } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (weight go-live: self-heal then verify)
 // ── GUARD TK-11357 BEGIN ─ do not edit without re-running the fixture proof ──────────
 // A $0 / quote-only sellable variant must NEVER receive positive stock: positive stock is what
 // flips availableForSale=true, making it checkout-orderable at $0 (lineage TK-10825 -> 10965 ->
@@ -121,6 +122,15 @@ async function goLive(pid, sku, mfrSku) {
     if (!((_dh?.product?.descriptionHtml || '').replace(/<[^>]*>/g, '').trim())) {
       console.error('  ⛔ HELD (empty description — TK-11237 go-live gate): ' + gidP);
       return { heldNoDesc: true, errs: ['heldNoDesc: empty description'] }; } }
+  // TK-11471 (TK-11414 follow-on) WEIGHT go-live gate: never flip a product ACTIVE at zero weight —
+  // zero collapses the order into the lowest weight tier / free-shipping band and mis-costs DW
+  // freight. SELF-HEAL first (assign the already-approved default rather than strand the product,
+  // per sanderson-onboard create_sdg.mjs 8d09eed), then RE-VERIFY; still zero ⇒ HOLD as draft.
+  { const _w = await healAndVerifyWeights(gql, gidP, d.product);
+    _w.errs.forEach(e => errs.push(e));
+    if (!_w.ok) {
+      console.error('  ⛔ HELD (weight>0 — TK-11414 weight go-live gate): ' + gidP + (_w.stillZero.length ? ' [' + _w.stillZero.join(',') + ']' : ''));
+      return { heldZeroWeight: true, errs: [...errs, 'heldZeroWeight: weight>0'] }; } }
   const r4 = await gql(M_ACTIVE, { id: gidP });
   (r4.productUpdate?.userErrors || []).forEach(e => errs.push('active:' + e.message));
   const status = r4.productUpdate?.product?.status;
diff --git a/scripts/muralsource-onboard/go-live.mjs b/scripts/muralsource-onboard/go-live.mjs
index 3753c22..37346a0 100644
--- a/scripts/muralsource-onboard/go-live.mjs
+++ b/scripts/muralsource-onboard/go-live.mjs
@@ -45,8 +45,9 @@ const PUBLICATIONS = [
 ].map(n => `gid://shopify/Publication/${n}`);
 const sleep = ms => new Promise(r => setTimeout(r, ms));
 
-const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status handle vendor tags variants(first:10){edges{node{title price inventoryItem{id}}}}}}`;
+const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status productType handle vendor tags variants(first:10){edges{node{sku title price inventoryItem{id measurement{weight{value unit}}}}}}}}`;
 import { safeStampQuantity } from '../lib/inventory-stamp-guard.mjs';  // GUARD TK-11357 (shared guard)
+import { healAndVerifyWeights } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (weight go-live: self-heal then verify)
 // ── GUARD TK-11357 BEGIN ─ do not edit without re-running the fixture proof ──────────
 // A $0 / quote-only sellable variant must NEVER receive positive stock: positive stock is what
 // flips availableForSale=true, making it checkout-orderable at $0 (lineage TK-10825 -> 10965 ->
@@ -134,6 +135,15 @@ async function goLive(pid, sku, mfrSku) {
     if (!((_mf?.product?.c?.value || _mf?.product?.d?.value || '').trim())) {
       console.error('  ⛔ HELD (no manufacturer_sku metafield — TK-10303 go-live gate): ' + gidP);
       return { heldNoMfr: true }; } }
+  // TK-11471 (TK-11414 follow-on) WEIGHT go-live gate: never flip a product ACTIVE at zero weight —
+  // zero collapses the order into the lowest weight tier / free-shipping band and mis-costs DW
+  // freight. SELF-HEAL first (assign the already-approved default rather than strand the product,
+  // per sanderson-onboard create_sdg.mjs 8d09eed), then RE-VERIFY; still zero ⇒ HOLD as draft.
+  { const _w = await healAndVerifyWeights(gql, gidP, d.product);
+    _w.errs.forEach(e => errs.push(e));
+    if (!_w.ok) {
+      console.error('  ⛔ HELD (weight>0 — TK-11414 weight go-live gate): ' + gidP + (_w.stillZero.length ? ' [' + _w.stillZero.join(',') + ']' : ''));
+      return { heldZeroWeight: true, errs: [...errs, 'heldZeroWeight: weight>0'] }; } }
   const r4 = await gql(M_ACTIVE, { id: gidP });
   (r4.productUpdate?.userErrors || []).forEach(e => errs.push('active:' + e.message));
   const status = r4.productUpdate?.product?.status;
diff --git a/scripts/osborne-onboard/go-live.mjs b/scripts/osborne-onboard/go-live.mjs
index 6608384..87dd258 100644
--- a/scripts/osborne-onboard/go-live.mjs
+++ b/scripts/osborne-onboard/go-live.mjs
@@ -51,8 +51,9 @@ async function gql(query, variables = {}) {
     } catch (e) { if (a === 3) throw e; await sleep(900 * (a + 1)); }
   }
 }
-const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status vendor tags variants(first:10){edges{node{title price inventoryItem{id}}}}}}`;
+const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status productType vendor tags variants(first:10){edges{node{sku title price inventoryItem{id measurement{weight{value unit}}}}}}}}`;
 import { safeStampQuantity } from '../lib/inventory-stamp-guard.mjs';  // GUARD TK-11357 (shared guard)
+import { healAndVerifyWeights } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (weight go-live: self-heal then verify)
 // ── GUARD TK-11357 BEGIN ─ do not edit without re-running the fixture proof ──────────
 // A $0 / quote-only sellable variant must NEVER receive positive stock: positive stock is what
 // flips availableForSale=true, making it checkout-orderable at $0 (lineage TK-10825 -> 10965 ->
@@ -114,6 +115,15 @@ async function goLive(pid) {
     if (!((_dh?.product?.descriptionHtml || '').replace(/<[^>]*>/g, '').trim())) {
       console.error('  ⛔ HELD (empty description — TK-11237 go-live gate): ' + gidP);
       return { heldNoDesc: true, errs: ['heldNoDesc: empty description'] }; } }
+  // TK-11471 (TK-11414 follow-on) WEIGHT go-live gate: never flip a product ACTIVE at zero weight —
+  // zero collapses the order into the lowest weight tier / free-shipping band and mis-costs DW
+  // freight. SELF-HEAL first (assign the already-approved default rather than strand the product,
+  // per sanderson-onboard create_sdg.mjs 8d09eed), then RE-VERIFY; still zero ⇒ HOLD as draft.
+  { const _w = await healAndVerifyWeights(gql, gidP, d.product);
+    _w.errs.forEach(e => errs.push(e));
+    if (!_w.ok) {
+      console.error('  ⛔ HELD (weight>0 — TK-11414 weight go-live gate): ' + gidP + (_w.stillZero.length ? ' [' + _w.stillZero.join(',') + ']' : ''));
+      return { heldZeroWeight: true, errs: [...errs, 'heldZeroWeight: weight>0'] }; } }
   const r4 = await gql(M_ACTIVE, { id: gidP });
   (r4.productUpdate?.userErrors || []).forEach(e => errs.push('active:' + e.message));
   return { status: r4.productUpdate?.product?.status, errs };
diff --git a/scripts/stout-onboard/go-live.mjs b/scripts/stout-onboard/go-live.mjs
index f429d8d..69d7219 100644
--- a/scripts/stout-onboard/go-live.mjs
+++ b/scripts/stout-onboard/go-live.mjs
@@ -48,8 +48,9 @@ const PUBLICATIONS = [
 ].map(n => `gid://shopify/Publication/${n}`);
 const sleep = ms => new Promise(r => setTimeout(r, ms));
 
-const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status handle vendor tags variants(first:10){edges{node{title price inventoryItem{id}}}}}}`;
+const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status productType handle vendor tags variants(first:10){edges{node{sku title price inventoryItem{id measurement{weight{value unit}}}}}}}}`;
 import { safeStampQuantity } from '../lib/inventory-stamp-guard.mjs';  // GUARD TK-11357 (shared guard)
+import { healAndVerifyWeights } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (weight go-live: self-heal then verify)
 // ── GUARD TK-11357 BEGIN ─ do not edit without re-running the fixture proof ──────────
 // A $0 / quote-only sellable variant must NEVER receive positive stock: positive stock is what
 // flips availableForSale=true, making it checkout-orderable at $0 (lineage TK-10825 -> 10965 ->
@@ -137,6 +138,15 @@ async function goLive(pid, sku, mfrSku) {
     if (!((_mf?.product?.c?.value || _mf?.product?.d?.value || '').trim())) {
       console.error('  ⛔ HELD (no manufacturer_sku metafield — TK-10303 go-live gate): ' + gidP);
       return { heldNoMfr: true }; } }
+  // TK-11471 (TK-11414 follow-on) WEIGHT go-live gate: never flip a product ACTIVE at zero weight —
+  // zero collapses the order into the lowest weight tier / free-shipping band and mis-costs DW
+  // freight. SELF-HEAL first (assign the already-approved default rather than strand the product,
+  // per sanderson-onboard create_sdg.mjs 8d09eed), then RE-VERIFY; still zero ⇒ HOLD as draft.
+  { const _w = await healAndVerifyWeights(gql, gidP, d.product);
+    _w.errs.forEach(e => errs.push(e));
+    if (!_w.ok) {
+      console.error('  ⛔ HELD (weight>0 — TK-11414 weight go-live gate): ' + gidP + (_w.stillZero.length ? ' [' + _w.stillZero.join(',') + ']' : ''));
+      return { heldZeroWeight: true, errs: [...errs, 'heldZeroWeight: weight>0'] }; } }
   const r4 = await gql(M_ACTIVE, { id: gidP });
   (r4.productUpdate?.userErrors || []).forEach(e => errs.push('active:' + e.message));
   const status = r4.productUpdate?.product?.status;
diff --git a/scripts/stroheim-onboard/go-live.mjs b/scripts/stroheim-onboard/go-live.mjs
index 9985a26..f6d2c6e 100644
--- a/scripts/stroheim-onboard/go-live.mjs
+++ b/scripts/stroheim-onboard/go-live.mjs
@@ -50,8 +50,9 @@ const PUBLICATIONS = [
 ].map(n => `gid://shopify/Publication/${n}`);
 const sleep = ms => new Promise(r => setTimeout(r, ms));
 
-const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status handle vendor tags variants(first:10){edges{node{title price inventoryItem{id}}}}}}`;
+const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status productType handle vendor tags variants(first:10){edges{node{sku title price inventoryItem{id measurement{weight{value unit}}}}}}}}`;
 import { safeStampQuantity } from '../lib/inventory-stamp-guard.mjs';  // GUARD TK-11357 (shared guard)
+import { healAndVerifyWeights } from '../lib/weight-guard.mjs'; // GUARD TK-11471 (weight go-live: self-heal then verify)
 // ── GUARD TK-11357 BEGIN ─ do not edit without re-running the fixture proof ──────────
 // A $0 / quote-only sellable variant must NEVER receive positive stock: positive stock is what
 // flips availableForSale=true, making it checkout-orderable at $0 (lineage TK-10825 -> 10965 ->
@@ -138,6 +139,15 @@ async function goLive(pid, sku, mfrSku) {
     if (!((_mf?.product?.c?.value || _mf?.product?.d?.value || '').trim())) {
       console.error('  ⛔ HELD (no manufacturer_sku metafield — TK-10303 go-live gate): ' + gidP);
       return { heldNoMfr: true }; } }
+  // TK-11471 (TK-11414 follow-on) WEIGHT go-live gate: never flip a product ACTIVE at zero weight —
+  // zero collapses the order into the lowest weight tier / free-shipping band and mis-costs DW
+  // freight. SELF-HEAL first (assign the already-approved default rather than strand the product,
+  // per sanderson-onboard create_sdg.mjs 8d09eed), then RE-VERIFY; still zero ⇒ HOLD as draft.
+  { const _w = await healAndVerifyWeights(gql, gidP, d.product);
+    _w.errs.forEach(e => errs.push(e));
+    if (!_w.ok) {
+      console.error('  ⛔ HELD (weight>0 — TK-11414 weight go-live gate): ' + gidP + (_w.stillZero.length ? ' [' + _w.stillZero.join(',') + ']' : ''));
+      return { heldZeroWeight: true, errs: [...errs, 'heldZeroWeight: weight>0'] }; } }
   const r4 = await gql(M_ACTIVE, { id: gidP });
   (r4.productUpdate?.userErrors || []).forEach(e => errs.push('active:' + e.message));
   const status = r4.productUpdate?.product?.status;
diff --git a/scripts/tres-tintas-onboard/go-live-tres-tintas.js b/scripts/tres-tintas-onboard/go-live-tres-tintas.js
index 2be4bb8..5e93b85 100644
--- a/scripts/tres-tintas-onboard/go-live-tres-tintas.js
+++ b/scripts/tres-tintas-onboard/go-live-tres-tintas.js
@@ -58,8 +58,9 @@ async function gql(query, variables = {}) {
     } catch (e) { if (a === 3) throw e; await sleep(900 * (a + 1)); }
   }
 }
-const Q_V = `query($id:ID!){ product(id:$id){ status vendor tags variants(first:10){edges{node{id title price inventoryItem{id}}}} } }`;
+const Q_V = `query($id:ID!){ product(id:$id){ status productType vendor tags variants(first:10){edges{node{sku id title price inventoryItem{id measurement{weight{value unit}}}}}} } }`;
 const { safeStampQuantity } = require('../lib/inventory-stamp-guard.mjs');  // GUARD TK-11357 (shared guard)
+const { healAndVerifyWeights } = require('../lib/weight-guard.mjs');  // GUARD TK-11471 (weight go-live: self-heal then verify)
 // ── GUARD TK-11357 BEGIN ─ do not edit without re-running the fixture proof ──────────
 // A $0 / quote-only sellable variant must NEVER receive positive stock: positive stock is what
 // flips availableForSale=true, making it checkout-orderable at $0 (lineage TK-10825 -> 10965 ->
@@ -120,6 +121,15 @@ async function goLive(pid) {
     if (!((_dh?.product?.descriptionHtml || '').replace(/<[^>]*>/g, '').trim())) {
       console.error('  ⛔ HELD (empty description — TK-11237 go-live gate): ' + gid);
       return { heldNoDesc: true, errs: ['heldNoDesc: empty description'] }; } }
+  // TK-11471 (TK-11414 follow-on) WEIGHT go-live gate: never flip a product ACTIVE at zero weight —
+  // zero collapses the order into the lowest weight tier / free-shipping band and mis-costs DW
+  // freight. SELF-HEAL first (assign the already-approved default rather than strand the product,
+  // per sanderson-onboard create_sdg.mjs 8d09eed), then RE-VERIFY; still zero ⇒ HOLD as draft.
+  { const _w = await healAndVerifyWeights(gql, gid, d.product);
+    _w.errs.forEach(e => errs.push(e));
+    if (!_w.ok) {
+      console.error('  ⛔ HELD (weight>0 — TK-11414 weight go-live gate): ' + gid + (_w.stillZero.length ? ' [' + _w.stillZero.join(',') + ']' : ''));
+      return { heldZeroWeight: true, errs: [...errs, 'heldZeroWeight: weight>0'] }; } }
   const ra = await gql(M_ACTIVE, { id: gid });
   (ra.productUpdate?.userErrors || []).forEach(e => errs.push('active:' + e.message));
   return { errs };
diff --git a/verification/tk11471/golive-wiring.test.mjs b/verification/tk11471/golive-wiring.test.mjs
new file mode 100644
index 0000000..2b3ee09
--- /dev/null
+++ b/verification/tk11471/golive-wiring.test.mjs
@@ -0,0 +1,103 @@
+#!/usr/bin/env node
+/**
+ * golive-wiring.test.mjs — TK-11471. Offline static assertion that EVERY go-live site is wired to
+ * the weight gate.  node verification/tk11471/golive-wiring.test.mjs
+ *
+ * This is the check that catches the specific false-green named in the brief: a gate that runs
+ * against a product query which does NOT select inventoryItem{measurement{weight}} measures
+ * nothing and silently passes. So the wiring test asserts FOUR things per site, not one:
+ *   1. it imports healAndVerifyWeights from the shared module (no re-implementation)
+ *   2. the product query selects measurement{weight{...}}  — the guard can SEE the weight
+ *   3. the product query selects productType               — the heal picks the right default,
+ *                                                            not a blanket FALLBACK_LB
+ *   4. the gate call sits BEFORE the status:ACTIVE mutation
+ *
+ * Reads source only. No network, no DB, no Shopify.
+ */
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../scripts');
+
+// Every script in scripts/*-onboard/ that flips a product to status:ACTIVE.
+export const GOLIVE_SITES = [
+  'artmura-onboard/go-live-artmura.js',
+  'fallingstar-onboard/go-live-fallingstar.js',
+  'knoll-onboard/go-live.mjs',
+  'maharam-onboard/go-live.mjs',
+  'muralsource-onboard/go-live.mjs',
+  'osborne-onboard/go-live.mjs',
+  'sanderson-onboard/go-live.mjs',   // pre-existing gate (validate() 'weight>0'), the reference
+  'stout-onboard/go-live.mjs',
+  'stroheim-onboard/go-live.mjs',
+  'tres-tintas-onboard/go-live-tres-tintas.js',
+];
+
+/** The detector, applied to SOURCE TEXT so it can also be run against an injected fault. */
+export function auditSource(src) {
+  const importsGuard = /from\s+['"][^'"]*lib\/weight-guard\.mjs['"]/.test(src)
+                    || /require\(['"][^'"]*lib\/weight-guard\.mjs['"]\)/.test(src);
+  const selectsWeight = /measurement\s*\{\s*weight\s*\{/.test(src);
+  const selectsProductType = /product\(id:\$id\)\{[^`]*productType/.test(src);
+  const healsInPlace = /healAndVerifyWeights\s*\(/.test(src);   // the heal needs productType to pick a default
+  const usesGate = healsInPlace || /hasZeroWeight\s*\(/.test(src);
+  const iGate = Math.max(src.indexOf('healAndVerifyWeights('), src.indexOf('hasZeroWeight('));
+  // Compare against the mutation CALL SITE, not the `status:ACTIVE` text inside the M_ACTIVE
+  // constant near the top of the file — that constant always precedes the gate and would make
+  // this assertion meaningless (it did, on the first cut).
+  const iActive = src.indexOf('gql(M_ACTIVE');
+  const gateBeforeActivate = usesGate && iGate >= 0 && iActive >= 0 && iGate < iActive;
+  const missing = [];
+  if (!importsGuard) missing.push('import weight-guard');
+  if (!selectsWeight) missing.push('query selects measurement.weight');
+  if (healsInPlace && !selectsProductType) missing.push('query selects productType');
+  if (!usesGate) missing.push('calls the gate');
+  if (!gateBeforeActivate) missing.push('gate precedes status:ACTIVE');
+  return { ok: missing.length === 0, missing };
+}
+
+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 : ''}`); } };
+
+console.log('── every go-live site is wired to the weight gate');
+for (const rel of GOLIVE_SITES) {
+  const p = path.join(ROOT, rel);
+  if (!fs.existsSync(p)) { fail++; console.error(`  FAIL ${rel} — file missing`); continue; }
+  const r = auditSource(fs.readFileSync(p, 'utf8'));
+  ok(rel, r.ok, r.missing.join(', '));
+}
+
+console.log('\n── NEGATIVE: the detector goes red on each injected fault');
+{
+  const good = fs.readFileSync(path.join(ROOT, 'osborne-onboard/go-live.mjs'), 'utf8');
+  ok('control: the real file passes', auditSource(good).ok);
+
+  // fault 1 — the import is removed (someone re-implements the check inline)
+  const f1 = good.replace(/^import \{ healAndVerifyWeights \}.*$/m, '');
+  ok('removing the import => red', !auditSource(f1).ok && auditSource(f1).missing.includes('import weight-guard'));
+
+  // fault 2 — THE FALSE-GREEN CLASS: the query stops selecting the weight, so the guard
+  // measures nothing. This is exactly what the brief warned about.
+  const f2 = good.replace(' measurement{weight{value unit}}', '');
+  ok('query stops selecting measurement.weight => red',
+     !auditSource(f2).ok && auditSource(f2).missing.includes('query selects measurement.weight'));
+
+  // fault 3 — productType dropped, so every heal would silently use FALLBACK_LB
+  const f3 = good.replace('status productType vendor', 'status vendor');
+  ok('query stops selecting productType => red',
+     !auditSource(f3).ok && auditSource(f3).missing.includes('query selects productType'));
+
+  // fault 4 — the gate call is deleted entirely
+  const f4 = good.replace(/\{ const _w = await healAndVerifyWeights[\s\S]*?heldZeroWeight: weight>0'\] \}; \} \}/, '');
+  ok('deleting the gate call => red', !auditSource(f4).ok, JSON.stringify(auditSource(f4)));
+
+  // fault 5 — the gate is moved AFTER the activate mutation (present, but useless)
+  const moved = good.replace(/(\{ const _w = await healAndVerifyWeights[\s\S]*?heldZeroWeight: weight>0'\] \}; \} \})\n(  const r4 = await gql\(M_ACTIVE[^\n]*\n)/, '$2$1\n');
+  ok('moving the gate after status:ACTIVE => red',
+     !auditSource(moved).ok && auditSource(moved).missing.includes('gate precedes status:ACTIVE'),
+     JSON.stringify(auditSource(moved)));
+}
+
+console.log(`\n${fail === 0 ? 'PASS' : 'FAIL'} — ${pass} passed, ${fail} failed`);
+process.exit(fail === 0 ? 0 : 1);

← 8dc084a TK-11461: Sanderson gallery contamination root cause — input  ·  back to Designerwallcoverings  ·  TK-11471: every create payload carries a guard-sourced weigh 231a90e →