← back to Dw Signup Fulfillment

verification/tk11285/reverse-race.js

62 lines

'use strict';
// Independent check of the red-team's claim: does the SERVICE clobber fields the
// recover script owns? approve() mutates a row object captured BEFORE its awaits,
// then checkpointApproval spreads that whole stale object over the fresh row.
const fs = require('fs'), path = require('path'), { execFileSync } = require('child_process');
const ROOT = path.join(__dirname, "..", "..");
const trade = require(path.join(ROOT, 'lib', 'trade'));
const APPS = trade.APPS_PATH;
const read = () => fs.readFileSync(APPS, 'utf8').split('\n').filter(Boolean).map(l => JSON.parse(l));

// A realistic failed-intake row: applyAndLink degraded gracefully, so it is unlinked.
fs.writeFileSync(APPS, JSON.stringify({
  id: 'TRADE-X', email: 'designer@example.com', business_name: 'Studio X',
  status: 'pending', created_at: '2026-09-01T00:00:00.000Z',
  shopify_customer_id: null, link_status: 'unlinked', link_error: 'shopify_create_failed',
}) + '\n');

const SOLO = process.argv.includes('--solo'); // control: no second writer at all
let injected = false;
// Interleave the recover script's OWN commitRows, from a separate process, during
// the service's first await.
global.__inject = () => {
  if (SOLO || injected) return; injected = true;
  execFileSync(process.execPath, ['-e', `
    const p=require(${JSON.stringify(path.join(ROOT, 'scripts', 'recover-stuck-apps.js'))});
    const rows=p.readRows();
    const r=rows.find(x=>x.id==='TRADE-X');
    r.shopify_customer_id='777'; r.link_status='linked'; r.link_via='existing';
    r.recovered_at='2026-09-10T00:00:00.000Z'; r.recovery_emailed=true;
    p.commitRows([r]);
  `], { cwd: ROOT, env: { ...process.env, DRY_RUN: '0' } });
};

(async () => {
  const beforeApprove = read()[0];
  const res = await trade.approve('TRADE-X');
  const after = read()[0];
  const out = {
    claim: 'service (checkpointApproval) clobbers recover-owned fields with a pre-await row snapshot',
    approve_ok: res.ok,
    recover_wrote: { link_status: 'linked', recovery_emailed: true, recovered_at: '2026-09-10T00:00:00.000Z' },
    final_row: {
      status: after.status, shopify_customer_id: after.shopify_customer_id,
      link_status: after.link_status, link_error: after.link_error,
      recovery_emailed: after.recovery_emailed, recovered_at: after.recovered_at,
    },
    // The CLOBBER is: a value that was 'linked' ON DISK got reverted to 'unlinked'.
    // Only meaningful when a second writer actually wrote 'linked' first.
    link_status_clobbered: !SOLO && after.link_status !== 'linked',
    receipt_clobbered: !SOLO && after.recovery_emailed !== true,
    // NOT a race detector. approve() resolves the customer via the email_lookup
    // fallback but never reconciles link_status/link_error, so this same
    // contradictory shape appears with ZERO concurrency - run with --solo to see it.
    // Tracked separately; do not read it as evidence of the race.
    contradictory_shape_not_race_specific: after.status === 'approved' && !!after.shopify_customer_id && after.link_status !== 'linked',
  };
  out.VERDICT = SOLO
    ? (out.contradictory_shape_not_race_specific ? 'SOLO: contradictory row with NO concurrency (separate approve() gap)' : 'SOLO: clean')
    : ((out.link_status_clobbered || out.receipt_clobbered) ? 'CLAIM CONFIRMED — service clobbers' : 'CLAIM NOT REPRODUCED');
  console.log(JSON.stringify(out, null, 2));
})();