← 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
M shopify/scripts/trade-approved-retag.js
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 →