Microsoft's JMRP feedback loop ships a well-formed
multipart/report;report-type=feedback-report whose
message/feedback-report block only carries
Feedback-Type/User-Agent/Version. Source-IP, Arrival-Date,
Original-Mail-From and Original-Rcpt-To are omitted; that data lives
only in the embedded original-message headers.
parse_arf now recovers those fields from the original headers, filling
only what the report itself left empty and recording provenance in
result.derived so callers can tell reported data from inferred data:
* source_ip: X-Originating-IP, then client-ip= of Received-SPF /
Authentication-Results, then the first public IP while walking the
Received chain (is_nonpublic_ip skips RFC1918/CGNAT/ULA hops, which
ip:is_local() does not).
* arrival_date: topmost Received timestamp, else Date.
* original_mail_from: Return-Path, else smtp.mailfrom.
* original_rcpt_to: Delivered-To / X-Delivered-To / envelope-to.
* reported_domain: falls back to the envelope-from domain.
Also accept Received-Date as an Arrival-Date alias in the report block.
extract_original_message now returns the raw header maps alongside the
parsed subset; DSN parsing is unchanged. Adds unit tests for the
Received-SPF path and the public-IP-in-Received-chain fallback.