← back to Dw Signup Fulfillment
TK-11114: dry-run-safe verify-email backfill executor (double-locked send)
cb069ce1c8f5365c0822effd1605d92c6828b2e9 · 2026-09-02 11:50:22 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HuW5Mim1jbLsbaaEWsnq7D
Files touched
A verification/tk11114-remediation/backfill.js
Diff
commit cb069ce1c8f5365c0822effd1605d92c6828b2e9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 2 11:50:22 2026 -0700
TK-11114: dry-run-safe verify-email backfill executor (double-locked send)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HuW5Mim1jbLsbaaEWsnq7D
---
verification/tk11114-remediation/backfill.js | 50 ++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/verification/tk11114-remediation/backfill.js b/verification/tk11114-remediation/backfill.js
new file mode 100644
index 0000000..5bf3074
--- /dev/null
+++ b/verification/tk11114-remediation/backfill.js
@@ -0,0 +1,50 @@
+'use strict';
+// TK-11114 verify-email BACKFILL executor.
+// DRY-RUN BY DEFAULT: classifies each id (eligible / skip-reason) and sends NOTHING.
+// Sending requires BOTH `--send` AND env TK11114_SEND_CONFIRM=YES-SEND (double-lock),
+// and is paced 1/sec. Idempotent: only sends when sample_verify_sent is unset,
+// customer exists, has a real email, and is not a tk11114-test record.
+process.env.DRY_RUN = '0'; // force real Shopify reads (this file never fabricates)
+const fs = require('fs');
+const path = require('path');
+const shopify = require(path.join(__dirname, '..', '..', 'lib', 'shopify'));
+const config = require(path.join(__dirname, '..', '..', 'lib', 'config'));
+const verify = require(path.join(__dirname, '..', '..', 'lib', 'verify'));
+
+const args = process.argv.slice(2);
+const SEND = args.includes('--send') && process.env.TK11114_SEND_CONFIRM === 'YES-SEND';
+const fileArg = args.find(a => !a.startsWith('--')) || path.join(__dirname, 'tk11114_affected.txt');
+
+function truthy(mf) { return mf !== null && mf !== undefined && mf !== '' && mf !== false && mf !== '0'; }
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+(async () => {
+ if (!config.SHOPIFY_FULFILLMENT_TOKEN) { console.log(JSON.stringify({ ok: false, error: 'no fulfillment token resolved' })); process.exit(1); }
+ const ids = fs.readFileSync(fileArg, 'utf8').split(/\s+/).map(s => s.trim()).filter(Boolean);
+ const mode = SEND ? 'LIVE-SEND' : 'DRY-RUN';
+ console.log(`== TK-11114 backfill [${mode}] · ${ids.length} candidate ids · source=${fileArg} ==`);
+ const buckets = { eligible: [], already_sent: [], no_email: [], test_record: [], not_found: [], sent_ok: [], send_failed: [] };
+ for (const id of ids) {
+ let r; try { r = await shopify.getCustomer(id); } catch (e) { buckets.not_found.push({ id, err: e.message }); continue; }
+ if (!r || r.status === 404 || !(r.json && r.json.customer)) { buckets.not_found.push({ id, status: r && r.status }); continue; }
+ const c = r.json.customer;
+ const tags = (c.tags || '');
+ if (/tk11114-test/i.test(tags)) { buckets.test_record.push({ id }); continue; }
+ if (!c.email) { buckets.no_email.push({ id }); continue; }
+ let sentFlag = null; try { sentFlag = await shopify.getCustomerMetafield(id, 'custom', 'sample_verify_sent'); } catch (e) {}
+ if (truthy(sentFlag)) { buckets.already_sent.push({ id }); continue; }
+ // ELIGIBLE
+ if (!SEND) { buckets.eligible.push({ id, email_domain: String(c.email).split('@')[1] || '?' }); continue; }
+ // LIVE send path (double-locked)
+ try {
+ const res = await verify.startVerification({ email: c.email, customerId: id, firstName: c.first_name || '' });
+ if (res && res.ok) buckets.sent_ok.push({ id });
+ else buckets.send_failed.push({ id, reason: res && res.reason, status: res && res.status, errorCode: res && res.errorCode });
+ } catch (e) { buckets.send_failed.push({ id, err: e.message }); }
+ await sleep(1000); // pace
+ }
+ const summary = Object.fromEntries(Object.entries(buckets).map(([k, v]) => [k, v.length]));
+ console.log('== SUMMARY =='); console.log(JSON.stringify(summary, null, 2));
+ console.log('== eligible ids =='); console.log(buckets.eligible.map(x => x.id).join(' ') || '(none)');
+ if (SEND) { console.log('== send_failed detail =='); console.log(JSON.stringify(buckets.send_failed, null, 2)); }
+})().catch(e => { console.error('backfill error:', e.message); process.exit(1); });
← 49e1750 TK-10830: one-shot guard on Kelly reply (prevent duplicate s
·
back to Dw Signup Fulfillment
·
TK-11114: durable persist confirmed — signup email now resta 3efc18c →