From 0c290a95d14d6b46a255073729be3b791035d5be Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Fri, 10 Apr 2026 14:28:11 -0700 Subject: [PATCH] =?UTF-8?q?fix(trigger):=20DPLAN-0112=20Option=20C=20?= =?UTF-8?q?=E2=80=94=20strip=20dispatch=20from=20error=5Flogged,=20route?= =?UTF-8?q?=20through=20Medic=20v2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit error_logged.py handler stripped to monitor-only (log event, no email/dispatch). Centralized watcher (watchers/log_watcher.py) now calls registry_report() + fires error_detected for ERROR-level lines, routing through full Medic v2 pipeline (count threshold, circuit breaker, fingerprint backoff). Falls back to error_logged (monitor-only) if registry unavailable. 370 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../apps/handlers/events/error_logged.py | 71 ++----------------- .../apps/handlers/watchers/log_watcher.py | 29 +++++++- 2 files changed, 33 insertions(+), 67 deletions(-) diff --git a/src/aipass/trigger/apps/handlers/events/error_logged.py b/src/aipass/trigger/apps/handlers/events/error_logged.py index 210fd948..b72688d7 100644 --- a/src/aipass/trigger/apps/handlers/events/error_logged.py +++ b/src/aipass/trigger/apps/handlers/events/error_logged.py @@ -270,70 +270,13 @@ def handle_error_logged( # Resolve source_module from either parameter name effective_module = source_module or module_name or "unknown" - # --- Medic gating (FPLAN-0371 Phase 1) --- - - # Gate 1: Global medic toggle - if not _is_medic_enabled(): - _log_suppression("Medic OFF", branch, effective_module, message) - return - - # Gate 2: Per-branch mute - if _is_branch_muted(branch): - _log_suppression("Branch muted", branch, effective_module, message) - return - - # Gate 3: Convert branch name to email format - recipient = f"@{branch.lower()}" - - # devpulse is protected from auto-triggering - if recipient == '@devpulse': - return - - # Gate 4: Validate target branch exists in registry - registered_emails = _get_registered_emails() - if recipient not in registered_emails: - _log_suppression("Unknown branch skipped", branch, effective_module, message) - return - - # Gate 5: Rate limiting (3 dispatches per 10 minutes per branch) - if _is_rate_limited(recipient): - _log_suppression("Rate limited", branch, effective_module, message) - return - - # --- Dispatch --- - - try: - from aipass.ai_mail.apps.modules.email import deliver_email_to_branch - except ImportError: - return - - effective_timestamp = timestamp or datetime.now().strftime("%Y-%m-%d %H:%M:%S") - effective_log_file = log_file or "unknown" - - email_subject = f"[ERROR] {effective_module} - investigation needed" - - notification_message = _build_notification_message( - error_hash=error_hash, - source_module=effective_module, - message=message, - timestamp=effective_timestamp, - log_file=effective_log_file - ) - - email_data = { - "from": "@trigger", - "from_name": "TRIGGER", - "to": recipient, - "subject": email_subject, - "message": f"⚡ DISPATCH TASK - READ THIS FIRST ⚡\n\n{notification_message}", - "timestamp": effective_timestamp, - } - deliver_email_to_branch(recipient, email_data) - - # Record dispatch for rate limiting - _record_dispatch(recipient) - - json_handler.log_operation("error_logged_event", {"success": True}) + # Monitor-only: log the event for tracking, no dispatch. + # All dispatch now goes through error_detected.py (Medic v2). + json_handler.log_operation("error_logged_event", { + "branch": branch, + "module": effective_module, + "error_hash": error_hash, + }) except Exception as exc: _log_warning(f"handle_error_logged failed: {exc}") diff --git a/src/aipass/trigger/apps/handlers/watchers/log_watcher.py b/src/aipass/trigger/apps/handlers/watchers/log_watcher.py index f0e866da..29b1cdbf 100644 --- a/src/aipass/trigger/apps/handlers/watchers/log_watcher.py +++ b/src/aipass/trigger/apps/handlers/watchers/log_watcher.py @@ -13,9 +13,9 @@ Watches system_logs/ for log file changes. Detects ERROR/WARNING/INFO entries and fires appropriate events. Events fired: - - error_logged: When ERROR level log detected + - error_detected: When ERROR level log detected (Medic v2 pipeline via registry_report) + - error_logged: Monitoring-only event (no dispatch) - warning_logged: When WARNING level log detected - - log_entry: All log entries (for monitoring systems) Architecture: - Trigger OWNS all file watching (filesystem events) @@ -257,7 +257,30 @@ class LogFileWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else o } if level == 'error': - trigger.fire('error_logged', **event_data) + # Route through Medic v2: registry_report() for dedup/count, then error_detected + try: + from aipass.trigger.apps.handlers.error_registry import report as registry_report + result = registry_report( + error_type='ERROR', + message=message, + component=branch, + log_path=log_file, + severity='medium' + ) + error_count = result.get('count', 1) + trigger.fire('error_detected', + branch=branch, module=module_name, message=message, + log_path=log_file, error_hash=result.get('id', error_hash), + timestamp=timestamp, + fingerprint=result.get('fingerprint', ''), + registry_id=result.get('id', ''), + first_seen=result.get('first_seen', ''), + last_seen=result.get('last_seen', ''), + count=error_count, + ) + except Exception: + # Registry unavailable — fire error_logged as monitoring-only fallback + trigger.fire('error_logged', **event_data) json_handler.log_operation("system_log_event", {"level": level, "module": module_name}) elif level == 'warning': trigger.fire('warning_logged', **event_data)