Interlock

Scenario email_tier3: an email that cannot be undone (Resend, live)

Generated 2026-09-13 22:47 UTC by experiments/scenario_email_tier3.py. Model claude-haiku-4-5-20251001, Resend API, Stripe test mode.

Each cell is one support case: a new $100 Stripe test payment, a real model call that reads the case (support approved a $20 partial refund) and decides the refund and the email text, the refund issued in Stripe, and a worker process that emails “Your refund is on the way” to delivered@resend.dev through Resend. The worker SIGKILLs itself at the fault’s crash point (exit code -9 recorded per cell). The harness waits out the outage, starts a new worker process, and reads the result back from Resend and Stripe. An email cannot be recalled once Resend accepts it.

What Resend offers, from evidence

question how it was tested in this run result
Does POST /emails dedupe on Idempotency-Key? the same key and body sent twice first bc81630f-fd70-4ecc-bb88-d90ef650ab94 replayed=False, second bc81630f-fd70-4ecc-bb88-d90ef650ab94 replayed=True: yes
Is the key bound to the body? the same key with another body 409 invalid_idempotent_request: yes
Can a sent email be read back by id or listed? GET /emails/{id} and GET /emails?limit=1 with the key provided 401 restricted_api_key and 401 restricted_api_key: not with this key. Docs: list returns id, to, subject, created_at, last_event and has no tag or header filter; get by id returns tags. A full-access key was not available, so these were not exercised.
Can this key ask “did an email go out under key K” without sending? POST a well-formed body from an unverifiable domain under K used key: True (409); fresh key: False (403, nothing sent); a real send under the probed key was new (replayed=False) and the key then probed used=True: yes, inside 24h

Docs checked 2026-09-13: resend.com/docs/dashboard/emails/idempotency-keys (24h, 409 invalid_idempotent_request, 409 concurrent_idempotent_requests), api-reference list-emails and retrieve-email, resend.com/pricing (30-day data retention on the free plan). The probe is not a documented lookup; it follows from the documented 409 and from Resend checking the key before the sender’s domain, which this run confirms.

Tier, from that evidence. Inside 24h Resend is tier 1 (it dedupes on the key) with a lookup (the probe), the same shape as the gate’s Stripe target. The lookup is an observed behavior, not a contract: if Resend started checking the sender’s domain before the key, the probe would stop answering (it raises rather than guess), and Interlock would be tier 1 without a lookup, which is interlock_noprobe below. After 24h, with a sending-only key, it is tier 3: nothing dedupes and nothing can be asked. With a full-access key the docs describe a lookup past 24h (list, then get each email for its tags, within the retention window), not verified here. The README’s row “SendGrid / most email: tier 3” does not describe Resend inside its key window.

Results

system crash_after_send refused_before_send refused_after_send
no_check SENT_REPLAYED; 1 email (want 1); held; answer matches Resend; record proves it: no; 0.2s SENT; 1 email (want 0); VIOLATED (1 email, want 0); answer matches Resend; record proves it: no; 2.0s SENT_REPLAYED; 1 email (want 1); held; answer matches Resend; record proves it: no; 2.5s
hand_check ALREADY_SENT; 1 email (want 1); held; answer matches Resend; record proves it: yes; 0.8s SKIPPED:refund_failed; 0 email (want 0); held; answer matches Resend; record proves it: yes; 4.6s ALREADY_SENT; 1 email (want 1); held; answer matches Resend; record proves it: yes; 2.6s
hand_check_noprobe SENT_REPLAYED; 1 email (want 1); held; answer matches Resend; record proves it: yes; 0.5s SKIPPED:refund_failed; 0 email (want 0); held; answer matches Resend; record proves it: yes; 1.6s SKIPPED:refund_failed; 1 email (want 1); held; answer CONTRADICTS Resend; record proves it: no; 6.1s
interlock COMMITTED_BY_RETRY; 1 email (want 1); held; answer matches Resend; record proves it: yes; 35.9s REFUSED:stale_premise_at_recovery; 0 email (want 0); held; answer matches Resend; record proves it: yes; 36.7s COMMITTED_ON_QUERY; 1 email (want 1); held; answer matches Resend; record proves it: yes; 36.8s
interlock_noprobe COMMITTED_BY_RETRY; 1 email (want 1); held; answer matches Resend; record proves it: yes; 35.8s AMBIGUOUS; 0 email (want 0); held; answer cannot know (said so); record proves it: no; 36.0s AMBIGUOUS; 1 email (want 1); held; answer cannot know (said so); record proves it: no; 35.8s
interlock_tier3 AMBIGUOUS; 1 email (want 1); held; answer cannot know (said so); record proves it: no; 35.4s AMBIGUOUS; 0 email (want 0); held; answer cannot know (said so); record proves it: no; 35.9s AMBIGUOUS; 1 email (want 1); held; answer cannot know (said so); record proves it: no; 35.9s

Faults where the two systems left a different number of emails or gave a different answer: hand_check vs interlock: none; hand_check_noprobe vs interlock_noprobe: refused_before_send, refused_after_send; hand_check_noprobe vs interlock: refused_after_send.

Better, equal, worse

Systems

Faults

pm_card_refundFail is Stripe’s test card whose refund starts succeeded and turns failed a few seconds later (4 to 7s when measured). A worker that reads the refund after it failed skips (or is refused) before the send and never reaches the crash point. Such an attempt is rerun with a new case, the same rule for every system, and kept in the JSON (discarded_attempts) with its own ground truth:

The previous run was a near miss, and a real bug. In the run generated 2026-09-13 22:33 UTC, Interlock’s premise was “the refund’s OK-ness is unchanged since capture”. Its refused_before_send cell captured the refund at 1789338900.44; Stripe’s refund.failed event for re_3UFLzB88KhIqqdFL1X8xhi2V is stamped 1789338901, so the refund failed 0.56s to 1.56s later. Had it failed before the capture, the premise would have recorded “not OK”, matched it, and sent “your refund is on the way” for a failed refund; and because that worker still reaches the send and the crash, the harness would have scored it rather than rerun it. The premise is now absolute (the refund is succeeded or pending), tests/test_scenario_email_tier3.py covers a refund already failed at capture, and every system follows the same rerun rule, with discarded attempts kept above.

How close each refused cell came to the other branch (the worker’s last refund read before the send against Stripe’s refund.failed event):

Ground truth

Resend’s GET endpoints refuse the key provided (above), so ground truth is read from Resend’s key store: after the cell, probe the cell’s key; if it was used, replay the recorded body under it, which returns the email’s id and must come back Idempotent-Replayed: true (checked; the run aborts otherwise). Every system sends under one stable key per case, so one used key is one email. An email under some other key would not be seen this way; with a full-access key, GET /emails would show it. Stripe’s refund status is read directly. “Answer” compares what the system itself reported (sent, not sent, or cannot know) with that.

“Record proves it” means the system’s own record, read alone, names the email sent (or the skip), the case, and the checks that ran before it, and what it says agrees with Resend: for the gates, interlock.receipts.verify() is valid and its happened equals the ground truth; for the hand checks and no_check, the app log has a check entry and the email id or the skip, and that claim equals the ground truth. A log that records a skip while an email went out proves nothing.

What is emulated

Everything else is live: Resend sends to delivered@resend.dev, Stripe test-mode payments and refunds, Anthropic model calls, and SIGKILLs of separate worker processes. No mocks.

Ids

Emails cannot be looked up in the Resend dashboard by key; the ids below are Resend’s email ids returned by replay.

Re-run

python3 experiments/scenario_email_tier3.py
python3 -m unittest tests.test_scenario_email_tier3     # offline logic