Adds harness.deliver_raw() to inject crafted .eml fixtures through the harness,
so they pick up the real Received: headers an MTA adds, and
harness.inject_maildir() as the byte-exact offline counterpart.
tests/test_evidence_realism.py asserts all ten recognized evidence classes
after passing through a real MTA, proves MTA delivery does not change which
evidence is produced, and covers Maildir injection offline.
Documents what no local server can honestly produce -- provider-generated DSNs,
real 4xx deferral and retry, ISP feedback loops, provider suppression behavior,
MX acceptance as distinct from provider acceptance -- and names SES simulator
addresses as the staging path. That tier stays out of the test run because it
needs real credentials.
Adds docs/test-harness-tutorial.md covering the three test tiers and the
start/send/scan/assert/reset walkthrough, with an explicit
assertable/not-assertable list so the evidence ceiling is stated where tests
get written. Adds a Maildir section to the mailbox report tutorial.
Completes EMAIL-WP-0005. Suite: 85 passed with the harness up, 64 passed +
21 skipped with it down.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds tests/test_integration_send_scan.py: 7 harness-gated cases covering
delivery and Message-ID correlation, scanner ingestion without a delivery
claim, idempotency, resend, suppression, rejected requests never reaching the
provider, and verification mail carrying no authorization.
Fixes the reply heuristic, which matched against headers as well as body. The
Received: trace that every MTA-handled message carries matched its "received"
keyword, so ordinary mail was classified human_reply with a
success.reply_received assessment at medium confidence -- the exact overclaim
this repo exists to prevent. Hand-written fixtures have no Received headers,
so only real scanned mail exposed it. The heuristic now takes the body alone;
DSN detection still sees headers, which it needs. Regression test is offline.
Fixes the provider reference: SMTPProvider set no Message-ID, so send() fell
back to abs(hash((recipient, subject))) -- randomized per process and colliding
for equal recipient/subject. Outgoing mail now carries a proper RFC 5322
Message-ID, returned as the reference, which is what makes send-to-scan
correlation testable.
Suite: 72 passed with the harness up, 62 passed + 10 skipped with it down.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the tests/harness helper package: address(), available(), reset(),
users(), smtp_provider() and mailbox_config().
Tests address mailboxes as harness.address("<test name>"), which slugs the name
under the RFC 2606 reserved harness.email-connect.test domain, so distinct test
names cannot collide and a stray send cannot leave the host. No provisioning
call is needed -- the auth-disabled harness creates the mailbox on first login.
reset() drops every user and message via GreenMail's service reset; the next
login recreates the mailbox empty. It is documented to run at test start so a
crashed test cannot leak state forward, and it is global, so resetting tests
cannot run in parallel against one harness.
Suite: 64 passed with the harness up, 61 passed + 3 skipped with it down.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds tests/harness/docker-compose.yml running GreenMail 2.1.12 (digest-pinned)
with SMTP 3025, IMAP 3143 and the API bound to 127.0.0.1 only, plus a
config/harness-imap.yml scanner profile and harness README. Auth is disabled
and no users are declared, so a mailbox is created on first login and per-test
users need no provisioning.
GreenMail standalone offers no STARTTLS, only plaintext or implicit TLS, while
SMTPProvider hardcoded starttls() -- so no send could reach it. SMTPProvider
now takes a security mode via EMAIL_CONNECT_SMTP_SECURITY, defaulting to
starttls. plaintext is refused for any non-loopback host, and hostnames are
never resolved to decide that, so a misconfigured deployment fails at startup
rather than sending credentials in the clear. Trusting GreenMail's self-signed
cert was rejected as the wider risk; see DECISIONS.md.
Verified end to end against the live harness: SMTPProvider.send -> GreenMail ->
ImapMailboxSource, and the documented scan-mailbox CLI. Suite: 52 passed with
the harness down.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds MaildirMailboxSource reading new/ and cur/, wired through
source.maildir_dir and mailbox.protocol: maildir. Message identity is the
Maildir unique name without its :2,FLAGS suffix, so it survives the new/ to
cur/ move; it is carried on MailboxSourceMessage.dedup_uid and appended to the
message dedup key only when set, leaving fixture and IMAP keys unchanged.
Cursor ordering parses the Maildir delivery time instead of comparing names
lexically. mark_seen and a missing directory are rejected, matching the
read-only IMAP contract.
Also fixes the parse-failure path, which keyed identity on raw_message_ref.
A new/ to cur/ move rewrites that ref, so an unparseable message re-registered
as new on every rescan; it now prefers the source uid when one exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ship the railiance01 deploy package (OpenBao/ESO custody, NetworkPolicy,
probes), provider failure classification and suppression, T04 unit and live
proof, and non-secret NK-WP-0024 hand-back evidence. Workplan finished.