← back to Rebel Walls Push
TK-10405: A' mitigation executor + undo for the 2 held RW pos-1 orphans (append -DUP + inventory 0)
89338f9d8407357ce51ae69e5a89665f0aba378b · 2026-09-04 13:25:31 -0700 · Steve Abrams
Files touched
A scripts/TK-10405-aprime-undo.mjsA scripts/TK-10405-aprime.mjs
Diff
commit 89338f9d8407357ce51ae69e5a89665f0aba378b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 4 13:25:31 2026 -0700
TK-10405: A' mitigation executor + undo for the 2 held RW pos-1 orphans (append -DUP + inventory 0)
---
scripts/TK-10405-aprime-undo.mjs | 39 ++++++++++++
scripts/TK-10405-aprime.mjs | 133 +++++++++++++++++++++++++++++++++++++++
2 files changed, 172 insertions(+)
diff --git a/scripts/TK-10405-aprime-undo.mjs b/scripts/TK-10405-aprime-undo.mjs
new file mode 100644
index 0000000..f598c9e
--- /dev/null
+++ b/scripts/TK-10405-aprime-undo.mjs
@@ -0,0 +1,39 @@
+#!/usr/bin/env node
+// UNDO for TK-10405 A' mitigation — restores each held orphan's original SKU (strips -DUP)
+// and restores its inventory available to qty_before, from the recorded restore map.
+// Run: node scripts/TK-10405-aprime-undo.mjs
+import fs from 'node:fs';
+const DOM = 'designer-laboratory-sandbox.myshopify.com';
+const VER = '2024-10';
+const RESTORE = new URL('../data/TK-10405-restore-map.json', import.meta.url).pathname;
+const env = fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.env', 'utf8');
+const TOKEN = (env.match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m)?.[1] || '').trim().replace(/^["']|["']$/g, '');
+
+async function gql(query, variables, tries = 6) {
+ for (let i = 0; i < tries; i++) {
+ const r = await fetch(`https://${DOM}/admin/api/${VER}/graphql.json`, {
+ method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query, variables }) });
+ if ([429, 500, 502, 503].includes(r.status) && i < tries - 1) { await new Promise(s => setTimeout(s, 1500 * (i + 1))); continue; }
+ return r.json();
+ }
+}
+const M_SKU = `mutation($productId:ID!,$variants:[ProductVariantsBulkInput!]!){
+ productVariantsBulkUpdate(productId:$productId,variants:$variants){ productVariants{ id sku } userErrors{ field message } } }`;
+const M_INV = `mutation($input:InventorySetQuantitiesInput!){
+ inventorySetQuantities(input:$input){ inventoryAdjustmentGroup{ createdAt } userErrors{ field message } } }`;
+
+(async () => {
+ const rm = JSON.parse(fs.readFileSync(RESTORE, 'utf8'));
+ const ps = rm.a_prime_mitigation?.prestate;
+ if (!ps) { console.error('no a_prime_mitigation.prestate in restore map — nothing to undo'); process.exit(1); }
+ for (const [id, e] of Object.entries(ps)) {
+ console.log(`Undo ${e.label} (${id}): sku -> ${e.sku_before}, qty -> ${e.qty_before}`);
+ const rs = await gql(M_SKU, { productId: e.product_gid, variants: [{ id: e.variant_gid, inventoryItem: { sku: e.sku_before } }] });
+ console.log(' sku userErrors:', JSON.stringify(rs?.data?.productVariantsBulkUpdate?.userErrors || rs?.errors || []));
+ const ri = await gql(M_INV, { input: { name: 'available', reason: 'correction', ignoreCompareQuantity: true,
+ quantities: [{ inventoryItemId: e.inventory_item_gid, locationId: e.location_gid, quantity: e.qty_before }] } });
+ console.log(' inv userErrors:', JSON.stringify(ri?.data?.inventorySetQuantities?.userErrors || ri?.errors || []));
+ }
+ console.log('UNDO complete.');
+})();
diff --git a/scripts/TK-10405-aprime.mjs b/scripts/TK-10405-aprime.mjs
new file mode 100644
index 0000000..5b688b2
--- /dev/null
+++ b/scripts/TK-10405-aprime.mjs
@@ -0,0 +1,133 @@
+#!/usr/bin/env node
+// TK-10405 A' mitigation for the 2 held Rebel Walls position-1 Default-Title orphans.
+// Steve-approved in-session (TK-10405/TK-11248). A' = append "-DUP" to each orphan SKU
+// + set its inventory available to 0. Touches NO variant id, NO position, and NOT the
+// real Mural (per m²) or Sample variants. Reversible (restore map + ledger).
+//
+// SCOPE = exactly these 2 variants, nothing else:
+// 44453186830387 dwrw-76016 A City Rises (product 6679729078323)
+// 44453188206643 dwrw-76264 A Priori (product 6679739629619)
+//
+// Run: node scripts/TK-10405-aprime.mjs
+import fs from 'node:fs';
+import { execFileSync } from 'node:child_process';
+
+const DOM = 'designer-laboratory-sandbox.myshopify.com';
+const VER = '2024-10';
+const RESTORE = new URL('../data/TK-10405-restore-map.json', import.meta.url).pathname;
+const LOGEXEC = process.env.HOME + '/.claude/yolo-queue/executed-reversible/log-exec.mjs';
+
+const env = fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.env', 'utf8');
+const TOKEN = (env.match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m)?.[1] || '').trim().replace(/^["']|["']$/g, '');
+if (!TOKEN) { console.error('no SHOPIFY_FULL_ACCESS_TOKEN'); process.exit(1); }
+
+const TARGETS = [
+ { vid: 'gid://shopify/ProductVariant/44453186830387', pid: 'gid://shopify/Product/6679729078323',
+ inv: 'gid://shopify/InventoryItem/46565669142579', loc: 'gid://shopify/Location/5795643504',
+ handle: 'dwrw-76016', label: 'A City Rises', sku0: 'DWRW-76016-Sample', sku1: 'DWRW-76016-Sample-DUP', price: '80.48' },
+ { vid: 'gid://shopify/ProductVariant/44453188206643', pid: 'gid://shopify/Product/6679739629619',
+ inv: 'gid://shopify/InventoryItem/46565670518835', loc: 'gid://shopify/Location/5795643504',
+ handle: 'dwrw-76264', label: 'A Priori', sku0: 'DWRW-76264-Sample', sku1: 'DWRW-76264-Sample-DUP', price: '4.25' },
+];
+
+async function gql(query, variables, tries = 6) {
+ for (let i = 0; i < tries; i++) {
+ const r = await fetch(`https://${DOM}/admin/api/${VER}/graphql.json`, {
+ method: 'POST',
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query, variables }),
+ });
+ if ([429, 500, 502, 503].includes(r.status) && i < tries - 1) { await new Promise(s => setTimeout(s, 1500 * (i + 1))); continue; }
+ return r.json();
+ }
+}
+const Q_VAR = `query($id:ID!){ productVariant(id:$id){ id title position price sku inventoryPolicy inventoryQuantity
+ inventoryItem{ id tracked } product{ id } } }`;
+const Q_PROD = `query($id:ID!){ product(id:$id){ id variants(first:20){ nodes{ id title position price sku } } } }`;
+const M_SKU = `mutation($productId:ID!,$variants:[ProductVariantsBulkInput!]!){
+ productVariantsBulkUpdate(productId:$productId,variants:$variants){ productVariants{ id sku } userErrors{ field message } } }`;
+const M_INV = `mutation($input:InventorySetQuantitiesInput!){
+ inventorySetQuantities(input:$input){ inventoryAdjustmentGroup{ createdAt reason } userErrors{ field message } } }`;
+
+function ledger(rec) {
+ execFileSync('node', [LOGEXEC], { input: JSON.stringify(rec), stdio: ['pipe', 'inherit', 'inherit'] });
+}
+
+(async () => {
+ // 1. RECORD PRE-STATE into restore map (reversibility record FIRST)
+ const rm = JSON.parse(fs.readFileSync(RESTORE, 'utf8'));
+ const prestate = {};
+ for (const t of TARGETS) {
+ const r = await gql(Q_VAR, { id: t.vid });
+ const v = r?.data?.productVariant;
+ if (!v) { console.error(`ABORT: ${t.label} variant not found — ${JSON.stringify(r.errors || r)}`); process.exit(1); }
+ if (v.sku && v.sku.endsWith('-DUP')) { console.log(`SKIP ${t.label}: SKU already ${v.sku} (A' already applied) — idempotent, not double-appending`); t.skip = true; continue; }
+ prestate[t.vid.split('/').pop()] = { variant_gid: t.vid, product_gid: t.pid, label: t.label,
+ sku_before: v.sku, sku_after: t.sku1, price: v.price, position: v.position,
+ inventory_item_gid: t.inv, location_gid: t.loc, qty_before: v.inventoryQuantity,
+ qty_after: 0, inventory_policy: v.inventoryPolicy, tracked: v.inventoryItem?.tracked };
+ }
+ rm.a_prime_mitigation = { ts: new Date().toISOString(), approved_by: 'Steve in-session TK-10405/TK-11248',
+ action: "A' = append -DUP to orphan SKU + set inventory available to 0; no variant id/position/other-variant change",
+ undo: 'restore sku_before via productVariantsBulkUpdate(inventoryItem.sku) + set available back to qty_before via inventorySetQuantities',
+ prestate };
+ fs.writeFileSync(RESTORE, JSON.stringify(rm, null, 2));
+ console.log('PRE-STATE recorded to restore map:', JSON.stringify(prestate, null, 2));
+
+ // 2. EXECUTE A' on each non-skipped target
+ for (const t of TARGETS) {
+ if (t.skip) continue;
+ console.log(`\n=== ${t.label} (${t.vid.split('/').pop()}) ===`);
+ const rs = await gql(M_SKU, { productId: t.pid, variants: [{ id: t.vid, inventoryItem: { sku: t.sku1 } }] });
+ const ds = rs?.data?.productVariantsBulkUpdate;
+ if (ds?.userErrors?.length || rs?.errors) { console.error('SKU rename FAILED:', JSON.stringify(ds?.userErrors || rs.errors)); process.exit(1); }
+ console.log(' SKU renamed ->', ds.productVariants?.[0]?.sku);
+ const ri = await gql(M_INV, { input: { name: 'available', reason: 'correction', ignoreCompareQuantity: true,
+ quantities: [{ inventoryItemId: t.inv, locationId: t.loc, quantity: 0 }] } });
+ const di = ri?.data?.inventorySetQuantities;
+ if (di?.userErrors?.length || ri?.errors) { console.error('inventory set FAILED:', JSON.stringify(di?.userErrors || ri.errors)); process.exit(1); }
+ console.log(' inventory available set to 0 @', di.inventoryAdjustmentGroup?.createdAt);
+ }
+
+ // 3. VERIFY live (re-query, printed line != landed)
+ console.log('\n===== VERIFY (Admin API) =====');
+ let allok = true;
+ for (const t of TARGETS) {
+ const r = await gql(Q_VAR, { id: t.vid });
+ const v = r.data.productVariant;
+ const p = await gql(Q_PROD, { id: t.pid });
+ const sibs = p.data.product.variants.nodes.filter(n => n.id !== t.vid);
+ const skuOk = v.sku === t.sku1;
+ const qtyOk = v.inventoryQuantity === 0;
+ const idOk = v.id === t.vid;
+ const sibOk = sibs.every(s => (s.title === 'Mural (per m²)' || s.title === 'Sample'));
+ const ok = skuOk && qtyOk && idOk && sibOk;
+ allok = allok && ok;
+ console.log(`${t.label}: sku=${v.sku}(${skuOk?'OK':'BAD'}) qty=${v.inventoryQuantity}(${qtyOk?'OK':'BAD'}) id-unchanged=${idOk} pos=${v.position} siblings-intact=${sibOk}`);
+ for (const s of sibs) console.log(` survivor pos${s.position} ${s.id.split('/').pop()} '${s.title}' $${s.price} sku=${s.sku}`);
+ }
+
+ // 4. VERIFY storefront (rendered products.json) — Default-Title now OutOfStock
+ console.log('\n===== VERIFY (rendered storefront) =====');
+ for (const t of TARGETS) {
+ try {
+ const j = await (await fetch(`https://${DOM}/products/${t.handle}.json`, { headers: { 'User-Agent': 'Mozilla/5.0' } })).json();
+ const dt = j.product.variants.find(x => x.title === 'Default Title');
+ console.log(`${t.handle} (${t.label}) Default-Title available=${dt ? dt.available : 'n/a'} price=$${dt ? dt.price : '?'}`);
+ if (dt && dt.available !== false) { console.error(' WARN: Default-Title still available on storefront (CDN cache may lag a few min)'); }
+ } catch (e) { console.error(`${t.handle} storefront fetch failed:`, e.message); }
+ }
+
+ // 5. LEDGER each executed A'
+ console.log('\n===== LEDGER =====');
+ for (const t of TARGETS) {
+ if (t.skip) { console.log(`(skipped ${t.label} — already -DUP, no ledger)`); continue; }
+ const id = t.vid.split('/').pop();
+ ledger({ agent: 'vp-dw-commerce', ticket: 'TK-10405',
+ action: `A' mitigation on held RW pos-1 orphan ${id} (${t.label}): SKU ${t.sku0} -> ${t.sku1} + inventory available 0`,
+ blast_radius: 1, target: id,
+ undo_cmd: `node ~/Projects/rebel-walls-push/scripts/TK-10405-aprime-undo.mjs # restores ${t.sku0} + qty from data/TK-10405-restore-map.json a_prime_mitigation.prestate.${id}`,
+ verify: `productVariant ${id}: sku=${t.sku1}, inventoryQuantity=0, id/position unchanged, Mural+Sample survivors intact; storefront Default-Title available=false` });
+ }
+ console.log('\n' + (allok ? 'A\' COMPLETE — verification PASSED for all non-skipped targets.' : 'A\' RAN but verification found a mismatch — review output above.'));
+})();
← 8debf6b TK-11248 EXECUTED: Aries title fixed live; guard held 2 posi
·
back to Rebel Walls Push
·
auto-data-snapshot: 2026-09-04T14:52:10 (1 data files) — dat d6494a0 →