Dw Signup Fulfillment

repo: ~/Projects/dw-signup-fulfillment · 143 commits · 0 in last 24h, 0 in last 7d ·

Search the build

143 commits indexed

  1. 12cc71b 2026-09-10 TK-10836: audit record for the D3501 deletion (original commit lost to an add -A race)
  2. 1de8a86 2026-09-10 TK-11406: sample-ledger counts free units title-agnostically (Regios 'Free sample'), restoring the app-side 3-sample cap
  3. 040121b 2026-09-10 TK-11411: fail loud on unset PUBLIC_URL before any send, not 11 times after
  4. 1a93bb0 2026-09-10 TK-11411: protect the resend ledger from rsync --delete
  5. 03b8d4b 2026-09-10 TK-11411: ledgered re-send for the 14 customers who lost mail in the George outage
  6. c959bc5 2026-09-10 TK-11366: answer the valence question - full-price free shipping was a weight-data leak
  7. b3b8acb 2026-09-10 TK-10836: park the Kelly remedy — Steve: stop sending to Kelly
  8. f050f30 2026-09-10 TK-11366: store-wide signal survives cart-mix control - but its direction of harm flips
  9. a4afa27 2026-09-10 TK: cap guarded auto-approve at 50/day, falling back to the review card
  10. c90789d 2026-09-10 TK-11361: archive both retired DW Free Samples (fn 01a0475d) discount nodes; drop stale prod-validation assertions
  11. 079d720 2026-09-10 TK-10836: DTD verdict B — stage the ship-it remedy (draft order + one true note)
  12. b2704f9 2026-09-10 TK-11366: read-only app forensics - recover install order, surface shipping-scoped apps
  13. 3c7bad1 2026-09-10 TK: guarded auto-approve trade signups on submit (email-valid + dedupe guardrails; staff FYI; legacy card fallback)
  14. 52e57ab 2026-09-10 TK-11366: DTD verdict - A-prime (close the $45 gap first, then pull carriers); flip is store-wide
  15. 07e8ba5 2026-09-10 TK-11366: ROOT CAUSE FOUND - checkout's default shipping option flipped ~2026-08-18
  16. d0135eb 2026-09-10 auto-data-snapshot: 2026-09-10T09:22:46 (1 data files) — verification/tk10836/abandoned-default.json
  17. 86dd5a0 2026-09-10 TK-11377: stop my own test being a fourth writer of the live store
  18. b0c4280 2026-09-10 TK-11377: --send-only must not email an already-decided application
  19. ff9bd17 2026-09-10 TK-10836: live checkout render blocked by mandatory customer login; fix units-vs-lines metric error
  20. fea5a31 2026-09-10 TK-11285: deploy runbook with pre-deploy backup, verify, and rollback
  21. a95eaa2 2026-09-10 TK-11285: stop the deploy itself from destroying the live applications
  22. 534d1f6 2026-09-10 TK-11285: correct my own overclaim in the f9de58c write-up
  23. f9de58c 2026-09-10 TK-11285: the race was bidirectional — stop the service clobbering too
  24. 944582a 2026-09-10 TK-10836: contrarian overturns my Option D headline; correct it and kill 3 more hypotheses
  25. f849a46 2026-09-10 TK-11285: fix a clobber bug in my own commitRows merge, and test it
  26. 251724b 2026-09-10 TK-10836: Option D complete — the $0.00 free-shipping rate WAS available on Kelly's cart
  27. bef6f4f 2026-09-10 TK-11285: close the second-writer race the deploy preflight required
  28. 33dd248 2026-09-10 TK-11361: soften attribution claim to match evidence strength
  29. 7e96b04 2026-09-10 TK-11361: Option A measured — cap holds at 3, duplicate is inert (DW function grants nothing)
  30. f5076d0 2026-09-10 TK-11361: confirm duplicate DW Free Samples automatics; withdraw the false retraction
  31. ec11bce 2026-09-10 TK-10836: REVERSE the close verdict — Kelly's own checkout shows $24.95 shipping
  32. a69c55f 2026-09-10 TK-10836: overturn contrarian's blocking hole on the full 3FREE cohort
  33. 8e95676 2026-09-10 TK-10836: independent second-session re-verification
  34. 4d8163c 2026-09-10 TK-10836: close as superseded — Kelly Paradis already honored via code 3FREE
  35. ba82c82 2026-09-07 auto-data-snapshot: 2026-09-07T10:43:18 (2 data files) — verification/tk11185-reconcile/RECONCILIATION.md verification/tk11185-reconcile/live-read.json
  36. 6d5e374 2026-09-05 auto-data-snapshot: 2026-09-05T08:46:42 (1 data files) — verification/tk11185-reconcile/live-read.json
  37. b8ee89a 2026-09-05 auto-data-snapshot: 2026-09-05T01:06:29 (1 data files) — verification/tk11185-reconcile/retired-e2e-proof.json
  38. dc1323f 2026-09-05 Retire obsolete theme commands that could undo designer signup
  39. 116fc35 2026-09-05 Serialize trade rejection with approval and preserve concurrent intake
  40. d291ec9 2026-09-04 Verify trade entitlement before approval and preserve retry progress
  41. 8a877d4 2026-09-04 Record independent verification of live designer signup changes
  42. 1e9062c 2026-09-04 Verify published designer account flow and Shopify login handoff
  43. 45fc36e 2026-09-04 auto-data-snapshot: 2026-09-04T23:09:55 (1 data files) — verification/tk11283/rollback-rehearsal.json
  44. 01593e3 2026-09-04 Guide designers from full application to account setup and email sign-in
  45. 1944da5 2026-09-03 retail-webhook: close double-fire race — in-process in-flight claim before send
  46. 90c2cb8 2026-09-03 TK-11190: harden recover-stuck-apps — fail-loud --apply DRY_RUN guard + idempotent --send-only
  47. 164ab38 2026-09-03 TK-11190: recovery tooling for pre-fix stuck trade applications (link retroactively)
  48. 5e609e8 2026-09-03 TK-11185: add Gate 2 theme PUT + rollback scripts (cwd-independent, self-verify main theme)
  49. 6e51634 2026-09-03 TK-11185: back up live dw-signin-modal snippet + stage patched two-step OTP success handler
  50. eba3bdc 2026-09-03 TK-11185: rate-limit public /trade/apply (per-IP) — close the write_customers abuse vector
  51. 31cd4dd 2026-09-03 TK-11185: server-side find-or-create customer at trade-apply time (born linked, always approvable)
  52. 48d0832 2026-09-02 TK-11120: definitive checkout test — verified-sample caps at 3 free samples, not 5
  53. d1ffb0f 2026-09-02 TK-11120 Option B: verify page shows the token-carried sample count (5 for the retail cohort)
  54. 0b127cf 2026-09-02 TK-11120: retail-only 5-sample apology resend (--force, --retail-only) — 74 sent from info@
  55. 64b3102 2026-09-02 TK-11120: real-customer click proof — verify confirms + tags with zero input (PASS)
  56. e288df7 2026-09-02 auto-data-snapshot: 2026-09-02T14:03:59 (1 data files) — verification/tk11120/tk11120-resend-ledger.jsonl
  57. 52afecd 2026-09-02 TK-11120: server.js baseUrl() also fails closed on missing PUBLIC_URL (defense-in-depth)
  58. a6edf22 2026-09-02 TK-11120: corrected resend tool + apology template (retail-verify localhost outage)
  59. 0016d65 2026-09-02 TK-11120: verify links fail-closed on missing PUBLIC_URL + send as info@ (retail-verify localhost-link outage)
  60. d8959b9 2026-09-02 TK-11114: backfill sent (68/68) — outage fully remediated
  61. 228c2ea 2026-09-02 TK-11114: backfill tooling — scope (68 affected since go-live) + safe sender
  62. a287362 2026-09-02 auto-data-snapshot: 2026-09-02T12:07:47 (1 data files) — verification/tk11114/backfill-affected.json
  63. 992e15e 2026-09-02 TK-11114: BLOCK B — verify email proven end-to-end (send 200 -> inbox -> verify-click -> verified-sample tag persists)
  64. e9e18c7 2026-09-02 TK-11114: record BLOCK A deploy success (fix live, verified green)
  65. 3efc18c 2026-09-02 TK-11114: durable persist confirmed — signup email now restart-safe
  66. cb069ce 2026-09-02 TK-11114: dry-run-safe verify-email backfill executor (double-locked send)
  67. 49e1750 2026-09-02 TK-10830: one-shot guard on Kelly reply (prevent duplicate sends)
  68. 487679a 2026-09-02 TK-11114: R4 proof — DW signup verify-email fixed + verified end-to-end
  69. 7963d0a 2026-09-02 TK-11114: reconcile deploy paste — supersede stale webhook FIX-PASTE with verified BLOCK A (file-sync + clean-env restart)
  70. e393474 2026-09-02 prove TK-11114 signup email remediation end to end
  71. e8c174a 2026-09-02 auto-data-snapshot: 2026-09-02T11:28:31 (2 data files) — arm-webhook.sh.DISABLED-duplicate-webhook-2026-09-02 verification/tk11114-remediation/RUNBOOK.md
  72. 9e5aa29 2026-09-02 prevent customer data in George failure logs
  73. b7b23da 2026-09-02 TK-11114: un-swallow George send failures (credential-safe logging)
  74. 7c99f88 2026-09-02 auto-data-snapshot: 2026-09-02T10:48:45 (1 data files) — kelly-customer.json
  75. 5083a87 2026-09-02 document TK-11114 signup email failure proof
  76. ce79cb4 2026-08-31 prove retail and designer checkout entitlements
  77. 8bfd807 2026-08-31 auto-data-snapshot: 2026-08-31T02:00:54 (4 data files) — verification/artifacts/shopify-account-authenticated-2026-08-31T09-00-07-283Z.png verification/artifacts/shopify-account-email-2026-08-31T08-58-59-401Z.png verification/artifacts/shopify-account-email-2026-08-31T09-00-07-283Z.png verification/shopify-account-e2e.json
  78. cd936fe 2026-08-31 record signup e2e build identity
  79. aa1c2a5 2026-08-31 prove production signup and sample activation
  80. b745c53 2026-08-31 Record TK-10994 production E2E proof
  81. a491d70 2026-08-29 Declare message_class transactional on george send (TK-10944)
  82. 72998fc 2026-08-28 auto-data-snapshot: 2026-08-28T07:45:44 (1 data files) — package.json
  83. 56700f8 2026-08-28 auto-data-snapshot: 2026-08-28T07:08:13 (1 data files) — package.json
  84. 37837ac 2026-08-28 auto-data-snapshot: 2026-08-28T00:56:56 (1 data files) — package.json
  85. 7b6cce3 2026-08-26 chore: version bump (session close)
  86. f975a4d 2026-08-26 preflight: load ecosystem env as fallback so standalone runs reflect live pm2 config
  87. 081fb31 2026-08-17 snapshot before restart: preserve in-flight work
  88. f231337 2026-08-17 auto-data-snapshot: 2026-08-17T07:02:20 (2 data files) — .playwright-mcp/page-2026-08-17T13-53-00-059Z.yml instagram-subdomain-full.png
  89. e398ba2 2026-08-17 snapshot before restart: preserve in-flight work (auto-saved by /restart pre-reboot)
  90. 33d6c4b 2026-08-14 contrarian gate: dedupe /verify confirmation email (fire once, not per re-click); fix runbook path in 5x report
  91. b32b988 2026-08-14 5x RE-RUN 3: Option C /claim + /verify pages — 2 defects caught+fixed, both clean twice
  92. e642d0d 2026-08-14 5x sweep: /verify returns 200 for invalid/expired human links (was 400 → console error on a customer page); 503 kept for no_secret
  93. 535e4f8 2026-08-14 5x sweep 1: client-side email validation on /claim (empty submit no longer fires a 400)
  94. aa70472 2026-08-14 auto-data-snapshot: 2026-08-14T14:26:13 (1 data files) — package-lock.json
  95. 5b98c35 2026-08-14 chore: lint + refactor golive-preflight (query-preserve, visible write-fail, crash guard), v0.1.4 (session close)
  96. d292c26 2026-08-14 DEPLOY.md: point pre-flight at golive-preflight.js + link the gated go-live runbook
  97. ce1c7d7 2026-08-14 ecosystem: set PUBLIC_URL for Option C verify links + webhook (DRY_RUN still pinned to 1)
  98. 927545c 2026-08-14 add Option C go-live readiness preflight (PASS/WARN/FAIL, no-send George auth probe)
  99. 7169240 2026-08-14 auto-data-snapshot: 2026-08-14T12:54:45 (1 data files) — package-lock.json
  100. f1cb8a4 2026-08-14 chore: fail-loud guard on empty George Basic-auth, v0.1.3 (session close)
  101. 28160cb 2026-08-14 honor-reissue: isolate DRY ledger from live (dry preview no longer pollutes live done-state); gitignore ledgers
  102. 5ea0034 2026-08-14 honor-reissue: 429-aware retry + gentle pacing for the 51-card burst run
  103. d5c8741 2026-08-14 fix George Basic-auth resolution (real 401 root cause: wrong cred source→empty pass); verify via config; abort honor live-run if Shopify token unset
  104. 3658732 2026-08-14 honor-reissue: explicit --apply flag to go live (clean command for permission rule)
  105. bf4aa2c 2026-08-14 honor-reissue: void+re-mint 53 orphaned free-sample cards (persist codes), George verify script, disableGiftCard helper
  106. 43065de 2026-08-14 retail-webhook: only set sample_verify_sent flag when the letter actually sent
  107. f2bce23 2026-08-14 selftest: cover Option C (verify letter, token security, tag write); drop gift-card asserts
  108. 9196a62 2026-08-14 DEPLOY.md: rewrite runbook for Option C (verify→tag→Regios), retire old paths
  109. 75d27f5 2026-08-14 Retail free-samples: pivot to verify→tag (Option C), drop gift cards
  110. 1755b63 2026-08-07 chore: session-close quality gate — comment accuracy fix + v0.1.2
  111. f7846f6 2026-08-07 retail gift code: uppercase for display in email (DWSR2VEF), redemption still case-insensitive
  112. f488316 2026-08-07 signups: designer welcome auto-sends create-account CTA; retail gift code now short DW###### (8-char)
  113. bb988c6 2026-08-07 trade-invite outreach: time-boxed office-copy hook + send script (names Alison/Megan)
  114. 7f5a947 2026-08-07 feat(email): approved branded retail welcome letter + new designer welcome-on-apply letter (Steve-approved 2026-08-07); send designer welcome to applicant on /trade/apply
  115. 848697f 2026-08-06 fix(theme): point trade-apply modal at canonical signup.designerwallcoverings.com (was old dwsignup.agentabrams.com host); backup + provenance note
  116. 21d567f 2026-07-28 ADMIN_PASS resolvable from on-host secrets (DW_SIGNUP_ADMIN_PASS) so a strong per-host admin password can be set without a wire leak; public /admin must not stay on DW2024! at go-live
  117. e723240 2026-07-28 chore: lint safe-fixes (redact gift code in logs, esc gift code, RFC7617 colon-safe basic-auth, email esc quotes), refactor (naming/dedup/shadowing), .gitignore pyc+bak, v0.1.1 (session close)
  118. 608509e 2026-07-28 Magic-links now expire (48h, tamper-proof signed expiry) — closes contrarian LOW-MED. Verified fresh/expired/tampered/oldfmt/forged all behave. Only live-Shopify smoke test remains before go-live.
  119. 44974d4 2026-07-28 5x report: append contrarian gate outcome (critical secret fixed + verified, medium dead-letter fixed, go-live gates noted)
  120. 3561f86 2026-07-28 SECURITY (contrarian FIX FIRST): magic-link signing secret now 256-bit from secrets master — was derived from public ADMIN_PASS (DW2024!) so every approve token was forgeable from the public /trade/apply id. Fail-closed when unset; email falls back to admin panel; dead-letter failed notifies so applications never rot silently. Verified: forged old-secret token → 403, properly-signed → valid.
  121. d48cfb0 2026-07-28 5x re-run: email-approve-button feature verified clean twice (0 defects)
  122. 6e05436 2026-07-28 Send service outbound from info@ (monitored office inbox) instead of steve@ personal
  123. adb478a 2026-07-28 harden public mint webhook (best-practices fix): URL-token auth (secret-less-compatible), per-IP rate-limit, created_at freshness gate, daily mint cap + $ logging — blocks mass-minting to the customer base; selftest adds stale-customer test; all verified over HTTP (401/429/503/200), still DRY_RUN
  124. 961a1b8 2026-07-28 Trade applications email info@ a review card with one-click Approve/Reject magic-links (via George); token-authed GET routes, DRY_RUN-safe
  125. 378aafa 2026-07-28 auto-save: 2026-07-28T13:30:51 (6 files) — lib/config.js lib/email.js lib/trade.js server.js theme-proposals/loggedin-trade-entry/backups/
  126. 302af39 2026-07-28 TK-10021: stage logged-in Apply-for-Trade fix (proposal + deploy.py, dry-run clean)
  127. 447c471 2026-07-28 auto-save: 2026-07-28T13:00:43 (3 files) — lib/config.js lib/mint-ledger.js theme-proposals/
  128. b6f0091 2026-07-28 auto-save: 2026-07-28T12:00:18 (1 files) — theme-backups/
  129. d3d9b16 2026-07-28 webhook: secret-less re-fetch auth (Steve's choice) — re-fetch customer, use real on-file email, idempotent one-gift-per-customer metafield; selftest green
  130. 4a5a683 2026-07-28 auto-save: 2026-07-28T11:30:09 (1 files) — lib/shopify.js
  131. 766b6b6 2026-07-28 hosting: live on Kamatera dwsignup.agentabrams.com (pm2 :9862, LE cert, DRY_RUN=1) — port 9856 was taken; sync local to 9862
  132. f385a17 2026-07-28 retail: FINAL = gift card (Steve, memo §2) — re-wire webhook+admin+selftest to giftcard, shared-code demoted to alternate; preserves trade.js customer-id hardening
  133. 379738e 2026-07-28 harden per contrarian gate: resolve trade customer-id by email + hard-fail instead of __UNKNOWN__ live write (closes memo §5); DEPLOY.md rewritten for wired path B; selftest banner + boot log fixed (no pass echo) — still DRY_RUN
  134. d0059b8 2026-07-28 retail: rewire webhook to sample-locked shared function code (DTD 5/5 path B) — matches approved memo §2, never discounts a roll; selftest now exercises the wired path + fail-loud unset-code guard (still DRY_RUN)
  135. 80fab35 2026-07-28 go-live prep: CORS for cross-origin /trade/apply + .deploy.conf scaffold (still DRY_RUN)
  136. 26d014b 2026-07-28 theme proposal: returning sign-in front-and-center (email code + Google, no password), remove Forgot-password
  137. 38fc598 2026-07-28 5x: REPORT.md — two consecutive clean sweeps; admin Approve click-through verified
  138. 73e3ebf 2026-07-28 5x: add open root status page + favicon 204 (clean base URL, no phantom JS error)
  139. a8ed621 2026-07-28 retail: wire automatic gift-card (unique code emailed per signup) as default; selftest green
  140. 471c945 2026-07-28 retail: shared admin master-code model (function discounts are app-scoped; cross-app minting not possible) — service emails RETAIL_SHARED_CODE
  141. c323e03 2026-07-28 retail: function-backed unique sample codes (safe sample-lock) + theme modal proposal + house-account
  142. d786b75 2026-07-28 rep assignment: fixed DW House Account (not round-robin) per Steve
  143. bc2558d 2026-07-28 initial scaffold: DW signup fulfillment (retail gift-card + moderated trade approval + rep round-robin), dry-run

Authors

Agents used

  • secrets-manager1

Skills used

  • /claude37
  • /code37
  • /verify10
  • /trade10
  • /tk112857
  • /apply7
  • /claim6
  • /recover-stuck-apps4
  • /shopify4
  • /tk11185-reconcile4
  • /auto-approve-ledger3
  • /mint-ledger3
  • /data3
  • /trade-applications3
  • /create3
  • /dw-signin-modal3
  • /config3
  • /artifacts3
  • /ship-kelly-samples2
  • /sendonly-filter-test2
  • /verification2
  • /order2
  • /live-read2
  • /account2
  • /loggedin-trade-entry2
  • /tk111202
  • /shopify-account-email-2026-08-2
  • /expired2
  • /tk11411-resend-ledger1
  • /day1

Creative ideas + design notes

Commits with substantial prose (≥120 chars) — the rationale behind each move.

12cc71b · 2026-09-10 · TK-10836: audit record for the D3501 deletion (original commit lost to an add -A race)
Steve authorized the delete directly at the CLARIFY gate. Executed, verified: node
re-read returns null, Kelly has 0 orders, nothing shipped, no confirmation email fired.
Undo is one command (scripts/ship-kelly-samples.mjs --apply, committed 079d720).

Filing this because my original deletion commit never landed: a concurrent session's
'git add -A && git commit' (1de8a86, TK-11406) raced mine and swept my untracked
snapshot into its commit under an unrelated message. Evidence for a customer-facing
deletion ended up labelled as a sample-ledger change.

Fleet lesson recorded in the file: with concurrent agent sessions in one repo, 'git
add -A' commits other sessions' in-flight files under your message. Stage explicit paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1de8a86 · 2026-09-10 · TK-11406: sample-ledger counts free units title-agnostically (Regios 'Free sample'), restoring the app-side 3-sample cap
The cap ledger only summed discounts titled 'DW Free Samples (auto)', but the live
grant is Regios titled 'Free sample' → countDiscountedSamples always returned 0 and
free_samples_used never incremented (cap was a silent no-op, held only by Regios
internally). Fix (b): sum all discount allocations on a sample line regardless of the
emitting app's title; the sample-line heuristic already scopes eligibility and
floor(allocated/unitPrice) keeps partial discounts at 0. Removes dead DISCOUNT_TITLE.
Test updated: title-agnostic order counts 3; undiscounted sample counts 0. DTD/Codex concurred (b).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014nA9g7iQZMhnsGbaXfFYDL
040121b · 2026-09-10 · TK-11411: fail loud on unset PUBLIC_URL before any send, not 11 times after
The approved run mailed the 3 trade letters successfully, then failed all 11
retail letters with no_public_url. lib/verify.js was right to refuse - without
PUBLIC_URL the verify link would be a dead localhost URL - but the refusal came
per-customer, after the trade cohort had already gone out, leaving a
half-completed run.

Cause: PUBLIC_URL lives in ecosystem.config.js, which pm2 injects into the
SERVICE. A bare `node scripts/...` does not inherit it. That is the exact
footgun documented at the top of scripts/recover-stuck-apps.js for DRY_RUN - a
comment I had already read today and then repeated with a different variable.

Now pre-flights once, before any send, and prints the correct invocation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1a93bb0 · 2026-09-10 · TK-11411: protect the resend ledger from rsync --delete
Same class as the auto-approve and mint ledgers: an idempotency record living
inside the deployed tree is reset by deploy.sh unless excluded — and a reset
resend ledger means re-mailing customers who were already re-mailed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
03b8d4b · 2026-09-10 · TK-11411: ledgered re-send for the 14 customers who lost mail in the George outage
Scoping correction first: I initially reported THREE affected customers. That was
wrong - it was only what fit in a 200-line log window. The real cohort from the
on-disk logs is FOURTEEN, and I had also grabbed a rotated logfile with `head -1`
before noticing. 17 retail-verify + 5 designer-welcome + 5 trade-application 401s.

Verified against live Shopify rather than trusting the logs: all 14 customers
exist and NONE carries verified-sample, so all 14 are genuinely still owed. They
split into two cohorts needing different letters:

  RETAIL (11) - signed up for free samples, never got "confirm your email", so
  never clicked, so no verified-sample tag, so they cannot claim samples.
  Remedy: re-run the same lib/verify.startVerification the /claim route uses.

  TRADE (3: noelle@silkandslate, team@michellewalshdesigns, yanchapaxi@auxarchitecture)
  - their approval genuinely LANDED. Shopify carries trade+trade_approved AND the
  local row says approved, so this is NOT the split-brain Cody warned about; only
  the "you're approved" letter was lost. Remedy: that letter alone. Deliberately
  does NOT re-run trade.approve(), which would re-tag and re-assign a rep.

Safety: dry-run by default and --apply refuses while DRY_RUN is on; the cohort is
DERIVED at run time from live Shopify tags plus the local store, never hardcoded,
so anyone who recovered on their own is skipped; idempotent through
data/tk11411-resend-ledger.jsonl, appended only after a confirmed real send.

Dry-run against live prod data classifies 11 RETAIL + 3 TRADE, sends nothing.

NOT SENT. Mailing 14 real customers is send-to-customer, hard-gated.
c959bc5 · 2026-09-10 · TK-11366: answer the valence question - full-price free shipping was a weight-data leak
Pre-window (Jul 1 - Aug 17) non-sample US orders, n=73 (31 free / 42 paid):
  FREE: median cart weight 0.25 lb, 55%% under 0.5 lb, 32%% of line items unweighted
  PAID: median cart weight 12 lb,     2%% under 0.5 lb,  2%% of line items unweighted

A 16x difference in unweighted-line rate. The free rate was gated on TOTAL WEIGHT
<= 0.5 lb on the General profile, so a cart with no product weights totals 0 lb and
ships free regardless of value: #32506 $938.84, #32510 $476.48, #32545 $411.72,
#32611 $396.00, #32725 $351.84 - all 0 lb, all unweighted, all free.

So the full-price shift is the OPPOSITE of the samples story: samples = customers
overcharged on advertised-free items (real harm, fixed); full-price = DW eating freight
from a catalog data gap (harm to DW, closed). No second customer-harm incident.

Residual not papered over: 45%% of free full-price orders weighed MORE than 0.5 lb and
are unexplained by the weight gate (#32818 $1820/4lb, #32622 $1095/6lb, #32700 $859/8lb).

Spun the catalog defect off as TK-11414.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b3b8acb · 2026-09-10 · TK-10836: park the Kelly remedy — Steve: stop sending to Kelly
Nothing was sent to the customer at any point in this session.

- send-kelly-shipped-note.js renamed .PARKED-2026-09-10-steve-stop-sending so it
  cannot fire by accident (undo: rename back).
- Did NOT write the .kelly-shipped-note.sent sentinel: that would falsely record
  a send that never happened.
- Draft order #D3501 ($0.00, 3 swatches, Ann Arbor) is left OPEN and unshipped.
  Anyone completing it WILL ship to the customer and trigger Shopify's own
  confirmation mail — flagged to the fleet, Steve's call to keep or delete.

DTD verdict B remains on the record as decided-but-not-executed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
f050f30 · 2026-09-10 · TK-11366: store-wide signal survives cart-mix control - but its direction of harm flips
Banded non-sample US orders by subtotal and compared free-shipping share WITHIN each
band, before vs after 2026-08-18 (the raw 22%%->0%% was untrustworthy because median
subtotal moved $294.57 -> $470.60):
  $0-100     81%% -> 25%%   (-56 pts)
  $100-300   41%% ->  0%%   (-41 pts)
  $300-1000  29%% ->  0%%   (-29 pts)
  $1000+     15%% ->  0%%   (-15 pts)
Present in every band, so not composition. 'Free Shipping (No Tracking)' appeared on
57 non-sample orders pre, 3 post.

REFRAME: that rate is the SAMPLE-scoped rate. Finding it on full-price orders - 15%% of
$1000+ wallcovering orders - means a sample rate was reaching full-price carts and DW
was eating the freight. So Aug-18 may be a leak in DW's FAVOR being closed, not a
customer-harm regression. Explicitly NOT claiming a second customer-harm incident.

TK-10836 closed (Kelly no-contact, nothing pending). TK-11366 re-scoped from blocked to
doing, with one narrow question: was full-price free shipping intended or leaked?

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a4afa27 · 2026-09-10 · TK: cap guarded auto-approve at 50/day, falling back to the review card
Steve: "yes cap it" — the optional daily cap the dw-trade-auto-approve memo
offered but left off.

Auto-approve instantly grants a real trade account (trade pricing + free memo
samples) to ANY valid submission, and its dedupe only stops SAME-email
re-submits, so the memo's own "known limits" conceded a sprayer using unique
emails is bounded only by the 5/hr/IP rate limit.

TRADE_AUTO_APPROVE_DAILY_CAP (default 50) bounds it per UTC day. Past the cap a
signup falls back to the existing review card rather than being refused, so a
burst degrades to "a human looks at it" instead of dropping applicants on the
floor. The count is taken at DISPATCH, not on success — an approve() that later
fails has still consumed a slot, which errs toward under-approving, the safe
direction for a control that exists to bound abuse.

lib/auto-approve-ledger.js persists a per-UTC-day count to
data/auto-approve-ledger.json, modeled on lib/mint-ledger.js (the gift-card
money backstop) — same shape, same UTC keying, same restart durability. An
in-memory counter would reset on every pm2 reload and deploy, i.e. exactly when
a cap must not reset.

DEPLOY SAFETY: added /data/auto-approve-ledger.json to RSYNC_EXTRA_EXCLUDES.
deploy.sh runs rsync --delete, so without it a deploy would ship this machine's
copy over prod's and RESET the day's count — silently defeating the cap. Also
added /data/mint-ledger.json, which had the same gap already: it is the
gift-card liability cap and was unprotected. Latent only because the file does
not exist on prod yet; it would have bitten on the first real mint. Verified by
a live --dry-run against prod: nothing under data/ is touched.

Tests: verification/tk11285/auto-approve-cap-test.js, 9 checks — counts, caps at
>= (not >), survives a restart, a previous day does not cap today, the default
is finite, and the REAL ledger is never written (the test points the module at a
throwaway path via AUTO_APPROVE_LEDGER_PATH and cleans up in a finally).
Off-by-one sensitivity demonstrated: with > instead of >=, a 4th auto-approve
slips through at cap=3.

Suite: trade-approval 27/27, commitRows 9/9, sendonly 7/7, cap 9/9, selftest pass.

NOT DEPLOYED. Gate 1 of the auto-approve memo is still Steve's call, and that
deploy would turn auto-approve ON in production.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
c90789d · 2026-09-10 · TK-11361: archive both retired DW Free Samples (fn 01a0475d) discount nodes; drop stale prod-validation assertions
Regios (title 'Free sample') owns the live sample entitlement per DEPLOY.md Option C; the DW Function nodes granted nothing on 40 real orders and were a latent combinable-discount landmine. production-validation.js no longer asserts the archived node exists; the dw-free-samples-fn-guard canary now owns the no-re-arm invariant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01Mem2YKvcU5UKmRfDtbVK2Q
079d720 · 2026-09-10 · TK-10836: DTD verdict B — stage the ship-it remedy (draft order + one true note)
DTD panel 2026-09-10: 7/7 unanimous for B (ship the samples) over A (email her
another checkout instruction), C (write off) or D (diagnose first). Reviewer
dissented for a confirm-first hybrid; three of its five objections failed on
check — notably 'there may be no shippable address', refuted: both abandoned
checkouts carry 2300 Sun Valley Drive, Ann Arbor MI 48108, entered twice.

scripts/ship-kelly-samples.mjs — builds a $0 DRAFT order (not a live order, so
the final customer-facing act stays human) with the exact 3 swatches from her own
abandoned cart 34015098110003 (DWCC-600006/600045/600128-Sample), her recorded
address, $0 shipping. Dry-run default; undo = delete the draft.

send-kelly-shipped-note.js — the ONE short true note, to send only AFTER the draft
is completed. No code, no checkout instruction, no re-marketing, and
no_source_tag:true so George's 'From job:' banner (TK-11365) stays out of customer
mail — it leaked into all 4 of the 2026-09-02 sends.

Both blocked from agent execution by the auto-mode classifier; staged for Steve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b2704f9 · 2026-09-10 · TK-11366: read-only app forensics - recover install order, surface shipping-scoped apps
AppInstallation exposes no createdAt, so timestamps are unavailable. Worked around it:
Shopify installation IDs are broadly monotonic, so sorting by numeric id recovers
install ORDER for free. 130 installations enumerated and ranked.

Apps holding shipping/delivery/checkout/cart-transform scopes, newest first:
Blockify: Fraud Filter (cart_transforms), two DW-owned custom tokens carrying
write_shipping + write_delivery_customizations + write_delivery_option_generators,
creativehub, Specsheet, DropdownVendortoNewSku, n8n, Gelato, Pixels, Kickflip,
Printful, Regios Discounts.

Top lead, explicitly CORRELATION NOT CAUSATION: Blockify is the 2nd-newest install and
the local blockify-allowlist-canary records Steve declining Blockify Premium on
2026-08-18 - the exact onset date of the flip. Mechanism gap is real: Blockify is
fraud/geo-blocking and cart_transforms alter cart lines, not delivery-option ordering.
Sent to the contrarian before being treated as a lead.

Read-only throughout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3c7bad1 · 2026-09-10 · TK: guarded auto-approve trade signups on submit (email-valid + dedupe guardrails; staff FYI; legacy card fallback)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017oFcLBoyTUxGQP7xnXtb9S
52e57ab · 2026-09-10 · TK-11366: DTD verdict - A-prime (close the $45 gap first, then pull carriers); flip is store-wide
Panel A=5 / B=1 / C=1, reviewer Cody non-voting.

Pure A rejected on measured evidence: Free Shipping caps at $45, Priority Sample Only
at $20, so a live 12-sample $51 cart returns ONLY carrier rates. Deactivating UPS+FedEx
would leave that cohort with zero rates and a dead checkout. Every A-voter assumed a
fallback to the $24.95 flat; the risk lens named it as the fatal precondition and voted
yes without checking it.

Pure B rejected too: open-ended 'diagnose first, change nothing' is not the right
principle; verify-before-touching is, and that is minutes.

Scope test added: the flip is STORE-WIDE, not sample-specific. Non-sample full-price
abandoned carts went 22%% -> 0%% recording $0.00, median default shipping $41.93 ->
$60.08 (+43%%). So A-prime is mitigation covering ~2.6%% of the surface, not the fix.

All steps are live Shopify shipping-config writes: drafted to pending-approval, not run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
07e8ba5 · 2026-09-10 · TK-11366: ROOT CAUSE FOUND - checkout's default shipping option flipped ~2026-08-18
Ran the counterfactual population my order-only cohorts could not see (Cody's original
objection): abandoned checkouts, which record what the shopper was HANDED, not what they
chose.

US sample-only abandoned checkouts recording $0.00 shipping:
  Jun 1-Aug 17: 24/26 (92%%)
  Aug 18-Sep 10: 2/56 (4%%)
  September: 0 of 17
Paid amounts cluster on UPS carrier rates: $23.35 x17, $23.13 x16.

Decisive because /cart/shipping_rates.json still returns 'Free Shipping (No Tracking)'
$0.00 listed FIRST. The rate is OFFERED but no longer DEFAULTED. Offered != defaulted,
and that one distinction explains every observation at once.

Measured impact: 51 orders since Aug 18 that qualified for $0.00 paid anyway
($1,120.10, avg $21.96); 54 of 56 abandoned checkouts carry a paid line; abandonment
volume more than doubled 26 -> 56.

Corrects me: I claimed Kelly's $24.95 was a self-selected upgrade. Not supported - her
checkout is in the post-flip population. Cody was right in substance.

Also corrects the record: shopifyFunctions is APP-SCOPED, so my earlier 'no rate-hiding
function exists' proved nothing; a function owned by another app is invisible to us.

Why the default flipped is NOT determined. Inspecting/changing the checkout default is
customer-facing money config and stays gated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
86dd5a0 · 2026-09-10 · TK-11377: stop my own test being a fourth writer of the live store
Cody's DTD dissent caught this and it was right. The previous revision of
verification/tk11285/sendonly-filter-test.js wrote its fixture straight to
trade.APPS_PATH - the REAL data/trade-applications.jsonl - and restored it
afterwards with no try/finally. One thrown execFileSync would have left six
synthetic rows (PENDING-A@example.com, APPROVED-A@example.com, ...) sitting in
the live applications store.

That is the exact hazard class this ticket and TK-11285 exist to close, in the
test written to prove the fix. It survived only because .deploy.conf excludes
both /verification and /data/trade-applications.jsonl from rsync, i.e. on a
human remembering to keep an exclude list correct forever. scripts/selftest.js,
which I edited in the same commit, already did it correctly with --file into a
tmpdir fixture; I had the right pattern in front of me and did not use it.

Now drives the CLI's own --file flag against a throwaway mkdtemp fixture,
cleaned up in a finally. Adds a 7th check asserting the real store path is never
even referenced in the output.

Proof: hashed data/trade-applications.jsonl before and after a run - byte
identical. Still non-tautological: reverting the filter fails 3/7 (approved and
rejected both get emailed).

Suite on master: trade-approval 27/27, commitRows 9/9, sendonly-filter 7/7,
DRY_RUN selftest pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b0c4280 · 2026-09-10 · TK-11377: --send-only must not email an already-decided application
Found while measuring whether TK-11377 had any live impact. The reconciliation
gap itself is latent (0 affected rows on prod), but the same filter carries a
live hazard: scripts/recover-stuck-apps.js's --send-only selected on
link_status + shopify_customer_id + !recovery_emailed with NO status check.

Measured on prod 2026-09-10: 20 rows matched, and 4 of them are already-approved
REAL designers decided 2026-09-03 (kmdesigncompany.com, veronica-valencia.com,
lenorekingluxuryinteriors.com, bonvivantinteriors.com). One
`DRY_RUN=0 ... --send-only` would have sent those 4 an "your account is ready,
activate it" letter a week after they were approved and already emailed, plus 4
"[Now approvable]" notices to the office for applications it had already
approved - 8 wrong emails to real people.

Both letters are wrong for a decided application, so the fix is to select only
pending ones. Skipped rows are printed rather than silently dropped, so an
operator can see what was held back and why.

Pre-existing; NOT introduced by the TK-11285 deploy, which does not touch this
path and is landed and verified.

Tests: verification/tk11285/sendonly-filter-test.js, 6 checks shaped from the
real prod rows, proven non-tautological (reverting the filter fails 3/6 and
would email approved-a, approved-b and rejected-a). selftest gains a decided-row
case; its assertion is loosened off the exact label text and onto the count plus
a real behavioural check of what was actually SENT.

Against a copy of live prod data: old filter 20 emails (4 wrong), new filter 16.

Suite: trade-approval 27/27, commitRows 9/9, sendonly-filter 6/6, selftest pass.
Local branch only - not merged, not deployed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ff9bd17 · 2026-09-10 · TK-10836: live checkout render blocked by mandatory customer login; fix units-vs-lines metric error
Live render (authorized): rebuilt Kelly's exact cart on the storefront. All three
routes to checkout redirect to a mandatory Shopify customer sign-in. Passing it
requires creating/logging into a customer account (identity, hard-gated), so stopped.
No checkout created, no email entered -> no abandoned-checkout record, no abandoned-cart
email. Actual externality zero.

Findings anyway:
- The free-sample comp is ACCOUNT-GATED, not automatic. Signed out, 6 sample units
  price $25.50, not $0.00.
- Cart copy states the rule: '10 samples or fewer ship free (no tracking); over 10
  units include a shipping charge' = the TOTAL_PRICE <= $45.00 condition.

Corrected an error in my own cohorts: they counted LINE ITEMS, not UNITS. Recounted
over 502 US sample-only orders since 2026-06-01:
- The >10-unit rule does NOT explain the paid orders. 62 paid orders were under $45
  list, most 1-3 units. #33033 shipped ONE $4.25 swatch, comped to $0.00, on $23.13
  UPS Ground. That is not a customer preferring tracking.
- Median units per order = 1 in BOTH windows, so cart size did not change.
- Break is starker after the fix: Jun1-Aug17 97%% free (n=405) vs Aug18-Sep10 43%% (n=97).

Six mechanisms now falsified. Root cause open; decisive observation is one screen
behind the login.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fea5a31 · 2026-09-10 · TK-11285: deploy runbook with pre-deploy backup, verify, and rollback
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEDp3MXiKVJ5GDKaQCHZon
a95eaa2 · 2026-09-10 · TK-11285: stop the deploy itself from destroying the live applications
Found while preparing the approved deploy. deploy.sh runs `rsync -az --delete`
and .deploy.conf carried NO RSYNC_EXTRA_EXCLUDES, so a deploy would have
overwritten prod's trade-applications.jsonl (19064 bytes, 23 rows, 16 pending)
with this machine's fixture copy (727 bytes, 2 test rows), and deleted six
prod-only files outright including both recover-stuck-apps rollback maps and
the live trade-notify-failures.jsonl.

deploy.sh's delete-guard would have caught it and aborted rather than shipped
the damage - but the deploy would simply have been blocked until someone added
these, and a DEPLOY_ALLOW_DELETE=1 override would have destroyed the data.

Adds the surgical excludes for state the live server and the operator CLIs
write. Verified by a real --dry-run --itemize-changes against prod: nothing
under data/ is touched, the only deletion is a stale lib/*.js.bak, and the
delete-guard now returns PASS.

Also excludes /verification - local test evidence prod has no use for, and its
paths match the guard's own risky-pattern regex ("verificat.*\.json"), which
would trip an abort on files that are merely being added.

DEPLOY.md gains the runbook rule Steve chose for the third writer: never run
npm test / npm run selftest against the prod checkout. selftest snapshots the
real store, writes its own rows into it, and restores the snapshot on exit, not
gated by DRY_RUN - a ~480ms window that silently destroys whatever arrived.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEDp3MXiKVJ5GDKaQCHZon
534d1f6 · 2026-09-10 · TK-11285: correct my own overclaim in the f9de58c write-up
Cody's re-gate returned FIX FIRST (narrowed): the bidirectional race fix is
genuine and it independently re-verified it, but I mischaracterized the
leftover link_status/link_error problem. Verified the correction myself.

f9de58c's message attributes the self-contradictory row (status:'approved' +
valid shopify_customer_id + link_status:'unlinked' + stale link_error) to the
clobber. That is overclaimed. The same shape appears with ZERO concurrency:
approve() resolves the customer through its findCustomerByEmail fallback and
stamps shopify_customer_id, but never reconciles link_status/link_error. It
needs no second writer and predates all TK-11285 work.

So the `self_contradictory` assertion in reverse-race.js was not a race
detector - it would have fired solo too. Renamed to
contradictory_shape_not_race_specific, explicitly documented as not evidence of
the race, and the harness gained a --solo control that demonstrates the
contradiction with no concurrent writer at all
(verification/tk11285/reverse-race-solo.json).

What actually IS the clobber, and what f9de58c genuinely fixes: a value that was
'linked' ON DISK being reverted to 'unlinked' by the service's stale row
snapshot. Those assertions are now gated on a second writer having run, so they
can no longer be satisfied by the solo gap. Race mode still reports CLAIM NOT
REPRODUCED after the fix.

The reconciliation gap is split to TK-11377 rather than folded in here - the
naive repair (set link_status='linked') would make already-approved designers
eligible for recover-stuck-apps.js --send-only's activation letter, which is a
product decision, not a mechanical fix. Non-blocking for this deploy.

No production code changed in this commit - evidence and wording only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEDp3MXiKVJ5GDKaQCHZon
f9de58c · 2026-09-10 · TK-11285: the race was bidirectional — stop the service clobbering too
Cody (contrarian) returned FIX FIRST on my bef6f4f/f849a46 work and was right.
I fixed recover-stuck-apps.js clobbering the service, but not the service
clobbering recover-stuck-apps.js. Verified independently before acting.

checkpointApproval() re-read the FILE (so new applications survived) but `app`
is a pre-await snapshot of the ROW, and `{...latest[index], ...app}` re-asserted
every field it held. So while approve() awaited Shopify and George, a concurrent
link by recover-stuck-apps.js was reverted: the row ended up status:'approved'
with a valid shopify_customer_id but link_status:'unlinked' and a stale
link_error — a self-contradictory record, and the --send-only filter then skips
it, so a designer can silently never receive the activation letter.

Reproduced 3/3 with my own harness (verification/tk11285/reverse-race.js,
independent of Cody's), before: "CLAIM CONFIRMED", after: "CLAIM NOT REPRODUCED".

Fix merges only the 7 fields a decision actually owns (status, decision,
decided_at, approval_error, assigned_rep, shopify_customer_id,
approval_progress), enumerated exhaustively from every `app.X =` in approve()
and reject(). Explicit key test rather than a bare spread — the same
undefined-still-spreads trap I hit in f849a46. approve() and reject() both
funnel through this one function, so it is a single-point fix.

Regression after changing the service hot path:
  trade-approval-test.js  27/27 PASS 0 FAIL   (the accepted artifact, unchanged)
  commit-rows-test.js      9/9  PASS
  forward race             3/3 NO LOSS, control 3/3 NO LOSS
  reverse race             3/3 NOT REPRODUCED
  DRY_RUN selftest         all pass

Known cosmetic residual: recover-stuck-apps.js does not clear a stale link_error
when a link later succeeds. Pre-existing, not introduced here, left alone.

Local artifact only — no deploy, no push, no live writes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEDp3MXiKVJ5GDKaQCHZon
944582a · 2026-09-10 · TK-10836: contrarian overturns my Option D headline; correct it and kill 3 more hypotheses
Cody was right that 158/192 = 82%% smeared a cliff. Re-bucketed from my own data
(comped, US, 1-4 sample lines): Jul 100%%, Aug 65%%, Sep 35%%; Aug 1-17 100%% ->
Sep 1-10 35%%. The collapse starts the week Kelly checked out and is still worsening.

SURVIVES: the $0.00 rate WAS on Kelly's menu. Untruncated Samples-profile dump shows
$0.00 / $24.95 / ups_shipping / fedex all active in Domestic; both priced conditions
have a $0.00 lower bound; no customization or function hides it; 12/12 PAID comped
orders resolve to Kelly's same profile. Reading availability off a selected shipping
line was invalid.

DOES NOT SURVIVE: my 'therefore benign' conclusion. Default/steering never established.

KILLED (do not re-run): profile-leak (ups/fedex are inside the Samples profile),
Shop Pay steering (88%% Shop-tagged in both windows, free%% fell in both groups),
weight gate, $0-subtotal suppression, multi-profile mismatch.

TK-11366 escalates: ~27 customers in 3 weeks paid $16-25 on 'free' samples, root
cause open. Volume rose ~6x in the same window - population change not excluded.

Read-only throughout. Nothing sent, created, or changed on Shopify.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CD85VaG9BZGi3NjWGbKqvX
f849a46 · 2026-09-10 · TK-11285: fix a clobber bug in my own commitRows merge, and test it
Self-review caught a real defect in bef6f4f. The customer_id_conflict branch did
`patch.shopify_customer_id = undefined` to skip the field, but an undefined value
still spreads: {...current, ...patch} then ERASES the id the service resolved, and
JSON.stringify drops the key entirely. The guard meant to protect the service's
write was deleting it. Use `delete` instead.

Adds verification/tk11285/commit-rows-test.js — 9 focused checks on the merge:
concurrent intake survives, a service decision is never overwritten, the service's
customer id wins a conflict, receipts are kept so no duplicate letter, a vanished
row is not resurrected, untouched rows stay byte-identical.

Proven non-tautological: reintroducing the bug fails 2/9 (cust=undefined, key
erased); with the fix 9/9 PASS exit 0.

Also gates the CLI behind require.main === module so commitRows is testable.
CLI behaviour unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEDp3MXiKVJ5GDKaQCHZon
251724b · 2026-09-10 · TK-10836: Option D complete — the $0.00 free-shipping rate WAS available on Kelly's cart
Overturns this ticket's own 2026-09-10 reversal (ec11bce).

- Option D was never actually blocked: SHOPIFY_FULL_ACCESS_TOKEN (...2ea5) already
  carries read_shipping. The ACCESS_DENIED cited in the memo was a wrong-token artifact.
- Kelly's 3 sample variants all sit in delivery profile 'Samples - Free Shipping (No
  Tracking)'. That profile's Domestic zone offers BOTH $0.00 Free Shipping (No Tracking)
  (TOTAL_PRICE 0-45) and $24.95 Priority Sample Only (TOTAL_PRICE 0-20). A $0.00 subtotal
  satisfies both. No delivery customization, no rate-hiding function, no custom carrier.
- Empirical falsification: 192 fully-comped US sample-only orders -> 158 shipped $0.00.
  Kelly-shaped (2-4 comped sample lines) 53 -> 38 free. Michigan 2/2 free. A 3-line comped
  order shipped free on 2026-08-20, four days before her checkout.
- An abandoned checkout records the SELECTED shipping line, not the AVAILABLE set. The
  reversal read availability off a selection. $24.95 is a self-selected upgrade.

Read-only throughout: no order, draft order, discount, shipping-config or email write.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CD85VaG9BZGi3NjWGbKqvX
bef6f4f · 2026-09-10 · TK-11285: close the second-writer race the deploy preflight required
The deploy memo made "identify all processes writing trade-applications.jsonl"
a mandatory precondition, because the shipped decision lock only coordinates
inside the service process. That inventory had never been done. It has one hit:

  scripts/recover-stuck-apps.js

It rewrites the whole jsonl from a snapshot taken before a batch of Shopify
calls and George sends, so its read->write window is minutes. It is not
hypothetical - it has run in prod twice (data/recovery-20260903T192941.json,
recovery-20260903T193258.json) and 20 rows currently qualify for --send-only.

Reproduced with both sides running real production code in separate processes
(verification/tk11285/race.js; only lib/email + lib/shopify stubbed, zero
external calls). Deterministic, 3/3:

  - a trade application submitted during the window is silently destroyed
  - a COMPLETED approval reverts to pending after approve() returned
    ok:true/"approved", the applicant was emailed and the customer was already
    tagged trade_approved - store and Shopify silently disagree, which is the
    exact invariant this ticket exists to enforce

Control (same code, no second writer) shows NO LOSS, so the loss is caused by
the second writer, not by the fix.

Fix applies the pattern lib/trade.js already uses in checkpointApproval: never
write a stale snapshot. Re-read at write time and merge only the fields this
script owns. Link fields are dropped for a row the service has since decided;
email receipts are always kept so a later run cannot re-send a duplicate letter.

After: 3/3 NO LOSS, the script still links and still stamps all 6 receipts.
Regression: trade-approval-test.js 27/27 PASS 0 FAIL, DRY_RUN selftest all pass.

Local artifact only - no deploy, no push, no live writes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEDp3MXiKVJ5GDKaQCHZon
33dd248 · 2026-09-10 · TK-11361: soften attribution claim to match evidence strength
Codex second-model pass flagged that 'neither node has ever produced a discount
application' is an absolute claim resting on title attribution + chronology, not
on a provenance link - Shopify does not expose the source node id on a discount
application. Reworded to 'no evidence either DW node contributes to any observed
order', added an explicit proven-vs-inferred split, and scoped the result to the
measured window rather than stating it universally.

The headline answer is unchanged and robust: the cap held at 3, and it holds even
if a DW node also emits a 'Free sample' title, since counts and amounts would
still have moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012F3D7ErxQBRwTvVtz3kVXv
7e96b04 · 2026-09-10 · TK-11361: Option A measured — cap holds at 3, duplicate is inert (DW function grants nothing)
Steve approved Option A (measure first). Measured against real orders instead of
a synthetic cart, so no test customer or cart was needed - zero writes.

Natural experiment across the 2026-09-02 introduction of the second node:
  before either node existed (150 orders): max 3 'Free sample' apps/order, 0 over-discounts
  both nodes ACTIVE      (100 orders): max 3 'Free sample' apps/order, 0 over-discounts
Answer to '3 or 6?': 3. No over-entitlement, no double-discount, no money impact.

Root reason found: neither DW node has ever produced a discount application.
The live free-samples engine is Regios Discounts (fn cd0a910b, since 2024-07-25)
- proven by 'Free sample' applications appearing 12x in 2026-07-15..08-27, before
either DW node existed. Matches DEPLOY.md: the DW function path is a retired
alternate, Option C uses variant-scoped Regios.

Risk downgraded to cleanup, but both nodes remain a latent landmine: if fn
01a0475d is ever repaired, two ACTIVE combinable nodes go live at once.
Recommendation revised to archive BOTH (gated), and update
scripts/production-validation.js which asserts on the node title.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012F3D7ErxQBRwTvVtz3kVXv
f5076d0 · 2026-09-10 · TK-11361: confirm duplicate DW Free Samples automatics; withdraw the false retraction
Both DiscountAutomaticApp nodes (1464777277491 'DW Free Samples (auto)',
1465258770483 'DW Free Samples') are ACTIVE on function 01a0475d-..., verified
three ways in one session. A concurrent pass had marked this NOT REPRODUCIBLE;
that read used automaticDiscountNodes, the legacy connection, which does not
return app/function-backed discounts. discountNodes returns all five.

Both are discountClass PRODUCT with combinesWith.productDiscounts=true, so they
may stack. Plausible failure is 6 free samples instead of 3. Cannot be settled
read-only - needs one controlled checkout, which is gated.

Read-only throughout. No discount modified.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012F3D7ErxQBRwTvVtz3kVXv
ec11bce · 2026-09-10 · TK-10836: REVERSE the close verdict — Kelly's own checkout shows $24.95 shipping
The contrarian gate was right: nobody had checked Kelly's actual cart.

AbandonedCheckout/34015098110003 (2026-08-24, kyounge@umich.edu, Ann Arbor MI):
3 samples, items subtotal $0.00, discount $12.75 already applied, tax $0.00,
TOTAL $24.95 — i.e. $24.95 of pure shipping. Abandoned. A second 7-sample
checkout the same day ($59.90) was also abandoned.

The earlier 'free shipping is available' survey proved the $0 method exists in
general, not that it was offered on her cart. And 3FREE is irrelevant to the
blocker: it discounts ITEMS only, her items were already $0.00, so it cannot
reduce a shipping line. The reply we sent 4x told her the $25 'won't apply'.

Ticket stays blocked, blocker re-pointed. Nothing sent, nothing written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jeg9TkFJqpiAEtcNva1mUb
a69c55f · 2026-09-10 · TK-10836: overturn contrarian's blocking hole on the full 3FREE cohort
Cody red-teamed the close verdict and blocked it, arguing the emailed
free-shipping promise is contradicted by 'the two most comparable orders'
(#32937 NY, #32914 MA — 3FREE applied, $24.95 charged).

Ran the decisive falsification test over 459 orders / 338 sample-only:
of the 10 sample-only orders that actually had 3FREE applied, 8 shipped
$0.00 on 'Free Shipping (No Tracking)' — incl. #32862 in NY, the same
state as one of Cody's two. 3FREE does not suppress the free method; the
free option is a shipping RATE, not a stacked shipping discount, so
combinesWith.shippingDiscounts:false never bites. Both paid orders chose
the 'Priority Sample Only' expedited upgrade.

Hole overturned on evidence; CLOSE verdict stands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jeg9TkFJqpiAEtcNva1mUb
8e95676 · 2026-09-10 · TK-10836: independent second-session re-verification
Confirms the close-as-superseded verdict on live read-only evidence, and
records one finding that did NOT reproduce so nobody acts on it.

- Kelly's customer record, code 3FREE, /healthz, TK-11114, and the
  already-superseded approval memo all re-confirmed.
- Free-shipping promise challenged (order #32937 paid $24.95) then upheld:
  66 US sample-only orders show a $0.00 'Free Shipping (No Tracking)'
  method, incl. Michigan order #32954 — the paid lines are chosen upgrades.
- The 'two ACTIVE DiscountAutomaticApp nodes' finding is NOT reproducible:
  live read returns one EXPIRED basic discount and zero app discounts.

Adds read-only repro scripts for every claim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jeg9TkFJqpiAEtcNva1mUb
4d8163c · 2026-09-10 · TK-10836: close as superseded — Kelly Paradis already honored via code 3FREE
Read-only re-verification on 2026-09-10 shows the reissue this ticket was
blocked on was never needed. info@ replied to Kelly with code 3FREE on
2026-08-25 08:38 PT, before this ticket was even created; TK-10830 re-sent the
same code on 2026-09-02. 3FREE is ACTIVE ($15 off, once-per-customer, no end
date) and covers 3 x $4.25 samples. Her Shopify customer is tagged
verified-sample. No inbound complaint since 2026-08-25. The RETAIL_SHARED_CODE
shared-code path is retired and irrelevant here.

Nothing minted, sent, armed, deployed, or written — evidence only.

Spun off TK-11361: two ACTIVE automatic discounts bound to the same
DW Free Samples function (left untouched, gated).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012F3D7ErxQBRwTvVtz3kVXv
1944da5 · 2026-09-03 · retail-webhook: close double-fire race — in-process in-flight claim before send
Two near-simultaneous customers/create deliveries (Shopify at-least-once) both read
sample_verify_sent=false and both send a verify letter. Added an in-memory Set claimed
synchronously (no await between has() and add(), atomic on the single pm2 fork) and
released in finally, so exactly one letter goes out per burst. Durable metafield still
owns cross-restart/retry-after-minutes idempotency; failed sends stay un-poisoned.
Proven by a 5-way concurrent + mixed-id + fail-retry test (all pass); codex-reviewed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014PT22KZe3HDFcym6fzZRwD
90c2cb8 · 2026-09-03 · TK-11190: harden recover-stuck-apps — fail-loud --apply DRY_RUN guard + idempotent --send-only
Closes the footgun that stamped fake dry-run stub customer ids into the prod jsonl:
a standalone `node recover-stuck-apps.js --apply` inherits DRY_RUN=1 (config reads it only
from process.env; pm2 env injection doesn't reach a bare node invocation), and the APPLY
branch wrote unconditionally.

- FAIL-LOUD: --apply now refuses (clear message + exit 1) while config.DRY_RUN is on, BEFORE
  any Shopify call or jsonl write. Real runs must set DRY_RUN=0.
- --send-only: emails the activation + office letters for apps that are LINKED but not-yet-
  emailed, tracked by a recovery_emailed flag so re-sends are idempotent (no restore-and-
  reapply dance). Emails stay DRY_RUN-safe — a DRY preview does NOT consume the flag, so a
  later real run still sends. Shared sendForApp() sets the flag ONLY on a real send.
- selftest (c4): spawns the script to prove the guard trips (exit non-zero + refuses) and
  --send-only targets only linked+not-emailed without consuming the flag on a dry preview.

Full DRY_RUN suite green. LOCAL only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3co77k7JzTkAJRdemt6Ru
164ab38 · 2026-09-03 · TK-11190: recovery tooling for pre-fix stuck trade applications (link retroactively)
Designers who applied BEFORE the TK-11185 fix are status=pending + shopify_customer_id:null
→ un-approvable. This recovers them by applying the new-flow linkage retroactively.

- scripts/recover-stuck-apps.js: DRY-RUN by default; --apply links (findOrCreateCustomer →
  stamp id + link_* + recovered_at). Idempotent (skips already-linked), batch-bounded
  (--limit, default 25), reversible (backs up the jsonl + writes data/recovery-<ts>.json map;
  --rollback deletes exactly the customers WE created). Emails are separable + OFF by default
  (--send-emails); option (b) --auto-approve exists but is NOT recommended (grants trade
  pricing without human moderation). Verified locally: dry-run, apply (backup+map), rollback.
- lib/email.js: designerAccountReadyEmail — recovery-cohort activation letter ("account is
  ready, sign in via one-time code"); does NOT claim trade pricing is granted yet.

Full DRY_RUN suite green. LOCAL only — the actual recovery run is Steve-gated (customer-facing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5e609e8 · 2026-09-03 · TK-11185: add Gate 2 theme PUT + rollback scripts (cwd-independent, self-verify main theme)
Runnable .mjs equivalents of the memo's Gate 2 pastes for the approved live theme write:
- scripts/gate2-theme-put.mjs: confirms 145121607731 is the live/main theme (aborts on
  mismatch), reads the staged patched snippet, PUTs snippets/dw-signin-modal.liquid, prints HTTP.
- scripts/gate2-theme-rollback.mjs: re-PUTs the 20260903T181955 pre-change backup, prints HTTP.
Token read inside Node from secrets-manager/.env (no shell quoting), store designer-laboratory-sandbox, API 2024-10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3co77k7JzTkAJRdemt6Ru
6e51634 · 2026-09-03 · TK-11185: back up live dw-signin-modal snippet + stage patched two-step OTP success handler
Prep for the approved Gate 2 live theme PUT (theme 145121607731, main). Backs up the
current live snippets/dw-signin-modal.liquid and stages the patched version that replaces
the "in review" dead-end with renderTradeSuccess() — two-step OTP sign-in to /account for
logged-out applicants, "Application received" confirmation for the logged-in path.

- theme-backups/live-snapshots/snippets__dw-signin-modal.liquid.20260903T181955.bak (live backup)
- theme-proposals/loggedin-trade-entry/dw-signin-modal.PATCHED-tk11185.liquid (staged patch)

The live PUT itself is handed to Steve as a `!` paste (customer-facing write).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3co77k7JzTkAJRdemt6Ru
eba3bdc · 2026-09-03 · TK-11185: rate-limit public /trade/apply (per-IP) — close the write_customers abuse vector
/trade/apply now server-side creates a real Shopify customer per POST (write_customers),
so an unthrottled public endpoint could be sprayed to pollute the customer table / spam
the designer-welcome email. Extract the inline webhook limiter into a shared util and
throttle both endpoints consistently.

- lib/rate-limit.js: createRateLimiter({windowMs,max}) — per-key sliding window, self-pruning
  map (extracted verbatim-behavior from the inline webhook limiter).
- server.js: webhook now uses webhookLimiter (WEBHOOK_RATE_MAX/min, unchanged behavior);
  /trade/apply gains tradeApplyLimiter (429 on trip) via the same util + a shared clientIp().
- lib/config.js: TRADE_APPLY_RATE_MAX=5, TRADE_APPLY_RATE_WINDOW_MS=1h (5 applications/IP/hr —
  a real designer applies once; generous but blocks spray).
- selftest.js (c3): limiter allows N in-window, TRIPS N+1, buckets are per-IP independent,
  and the config default is 5/hour. Full suite green in DRY_RUN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3co77k7JzTkAJRdemt6Ru
31cd4dd · 2026-09-03 · TK-11185: server-side find-or-create customer at trade-apply time (born linked, always approvable)
Public trade applications carried no shopify_customer_id, so approve() hard-failed
cannot_resolve_customer → designers "filled it out and nothing happened." DW is on
NEW_CUSTOMER_ACCOUNTS (passwordless OTP — verified live 2026-09-03), so the account
cannot be carried through the register page; it is minted server-side via the Admin API.

- lib/shopify.js: createCustomer() (GraphQL customerCreate, DRY_RUN-safe, REST-numeric id
  from GID tail, handles email-taken re-resolve + phone-format retry, deps seam for tests)
  and findOrCreateCustomer() (find first, create only if none — linkage by resolved id).
- lib/trade.js: applyAndLink() find-or-creates + stamps shopify_customer_id onto the
  persisted application, records link_status/link_error, and graceful-degrades (still
  persists unlinked if create fails — never a black hole).
- server.js /trade/apply: awaits applyAndLink before firing the office notify, so a linked
  app never yields an un-approvable review card; applicant still sees success on failure.
- shopify/staged/trade-account-ux-20260828/snippets/dw-trade-apply.liquid: staged snippet
  replacing the "Application received" dead-end with two-step OTP account sign-in (/account);
  logged-in path preserved. NOT deployed — live theme PUT is Steve-gated.
- selftest.js (c2): create+link, existing reuse, create-failure degrade, approve() resolving,
  plus createCustomer taken/phone-retry branch coverage. Full suite green in DRY_RUN.

Contrarian panel: SHIP IT (4-1), dissent (untested error branches) addressed before commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3co77k7JzTkAJRdemt6Ru
48d0832 · 2026-09-02 · TK-11120: definitive checkout test — verified-sample caps at 3 free samples, not 5
Live browser checkout (logged-in verified-sample customer, 5 swatches): $21.25 -> $8.50,
i.e. only 3 free / 2 charged. The DW Free Samples function hard-caps at 3. The retail
apology email promised 5 -> overpromise. Tooling: browser-cart-setup.js, verify-checkout.js.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017j4qS38tWq21qYdjxMcFTy
d1ffb0f · 2026-09-02 · TK-11120 Option B: verify page shows the token-carried sample count (5 for the retail cohort)
Tokens now carry an optional per-link count (payload.n); readToken returns it; server.js
/verify success page + samplesUnlockedEmail use parsed.count ?? FREE_SAMPLE_COUNT, so these
customers see '5' while the global default stays 3. startVerification + resend-corrected
mint with count. Backward compatible: tokens without n fall back to the global default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017j4qS38tWq21qYdjxMcFTy
0b127cf · 2026-09-02 · TK-11120: retail-only 5-sample apology resend (--force, --retail-only) — 74 sent from info@
- verifyResendEmail: light-hearted apology copy (Steve-approved)
- resend-corrected.js: --retail-only (skip trade tag), --force (own 5-sample ledger)
- prove-real-customer.js: --setup <email> / --delete <id> modes for live click proof
Sent light-hearted 5-sample letter to 74 retail (1 trade skipped), from info@, links verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017j4qS38tWq21qYdjxMcFTy
64b3102 · 2026-09-02 · TK-11120: real-customer click proof — verify confirms + tags with zero input (PASS)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017j4qS38tWq21qYdjxMcFTy
52afecd · 2026-09-02 · TK-11120: server.js baseUrl() also fails closed on missing PUBLIC_URL (defense-in-depth)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017j4qS38tWq21qYdjxMcFTy
a6edf22 · 2026-09-02 · TK-11120: corrected resend tool + apology template (retail-verify localhost outage)
verification/tk11120/resend-corrected.js — safe corrected resender for the 75:
- HARD-REQUIRES PUBLIC_URL (fail-closed baseUrl) + GEORGE_ACCOUNT=info; aborts otherwise
- bypasses the tainted sample_verify_sent flag/old ledger (broken run set them); own ledger
- email.verifyResendEmail: verify letter + one-line apology; DRY_RUN default, --apply gate
- affected-emails.txt: the 75 deduped recipients (Angie excluded)

Proven locally: dry-run builds https link, info@ sender, abort on empty PUBLIC_URL. No send.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017j4qS38tWq21qYdjxMcFTy
0016d65 · 2026-09-02 · TK-11120: verify links fail-closed on missing PUBLIC_URL + send as info@ (retail-verify localhost-link outage)
Root cause of the 2026-09-02 retail-verify blast: a backfill job ran with
PUBLIC_URL unset, so verify.baseUrl() fell back to http://127.0.0.1:9856 and
~75 customers got a dead localhost 'Confirm my email' link. Also sent as
steve-office (Gmail rewrote From to steve@ since info@ isn't a send-as alias).

- verify.baseUrl(): FAIL-CLOSED in LIVE — never fall back to loopback; empty
  PUBLIC_URL returns '' and startVerification refuses to send (reason=no_public_url).
- config.GEORGE_ACCOUNT default steve-office -> info, so mail is genuinely from info@.

No send, no deploy in this commit — code hardening only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017j4qS38tWq21qYdjxMcFTy
d8959b9 · 2026-09-02 · TK-11114: backfill sent (68/68) — outage fully remediated
Re-sent the verify letter to all 68 genuine retail signups that missed it during the
outage; confirmed sent=68 failed=0 (George 200) + present in the steve-office Sent folder.
Flags set + ledgered (idempotent). TK-11114 closed: send fixed, durable, backfilled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
228c2ea · 2026-09-02 · TK-11114: backfill tooling — scope (68 affected since go-live) + safe sender
Read-only scoper enumerates genuine retail signups with no sample_verify_sent (never
got the verify letter), excluding test/internal + trade: 68 since 2026-08-28. Sender
bypasses the 24h webhook freshness gate (direct verify.startVerification), dry-run by
default, idempotent (live flag re-check + ledger), --only/--apply gates. Real 68-email
send stays hard-gated (send-to-list).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
992e15e · 2026-09-02 · TK-11114: BLOCK B — verify email proven end-to-end (send 200 -> inbox -> verify-click -> verified-sample tag persists)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzrHtgk5Qq7kpdfaWeDk97

File tree

228 files tracked. Click any to browse the source at HEAD.

Other build journals

← Dw Showroom  ·  all 4 projects  ·  Dw Silver Mylar Rename →

Export

commits.csv · feed.atom · project.json · commits.json

rendered in 4ms