docs(openspec): add change artifacts for email-loop-prevention
Creates openspec/changes/email-loop-prevention/ with all artifacts: - proposal.md: Why (MAILER-DAEMON bounce loop incident), what changes (automated sender detection + message-count cap), capabilities affected - design.md: Two-layer defence rationale, detection signal table, decisions on channel/agent boundary split, 20-message cap, one-shot alert, CC-only routing; risks and trade-offs documented - specs/email-channel/spec.md: ADDED requirements for detect_automated_message(), is_automated/automated_reason message dict fields, no-reply guarantee - specs/registration-notifications/spec.md: ADDED requirements for notify_loop_escalation() — alert content, one-shot guarantee, CC-only routing, dev-mode guard - tasks.md: All 6 sections fully checked (implementation already complete) https://claude.ai/code/session_01KwvR5hDPjSuJg4kvw5b5e5
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
## 1. Automated Sender Detection (email_channel.py)
|
||||
|
||||
- [x] 1.1 Add `_AUTOMATED_SENDER_RE` regex for known non-human local-parts (mailer-daemon, postmaster, noreply, no-reply, donotreply, bounce, …)
|
||||
- [x] 1.2 Add `_AUTOMATED_SUBJECT_RE` regex for bounce/OOO subject patterns (German + English)
|
||||
- [x] 1.3 Implement `detect_automated_message(raw_msg, from_addr) → (bool, str)` checking all signals in priority order: sender pattern → Auto-Submitted → X-Auto-Response-Suppress → multipart/report → X-Loop → Precedence → subject
|
||||
- [x] 1.4 Add `is_automated` and `automated_reason` fields to the dict returned by `fetch_unread_messages()`
|
||||
|
||||
## 2. Poll Loop Guard (main.py)
|
||||
|
||||
- [x] 2.1 In `run_poll_loop()`, check `msg.get("is_automated")` before calling `agent.process_message()`
|
||||
- [x] 2.2 If automated: log a warning, call `agent.handle_automated_message()`, and `continue` (skip `send_reply`)
|
||||
|
||||
## 3. Agent — Automated Message Handler (agent/core.py)
|
||||
|
||||
- [x] 3.1 Add `MAX_USER_MESSAGES = 20` module-level constant
|
||||
- [x] 3.2 Implement `handle_automated_message(sender_email, subject, reason, inbound_message_id)` method
|
||||
- [x] 3.3 In `handle_automated_message`: load or create state; set `loop_escalated = True`; call `notify_loop_escalation()` once; silently skip if already escalated; save state
|
||||
- [x] 3.4 In `process_message()`, after appending the user message, count user messages; if count > `MAX_USER_MESSAGES` and not escalated: set `loop_escalated = True`, call `notify_loop_escalation()`, return `""`
|
||||
- [x] 3.5 If already escalated and over limit: silently save state and return `""`
|
||||
|
||||
## 4. Conversation State (models/conversation.py)
|
||||
|
||||
- [x] 4.1 Add `loop_escalated: bool = False` field to `ConversationState`
|
||||
- [x] 4.2 Include `loop_escalated` in `to_dict()`
|
||||
- [x] 4.3 Restore `loop_escalated` in `from_dict()` with default `False` for backward compatibility
|
||||
|
||||
## 5. Admin Notification (notifications/notifier.py)
|
||||
|
||||
- [x] 5.1 Implement `notify_loop_escalation(sender_email, conversation_id, reason, message_count)` method
|
||||
- [x] 5.2 Route alert to `self._cc_emails` only (not playgroup leaders)
|
||||
- [x] 5.3 Subject: `[WARNUNG] Automatische E-Mail / Endlosschleife erkannt: {sender_email}`
|
||||
- [x] 5.4 Body: sender, conversation ID, message count, reason, call-to-action in German
|
||||
- [x] 5.5 Guard: if no CC emails configured, log warning and return without SMTP call
|
||||
|
||||
## 6. Tests
|
||||
|
||||
- [x] 6.1 `TestDetectAutomatedMessageBySender` — mailer-daemon, postmaster, noreply, no-reply, donotreply, bounce; normal parent address not flagged
|
||||
- [x] 6.2 `TestDetectAutomatedMessageByHeaders` — Auto-Submitted (auto-replied, auto-generated, no); X-Auto-Response-Suppress; multipart/report; X-Loop; Precedence bulk/junk; Precedence list not flagged
|
||||
- [x] 6.3 `TestDetectAutomatedMessageBySubject` — Undelivered Mail, Mail Delivery Failed, Out of Office, Abwesenheitsnotiz, Automatische Antwort; case-insensitive; normal subject not flagged
|
||||
- [x] 6.4 `TestHandleAutomatedMessage` — sets loop_escalated; calls notifier once; creates state when none exists; drops silently if already escalated; notifier failure does not propagate; inbound message ID stored
|
||||
- [x] 6.5 `TestProcessMessageCountCap` — at limit still processes; over limit returns ""; sets loop_escalated; calls notifier once; no duplicate alert; notifier failure does not propagate; constant equals 20
|
||||
- [x] 6.6 `TestNotifyLoopEscalation` — sends to CC; [WARNUNG] in subject; sender in subject; reason in body; message count in body; no-CC guard; no-SMTP guard
|
||||
- [x] 6.7 `TestConversationStateLoopEscalated` (test_models.py) — default False; to_dict includes key; True round-trip; from_dict backward compatibility
|
||||
Reference in New Issue
Block a user