[object Object]

← back to Tk10895 GroupB

TK-10895 Group B reprice/rollback: name magic numbers, set exit code on partial failure

e638c84476a5d925cba52d4c6c25531a12488464 · 2026-09-10 11:54:30 -0700 · Steve Abrams

Light behavior-preserving cleanup: named the 0.65/0.85 margin constants, the
>10 sellable-price floor, and the 600ms write pace instead of leaving them
as bare literals; set process.exitCode=1 when any variant write/restore
fails so a partial failure can't read as success to a caller or canary.
No pricing math, API calls, dry-run default, or read-back assert changed.

Files touched

Diff

commit e638c84476a5d925cba52d4c6c25531a12488464
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 10 11:54:30 2026 -0700

    TK-10895 Group B reprice/rollback: name magic numbers, set exit code on partial failure
    
    Light behavior-preserving cleanup: named the 0.65/0.85 margin constants, the
    >10 sellable-price floor, and the 600ms write pace instead of leaving them
    as bare literals; set process.exitCode=1 when any variant write/restore
    fails so a partial failure can't read as success to a caller or canary.
    No pricing math, API calls, dry-run default, or read-back assert changed.
---
 reprice.mjs  | 14 +++++++++++---
 rollback.mjs |  5 ++++-
 2 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/reprice.mjs b/reprice.mjs
index fe5e18d..1da5c43 100755
--- a/reprice.mjs
+++ b/reprice.mjs
@@ -29,7 +29,14 @@ const T = {
   '709321': { wp: 142, row: '5050-04WP', pat: 'Birds II' },
   '709421': { wp: 142, row: '5050-04WP', pat: 'Birds II' },
 };
-const retail = c => Math.round((c / 0.65 / 0.85) * 100) / 100;
+// Retail formula: cost / WHOLESALE_MARGIN / RETAIL_MARGIN — unchanged DW markup chain (TK-10895 verdict).
+const WHOLESALE_MARGIN = 0.65;
+const RETAIL_MARGIN = 0.85;
+const retail = c => Math.round((c / WHOLESALE_MARGIN / RETAIL_MARGIN) * 100) / 100;
+// Variants priced at/under this are placeholder/sample rows, not the real sellable SKU.
+const SELLABLE_PRICE_FLOOR = 10;
+// Pace between writes — stay well under Shopify's REST rate limit.
+const PACE_MS = 600;
 
 const wl = JSON.parse(fs.readFileSync(EV + '/trancheB-worklist.json', 'utf8'));
 const byMfr = Object.fromEntries(wl.map(r => [r.mfr, r]));
@@ -48,7 +55,7 @@ for (const [mfr, t] of Object.entries(T)) {
   const pid = byMfr[mfr]?.pid;
   if (!pid) { console.log(`  ${mfr} NOT IN WORKLIST — skip`); continue; }
   const p = (await api(`products/${pid}.json?fields=id,handle,variants`)).product;
-  const v = p.variants.find(v => parseFloat(v.price) > 10);
+  const v = p.variants.find(v => parseFloat(v.price) > SELLABLE_PRICE_FLOOR);
   if (!v) { console.log(`  ${mfr} no sellable variant — skip`); continue; }
   const want = retail(t.wp).toFixed(2);
   if (v.price === want) { console.log(`  ${mfr} ${p.handle} already $${want} — skip (idempotent)`); continue; }
@@ -82,8 +89,9 @@ for (const m of map) {
     console.log(`  FAIL ${m.mfr} ${m.handle} :: ${e.message}`);
     fail++;
   }
-  await new Promise(r => setTimeout(r, 600));
+  await new Promise(r => setTimeout(r, PACE_MS));
 }
+if (fail) process.exitCode = 1; // a partial failure must not read as success to a caller/canary
 console.log(`\napplied=${ok} failed=${fail}`);
 console.log('NOTE: Shopify has read-after-write lag (this is what made the position fixer report 226');
 console.log('false failures). Verify from the STOREFRONT after ~30s, not immediately.');
diff --git a/rollback.mjs b/rollback.mjs
index d64c7ed..5f261c7 100755
--- a/rollback.mjs
+++ b/rollback.mjs
@@ -14,6 +14,8 @@ const SHOP = 'designer-laboratory-sandbox.myshopify.com';
 const API = '2024-10';
 const APPLY = process.argv.includes('--apply');
 const FORCE = process.argv.includes('--force');
+// Pace between writes — stay well under Shopify's REST rate limit (matches reprice.mjs).
+const PACE_MS = 600;
 const MAP = process.env.HOME + '/.claude/yolo-queue/evidence/TK-10895/groupB-reprice-rollback-20260910.json';
 
 if (!fs.existsSync(MAP)) { console.error('no rollback map at ' + MAP); process.exit(1); }
@@ -49,6 +51,7 @@ for (const m of map) {
     console.log(`  FAIL ${m.mfr} ${m.handle} :: ${e.message}`);
     fail++;
   }
-  await new Promise(r => setTimeout(r, 600));
+  await new Promise(r => setTimeout(r, PACE_MS));
 }
+if (fail) process.exitCode = 1; // a partial failure must not read as success to a caller/canary
 console.log(APPLY ? `\nrestored=${ok} skipped=${skip} failed=${fail}` : '\nDRY RUN — nothing written. Re-run with --apply.');

← 92adb5c TK-10895: Quadrille vendor email — corrected body + human-ap  ·  back to Tk10895 GroupB  ·  auto-data-snapshot: 2026-09-10T13:02:09 (2 data files) — wf6 c5cad53 →