← 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));
})();