Normalize aggregate result words to lowercase (#835)

* fix: normalize aggregate result words

Normalize policy-evaluated and authentication result values so mixed-case reporter output does not create duplicate categories. Add a regression covering the existing uppercase sample.

* Move changelog entry to the Unreleased section

The entry landed in the released 10.2.4 section because the branch was
cut before the Unreleased heading existed on master; merged master and
moved it under Unreleased -> Bug fixes, matching the house entry style.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Apply review feedback: normalize disposition, share a guarded helper

- policy_evaluated disposition gets the same lowercase normalization as
  dkim/spf and the auth result words; the RFC 7489 Appendix C /
  RFC 9990 disposition enum is lowercase (none/quarantine/reject), and
  mixed-case values split the Message Disposition categories in every
  dashboard the same way Pass/pass did.
- All four normalization sites now share _normalize_result_word(),
  which guards with isinstance(str): xmltodict returns a dict for
  attribute-bearing elements, so the previously unguarded .lower()
  calls on auth results could raise AttributeError on malformed input
  that used to pass through.
- The #520 fixture's disposition is now mixed-case (None) and the test
  asserts it parses as "none"; verified the assertion fails against the
  pre-fix parser. Test docstring cites the RFC authority per the
  project's testing standards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Sean Whalen <44679+seanthegeek@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sanjay Santhanam
2026-07-21 20:02:24 -04:00
committed by GitHub
co-authored by Claude Fable 5 Sean Whalen
parent 6acacd01d1
commit e98e12acb0
4 changed files with 38 additions and 8 deletions
@@ -21,7 +21,7 @@
<source_ip>23.104.41.189</source_ip>
<count>1</count>
<policy_evaluated>
<disposition>none</disposition>
<disposition>None</disposition>
<dkim>Pass</dkim>
<spf>Pass</spf>
</policy_evaluated>