← back to Dw Signup Fulfillment
Verify trade entitlement before approval and preserve retry progress
d291ec9e62ff125cecb4a04420a00f6d1fcd9045 · 2026-09-04 23:57:09 -0700 · Steve Abrams
Files touched
M lib/shopify.jsM lib/trade.jsM scripts/selftest.jsA scripts/trade-approval-test.jsM verification/e2e-proof.json
Diff
commit d291ec9e62ff125cecb4a04420a00f6d1fcd9045
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 4 23:57:09 2026 -0700
Verify trade entitlement before approval and preserve retry progress
---
lib/shopify.js | 10 +
lib/trade.js | 191 +++++--
scripts/selftest.js | 25 +-
scripts/trade-approval-test.js | 202 +++++++
verification/e2e-proof.json | 1214 ++++++++++++++++++++++++++++++++++++++--
5 files changed, 1510 insertions(+), 132 deletions(-)
diff --git a/lib/shopify.js b/lib/shopify.js
index 32c2d7d..be11423 100644
--- a/lib/shopify.js
+++ b/lib/shopify.js
@@ -208,6 +208,16 @@ async function addTags(customerId, newTags) {
const wanted = (Array.isArray(newTags) ? newTags : [newTags]).map(t => t.trim()).filter(Boolean);
let existing = [];
const cur = await getCustomer(customerId);
+ // Never replace tags after a failed/malformed merge read. Approval's final
+ // readback cannot recover unrelated tags that an unsafe merge already erased.
+ if (!cur?.dryRun) {
+ const customer = cur?.json?.customer;
+ if (cur?.ok !== true || !Number.isFinite(cur.status) || cur.status < 200 || cur.status >= 300 ||
+ !customer || String(customer.id) !== String(customerId).replace('gid://shopify/Customer/', '') ||
+ typeof customer.tags !== 'string' || cur.json.errors) {
+ return { ok: false, status: cur?.status || 0, error: 'customer_tags_read_failed', json: cur?.json || null };
+ }
+ }
if (cur?.json?.customer?.tags) existing = cur.json.customer.tags.split(',').map(t => t.trim()).filter(Boolean);
const merged = Array.from(new Set([...existing, ...wanted])).join(', ');
return updateCustomer(customerId, { tags: merged });
diff --git a/lib/trade.js b/lib/trade.js
index a58d839..6134e09 100644
--- a/lib/trade.js
+++ b/lib/trade.js
@@ -155,73 +155,147 @@ function get(id) {
return readAll().find(a => a.id === id) || null;
}
-// APPROVE — runs the full fan-out, all writes dry-run-safe via lib/shopify.
+// Approval is serialized per application inside this service process. Completed
+// email receipts are checkpointed; uncertain delivery needs manual reconciliation.
+const approvalsInFlight = new Set();
+function customerKey(value) {
+ const match = String(value || '').match(/^(?:gid:\/\/shopify\/Customer\/)?([1-9]\d*)$/);
+ return match ? match[1] : null;
+}
+function hasApiErrors(value) {
+ if (!value || typeof value !== 'object') return false;
+ return Object.entries(value).some(([key, child]) =>
+ ((key === 'errors' || key === 'userErrors') && child &&
+ (Array.isArray(child) ? child.length > 0 : true)) || hasApiErrors(child));
+}
+function shopifyError(result) {
+ if (!result || result.ok !== true || result.status < 200 || result.status >= 300 || !Number.isFinite(result.status)) return 'shopify_http_error';
+ if (result.dryRun || result.noToken) return 'shopify_simulated';
+ if (!result.json || typeof result.json !== 'object' || hasApiErrors(result.json)) return 'shopify_response_error';
+ return null;
+}
+function tagsIncludeTrade(tags) {
+ const values = Array.isArray(tags) ? tags : typeof tags === 'string' ? tags.split(',') : [];
+ return values.some(tag => typeof tag === 'string' && tag.trim() === 'trade_approved');
+}
+// Do not retain a pre-await snapshot of the entire file: new applications may
+// arrive while Shopify or email is in flight.
+function checkpointApproval(app) {
+ const latest = readAll();
+ const index = latest.findIndex(row => row.id === app.id);
+ if (index < 0 || latest[index].status !== 'pending') throw new Error('application_changed');
+ latest[index] = { ...latest[index], ...app };
+ rewriteAll(latest);
+}
+
async function approve(id) {
- const rows = readAll();
- const app = rows.find(a => a.id === id);
+ if (approvalsInFlight.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}` };
-
+ // An ok:true result makes existing one-click callers display "approved". A
+ // simulation therefore explicitly returns ok:false and leaves the row pending.
+ if (config.DRY_RUN) return {
+ 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);
const steps = [];
+ let phase = 'resolve_customer';
+ const fail = error => {
+ app.status = 'pending';
+ app.decision = null;
+ app.decided_at = null;
+ app.approval_error = { error, step: phase, at: new Date().toISOString() };
+ checkpointApproval(app);
+ return { ok: false, id, status: 'pending', error, failedStep: phase, steps };
+ };
+ try {
+ const rep = app.assigned_rep || reps.assignRep();
+ let custId = customerKey(app.shopify_customer_id);
+ const resolvedBy = custId ? 'application' : 'email_lookup';
+ if (!custId) custId = customerKey(await shopify.findCustomerByEmail(app.email));
+ if (!custId) return fail('cannot_resolve_customer');
+ app.shopify_customer_id = custId;
+ app.assigned_rep = { id: rep.id, name: rep.name, email: rep.email };
+ const progress = app.approval_progress || { customer_id: custId, emails: {} };
+ if (customerKey(progress.customer_id) !== custId) return fail('approval_customer_changed');
+ app.approval_progress = progress;
+ progress.emails = progress.emails || {};
+ checkpointApproval(app);
+ steps.push({ step: 'assign_rep', rep: app.assigned_rep }, { step: 'resolve_customer', customer: custId, via: resolvedBy });
- // (a) assign to the DW House Account (fixed — not round-robin)
- const rep = reps.assignRep();
- steps.push({ step: 'assign_rep', rep: { id: rep.id, name: rep.name, email: rep.email } });
-
- // Resolve the Shopify customer id. Public-form applications carry no id, so look it
- // up by email (read_customers scope). NEVER write against a placeholder id — if we
- // can't resolve a real customer, HARD-FAIL the approve with a clear admin message so
- // the customer is never left silently untagged (which would still charge them for
- // samples). (Closes the original memo §5 "customer-id-by-email" TODO + Cody #4.)
- let custId = app.shopify_customer_id;
- let resolvedBy = custId ? 'application' : null;
- if (!custId) {
- custId = await shopify.findCustomerByEmail(app.email);
- if (custId) resolvedBy = 'email_lookup';
- }
- if (!custId) {
- return {
- ok: false,
- error: 'cannot_resolve_customer',
- message: `No Shopify customer found for ${app.email}. The applicant must have a store account (signed up) before trade approval. Not writing anything.`,
- email: app.email,
- };
- }
- steps.push({ step: 'resolve_customer', customer: custId, via: resolvedBy });
-
- // (b) `trade_approved` is the authoritative unlimited-samples entitlement.
- // Preserve the legacy `trade` tag during cutover so existing Regios pricing and
- // unrelated trade workflows cannot regress before their own migration.
- const tagRes = await shopify.addTags(custId, ['trade', 'trade_approved']);
- steps.push({ step: 'tag_trade_approved', customer: custId, result: summarizeShopify(tagRes) });
-
- // (c) metafield custom.assigned_rep
- const mfRes = await shopify.setCustomerMetafield(custId, {
- namespace: 'custom', key: 'assigned_rep', value: `${rep.name} <${rep.email}>`, type: 'single_line_text_field',
- });
- steps.push({ step: 'set_metafield', customer: custId, result: summarizeShopify(mfRes) });
-
- // (d) notify rep
- const repTpl = email.repNotifyEmail({ repName: rep.name, applicant: app });
- const repMail = await email.sendEmail({ to: rep.email, subject: repTpl.subject, html: repTpl.html, source: 'trade-rep-notify' });
- steps.push({ step: 'email_rep', to: rep.email, subject: repTpl.subject, dryRun: repMail.dryRun || false });
-
- // (e) email applicant approved
- const appTpl = email.tradeApprovedEmail({ applicant: app, repName: rep.name });
- const appMail = await email.sendEmail({ to: app.email, subject: appTpl.subject, html: appTpl.html, source: 'trade-approved' });
- steps.push({ step: 'email_applicant', to: app.email, subject: appTpl.subject, dryRun: appMail.dryRun || false });
-
- app.status = 'approved';
- app.decision = 'approved';
- app.decided_at = new Date().toISOString();
- app.shopify_customer_id = custId; // persist the resolved id for audit
- app.assigned_rep = { id: rep.id, name: rep.name, email: rep.email };
- rewriteAll(rows);
+ phase = 'tag_trade_approved';
+ const tagRes = await shopify.addTags(custId, ['trade', 'trade_approved']);
+ steps.push({ step: phase, customer: custId, result: summarizeShopify(tagRes) });
+ const tagError = shopifyError(tagRes);
+ if (tagError) return fail(tagError);
+ const tagged = tagRes.json.customer || tagRes.json.data?.tagsAdd?.node;
+ if (!tagged || customerKey(tagged.id) !== custId) return fail('invalid_tag_response');
+
+ phase = 'set_metafield';
+ const assignedValue = `${rep.name} <${rep.email}>`;
+ const mfRes = await shopify.setCustomerMetafield(custId, {
+ namespace: 'custom', key: 'assigned_rep', value: assignedValue, type: 'single_line_text_field',
+ });
+ steps.push({ step: phase, customer: custId, result: summarizeShopify(mfRes) });
+ const mfError = shopifyError(mfRes);
+ if (mfError) return fail(mfError);
+ const mf = mfRes.json.metafield || mfRes.json.data?.metafieldsSet?.metafields?.find(m => m.namespace === 'custom' && m.key === 'assigned_rep');
+ if (!mf || !mf.id || mf.namespace !== 'custom' || mf.key !== 'assigned_rep' || mf.value !== assignedValue ||
+ (mf.owner_id != null && customerKey(mf.owner_id) !== custId)) return fail('invalid_metafield_response');
+
+ phase = 'verify_entitlement';
+ const readback = await shopify.getCustomer(custId);
+ steps.push({ step: phase, customer: custId, result: summarizeShopify(readback) });
+ const readError = shopifyError(readback);
+ if (readError) return fail(readError);
+ const customer = readback.json.customer || readback.json.data?.customer;
+ if (!customer || customerKey(customer.id) !== custId || !tagsIncludeTrade(customer.tags)) return fail('entitlement_not_verified');
+ progress.entitlement_verified_at = new Date().toISOString();
+ checkpointApproval(app);
- return { ok: true, id, status: 'approved', rep: app.assigned_rep, steps };
+ for (const target of ['rep', 'applicant']) {
+ phase = `email_${target}`;
+ const receipt = progress.emails[target];
+ if (receipt?.state === 'sent') {
+ steps.push({ step: phase, skipped: true, reason: 'already_sent', sent_at: receipt.sent_at });
+ continue;
+ }
+ if (receipt?.state === 'sending' || receipt?.state === 'unknown') return fail('email_delivery_unknown');
+ const tpl = target === 'rep' ? email.repNotifyEmail({ repName: rep.name, applicant: app }) : email.tradeApprovedEmail({ applicant: app, repName: rep.name });
+ const to = target === 'rep' ? rep.email : app.email;
+ // Persist intent BEFORE send. A crash or transport exception cannot silently
+ // lead to duplicate delivery on the next attempt.
+ progress.emails[target] = { state: 'sending', attempted_at: new Date().toISOString() };
+ checkpointApproval(app);
+ const mail = await email.sendEmail({ to, subject: tpl.subject, html: tpl.html, source: target === 'rep' ? 'trade-rep-notify' : 'trade-approved' });
+ if (!mail || mail.ok !== true || mail.dryRun || mail.noToken || !Number.isFinite(mail.status) || mail.status < 200 || mail.status >= 300) {
+ const knownFailure = mail?.ok === false && Number.isFinite(mail.status) && mail.status >= 400;
+ progress.emails[target].state = knownFailure ? 'failed' : 'unknown';
+ return fail(knownFailure ? 'email_failed' : 'email_delivery_unknown');
+ }
+ progress.emails[target] = { state: 'sent', sent_at: new Date().toISOString(), status: mail.status };
+ checkpointApproval(app);
+ steps.push({ step: phase, to, subject: tpl.subject, dryRun: false });
+ }
+ app.status = 'approved';
+ app.decision = 'approved';
+ app.decided_at = new Date().toISOString();
+ app.approval_error = null;
+ checkpointApproval(app);
+ return { ok: true, id, status: 'approved', rep: app.assigned_rep, steps };
+ } catch (_) {
+ // Do not leak transport errors (which can contain secrets or customer data).
+ try { return fail('approval_exception'); }
+ catch (_) { return { ok: false, id, error: 'approval_checkpoint_failed', failedStep: phase, steps }; }
+ } finally {
+ approvalsInFlight.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 (!app) return { ok: false, error: 'not found' };
@@ -239,6 +313,7 @@ async function reject(id) {
}
function summarizeShopify(r) {
+ if (!r) return { ok: false };
if (r.dryRun) return { WOULD: `${r.method} ${r.url}`, payload: r.body, noToken: r.noToken || false };
return { status: r.status, ok: r.ok };
}
diff --git a/scripts/selftest.js b/scripts/selftest.js
index c0dfbb8..fc59f56 100644
--- a/scripts/selftest.js
+++ b/scripts/selftest.js
@@ -141,20 +141,10 @@ async function main() {
const pendingBefore = trade.listPending().length;
const approve = await trade.approve(created.id);
console.log(' approve result: ' + JSON.stringify(approve, null, 2));
- if (approve.ok && approve.status === 'approved') ok('approved; assigned rep = ' + approve.rep.name + ' <' + approve.rep.email + '>');
- else fail('approve failed');
- const tagStep = approve.steps.find(s => s.step === 'tag_trade_approved');
- if (tagStep && tagStep.result && tagStep.result.WOULD) ok('WOULD preserve `trade` and add authoritative `trade_approved`: ' + tagStep.result.WOULD);
- else fail('no trade-tag step');
- const mfStep = approve.steps.find(s => s.step === 'set_metafield');
- if (mfStep && mfStep.result && mfStep.result.WOULD) ok('WOULD set custom.assigned_rep metafield: ' + mfStep.result.WOULD);
- else fail('no metafield step');
- const repMailStep = approve.steps.find(s => s.step === 'email_rep');
- if (repMailStep && repMailStep.dryRun) ok('WOULD email rep ' + repMailStep.to + ' (dry-run)'); else fail('no rep email');
- const appMailStep = approve.steps.find(s => s.step === 'email_applicant');
- if (appMailStep && appMailStep.dryRun) ok('WOULD email applicant ' + appMailStep.to + ' (dry-run)'); else fail('no applicant email');
- if (trade.listPending().length === pendingBefore - 1) ok('application moved out of pending queue');
- else fail('application still pending after approve');
+ if (!approve.ok && approve.simulated && approve.dryRun && approve.status === 'pending') ok('approval explicitly simulated; no entitlement or email claimed');
+ else fail('dry-run approval must report a pending simulation: ' + JSON.stringify(approve));
+ if (approve.steps.length === 0 && trade.get(created.id).status === 'pending' && trade.listPending().length === pendingBefore) ok('dry run leaves application pending and performs no approval side effects');
+ else fail('dry run unexpectedly advanced approval');
// ---------------------------------------------------------------------------
// TK-11185 — server-side find-or-create at apply-time so every public application is
@@ -212,15 +202,14 @@ async function main() {
if (s3persisted && s3.app.shopify_customer_id === null && s3.app.link_status === 'unlinked' && s3.app.link_error === 'user_errors') ok('create-failure: application STILL persisted (unlinked, link_error recorded) — applicant sees success, no black hole');
else fail('graceful degrade failed: ' + JSON.stringify(s3));
- // Scenario 4 — a linked new app can now be APPROVED (no cannot_resolve_customer).
+ // Scenario 4 — a linked app remains pending under honest approval simulation.
shopify.findOrCreateCustomer = async () => ({ ok: true, id: '800000009', created: true, via: 'created' });
const s4 = await trade.applyAndLink({ email: 'Approve@Me.com', business_name: 'Approve Me Co' });
shopify.getCustomer = async () => ({ ok: true, json: { customer: { id: 800000009, tags: 'trade' } } }); // addTags merge read
const s4ap = await trade.approve(s4.app.id);
shopify.getCustomer = _gc; // restore
- const s4resolve = s4ap.steps && s4ap.steps.find(s => s.step === 'resolve_customer');
- if (s4ap.ok && s4ap.status === 'approved' && s4ap.error !== 'cannot_resolve_customer' && s4resolve && s4resolve.via === 'application') ok('approve() resolves via the STAMPED id (via=application) — cannot_resolve_customer can never fire for a linked app');
- else fail('approve did not resolve via stamped id: ' + JSON.stringify(s4ap));
+ if (!s4ap.ok && s4ap.simulated && s4ap.status === 'pending' && trade.get(s4.app.id).shopify_customer_id === '800000009') ok('linked customer ID is preserved while dry-run approval remains pending');
+ else fail('linked application simulation failed: ' + JSON.stringify(s4ap));
shopify.findOrCreateCustomer = _foc; // restore
diff --git a/scripts/trade-approval-test.js b/scripts/trade-approval-test.js
new file mode 100644
index 0000000..3cfd295
--- /dev/null
+++ b/scripts/trade-approval-test.js
@@ -0,0 +1,202 @@
+#!/usr/bin/env node
+'use strict';
+// Real trade + Shopify modules, actual retained temp JSONL store, deterministic
+// transport adapter. Real config is NEVER imported; no network module is exposed.
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+const os = require('node:os');
+const vm = require('node:vm');
+const crypto = require('node:crypto');
+const repo = path.resolve(__dirname, '..');
+const root = fs.mkdtempSync(path.join(os.tmpdir(), 'tk11285-flow-'));
+const checks = [];
+function fixture(name, fault = null, settings = {}) {
+ const dir = path.join(root, name);
+ fs.mkdirSync(path.join(dir, 'lib'), { recursive: true });
+ const calls = [], mails = [], state = { fault, customer: { id: 123, tags: 'existing', email: 'fixture@example.invalid' } };
+ const config = { DRY_RUN: false, SHOP_DOMAIN: 'fixture.invalid', SHOPIFY_API_VERSION: 'fixture', SHOPIFY_FULFILLMENT_TOKEN: 'fixture-token', ...settings };
+ const response = (json, status = 200) => ({ ok: status >= 200 && status < 300, status, json: async () => structuredClone(json), headers: { get: () => null } });
+ async function fetchAdapter(url, options) {
+ assert.equal(new URL(url).hostname, 'fixture.invalid');
+ const method = options.method;
+ calls.push(method);
+ const currentFault = state.fault;
+ if (currentFault === 'throw' && method === 'PUT') throw Error('fixture transport failed');
+ if ((currentFault === 'http500' && method === 'PUT') || (currentFault === 'mf500' && method === 'POST') || (currentFault === 'read500' && method === 'GET')) return response({}, 500);
+ if (method === 'PUT') {
+ if (currentFault === 'graphql_top') return response({ errors: [{ message: 'Denied' }] });
+ if (currentFault === 'graphql_user') return response({ data: { tagsAdd: { node: { id: 'gid://shopify/Customer/123' }, userErrors: [{ message: 'Denied' }] } } });
+ if (currentFault === 'malformed') return response(null);
+ if (currentFault === 'empty') return response({});
+ state.customer.tags = JSON.parse(options.body).customer.tags;
+ return response({ customer: state.customer });
+ }
+ if (method === 'POST') {
+ if (currentFault === 'mf_user') return response({ data: { metafieldsSet: { userErrors: [{ message: 'Denied' }] } } });
+ return response({ metafield: { id: 789, owner_id: 123, ...JSON.parse(options.body).metafield } }, 201);
+ }
+ const customer = { ...state.customer };
+ // These faults affect the final readback only, leaving tag-merge intact.
+ if (calls.includes('POST') && currentFault === 'missing_tag') customer.tags = 'trade';
+ if (calls.includes('POST') && currentFault === 'wrong_id') customer.id = 999;
+ if (calls.includes('POST') && currentFault === 'missing_customer') return response({ customer: null });
+ return response({ customer });
+ }
+ const email = {
+ repNotifyEmail: () => ({ subject: 'Fixture rep', html: 'Fixture' }),
+ tradeApprovedEmail: () => ({ subject: 'Fixture approval', html: 'Fixture' }),
+ async sendEmail(payload) {
+ mails.push(payload.source);
+ if (state.fault === 'mail503' && payload.source === 'trade-approved') return { ok: false, status: 503 };
+ if (state.fault === 'mail_throw' && payload.source === 'trade-approved') throw Error('ambiguous delivery');
+ return { ok: true, status: 200 };
+ },
+ };
+ const reps = { assignRep: () => ({ id: 'fixture-rep', name: 'Fixture Rep', email: 'rep@example.invalid' }) };
+ const compile = (name, deps) => {
+ const module = { exports: {} };
+ vm.runInNewContext(fs.readFileSync(path.join(repo, 'lib', name + '.js'), 'utf8'), {
+ module, exports: module.exports, __dirname: path.join(dir, 'lib'), Buffer,
+ console: { log() {} }, setTimeout, clearTimeout, fetch: fetchAdapter,
+ require(specifier) { if (!(specifier in deps)) throw Error('Blocked dependency: ' + specifier); return deps[specifier]; },
+ }, { filename: name + '.js' });
+ return module.exports;
+ };
+ const shopify = compile('shopify', { './config': config });
+ const storeFs = { ...fs, writeFileSync(file, data, ...options) {
+ if (state.fault === 'final_checkpoint' && String(data).includes('"status":"approved"')) {
+ state.fault = null;
+ throw Error('fixture one-shot final persistence failure');
+ }
+ return fs.writeFileSync(file, data, ...options);
+ } };
+ 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 };
+}
+async function check(name, fn) {
+ try { const detail = await fn(); checks.push({ name, verdict: 'PASS', ...detail }); }
+ catch (error) { checks.push({ name, verdict: 'FAIL', error: error.stack }); process.exitCode = 1; }
+}
+(async () => {
+ if (process.argv.includes('--baseline')) {
+ await check('original HTTP500 falsely approves and sends both emails', async () => {
+ const f = fixture('baseline', 'http500');
+ const result = await f.trade.approve(f.app.id);
+ assert.equal(result.ok, true);
+ assert.equal(f.persisted().status, 'approved');
+ assert.equal(f.mails.length, 2);
+ return { result, persisted: f.persisted(), calls: f.calls, emails: f.mails, store: f.store };
+ });
+ } else {
+ for (const fault of ['http500', 'mf500', 'read500', 'graphql_top', 'graphql_user', 'mf_user', 'malformed', 'empty', 'missing_tag', 'wrong_id', 'missing_customer', 'throw']) {
+ await check(fault + ' fails closed', async () => {
+ const f = fixture(fault, fault), result = await f.trade.approve(f.app.id);
+ assert.equal(result.ok, false);
+ assert.equal(f.persisted().status, 'pending');
+ assert.equal(f.mails.length, 0);
+ assert.equal(f.reload().get(f.app.id).status, 'pending');
+ return { result, calls: f.calls, store: f.store };
+ });
+ }
+ await check('verified entitlement then persisted approval and reload', async () => {
+ const f = fixture('happy'), result = await f.trade.approve(f.app.id);
+ assert.equal(result.ok, true);
+ assert.equal(f.persisted().status, 'approved');
+ assert.equal(f.reload().get(f.app.id).status, 'approved');
+ assert.match(f.state.customer.tags, /existing/);
+ assert.match(f.state.customer.tags, /trade_approved/);
+ assert.equal(f.mails.length, 2);
+ assert.ok(f.persisted().approval_progress.entitlement_verified_at);
+ await f.reload().approve(f.app.id);
+ assert.equal(f.mails.length, 2);
+ return { result, persisted: f.persisted(), calls: f.calls, emails: f.mails, store: f.store };
+ });
+ await check('failed mutation can retry after reload', async () => {
+ const f = fixture('retry', 'mf500');
+ assert.equal((await f.trade.approve(f.app.id)).ok, false);
+ assert.equal(f.mails.length, 0);
+ f.state.fault = null;
+ assert.equal((await f.reload().approve(f.app.id)).ok, true);
+ assert.equal(f.mails.length, 2);
+ return { persisted: f.persisted(), store: f.store };
+ });
+ await check('email partial success survives restart without duplicate rep send', async () => {
+ const f = fixture('mail-retry', 'mail503');
+ assert.equal((await f.trade.approve(f.app.id)).ok, false);
+ assert.equal(f.persisted().status, 'pending');
+ assert.equal(f.persisted().approval_progress.emails.rep.state, 'sent');
+ f.state.fault = null;
+ assert.equal((await f.reload().approve(f.app.id)).ok, true);
+ assert.equal(f.mails.filter(x => x === 'trade-rep-notify').length, 1);
+ assert.equal(f.mails.filter(x => x === 'trade-approved').length, 2);
+ return { persisted: f.persisted(), emails: f.mails, store: f.store };
+ });
+ await check('ambiguous email delivery blocks resend across restart', async () => {
+ const f = fixture('ambiguous-mail', 'mail_throw');
+ assert.equal((await f.trade.approve(f.app.id)).ok, false);
+ f.state.fault = null;
+ const result = await f.reload().approve(f.app.id);
+ assert.equal(result.ok, false);
+ assert.match(result.error, /delivery_unknown/);
+ assert.equal(f.mails.length, 2);
+ return { result, persisted: f.persisted(), store: f.store };
+ });
+ await check('final checkpoint failure stays pending and retry skips completed emails', async () => {
+ const f = fixture('checkpoint-failure', 'final_checkpoint');
+ const first = await f.trade.approve(f.app.id);
+ assert.equal(first.ok, false);
+ assert.equal(f.persisted().status, 'pending');
+ assert.equal(f.persisted().decision, null);
+ assert.equal(f.mails.length, 2);
+ assert.equal((await f.reload().approve(f.app.id)).ok, true);
+ assert.equal(f.mails.length, 2);
+ return { first, persisted: f.persisted(), store: f.store };
+ });
+ await check('same-process concurrent duplicate does not duplicate emails', async () => {
+ const f = fixture('concurrent');
+ const results = await Promise.all([f.trade.approve(f.app.id), f.trade.approve(f.app.id)]);
+ assert.equal(results.filter(r => r.ok).length, 1);
+ assert.equal(f.mails.length, 2);
+ return { results, store: f.store };
+ });
+ await check('intake during approval await survives checkpoint rewrites', async () => {
+ const f = fixture('intake-race');
+ const original = f.shopify.addTags;
+ let newer;
+ f.shopify.addTags = async (...args) => {
+ newer = f.trade.apply({ email: 'newer@example.invalid', business_name: 'Newer fixture' });
+ return original(...args);
+ };
+ assert.equal((await f.trade.approve(f.app.id)).ok, true);
+ assert.equal(f.reload().get(newer.id).status, 'pending');
+ assert.equal(f.reload().readAll().length, 2);
+ return { store: f.store, preservedApplication: newer.id };
+ });
+ 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);
+ assert.equal(result.ok, false); // Existing one-click caller must not say approved.
+ assert.equal(result.simulated, true);
+ assert.equal(result.dryRun, true);
+ assert.equal(f.persisted().status, 'pending');
+ assert.equal(f.mails.length, 0);
+ assert.equal(f.calls.length, 0);
+ return { result, store: f.store };
+ });
+ await check('live missing token cannot be treated as approval', async () => {
+ const f = fixture('no-token', null, { SHOPIFY_FULFILLMENT_TOKEN: '' });
+ assert.equal((await f.trade.approve(f.app.id)).ok, false);
+ assert.equal(f.persisted().status, 'pending');
+ assert.equal(f.mails.length, 0);
+ assert.equal(f.calls.length, 0);
+ return { store: f.store };
+ });
+ }
+ const report = { timestamp: new Date().toISOString(), repo, environment: 'Real trade and Shopify modules in VM; real retained temp JSONL; config entirely stubbed; fetch replaced; require allowlisted; no external network', retainedRoot: root, sourceSha256: crypto.createHash('sha256').update(fs.readFileSync(path.join(repo, 'lib/trade.js'))).digest('hex'), checks };
+ const output = process.env.TRADE_TEST_REPORT || path.join(root, 'report.json');
+ fs.writeFileSync(output, JSON.stringify(report, null, 2) + '\n');
+ console.log(JSON.stringify({ report: output, checks: checks.map(({ name, verdict, error }) => ({ name, verdict, error })) }, null, 2));
+})();
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
index de9ef1e..aad2b1a 100644
--- a/verification/e2e-proof.json
+++ b/verification/e2e-proof.json
@@ -1,89 +1,1191 @@
{
- "intent": "Full production signup journey for retail/returning and trade applicants",
- "riskTier": "R4",
- "environment": "production canary",
- "buildIdentity": {
- "signupServiceCommit": "aa1c2a5",
- "shopifyFunctionTestCommit": "ebdcac3d",
- "liveSourceParity": true
+ "task_id": "TK11285-cycle0645-owner",
+ "ticket": "TK-11285",
+ "correlation_id": "M-02033",
+ "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",
+ "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/shopify.js": "4cc0226a689e899a70bd5c1e133aab2806afcc8cebf023ec1c277a51cd03b6af",
+ "scripts/selftest.js": "6e5a18941d55d3851a0f988af51f7968c25621636fd535c7d6ed861ac860fe8e",
+ "scripts/trade-approval-test.js": "e3033478fc376ec64c39c3f6aa9ec26f66142e3c178f113d84ecd03f0dd785bf"
+ }
+ },
+ "preconditions": {
+ "main_checkout": "/Users/macstudio3/Projects/dw-signup-fulfillment",
+ "isolated_worktree": "/private/tmp/tk11285-owner.URtJU0/worktree",
+ "main_clean_at_start": true,
+ "cost_mode": "ZERO_COST_REQUIRED",
+ "DTD_ZERO_COST": "1",
+ "real_config_loaded": false,
+ "real_external_io": false
+ },
+ "baseline": {
+ "command": "node scripts/trade-approval-test.js --baseline",
+ "verdict": "PASS",
+ "meaning": "Reproduced original defect before editing production modules: HTTP500 returned/persisted approved and sent both fixture emails.",
+ "report": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-9oeZ25/report.json",
+ "checks": [
+ {
+ "name": "original HTTP500 falsely approves and sends both emails",
+ "verdict": "PASS",
+ "result": {
+ "ok": true,
+ "id": "TRADE-20260905-a05d4f",
+ "status": "approved",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ },
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": 123,
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": 123,
+ "result": {
+ "status": 500,
+ "ok": false
+ }
+ },
+ {
+ "step": "set_metafield",
+ "customer": 123,
+ "result": {
+ "status": 201,
+ "ok": true
+ }
+ },
+ {
+ "step": "email_rep",
+ "to": "rep@example.invalid",
+ "subject": "Fixture rep",
+ "dryRun": false
+ },
+ {
+ "step": "email_applicant",
+ "to": "fixture@example.invalid",
+ "subject": "Fixture approval",
+ "dryRun": false
+ }
+ ]
+ },
+ "persisted": {
+ "id": "TRADE-20260905-a05d4f",
+ "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-05T06:53:21.547Z",
+ "decided_at": "2026-09-05T06:53:21.551Z",
+ "decision": "approved",
+ "assigned_rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ "calls": [
+ "GET",
+ "PUT",
+ "POST"
+ ],
+ "emails": [
+ "trade-rep-notify",
+ "trade-approved"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-9oeZ25/baseline/data/trade-applications.jsonl"
+ }
+ ]
+ },
+ "commands": [
+ {
+ "command": "node scripts/trade-approval-test.js",
+ "verdict": "PASS",
+ "report": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/report.json",
+ "passed": 21
+ },
+ {
+ "command": "node --check lib/trade.js; node --check lib/shopify.js; node --check scripts/selftest.js",
+ "verdict": "PASS"
+ },
+ {
+ "command": "git diff --check",
+ "verdict": "PASS"
+ }
+ ],
+ "boundaries": {
+ "entry": "Actual trade.apply() persists a pending application in retained temp JSONL; actual approve() executes with real Shopify module under a transport adapter.",
+ "api": "Fetch adapter handles actual Shopify HTTP method, URL, JSON envelope and readback; returns injected transport/API/schema failures.",
+ "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."
},
- "timestamp": "2026-08-31T08:42:00.304Z",
"checks": [
{
- "name": "storefront entry",
+ "name": "http500 fails closed",
+ "verdict": "PASS",
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-fa5896",
+ "status": "pending",
+ "error": "shopify_http_error",
+ "failedStep": "tag_trade_approved",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 500,
+ "ok": false
+ }
+ }
+ ]
+ },
+ "calls": [
+ "GET",
+ "PUT"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/http500/data/trade-applications.jsonl"
+ },
+ {
+ "name": "mf500 fails closed",
+ "verdict": "PASS",
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-dbdf8e",
+ "status": "pending",
+ "error": "shopify_http_error",
+ "failedStep": "set_metafield",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "set_metafield",
+ "customer": "123",
+ "result": {
+ "status": 500,
+ "ok": false
+ }
+ }
+ ]
+ },
+ "calls": [
+ "GET",
+ "PUT",
+ "POST"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/mf500/data/trade-applications.jsonl"
+ },
+ {
+ "name": "read500 fails closed",
+ "verdict": "PASS",
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-3b532b",
+ "status": "pending",
+ "error": "shopify_http_error",
+ "failedStep": "tag_trade_approved",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 500,
+ "ok": false
+ }
+ }
+ ]
+ },
+ "calls": [
+ "GET"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/read500/data/trade-applications.jsonl"
+ },
+ {
+ "name": "graphql_top fails closed",
+ "verdict": "PASS",
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-ddddaf",
+ "status": "pending",
+ "error": "shopify_response_error",
+ "failedStep": "tag_trade_approved",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ }
+ ]
+ },
+ "calls": [
+ "GET",
+ "PUT"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/graphql_top/data/trade-applications.jsonl"
+ },
+ {
+ "name": "graphql_user fails closed",
+ "verdict": "PASS",
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-dd9b08",
+ "status": "pending",
+ "error": "shopify_response_error",
+ "failedStep": "tag_trade_approved",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ }
+ ]
+ },
+ "calls": [
+ "GET",
+ "PUT"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/graphql_user/data/trade-applications.jsonl"
+ },
+ {
+ "name": "mf_user fails closed",
"verdict": "PASS",
- "detail": "Trade form, samples banner, and daily DIG room rendered"
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-ac124e",
+ "status": "pending",
+ "error": "shopify_response_error",
+ "failedStep": "set_metafield",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "set_metafield",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ }
+ ]
+ },
+ "calls": [
+ "GET",
+ "PUT",
+ "POST"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/mf_user/data/trade-applications.jsonl"
},
{
- "name": "returning client",
+ "name": "malformed fails closed",
"verdict": "PASS",
- "detail": "Branded modal opens and hands off to Shopify customer authentication with return URL"
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-7931ac",
+ "status": "pending",
+ "error": "shopify_response_error",
+ "failedStep": "tag_trade_approved",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ }
+ ]
+ },
+ "calls": [
+ "GET",
+ "PUT"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/malformed/data/trade-applications.jsonl"
},
{
- "name": "client validation",
+ "name": "empty fails closed",
"verdict": "PASS",
- "detail": "Empty required form is blocked before any network submission"
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-d43a2a",
+ "status": "pending",
+ "error": "invalid_tag_response",
+ "failedStep": "tag_trade_approved",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ }
+ ]
+ },
+ "calls": [
+ "GET",
+ "PUT"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/empty/data/trade-applications.jsonl"
},
{
- "name": "SMS declined",
+ "name": "missing_tag fails closed",
"verdict": "PASS",
- "detail": "Application succeeds with optional SMS box unchecked and records disclosure evidence"
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-6be3c9",
+ "status": "pending",
+ "error": "entitlement_not_verified",
+ "failedStep": "verify_entitlement",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "set_metafield",
+ "customer": "123",
+ "result": {
+ "status": 201,
+ "ok": true
+ }
+ },
+ {
+ "step": "verify_entitlement",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ }
+ ]
+ },
+ "calls": [
+ "GET",
+ "PUT",
+ "POST",
+ "GET"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/missing_tag/data/trade-applications.jsonl"
},
{
- "name": "SMS accepted",
+ "name": "wrong_id fails closed",
"verdict": "PASS",
- "detail": "Explicit unchecked-by-default opt-in records affirmative consent, STOP/HELP, and DNC notice"
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-5107dd",
+ "status": "pending",
+ "error": "entitlement_not_verified",
+ "failedStep": "verify_entitlement",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "set_metafield",
+ "customer": "123",
+ "result": {
+ "status": 201,
+ "ok": true
+ }
+ },
+ {
+ "step": "verify_entitlement",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ }
+ ]
+ },
+ "calls": [
+ "GET",
+ "PUT",
+ "POST",
+ "GET"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/wrong_id/data/trade-applications.jsonl"
},
{
- "name": "API negative path",
+ "name": "missing_customer fails closed",
"verdict": "PASS",
- "detail": "Missing email returns HTTP 400"
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-09a84a",
+ "status": "pending",
+ "error": "entitlement_not_verified",
+ "failedStep": "verify_entitlement",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "set_metafield",
+ "customer": "123",
+ "result": {
+ "status": 201,
+ "ok": true
+ }
+ },
+ {
+ "step": "verify_entitlement",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ }
+ ]
+ },
+ "calls": [
+ "GET",
+ "PUT",
+ "POST",
+ "GET"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/missing_customer/data/trade-applications.jsonl"
},
{
- "name": "production application",
+ "name": "throw fails closed",
"verdict": "PASS",
- "detail": "Reused previously submitted live canary TRADE-20260831-4f173e; no duplicate message sent"
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-704eed",
+ "status": "pending",
+ "error": "approval_exception",
+ "failedStep": "tag_trade_approved",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ }
+ ]
+ },
+ "calls": [
+ "GET",
+ "PUT"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/throw/data/trade-applications.jsonl"
},
{
- "name": "review queue boundary",
+ "name": "verified entitlement then persisted approval and reload",
"verdict": "PASS",
- "detail": "Production admin credential correctly rejected locally; independent SSH verifier found the same pending ID, marker, and SMS evidence in the live ledger"
+ "result": {
+ "ok": true,
+ "id": "TRADE-20260905-abc417",
+ "status": "approved",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ },
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "set_metafield",
+ "customer": "123",
+ "result": {
+ "status": 201,
+ "ok": true
+ }
+ },
+ {
+ "step": "verify_entitlement",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "email_rep",
+ "to": "rep@example.invalid",
+ "subject": "Fixture rep",
+ "dryRun": false
+ },
+ {
+ "step": "email_applicant",
+ "to": "fixture@example.invalid",
+ "subject": "Fixture approval",
+ "dryRun": false
+ }
+ ]
+ },
+ "persisted": {
+ "id": "TRADE-20260905-abc417",
+ "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-05T06:56:33.051Z",
+ "decided_at": "2026-09-05T06:56:33.054Z",
+ "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-05T06:56:33.053Z",
+ "status": 200
+ },
+ "applicant": {
+ "state": "sent",
+ "sent_at": "2026-09-05T06:56:33.054Z",
+ "status": 200
+ }
+ },
+ "entitlement_verified_at": "2026-09-05T06:56:33.053Z"
+ },
+ "approval_error": null
+ },
+ "calls": [
+ "GET",
+ "PUT",
+ "POST",
+ "GET"
+ ],
+ "emails": [
+ "trade-rep-notify",
+ "trade-approved"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/happy/data/trade-applications.jsonl"
},
{
- "name": "approval security",
+ "name": "failed mutation can retry after reload",
"verdict": "PASS",
- "detail": "Review queue requires auth and forged approval token is rejected"
+ "persisted": {
+ "id": "TRADE-20260905-a87b67",
+ "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-05T06:56:33.062Z",
+ "decided_at": "2026-09-05T06:56:33.067Z",
+ "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-05T06:56:33.066Z",
+ "status": 200
+ },
+ "applicant": {
+ "state": "sent",
+ "sent_at": "2026-09-05T06:56:33.067Z",
+ "status": 200
+ }
+ },
+ "entitlement_verified_at": "2026-09-05T06:56:33.066Z"
+ },
+ "approval_error": null
+ },
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/retry/data/trade-applications.jsonl"
+ },
+ {
+ "name": "email partial success survives restart without duplicate rep send",
+ "verdict": "PASS",
+ "persisted": {
+ "id": "TRADE-20260905-da8c89",
+ "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-05T06:56:33.071Z",
+ "decided_at": "2026-09-05T06:56:33.077Z",
+ "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-05T06:56:33.072Z",
+ "status": 200
+ },
+ "applicant": {
+ "state": "sent",
+ "sent_at": "2026-09-05T06:56:33.076Z",
+ "status": 200
+ }
+ },
+ "entitlement_verified_at": "2026-09-05T06:56:33.075Z"
+ },
+ "approval_error": null
+ },
+ "emails": [
+ "trade-rep-notify",
+ "trade-approved",
+ "trade-approved"
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/mail-retry/data/trade-applications.jsonl"
+ },
+ {
+ "name": "ambiguous email delivery blocks resend across restart",
+ "verdict": "PASS",
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-d6afa1",
+ "status": "pending",
+ "error": "email_delivery_unknown",
+ "failedStep": "email_applicant",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "set_metafield",
+ "customer": "123",
+ "result": {
+ "status": 201,
+ "ok": true
+ }
+ },
+ {
+ "step": "verify_entitlement",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "email_rep",
+ "skipped": true,
+ "reason": "already_sent",
+ "sent_at": "2026-09-05T06:56:33.082Z"
+ }
+ ]
+ },
+ "persisted": {
+ "id": "TRADE-20260905-d6afa1",
+ "email": "fixture@example.invalid",
+ "business_name": "Fixture",
+ "resale_cert": "",
+ "phone": "",
+ "extra": {
+ "shopify_customer_id": 123
+ },
+ "shopify_customer_id": "123",
+ "status": "pending",
+ "created_at": "2026-09-05T06:56:33.080Z",
+ "decided_at": null,
+ "decision": null,
+ "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-05T06:56:33.082Z",
+ "status": 200
+ },
+ "applicant": {
+ "state": "sending",
+ "attempted_at": "2026-09-05T06:56:33.082Z"
+ }
+ },
+ "entitlement_verified_at": "2026-09-05T06:56:33.085Z"
+ },
+ "approval_error": {
+ "error": "email_delivery_unknown",
+ "step": "email_applicant",
+ "at": "2026-09-05T06:56:33.085Z"
+ }
+ },
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/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",
+ "status": "pending",
+ "error": "approval_exception",
+ "failedStep": "email_applicant",
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "set_metafield",
+ "customer": "123",
+ "result": {
+ "status": 201,
+ "ok": true
+ }
+ },
+ {
+ "step": "verify_entitlement",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "email_rep",
+ "to": "rep@example.invalid",
+ "subject": "Fixture rep",
+ "dryRun": false
+ },
+ {
+ "step": "email_applicant",
+ "to": "fixture@example.invalid",
+ "subject": "Fixture approval",
+ "dryRun": false
+ }
+ ]
+ },
+ "persisted": {
+ "id": "TRADE-20260905-f0fd1b",
+ "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-05T06:56:33.089Z",
+ "decided_at": "2026-09-05T06:56:33.094Z",
+ "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-05T06:56:33.091Z",
+ "status": 200
+ },
+ "applicant": {
+ "state": "sent",
+ "sent_at": "2026-09-05T06:56:33.092Z",
+ "status": 200
+ }
+ },
+ "entitlement_verified_at": "2026-09-05T06:56:33.094Z"
+ },
+ "approval_error": null
+ },
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/checkpoint-failure/data/trade-applications.jsonl"
+ },
+ {
+ "name": "same-process concurrent duplicate does not duplicate emails",
+ "verdict": "PASS",
+ "results": [
+ {
+ "ok": true,
+ "id": "TRADE-20260905-5454d0",
+ "status": "approved",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ },
+ "steps": [
+ {
+ "step": "assign_rep",
+ "rep": {
+ "id": "fixture-rep",
+ "name": "Fixture Rep",
+ "email": "rep@example.invalid"
+ }
+ },
+ {
+ "step": "resolve_customer",
+ "customer": "123",
+ "via": "application"
+ },
+ {
+ "step": "tag_trade_approved",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "set_metafield",
+ "customer": "123",
+ "result": {
+ "status": 201,
+ "ok": true
+ }
+ },
+ {
+ "step": "verify_entitlement",
+ "customer": "123",
+ "result": {
+ "status": 200,
+ "ok": true
+ }
+ },
+ {
+ "step": "email_rep",
+ "to": "rep@example.invalid",
+ "subject": "Fixture rep",
+ "dryRun": false
+ },
+ {
+ "step": "email_applicant",
+ "to": "fixture@example.invalid",
+ "subject": "Fixture approval",
+ "dryRun": false
+ }
+ ]
+ },
+ {
+ "ok": false,
+ "error": "approval_in_progress",
+ "id": "TRADE-20260905-5454d0"
+ }
+ ],
+ "store": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY/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"
+ },
+ {
+ "name": "dry run reports simulated pending without external side effects",
+ "verdict": "PASS",
+ "result": {
+ "ok": false,
+ "id": "TRADE-20260905-9b01cc",
+ "status": "pending",
+ "dryRun": true,
+ "simulated": true,
+ "error": "dry_run_simulated",
+ "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"
+ },
+ {
+ "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"
}
],
- "artifacts": [
- "/Users/macstudio3/Projects/dw-signup-fulfillment/verification/artifacts/signup-2026-08-31T08-42-00-304Z.png"
+ "limitations": [
+ "No live Shopify/customer/email operation or deployment performed; those require explicit deployment approval and a separate controlled live verification.",
+ "Single-process in-flight guard only; multiple processes sharing this JSONL store remain unsupported.",
+ "Crash or ambiguous email delivery leaves sending/unknown receipt for manual reconciliation; exactly-once delivery requires provider idempotency outside this increment.",
+ "Legacy full npm selftest was not executed because it imports real config and mutates runtime data; only approval assertions updated and syntax checked. Focused test has no real config or network capability.",
+ "The existing whole-file store is not redesigned; disk corruption/multi-process coordination are outside this scoped increment."
],
- "productionValidation": {
- "consecutivePasses": 5,
- "timestamp": "2026-08-31T08:43:00-07:00",
- "verdict": "PASS",
- "assertionsPerPass": [
- "live service health reports DRY_RUN=false",
- "invalid orders-paid webhook HMAC returns 401",
- "valid signed no-op webhook returns 200",
- "orders-paid webhook subscription exists",
- "DW Free Samples automatic Function discount is ACTIVE",
- "discount Function ID matches deployed function",
- "product-discount combination remains enabled",
- "2018 legacy customer fixture remains intact",
- "legacy retail fixture does not carry trade_approved",
- "storefront exposes returning-account and retail/trade sample copy"
- ],
- "passes": [1, 2, 3, 4, 5]
- },
- "sourceParity": {
- "verdict": "PASS",
- "detail": "SHA-256 hashes match between local and live Kamatera for server.js, config, trade approval, sample ledger, OAuth helper, and production validator."
- },
- "retainedState": {
- "applicationId": "TRADE-20260831-4f173e",
- "status": "pending",
- "reason": "Retained as a labeled production E2E canary; intentionally not approved or rejected to avoid Shopify customer mutation or another outbound email."
- },
- "verdict": "PASS"
+ "retained_test_state": [
+ "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-9oeZ25",
+ "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk11285-flow-Dh9eLY"
+ ],
+ "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"
}
← 8a877d4 Record independent verification of live designer signup chan
·
back to Dw Signup Fulfillment
·
Serialize trade rejection with approval and preserve concurr 116fc35 →