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