← back to Designer Wallcoverings
TK-11539: apply weight guard to Schumacher sample-variant chokepoint
335c4269a1eb4d3e43924c3c5f8d17176a751eca · 2026-09-15 08:40:24 -0700 · Steve
Create the Sample via productVariantsBulkCreate with inventoryItemWithWeight so
it never ships zero-weight (mirrors lib/shopify.ts; prevents TK-11414 recurrence
on this standalone addSampleVariant path). tracked flag is single-sourced so the
price gate's orderable and the write's tracked cannot drift.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M DW-Programming/ImportNewSkufromURL/add-sample-variant-schumacher.ts
Diff
commit 335c4269a1eb4d3e43924c3c5f8d17176a751eca
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Sep 15 08:40:24 2026 -0700
TK-11539: apply weight guard to Schumacher sample-variant chokepoint
Create the Sample via productVariantsBulkCreate with inventoryItemWithWeight so
it never ships zero-weight (mirrors lib/shopify.ts; prevents TK-11414 recurrence
on this standalone addSampleVariant path). tracked flag is single-sourced so the
price gate's orderable and the write's tracked cannot drift.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
.../add-sample-variant-schumacher.ts | 76 ++++++++++++++++++----
1 file changed, 64 insertions(+), 12 deletions(-)
diff --git a/DW-Programming/ImportNewSkufromURL/add-sample-variant-schumacher.ts b/DW-Programming/ImportNewSkufromURL/add-sample-variant-schumacher.ts
index 18ca08f9..ec0ffe1e 100644
--- a/DW-Programming/ImportNewSkufromURL/add-sample-variant-schumacher.ts
+++ b/DW-Programming/ImportNewSkufromURL/add-sample-variant-schumacher.ts
@@ -4,10 +4,19 @@
import * as dotenv from 'dotenv';
import * as path from 'path';
+// Price-integrity gate (TK-11403) — shared assertion before the variant price write.
+import { assertPriceIntegrity, resolveSampleFloor } from './lib/price-integrity-gate';
+import { recordOutcome } from './lib/price-integrity-record';
+// Weight guard (TK-11539) — this standalone addSampleVariant chokepoint must also inject a
+// positive inventoryItem.measurement.weight, or the Sample variant it creates ships zero-weight
+// (the exact TK-11414 recurrence the shared engine already fixed in lib/shopify.ts).
+import { inventoryItemWithWeight } from './lib/weight-guard';
// Load .env.local file
dotenv.config({ path: path.join(__dirname, '.env.local') });
+const VENDOR = 'Schumacher';
+
interface Product {
id: string;
title: string;
@@ -148,11 +157,13 @@ async function addSampleVariant(productId: string, productTitle: string): Promis
return false;
}
- // Now create the Sample variant
+ // Now create the Sample variant.
+ // TK-11539: use productVariantsBulkCreate (not the deprecated productVariantCreate) so we can
+ // attach inventoryItem.measurement.weight via the shared weight guard — mirrors lib/shopify.ts.
const variantMutation = `
- mutation productVariantCreate($input: ProductVariantInput!) {
- productVariantCreate(input: $input) {
- productVariant {
+ mutation($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
+ productVariantsBulkCreate(productId: $productId, variants: $variants) {
+ productVariants {
id
title
price
@@ -165,6 +176,40 @@ async function addSampleVariant(productId: string, productTitle: string): Promis
}
`;
+ // ---- PRICE-INTEGRITY GATE (TK-11403) — BEFORE the variant price write ----
+ // This site only writes a Sample variant at $4.25; the shared assertion
+ // guards Class A (sample must == $4.25). FAIL-SAFE: never throws out of the
+ // path; a real violation blocks the write, an infra error just proceeds.
+ // TK-11403: per-vendor declared sample price (default $4.25). Same value gates AND is written.
+ const sampleFloor = resolveSampleFloor(VENDOR);
+ // Single source of truth for the Sample variant's inventory tracking, so the
+ // gate's `orderable` and the write's `tracked` can never drift apart. The
+ // Sample is written UNTRACKED (DW convention) → Shopify sells it at checkout
+ // regardless of inventory, i.e. it IS orderable. Telling the gate `orderable:
+ // true` keeps its Class-C $0-orderable guard armed for this path (a future $0
+ // sample would be caught) instead of silently disarmed by an `orderable:false`
+ // that contradicts what ships.
+ const SAMPLE_TRACKED = false;
+ const sampleOrderable = !SAMPLE_TRACKED; // untracked ⇒ sellable at checkout
+ try {
+ const gate = assertPriceIntegrity({
+ dwSku: productId,
+ netCost: null, // no sellable price written here → cost not needed
+ sampleFloor,
+ variants: [{ role: 'sample', price: sampleFloor, orderable: sampleOrderable, sku: `${productId}-Sample` }],
+ });
+ if (gate.warnings.length) {
+ recordOutcome({ vendor: VENDOR, dwSku: productId, outcome: 'warn', codes: gate.warnings.map(w => w.code) });
+ }
+ if (!gate.ok) {
+ console.log(` ⛔ price_integrity_block: ${JSON.stringify(gate.violations.map(v => v.code))}`);
+ recordOutcome({ vendor: VENDOR, dwSku: productId, outcome: 'block', codes: gate.violations.map(v => v.code) });
+ return false; // do NOT write the bad price
+ }
+ } catch (gateErr) {
+ console.log(` ⚠️ price_integrity_gate_error (proceeding): ${gateErr instanceof Error ? gateErr.message : String(gateErr)}`);
+ }
+
const variantResponse = await fetch(GRAPHQL_ENDPOINT, {
method: 'POST',
headers: {
@@ -174,23 +219,30 @@ async function addSampleVariant(productId: string, productTitle: string): Promis
body: JSON.stringify({
query: variantMutation,
variables: {
- input: {
- productId: productId,
- options: ['Sample'],
- price: '4.25',
- },
+ productId,
+ variants: [
+ {
+ // TK-11403: per-vendor declared sample price ($4.25 for Schumacher).
+ price: String(sampleFloor),
+ optionValues: [{ name: 'Sample', optionName: 'Size' }],
+ // TK-11539: positive measurement.weight (0.25 lb) so the Sample variant is never
+ // created zero-weight. SAMPLE_TRACKED (untracked) matches DW's convention AND is the
+ // same flag the price-integrity gate's `orderable` is derived from above.
+ inventoryItem: inventoryItemWithWeight({ tracked: SAMPLE_TRACKED }, { role: 'sample' }),
+ },
+ ],
},
}),
});
const variantResult: any = await variantResponse.json();
- if (variantResult.errors || variantResult.data?.productVariantCreate?.userErrors?.length > 0) {
- console.log(` ⚠️ Error creating variant: ${JSON.stringify(variantResult.errors || variantResult.data.productVariantCreate.userErrors)}`);
+ if (variantResult.errors || variantResult.data?.productVariantsBulkCreate?.userErrors?.length > 0) {
+ console.log(` ⚠️ Error creating variant: ${JSON.stringify(variantResult.errors || variantResult.data.productVariantsBulkCreate.userErrors)}`);
return false;
}
- console.log(` ✅ Sample variant created ($4.25)`);
+ console.log(` ✅ Sample variant created ($${sampleFloor})`);
return true;
} catch (error) {
console.log(` ❌ Error: ${error}`);
← 7c89163b docs: correct stale --dry-run comment across 9 write-scripts
·
back to Designer Wallcoverings
·
auto-data-snapshot: 2026-09-15T08:44:47 (4 data files) — DW- 56b3577d →