[object Object]

← back to Designerwallcoverings

TK-11758: reversible CONTINUE->DENY fix tool for 6 $0 Latigo cork variants (dry-run verified)

6def336c2bfeaa6069d960bc2ca25913a04717ef · 2026-09-16 09:44:03 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Szengr4hMUDtYdw3jSCfA

Files touched

Diff

commit 6def336c2bfeaa6069d960bc2ca25913a04717ef
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 16 09:44:03 2026 -0700

    TK-11758: reversible CONTINUE->DENY fix tool for 6 $0 Latigo cork variants (dry-run verified)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_013Szengr4hMUDtYdw3jSCfA
---
 scripts/tk-11758-cork-continue-fix/.gitignore |   1 +
 scripts/tk-11758-cork-continue-fix/fix.mjs    | 103 ++++++++++++++++++++++++++
 2 files changed, 104 insertions(+)

diff --git a/scripts/tk-11758-cork-continue-fix/.gitignore b/scripts/tk-11758-cork-continue-fix/.gitignore
new file mode 100644
index 0000000..373f5f3
--- /dev/null
+++ b/scripts/tk-11758-cork-continue-fix/.gitignore
@@ -0,0 +1 @@
+snapshots/
diff --git a/scripts/tk-11758-cork-continue-fix/fix.mjs b/scripts/tk-11758-cork-continue-fix/fix.mjs
new file mode 100644
index 0000000..849634c
--- /dev/null
+++ b/scripts/tk-11758-cork-continue-fix/fix.mjs
@@ -0,0 +1,103 @@
+#!/usr/bin/env node
+/**
+ * TK-11758 — Latigo Real Cork | Phillipe Romano: 6 variants are $0.00 + inventoryPolicy=CONTINUE
+ * + availableForSale=true, i.e. checkout-orderable for FREE. Remedy per the canary's guidance:
+ * flip inventoryPolicy CONTINUE -> DENY on these 6 variants ONLY. Does NOT touch price.
+ * These products carry the `contact-for-price` tag — they are quote-only, not meant to be orderable.
+ *
+ * Modes:
+ *   (no flag)  DRY-RUN — read current state of the 6 variants, write a snapshot/restore-map,
+ *              print exactly what --apply would change. Read-only. Safe. $0.
+ *   --apply    GATED — flip the 6 CONTINUE variants to DENY (canonical customer-facing Shopify write).
+ *              Writes the snapshot FIRST, then mutates. Reversible via --undo.
+ *   --undo     Restore each variant's inventoryPolicy to the value recorded in the newest snapshot.
+ *
+ * Blast radius: exactly 6 variants, pinned by GID below. No price change, no publish, no send.
+ */
+import fs from 'node:fs';
+import path from 'node:path';
+import { gql } from '../lib/shopify.mjs';
+
+const VARIANT_GIDS = [
+  'gid://shopify/ProductVariant/17829266522177',
+  'gid://shopify/ProductVariant/17829266587713',
+  'gid://shopify/ProductVariant/17829266653249',
+  'gid://shopify/ProductVariant/17829266686017',
+  'gid://shopify/ProductVariant/17829266751553',
+  'gid://shopify/ProductVariant/17829267243073',
+];
+
+const MODE = process.argv.includes('--apply') ? 'apply'
+  : process.argv.includes('--undo') ? 'undo'
+  : 'dry-run';
+
+const HERE = path.dirname(new URL(import.meta.url).pathname);
+const SNAP_DIR = path.join(HERE, 'snapshots');
+fs.mkdirSync(SNAP_DIR, { recursive: true });
+
+async function readState() {
+  const q = `query($ids:[ID!]!){ nodes(ids:$ids){ ... on ProductVariant {
+    id sku price inventoryPolicy availableForSale product { id title status } } } }`;
+  const d = await gql(q, { ids: VARIANT_GIDS });
+  if (d?.__err) throw new Error('read failed: ' + JSON.stringify(d.__err).slice(0, 300));
+  return d.nodes.filter(Boolean);
+}
+
+function newestSnapshot() {
+  const files = fs.readdirSync(SNAP_DIR).filter(f => f.endsWith('.json')).sort();
+  if (!files.length) throw new Error('no snapshot to undo from — run dry-run or --apply first');
+  return JSON.parse(fs.readFileSync(path.join(SNAP_DIR, files.at(-1)), 'utf8'));
+}
+
+async function setPolicy(productId, variantId, policy) {
+  const m = `mutation($productId:ID!,$variants:[ProductVariantsBulkInput!]!){
+    productVariantsBulkUpdate(productId:$productId, variants:$variants){
+      productVariants { id inventoryPolicy } userErrors { field message } } }`;
+  const d = await gql(m, { productId, variants: [{ id: variantId, inventoryPolicy: policy }] });
+  if (d?.__err) throw new Error('mutation transport error: ' + JSON.stringify(d.__err).slice(0, 300));
+  const ue = d.productVariantsBulkUpdate.userErrors;
+  if (ue.length) throw new Error('userErrors: ' + JSON.stringify(ue));
+  return d.productVariantsBulkUpdate.productVariants[0].inventoryPolicy;
+}
+
+(async () => {
+  if (MODE === 'undo') {
+    const snap = newestSnapshot();
+    console.log(`UNDO — restoring inventoryPolicy from snapshot ${snap.ts}`);
+    for (const v of snap.variants) {
+      const now = await setPolicy(v.product.id, v.id, v.inventoryPolicy);
+      console.log(`  ${v.id.split('/').pop()} -> restored ${now}`);
+    }
+    console.log('undo complete.');
+    return;
+  }
+
+  const state = await readState();
+  const ts = new Date().toISOString().replace(/[:.]/g, '-');
+  const snapFile = path.join(SNAP_DIR, `snapshot-${ts}.json`);
+  fs.writeFileSync(snapFile, JSON.stringify({ ts, mode: MODE, variants: state }, null, 2));
+
+  console.log(`TK-11758 — ${MODE.toUpperCase()} — 6 Latigo Real Cork variants`);
+  console.log(`snapshot saved: ${snapFile}\n`);
+  const willChange = state.filter(v => v.inventoryPolicy === 'CONTINUE');
+  for (const v of state) {
+    const id = v.id.split('/').pop();
+    const flag = v.inventoryPolicy === 'CONTINUE' ? 'CONTINUE -> DENY' : `${v.inventoryPolicy} (leave)`;
+    console.log(`  ${id} price=$${v.price} forSale=${v.availableForSale} status=${v.product.status} | ${flag}`);
+  }
+  console.log(`\n${willChange.length} of ${state.length} variants would flip to DENY (price untouched).`);
+
+  if (MODE === 'dry-run') {
+    console.log('\nDRY-RUN only. No writes performed. To apply (GATED):');
+    console.log('  node scripts/tk-11758-cork-continue-fix/fix.mjs --apply');
+    return;
+  }
+
+  // --apply
+  console.log('\nAPPLYING (gated write)…');
+  for (const v of willChange) {
+    const now = await setPolicy(v.product.id, v.id, 'DENY');
+    console.log(`  ${v.id.split('/').pop()} -> ${now}`);
+  }
+  console.log('\napply complete. Reversible: node scripts/tk-11758-cork-continue-fix/fix.mjs --undo');
+})().catch(e => { console.error('FATAL:', e.message); process.exit(1); });

← 6eab755 auto-data-snapshot: 2026-09-16T09:33:20 (1 data files) — dat  ·  back to Designerwallcoverings  ·  WM/SDG reprice pt2: +36 William Morris (exact-code), Morris c843942 →