← back to Dw Signup Fulfillment

verification/tk11285/race.js

75 lines

'use strict';
// TK-11285 preflight repro: does the shipped in-process decision lock protect
// data/trade-applications.jsonl from the OTHER writer, scripts/recover-stuck-apps.js?
// Both sides are REAL production code. Only lib/email + lib/shopify are stubbed
// (zero external calls). Run: node -r ./repro/stubs.js repro/race.js [--control]
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
const ROOT = path.join(__dirname, '..', '..');
const trade = require(path.join(ROOT, 'lib', 'trade'));

const CONTROL = process.argv.includes('--control');
const APPS = trade.APPS_PATH;
const sleep = ms => new Promise(r => setTimeout(r, ms));
const N = 6;

function seed() {
  const rows = [];
  for (let i = 1; i <= N; i++) rows.push({
    id: `TRADE-SEED-${i}`, email: `seed${i}@example.com`, business_name: `Seed ${i}`,
    resale_cert: '', phone: '', extra: {}, shopify_customer_id: '5551111',
    link_status: 'linked', link_via: 'existing', status: 'pending',
    created_at: `2026-09-01T00:00:0${i}.000Z`, decided_at: null, decision: null, assigned_rep: null,
  });
  fs.mkdirSync(path.dirname(APPS), { recursive: true });
  fs.writeFileSync(APPS, rows.map(r => JSON.stringify(r)).join('\n') + '\n');
}
const read = () => fs.readFileSync(APPS, 'utf8').split('\n').filter(Boolean).map(l => JSON.parse(l));

(async () => {
  seed();
  const before = read();
  let child = null;
  if (!CONTROL) {
    // The OTHER writer: real scripts/recover-stuck-apps.js --send-only, separate PROCESS.
    child = spawn(process.execPath, ['-r', path.join(__dirname, 'stubs.js'),
      path.join(ROOT, 'scripts', 'recover-stuck-apps.js'),
      '--send-only', '--limit', String(N), '--delay', '50'],
      { cwd: ROOT, env: { ...process.env, DRY_RUN: '0', REPRO_SEND_MS: '250' }, stdio: ['ignore', 'pipe', 'pipe'] });
    let childOut = '';
    const snapshotTaken = new Promise(res => {
      child.stdout.on('data', d => { childOut += d; if (/LINKED not-yet-emailed=/.test(childOut)) res(); });
    });
    child.stderr.on('data', d => process.stderr.write('[child] ' + d));
    // Deterministic: wait until the child has ACTUALLY taken its snapshot (it prints the
    // target count right after readRows()), so the interleaving is not timing luck.
    await snapshotTaken;
    console.log('[t] child snapshot taken; live service now acts inside the window');
  }

  // --- the live service does its work DURING that window ---
  const fresh = trade.apply({ email: 'newdesigner@example.com', business_name: 'Arrived During Window' });
  const approveRes = await trade.approve('TRADE-SEED-1');

  if (child) await new Promise(r => child.on('exit', r));
  const after = read();

  const newApp = after.find(r => r.id === fresh.id);
  const seed1 = after.find(r => r.id === 'TRADE-SEED-1');
  const out = {
    mode: CONTROL ? 'CONTROL (no second writer)' : 'RACE (recover-stuck-apps.js --send-only concurrent)',
    approve_returned_ok: approveRes.ok === true,
    approve_status_returned: approveRes.status || null,
    rows_before: before.length, rows_after: after.length,
    new_intake_id: fresh.id,
    new_intake_survived: !!newApp,
    seed1_status_after: seed1 ? seed1.status : '<row missing>',
    seed1_decision_survived: !!(seed1 && seed1.status === 'approved'),
  };
  out.VERDICT = (out.new_intake_survived && out.seed1_decision_survived)
    ? 'NO LOSS' : 'DATA LOSS';
  console.log(JSON.stringify(out, null, 2));
  fs.writeFileSync(path.join(__dirname, CONTROL ? 'control.json' : 'race.json'), JSON.stringify(out, null, 2) + '\n');
})();