← back to Dw Signup Fulfillment
Serialize trade rejection with approval and preserve concurrent intake
116fc358f6bea4c8e36f36c53e880cf465349bd3 · 2026-09-05 00:04:46 -0700 · Steve Abrams
Files touched
M lib/trade.jsM scripts/trade-approval-test.jsM verification/e2e-proof.json
Diff
commit 116fc358f6bea4c8e36f36c53e880cf465349bd3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sat Sep 5 00:04:46 2026 -0700
Serialize trade rejection with approval and preserve concurrent intake
---
lib/trade.js | 35 ++--
scripts/trade-approval-test.js | 81 ++++++++-
verification/e2e-proof.json | 371 ++++++++++++++++++++++++++++++++---------
3 files changed, 389 insertions(+), 98 deletions(-)
diff --git a/lib/trade.js b/lib/trade.js
index 6134e09..4afa8aa 100644
--- a/lib/trade.js
+++ b/lib/trade.js
@@ -155,9 +155,10 @@ function get(id) {
return readAll().find(a => a.id === id) || null;
}
-// Approval is serialized per application inside this service process. Completed
+// Approve and reject share one per-application lock inside this service process.
+// This does not coordinate multiple processes writing the same JSONL. Completed
// email receipts are checkpointed; uncertain delivery needs manual reconciliation.
-const approvalsInFlight = new Set();
+const decisionsInFlight = new Set();
function customerKey(value) {
const match = String(value || '').match(/^(?:gid:\/\/shopify\/Customer\/)?([1-9]\d*)$/);
return match ? match[1] : null;
@@ -189,7 +190,7 @@ function checkpointApproval(app) {
}
async function approve(id) {
- if (approvalsInFlight.has(id)) return { ok: false, error: 'approval_in_progress', id };
+ if (decisionsInFlight.has(id)) return { ok: false, error: 'approval_in_progress', id };
const app = get(id);
if (!app) return { ok: false, error: 'not found' };
if (app.status !== 'pending') return { ok: false, error: `already ${app.status}` };
@@ -199,7 +200,7 @@ async function approve(id) {
ok: false, id, status: 'pending', dryRun: true, simulated: true,
error: 'dry_run_simulated', message: 'Approval simulation only; no entitlement or email was changed.', steps: [],
};
- approvalsInFlight.add(id);
+ decisionsInFlight.add(id);
const steps = [];
let phase = 'resolve_customer';
const fail = error => {
@@ -290,26 +291,30 @@ async function approve(id) {
try { return fail('approval_exception'); }
catch (_) { return { ok: false, id, error: 'approval_checkpoint_failed', failedStep: phase, steps }; }
} finally {
- approvalsInFlight.delete(id);
+ decisionsInFlight.delete(id);
}
}
async function reject(id) {
- if (approvalsInFlight.has(id)) return { ok: false, error: 'approval_in_progress', id };
- const rows = readAll();
- const app = rows.find(a => a.id === id);
+ if (decisionsInFlight.has(id)) return { ok: false, error: 'approval_in_progress', id };
+ const app = get(id);
if (!app) return { ok: false, error: 'not found' };
if (app.status !== 'pending') return { ok: false, error: `already ${app.status}` };
- const tpl = email.tradeRejectedEmail();
- const mail = await email.sendEmail({ to: app.email, subject: tpl.subject, html: tpl.html, source: 'trade-rejected' });
+ decisionsInFlight.add(id);
+ try {
+ const tpl = email.tradeRejectedEmail();
+ const mail = await email.sendEmail({ to: app.email, subject: tpl.subject, html: tpl.html, source: 'trade-rejected' });
- app.status = 'rejected';
- app.decision = 'rejected';
- app.decided_at = new Date().toISOString();
- rewriteAll(rows);
+ app.status = 'rejected';
+ app.decision = 'rejected';
+ app.decided_at = new Date().toISOString();
+ checkpointApproval(app);
- return { ok: true, id, status: 'rejected', email: { to: app.email, subject: tpl.subject, dryRun: mail.dryRun || false } };
+ return { ok: true, id, status: 'rejected', email: { to: app.email, subject: tpl.subject, dryRun: mail.dryRun || false } };
+ } finally {
+ decisionsInFlight.delete(id);
+ }
}
function summarizeShopify(r) {
diff --git a/scripts/trade-approval-test.js b/scripts/trade-approval-test.js
index 3cfd295..0d5c703 100644
--- a/scripts/trade-approval-test.js
+++ b/scripts/trade-approval-test.js
@@ -46,6 +46,7 @@ function fixture(name, fault = null, settings = {}) {
const email = {
repNotifyEmail: () => ({ subject: 'Fixture rep', html: 'Fixture' }),
tradeApprovedEmail: () => ({ subject: 'Fixture approval', html: 'Fixture' }),
+ tradeRejectedEmail: () => ({ subject: 'Fixture rejection', html: 'Fixture' }),
async sendEmail(payload) {
mails.push(payload.source);
if (state.fault === 'mail503' && payload.source === 'trade-approved') return { ok: false, status: 503 };
@@ -74,7 +75,7 @@ function fixture(name, fault = null, settings = {}) {
function loadTrade() { return compile('trade', { fs: storeFs, path, crypto, './config': config, './reps': reps, './email': email, './shopify': shopify }); }
let trade = loadTrade();
const app = trade.apply({ email: state.customer.email, business_name: 'Fixture', shopify_customer_id: 123 });
- return { state, mails, calls, shopify, config, app, trade, reload: loadTrade, persisted: () => JSON.parse(fs.readFileSync(trade.APPS_PATH, 'utf8').trim()), store: trade.APPS_PATH };
+ return { email, state, mails, calls, shopify, config, app, trade, reload: loadTrade, persisted: () => JSON.parse(fs.readFileSync(trade.APPS_PATH, 'utf8').trim()), store: trade.APPS_PATH };
}
async function check(name, fn) {
try { const detail = await fn(); checks.push({ name, verdict: 'PASS', ...detail }); }
@@ -175,6 +176,84 @@ async function check(name, fn) {
assert.equal(f.reload().readAll().length, 2);
return { store: f.store, preservedApplication: newer.id };
});
+ function holdMail(f, source) {
+ let release, entered;
+ const ready = new Promise(resolve => { entered = resolve; });
+ const held = new Promise(resolve => { release = resolve; });
+ const original = f.email.sendEmail;
+ f.email.sendEmail = async payload => {
+ if (payload.source === source) { entered(); await held; }
+ return original(payload);
+ };
+ return { ready, release };
+ }
+ await check('reject first blocks approval until rejection completes', async () => {
+ const f = fixture('reject-first'), gate = holdMail(f, 'trade-rejected');
+ const rejecting = f.trade.reject(f.app.id);
+ await gate.ready;
+ const approval = await f.trade.approve(f.app.id);
+ assert.equal(approval.ok, false);
+ assert.equal(f.calls.length, 0);
+ gate.release();
+ assert.equal((await rejecting).ok, true);
+ assert.equal(f.persisted().status, 'rejected');
+ assert.deepEqual(f.mails, ['trade-rejected']);
+ return { approval, persisted: f.persisted(), store: f.store };
+ });
+ await check('approve first blocks rejection and preserves approval receipts', async () => {
+ const f = fixture('approve-first'), gate = holdMail(f, 'trade-rep-notify');
+ const approving = f.trade.approve(f.app.id);
+ await gate.ready;
+ const rejection = await f.trade.reject(f.app.id);
+ assert.equal(rejection.ok, false);
+ gate.release();
+ assert.equal((await approving).ok, true);
+ assert.equal(f.persisted().status, 'approved');
+ assert.equal(f.persisted().approval_progress.emails.applicant.state, 'sent');
+ assert.equal(f.mails.includes('trade-rejected'), false);
+ return { rejection, persisted: f.persisted(), store: f.store };
+ });
+ await check('concurrent rejection attempts send only one rejection', async () => {
+ const f = fixture('reject-reject'), gate = holdMail(f, 'trade-rejected');
+ const first = f.trade.reject(f.app.id);
+ await gate.ready;
+ const duplicate = await f.trade.reject(f.app.id);
+ assert.equal(duplicate.ok, false);
+ gate.release();
+ assert.equal((await first).ok, true);
+ assert.deepEqual(f.mails, ['trade-rejected']);
+ return { duplicate, persisted: f.persisted(), store: f.store };
+ });
+ await check('intake during rejection survives final persistence', async () => {
+ const f = fixture('reject-intake'), gate = holdMail(f, 'trade-rejected');
+ const rejecting = f.trade.reject(f.app.id);
+ await gate.ready;
+ const newer = f.trade.apply({ email: 'newer@example.invalid' });
+ gate.release();
+ assert.equal((await rejecting).ok, true);
+ assert.equal(f.reload().get(newer.id).status, 'pending');
+ assert.equal(f.reload().get(f.app.id).status, 'rejected');
+ assert.equal(f.reload().readAll().length, 2);
+ return { newerId: newer.id, store: f.store };
+ });
+ for (const failure of ['template', 'send']) {
+ await check('rejection ' + failure + ' exception releases shared decision lock', async () => {
+ const f = fixture('reject-fail-' + failure);
+ if (failure === 'template') f.email.tradeRejectedEmail = () => { throw Error('fixture rejection template failed'); };
+ else {
+ const original = f.email.sendEmail;
+ f.email.sendEmail = async payload => {
+ if (payload.source === 'trade-rejected') throw Error('fixture rejection send failed');
+ return original(payload);
+ };
+ }
+ await assert.rejects(f.trade.reject(f.app.id), /fixture rejection/);
+ assert.equal(f.persisted().status, 'pending');
+ assert.equal((await f.trade.approve(f.app.id)).ok, true);
+ assert.equal(f.persisted().status, 'approved');
+ return { persisted: f.persisted(), store: f.store };
+ });
+ }
await check('dry run reports simulated pending without external side effects', async () => {
const f = fixture('dryrun', null, { DRY_RUN: true });
const result = await f.trade.approve(f.app.id);
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
index aad2b1a..8ac18f5 100644
--- a/verification/e2e-proof.json
+++ b/verification/e2e-proof.json
@@ -1,20 +1,21 @@
{
"task_id": "TK11285-cycle0645-owner",
"ticket": "TK-11285",
- "correlation_id": "M-02033",
+ "correlation_id": "M02037",
"intent": "An approval is recorded only after successful Shopify writes, readback of trade_approved on the intended customer, and acknowledged emails; retries preserve completed email receipts.",
"risk_tier": "R3 local external-integration simulation",
- "timestamp": "2026-09-05T06:57:09.440567+00:00",
+ "timestamp": "2026-09-05T07:04:45.802600+00:00",
"environment": "Real trade and Shopify modules in VM; real retained temp JSONL; config entirely stubbed; fetch replaced; require allowlisted; no external network",
"build": {
"base_commit": "8a877d4ed47ea559582c0944a702e60a8a6d927a",
"branch": "fix/tk11285-approval-verification",
"source_sha256": {
- "lib/trade.js": "e0a1278b4b065380b2457efa3387a10670cfb1b98f60c3a1c01f578498aa0e46",
+ "lib/trade.js": "4861f25f8a74472dd18578d0c4866f54482dc360c988f0fb8944ece18aa884fd",
"lib/shopify.js": "4cc0226a689e899a70bd5c1e133aab2806afcc8cebf023ec1c277a51cd03b6af",
"scripts/selftest.js": "6e5a18941d55d3851a0f988af51f7968c25621636fd535c7d6ed861ac860fe8e",
- "scripts/trade-approval-test.js": "e3033478fc376ec64c39c3f6aa9ec26f66142e3c178f113d84ecd03f0dd785bf"
- }
+ "scripts/trade-approval-test.js": "2637ff164cfcd5159a42a6d41e60e7c73c14d78011ace97b433d8d14988317a6"
+ },
+ "previous_increment": "d291ec9e62ff125cecb4a04420a00f6d1fcd9045"
},
"preconditions": {
"main_checkout": "/Users/macstudio3/Projects/dw-signup-fulfillment",
@@ -23,7 +24,9 @@
"cost_mode": "ZERO_COST_REQUIRED",
"DTD_ZERO_COST": "1",
"real_config_loaded": false,
- "real_external_io": false
+ "real_external_io": false,
+ "resumed_child_DTD_ZERO_COST": null,
+ "resumed_guard_verification": "ZERO_COST_REQUIRED file independently verified; installed panel.sh and post-decision-codex.sh force DTD_ZERO_COST=1 from canonical file even when child variable absent. Parent clarified continuation; no environment/control change or provider call."
},
"baseline": {
"command": "node scripts/trade-approval-test.js --baseline",
@@ -124,8 +127,8 @@
{
"command": "node scripts/trade-approval-test.js",
"verdict": "PASS",
- "report": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/report.json",
- "passed": 21
+ "report": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/report.json",
+ "passed": 27
},
{
"command": "node --check lib/trade.js; node --check lib/shopify.js; node --check scripts/selftest.js",
@@ -134,6 +137,11 @@
{
"command": "git diff --check",
"verdict": "PASS"
+ },
+ {
+ "command": "node <root-evidence>/parent-approval-probe.js /private/tmp/tk11285-owner.URtJU0/worktree <root-evidence>/owner-race-after.json --race",
+ "verdict": "PASS",
+ "result": "Approval blocked during held rejection; rejection completes once; newer application survives."
}
],
"boundaries": {
@@ -142,7 +150,8 @@
"data": "Actual fs writes and newly loaded trade module read back status/progress; one-shot persistence failure tested.",
"side_effects": "Injected email service receipts; failed entitlement sends none; completed rep receipt survives reload without duplicate send.",
"retry": "Mutation failure/retry; email503 retry; ambiguous delivery is held; concurrent same-process calls; intake during await; final checkpoint failure/retry.",
- "consumer_contract": "Approval errors return ok:false/error and pending; existing GET/POST consumers therefore do not report approved for errors or dry-run."
+ "consumer_contract": "Approval errors return ok:false/error and pending; existing GET/POST consumers therefore do not report approved for errors or dry-run.",
+ "shared_decision_lock": "Approve and reject acquire the same per-application lock synchronously before first await; finally releases for success and exceptions. Rejection checkpoints its row against freshly read data."
},
"checks": [
{
@@ -150,7 +159,7 @@
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-fa5896",
+ "id": "TRADE-20260905-1e1812",
"status": "pending",
"error": "shopify_http_error",
"failedStep": "tag_trade_approved",
@@ -182,14 +191,14 @@
"GET",
"PUT"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/http500/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/http500/data/trade-applications.jsonl"
},
{
"name": "mf500 fails closed",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-dbdf8e",
+ "id": "TRADE-20260905-ec292b",
"status": "pending",
"error": "shopify_http_error",
"failedStep": "set_metafield",
@@ -230,14 +239,14 @@
"PUT",
"POST"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/mf500/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/mf500/data/trade-applications.jsonl"
},
{
"name": "read500 fails closed",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-3b532b",
+ "id": "TRADE-20260905-f8fb45",
"status": "pending",
"error": "shopify_http_error",
"failedStep": "tag_trade_approved",
@@ -268,14 +277,14 @@
"calls": [
"GET"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/read500/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/read500/data/trade-applications.jsonl"
},
{
"name": "graphql_top fails closed",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-ddddaf",
+ "id": "TRADE-20260905-489ed7",
"status": "pending",
"error": "shopify_response_error",
"failedStep": "tag_trade_approved",
@@ -307,14 +316,14 @@
"GET",
"PUT"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/graphql_top/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/graphql_top/data/trade-applications.jsonl"
},
{
"name": "graphql_user fails closed",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-dd9b08",
+ "id": "TRADE-20260905-f1ebd9",
"status": "pending",
"error": "shopify_response_error",
"failedStep": "tag_trade_approved",
@@ -346,14 +355,14 @@
"GET",
"PUT"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/graphql_user/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/graphql_user/data/trade-applications.jsonl"
},
{
"name": "mf_user fails closed",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-ac124e",
+ "id": "TRADE-20260905-ac0496",
"status": "pending",
"error": "shopify_response_error",
"failedStep": "set_metafield",
@@ -394,14 +403,14 @@
"PUT",
"POST"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/mf_user/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/mf_user/data/trade-applications.jsonl"
},
{
"name": "malformed fails closed",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-7931ac",
+ "id": "TRADE-20260905-dbec19",
"status": "pending",
"error": "shopify_response_error",
"failedStep": "tag_trade_approved",
@@ -433,14 +442,14 @@
"GET",
"PUT"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/malformed/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/malformed/data/trade-applications.jsonl"
},
{
"name": "empty fails closed",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-d43a2a",
+ "id": "TRADE-20260905-7120e4",
"status": "pending",
"error": "invalid_tag_response",
"failedStep": "tag_trade_approved",
@@ -472,14 +481,14 @@
"GET",
"PUT"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/empty/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/empty/data/trade-applications.jsonl"
},
{
"name": "missing_tag fails closed",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-6be3c9",
+ "id": "TRADE-20260905-b52496",
"status": "pending",
"error": "entitlement_not_verified",
"failedStep": "verify_entitlement",
@@ -529,14 +538,14 @@
"POST",
"GET"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/missing_tag/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/missing_tag/data/trade-applications.jsonl"
},
{
"name": "wrong_id fails closed",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-5107dd",
+ "id": "TRADE-20260905-e8c2aa",
"status": "pending",
"error": "entitlement_not_verified",
"failedStep": "verify_entitlement",
@@ -586,14 +595,14 @@
"POST",
"GET"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/wrong_id/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/wrong_id/data/trade-applications.jsonl"
},
{
"name": "missing_customer fails closed",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-09a84a",
+ "id": "TRADE-20260905-97924a",
"status": "pending",
"error": "entitlement_not_verified",
"failedStep": "verify_entitlement",
@@ -643,14 +652,14 @@
"POST",
"GET"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/missing_customer/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/missing_customer/data/trade-applications.jsonl"
},
{
"name": "throw fails closed",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-704eed",
+ "id": "TRADE-20260905-b8535c",
"status": "pending",
"error": "approval_exception",
"failedStep": "tag_trade_approved",
@@ -674,14 +683,14 @@
"GET",
"PUT"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/throw/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/throw/data/trade-applications.jsonl"
},
{
"name": "verified entitlement then persisted approval and reload",
"verdict": "PASS",
"result": {
"ok": true,
- "id": "TRADE-20260905-abc417",
+ "id": "TRADE-20260905-8e7c72",
"status": "approved",
"rep": {
"id": "fixture-rep",
@@ -741,7 +750,7 @@
]
},
"persisted": {
- "id": "TRADE-20260905-abc417",
+ "id": "TRADE-20260905-8e7c72",
"email": "fixture@example.invalid",
"business_name": "Fixture",
"resale_cert": "",
@@ -751,8 +760,8 @@
},
"shopify_customer_id": "123",
"status": "approved",
- "created_at": "2026-09-05T06:56:33.051Z",
- "decided_at": "2026-09-05T06:56:33.054Z",
+ "created_at": "2026-09-05T07:04:15.770Z",
+ "decided_at": "2026-09-05T07:04:15.773Z",
"decision": "approved",
"assigned_rep": {
"id": "fixture-rep",
@@ -764,16 +773,16 @@
"emails": {
"rep": {
"state": "sent",
- "sent_at": "2026-09-05T06:56:33.053Z",
+ "sent_at": "2026-09-05T07:04:15.773Z",
"status": 200
},
"applicant": {
"state": "sent",
- "sent_at": "2026-09-05T06:56:33.054Z",
+ "sent_at": "2026-09-05T07:04:15.773Z",
"status": 200
}
},
- "entitlement_verified_at": "2026-09-05T06:56:33.053Z"
+ "entitlement_verified_at": "2026-09-05T07:04:15.772Z"
},
"approval_error": null
},
@@ -787,13 +796,13 @@
"trade-rep-notify",
"trade-approved"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/happy/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/happy/data/trade-applications.jsonl"
},
{
"name": "failed mutation can retry after reload",
"verdict": "PASS",
"persisted": {
- "id": "TRADE-20260905-a87b67",
+ "id": "TRADE-20260905-736047",
"email": "fixture@example.invalid",
"business_name": "Fixture",
"resale_cert": "",
@@ -803,8 +812,8 @@
},
"shopify_customer_id": "123",
"status": "approved",
- "created_at": "2026-09-05T06:56:33.062Z",
- "decided_at": "2026-09-05T06:56:33.067Z",
+ "created_at": "2026-09-05T07:04:15.783Z",
+ "decided_at": "2026-09-05T07:04:15.789Z",
"decision": "approved",
"assigned_rep": {
"id": "fixture-rep",
@@ -816,26 +825,26 @@
"emails": {
"rep": {
"state": "sent",
- "sent_at": "2026-09-05T06:56:33.066Z",
+ "sent_at": "2026-09-05T07:04:15.788Z",
"status": 200
},
"applicant": {
"state": "sent",
- "sent_at": "2026-09-05T06:56:33.067Z",
+ "sent_at": "2026-09-05T07:04:15.789Z",
"status": 200
}
},
- "entitlement_verified_at": "2026-09-05T06:56:33.066Z"
+ "entitlement_verified_at": "2026-09-05T07:04:15.787Z"
},
"approval_error": null
},
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/retry/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/retry/data/trade-applications.jsonl"
},
{
"name": "email partial success survives restart without duplicate rep send",
"verdict": "PASS",
"persisted": {
- "id": "TRADE-20260905-da8c89",
+ "id": "TRADE-20260905-3985f4",
"email": "fixture@example.invalid",
"business_name": "Fixture",
"resale_cert": "",
@@ -845,8 +854,8 @@
},
"shopify_customer_id": "123",
"status": "approved",
- "created_at": "2026-09-05T06:56:33.071Z",
- "decided_at": "2026-09-05T06:56:33.077Z",
+ "created_at": "2026-09-05T07:04:15.792Z",
+ "decided_at": "2026-09-05T07:04:15.800Z",
"decision": "approved",
"assigned_rep": {
"id": "fixture-rep",
@@ -858,16 +867,16 @@
"emails": {
"rep": {
"state": "sent",
- "sent_at": "2026-09-05T06:56:33.072Z",
+ "sent_at": "2026-09-05T07:04:15.794Z",
"status": 200
},
"applicant": {
"state": "sent",
- "sent_at": "2026-09-05T06:56:33.076Z",
+ "sent_at": "2026-09-05T07:04:15.800Z",
"status": 200
}
},
- "entitlement_verified_at": "2026-09-05T06:56:33.075Z"
+ "entitlement_verified_at": "2026-09-05T07:04:15.799Z"
},
"approval_error": null
},
@@ -876,14 +885,14 @@
"trade-approved",
"trade-approved"
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/mail-retry/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/mail-retry/data/trade-applications.jsonl"
},
{
"name": "ambiguous email delivery blocks resend across restart",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-d6afa1",
+ "id": "TRADE-20260905-7b3979",
"status": "pending",
"error": "email_delivery_unknown",
"failedStep": "email_applicant",
@@ -929,12 +938,12 @@
"step": "email_rep",
"skipped": true,
"reason": "already_sent",
- "sent_at": "2026-09-05T06:56:33.082Z"
+ "sent_at": "2026-09-05T07:04:15.806Z"
}
]
},
"persisted": {
- "id": "TRADE-20260905-d6afa1",
+ "id": "TRADE-20260905-7b3979",
"email": "fixture@example.invalid",
"business_name": "Fixture",
"resale_cert": "",
@@ -944,7 +953,7 @@
},
"shopify_customer_id": "123",
"status": "pending",
- "created_at": "2026-09-05T06:56:33.080Z",
+ "created_at": "2026-09-05T07:04:15.804Z",
"decided_at": null,
"decision": null,
"assigned_rep": {
@@ -957,30 +966,30 @@
"emails": {
"rep": {
"state": "sent",
- "sent_at": "2026-09-05T06:56:33.082Z",
+ "sent_at": "2026-09-05T07:04:15.806Z",
"status": 200
},
"applicant": {
"state": "sending",
- "attempted_at": "2026-09-05T06:56:33.082Z"
+ "attempted_at": "2026-09-05T07:04:15.807Z"
}
},
- "entitlement_verified_at": "2026-09-05T06:56:33.085Z"
+ "entitlement_verified_at": "2026-09-05T07:04:15.811Z"
},
"approval_error": {
"error": "email_delivery_unknown",
"step": "email_applicant",
- "at": "2026-09-05T06:56:33.085Z"
+ "at": "2026-09-05T07:04:15.812Z"
}
},
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/ambiguous-mail/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/ambiguous-mail/data/trade-applications.jsonl"
},
{
"name": "final checkpoint failure stays pending and retry skips completed emails",
"verdict": "PASS",
"first": {
"ok": false,
- "id": "TRADE-20260905-f0fd1b",
+ "id": "TRADE-20260905-2587ec",
"status": "pending",
"error": "approval_exception",
"failedStep": "email_applicant",
@@ -1037,7 +1046,7 @@
]
},
"persisted": {
- "id": "TRADE-20260905-f0fd1b",
+ "id": "TRADE-20260905-2587ec",
"email": "fixture@example.invalid",
"business_name": "Fixture",
"resale_cert": "",
@@ -1047,8 +1056,8 @@
},
"shopify_customer_id": "123",
"status": "approved",
- "created_at": "2026-09-05T06:56:33.089Z",
- "decided_at": "2026-09-05T06:56:33.094Z",
+ "created_at": "2026-09-05T07:04:15.816Z",
+ "decided_at": "2026-09-05T07:04:15.823Z",
"decision": "approved",
"assigned_rep": {
"id": "fixture-rep",
@@ -1060,20 +1069,20 @@
"emails": {
"rep": {
"state": "sent",
- "sent_at": "2026-09-05T06:56:33.091Z",
+ "sent_at": "2026-09-05T07:04:15.818Z",
"status": 200
},
"applicant": {
"state": "sent",
- "sent_at": "2026-09-05T06:56:33.092Z",
+ "sent_at": "2026-09-05T07:04:15.819Z",
"status": 200
}
},
- "entitlement_verified_at": "2026-09-05T06:56:33.094Z"
+ "entitlement_verified_at": "2026-09-05T07:04:15.822Z"
},
"approval_error": null
},
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/checkpoint-failure/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/checkpoint-failure/data/trade-applications.jsonl"
},
{
"name": "same-process concurrent duplicate does not duplicate emails",
@@ -1081,7 +1090,7 @@
"results": [
{
"ok": true,
- "id": "TRADE-20260905-5454d0",
+ "id": "TRADE-20260905-6e7a55",
"status": "approved",
"rep": {
"id": "fixture-rep",
@@ -1143,23 +1152,212 @@
{
"ok": false,
"error": "approval_in_progress",
- "id": "TRADE-20260905-5454d0"
+ "id": "TRADE-20260905-6e7a55"
}
],
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/concurrent/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/concurrent/data/trade-applications.jsonl"
},
{
"name": "intake during approval await survives checkpoint rewrites",
"verdict": "PASS",
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/intake-race/data/trade-applications.jsonl",
- "preservedApplication": "TRADE-20260905-5af8fd"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/intake-race/data/trade-applications.jsonl",
+ "preservedApplication": "TRADE-20260905-4568a3"
+ },
+ {
+ "name": "reject first blocks approval until rejection completes",
+ "verdict": "PASS",
+ "approval": {
+ "ok": false,
+ "error": "approval_in_progress",
+ "id": "TRADE-20260905-971cc0"
+ },
+ "persisted": {
+ "id": "TRADE-20260905-971cc0",
+ "email": "fixture@example.invalid",
+ "business_name": "Fixture",
+ "resale_cert": "",
+ "phone": "",
+ "extra": {
+ "shopify_customer_id": 123
+ },
+ "shopify_customer_id": 123,
+ "status": "rejected",
+ "created_at": "2026-09-05T07:04:15.845Z",
+ "decided_at": "2026-09-05T07:04:15.847Z",
+ "decision": "rejected",
+ "assigned_rep": null
+ },
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/reject-first/data/trade-applications.jsonl"
+ },
+ {
+ "name": "approve first blocks rejection and preserves approval receipts",
+ "verdict": "PASS",
+ "rejection": {
+ "ok": false,
+ "error": "approval_in_progress",
+ "id": "TRADE-20260905-fc216e"
+ },
+ "persisted": {
+ "id": "TRADE-20260905-fc216e",
+ "email": "fixture@example.invalid",
+ "business_name": "Fixture",
+ "resale_cert": "",
+ "phone": "",
+ "extra": {
+ "shopify_customer_id": 123
+ },
+ "shopify_customer_id": "123",
+ "status": "approved",
+ "created_at": "2026-09-05T07:04:15.852Z",
+ "decided_at": "2026-09-05T07:04:15.856Z",
+ "decision": "approved",
+ "assigned_rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ },
+ "approval_progress": {
+ "customer_id": "123",
+ "emails": {
+ "rep": {
+ "state": "sent",
+ "sent_at": "2026-09-05T07:04:15.854Z",
+ "status": 200
+ },
+ "applicant": {
+ "state": "sent",
+ "sent_at": "2026-09-05T07:04:15.855Z",
+ "status": 200
+ }
+ },
+ "entitlement_verified_at": "2026-09-05T07:04:15.854Z"
+ },
+ "approval_error": null
+ },
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/approve-first/data/trade-applications.jsonl"
+ },
+ {
+ "name": "concurrent rejection attempts send only one rejection",
+ "verdict": "PASS",
+ "duplicate": {
+ "ok": false,
+ "error": "approval_in_progress",
+ "id": "TRADE-20260905-7af14c"
+ },
+ "persisted": {
+ "id": "TRADE-20260905-7af14c",
+ "email": "fixture@example.invalid",
+ "business_name": "Fixture",
+ "resale_cert": "",
+ "phone": "",
+ "extra": {
+ "shopify_customer_id": 123
+ },
+ "shopify_customer_id": 123,
+ "status": "rejected",
+ "created_at": "2026-09-05T07:04:15.859Z",
+ "decided_at": "2026-09-05T07:04:15.860Z",
+ "decision": "rejected",
+ "assigned_rep": null
+ },
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/reject-reject/data/trade-applications.jsonl"
+ },
+ {
+ "name": "intake during rejection survives final persistence",
+ "verdict": "PASS",
+ "newerId": "TRADE-20260905-05fc42",
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/reject-intake/data/trade-applications.jsonl"
+ },
+ {
+ "name": "rejection template exception releases shared decision lock",
+ "verdict": "PASS",
+ "persisted": {
+ "id": "TRADE-20260905-e45a80",
+ "email": "fixture@example.invalid",
+ "business_name": "Fixture",
+ "resale_cert": "",
+ "phone": "",
+ "extra": {
+ "shopify_customer_id": 123
+ },
+ "shopify_customer_id": "123",
+ "status": "approved",
+ "created_at": "2026-09-05T07:04:15.874Z",
+ "decided_at": "2026-09-05T07:04:15.879Z",
+ "decision": "approved",
+ "assigned_rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ },
+ "approval_progress": {
+ "customer_id": "123",
+ "emails": {
+ "rep": {
+ "state": "sent",
+ "sent_at": "2026-09-05T07:04:15.877Z",
+ "status": 200
+ },
+ "applicant": {
+ "state": "sent",
+ "sent_at": "2026-09-05T07:04:15.878Z",
+ "status": 200
+ }
+ },
+ "entitlement_verified_at": "2026-09-05T07:04:15.877Z"
+ },
+ "approval_error": null
+ },
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/reject-fail-template/data/trade-applications.jsonl"
+ },
+ {
+ "name": "rejection send exception releases shared decision lock",
+ "verdict": "PASS",
+ "persisted": {
+ "id": "TRADE-20260905-091da2",
+ "email": "fixture@example.invalid",
+ "business_name": "Fixture",
+ "resale_cert": "",
+ "phone": "",
+ "extra": {
+ "shopify_customer_id": 123
+ },
+ "shopify_customer_id": "123",
+ "status": "approved",
+ "created_at": "2026-09-05T07:04:15.883Z",
+ "decided_at": "2026-09-05T07:04:15.888Z",
+ "decision": "approved",
+ "assigned_rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ },
+ "approval_progress": {
+ "customer_id": "123",
+ "emails": {
+ "rep": {
+ "state": "sent",
+ "sent_at": "2026-09-05T07:04:15.887Z",
+ "status": 200
+ },
+ "applicant": {
+ "state": "sent",
+ "sent_at": "2026-09-05T07:04:15.888Z",
+ "status": 200
+ }
+ },
+ "entitlement_verified_at": "2026-09-05T07:04:15.886Z"
+ },
+ "approval_error": null
+ },
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/reject-fail-send/data/trade-applications.jsonl"
},
{
"name": "dry run reports simulated pending without external side effects",
"verdict": "PASS",
"result": {
"ok": false,
- "id": "TRADE-20260905-9b01cc",
+ "id": "TRADE-20260905-e0447d",
"status": "pending",
"dryRun": true,
"simulated": true,
@@ -1167,12 +1365,12 @@
"message": "Approval simulation only; no entitlement or email was changed.",
"steps": []
},
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/dryrun/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/dryrun/data/trade-applications.jsonl"
},
{
"name": "live missing token cannot be treated as approval",
"verdict": "PASS",
- "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/no-token/data/trade-applications.jsonl"
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq/no-token/data/trade-applications.jsonl"
}
],
"limitations": [
@@ -1184,8 +1382,17 @@
],
"retained_test_state": [
"/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-9oeZ25",
- "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY"
+ "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY",
+ "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-AKX4Rq"
],
"rollback": "Local branch/worktree retained; main checkout untouched. No merge, service restart, external writes, or deployment.",
- "verdict": "PASS for isolated local acceptance; parent independent API verification and Cody acceptance remain with accountable finalizer /root"
+ "verdict": "PASS for27 isolated local checks and owner rerun of parent race harness; final parent/Cody acceptance remains pending",
+ "increment2": {
+ "intent": "Fix parent/Cody independently reproduced reject-first decision race and rejection/intake lost write.",
+ "dtd": "/private/tmp/dtd-cycle11285-final.GWFqzJ/codex-debate.txt FINAL KEEP",
+ "before": "<root-evidence>/parent-race-before.json",
+ "after": "<root-evidence>/owner-race-after.json",
+ "scope": "Single service process only; no multi-process guarantee and no redesign of rejection email failure policy.",
+ "new_checks": 6
+ }
}
← d291ec9 Verify trade entitlement before approval and preserve retry
·
back to Dw Signup Fulfillment
·
Retire obsolete theme commands that could undo designer sign dc1323f →