[object Object]

← back to Designer Wallcoverings

trade-retag: guard empty --query and log preimage only after a confirmed write

8dca38a73d9980514244dc6a36836fee8833b2a0 · 2026-09-22 14:50:34 -0700 · Steve

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

Files touched

Diff

commit 8dca38a73d9980514244dc6a36836fee8833b2a0
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Sep 22 14:50:34 2026 -0700

    trade-retag: guard empty --query and log preimage only after a confirmed write
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01DP24DdLpG6PHXgmjr47TVb
---
 shopify/scripts/trade-approved-retag.js | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/shopify/scripts/trade-approved-retag.js b/shopify/scripts/trade-approved-retag.js
index fd2caeee..5958e5b0 100644
--- a/shopify/scripts/trade-approved-retag.js
+++ b/shopify/scripts/trade-approved-retag.js
@@ -35,6 +35,7 @@ const APPLY = args.includes('--apply');
 const li = args.indexOf('--limit'); const LIMIT = li >= 0 ? Math.max(1, parseInt(args[li + 1], 10) || 0) : 0;
 const qi = args.indexOf('--query'); const QUERY = qi >= 0 ? String(args[qi + 1] || '') : 'tag:trade AND -tag:trade_approved';
 const EXPLICIT_QUERY = qi >= 0;
+if (qi >= 0 && !QUERY.trim()) { console.error('Refusing: empty --query would match ALL customers.'); process.exit(2); }
 
 // BLAST-RADIUS GATE (TK-11786 review, finding #3): a bare `--apply` uses the DEFAULT query,
 // which matches the ENTIRE ~15,943 `trade` cohort and grants each unlimited free trade samples
@@ -95,12 +96,15 @@ async function gql(query, variables) {
       const { id, tags } = e.node;
       if (tags.includes('trade_approved')) continue; // already has it — skip (idempotent)
       if (!APPLY) { if (seen <= 10) console.log(`  [DRY] would tag ${id}  (tags: ${tags.join('|')})`); continue; }
-      // record preimage BEFORE the write (reversibility)
-      fs.appendFileSync(LOG, JSON.stringify({ ts: new Date().toISOString(), id, before_tags: tags }) + '\n');
       const w = await gql(`mutation($id:ID!){tagsAdd(id:$id,tags:["trade_approved"]){userErrors{field message}}}`, { id });
       const ue = w.json?.data?.tagsAdd?.userErrors;
       if (w.status !== 200 || (Array.isArray(ue) && ue.length)) { failed++; console.error(`  ❌ ${id}: ${JSON.stringify(ue || w.status)}`); }
-      else { wrote++; if (wrote % 250 === 0) console.log(`  …tagged ${wrote}`); }
+      else {
+        wrote++;
+        // record preimage ONLY after a confirmed successful write (HTTP 200 + zero userErrors) — the log is proof of an actual write
+        fs.appendFileSync(LOG, JSON.stringify({ ts: new Date().toISOString(), id, before_tags: tags }) + '\n');
+        if (wrote % 250 === 0) console.log(`  …tagged ${wrote}`);
+      }
       await sleep(120); // ~8/s, well under the GraphQL bucket
     }
     if (!d.pageInfo.hasNextPage) break; cur = d.pageInfo.endCursor;

← 52658b4d trade-approved-retag: gate --apply against the default full-  ·  back to Designer Wallcoverings  ·  snapshot: sweep uncommitted working tree — TK-11503 explicit 9ef06c30 →