|
| 1 | +# ADR-0009: Skip Mailing List Activities With Unparseable/Implausible Dates |
| 2 | + |
| 3 | +**Date**: 2026-07-19 |
| 4 | +**Status**: accepted |
| 5 | +**Deciders**: Uros Marolt |
| 6 | + |
| 7 | +## Context |
| 8 | + |
| 9 | +The mailing list integration (`services/apps/mailing_list_integration`) parses |
| 10 | +RFC2822 `Date` headers from archived mailing list messages |
| 11 | +(`crowdmail/services/parse/noteren.py`, `parse_email()`) to build each |
| 12 | +activity's `timestamp`. During testing against a real archive |
| 13 | +(`linux-serial`, mirrored from lore.kernel.org), a message surfaced whose |
| 14 | +`Date` header parsed "successfully" via `email.utils.parsedate_tz()` but |
| 15 | +produced an implausible year (e.g. `102` instead of a 4-digit year). |
| 16 | +Python's stdlib already RFC2822-normalizes legitimate 2-digit years (`68` → |
| 17 | +`2068`/`02` → `2002`), so this is not a parsing bug in our port — the |
| 18 | +`Date` header itself is malformed in the source archive. Without a guard, |
| 19 | +`mktime_tz()` + `strftime("%Y-...")` emits a non-4-digit year (e.g. |
| 20 | +`"102-06-12T11:56:20.000000Z"`), which downstream JS `Date` parsing (in |
| 21 | +the Node ingestion pipeline) rejects, causing per-message ingestion errors. |
| 22 | + |
| 23 | +## Decision |
| 24 | + |
| 25 | +Reject any parsed date whose year falls outside `1970–9999` by raising |
| 26 | +`ValueError` (caught by the existing `except (ValueError, OverflowError)` |
| 27 | +block), leaving `timestamp` empty for that message. `list_worker.py` skips |
| 28 | +queuing any activity with an empty timestamp. The message is not ingested |
| 29 | +as an activity, and its shard head still advances past it (so it is not |
| 30 | +retried on subsequent polls). |
| 31 | + |
| 32 | +## Alternatives Considered |
| 33 | + |
| 34 | +### Alternative 1: Heuristically repair malformed years |
| 35 | +- **Pros**: Could recover activities that would otherwise be dropped. |
| 36 | +- **Cons**: No reliable signal for what year was intended once the header |
| 37 | + is already malformed at the source (e.g. `102` — off by 1900? 2000? |
| 38 | + truncated?). |
| 39 | +- **Why not**: Any heuristic risks silently mis-dating an activity, which |
| 40 | + is worse than dropping it — a wrong timestamp corrupts activity |
| 41 | + timelines and trend analytics without any visible error. |
| 42 | + |
| 43 | +### Alternative 2: Ingest with the corrupted timestamp as-is |
| 44 | +- **Pros**: No data loss. |
| 45 | +- **Cons**: Breaks downstream JS `Date` parsing in the ingestion pipeline, |
| 46 | + causing hard failures rather than a clean skip. |
| 47 | +- **Why not**: A hard pipeline failure for one bad message is worse than |
| 48 | + skipping that one message. |
| 49 | + |
| 50 | +## Consequences |
| 51 | + |
| 52 | +### Positive |
| 53 | +- Malformed dates never reach the ingestion pipeline as corrupt |
| 54 | + timestamps; no pipeline-level failures from a single bad message. |
| 55 | +- Matches the existing skip pattern already used for other |
| 56 | + unparseable/missing fields in this parser. |
| 57 | + |
| 58 | +### Negative |
| 59 | +- Messages with malformed `Date` headers are silently and permanently |
| 60 | + dropped — never retried, since the shard head advances past them |
| 61 | + regardless. The only visible trace is a `logging.warning()` line. |
| 62 | + |
| 63 | +### Risks |
| 64 | +- If a source archive has a systemic date-formatting issue (not just rare |
| 65 | + garbage), this could silently drop a meaningful fraction of activities. |
| 66 | + Mitigation: the warning log includes the raw header and commit id, so a |
| 67 | + spike can be diagnosed via log volume if ever suspected. |
0 commit comments