refactor(trigger): remove dead code from error_logged.py after monitor-only conversion
Removed 10 orphaned functions/constants left after DPLAN-0112 Option C stripped dispatch: _find_repo_root, _is_medic_enabled, _is_branch_muted, _get_registered_emails, _is_rate_limited, _record_dispatch, _log_suppression, _build_notification_message, and associated constants. Handler now only uses _log_warning + json_handler. 370 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
612a6f5afa
commit
4cf1b327dc
@@ -1,37 +1,23 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: error_logged.py
|
||||
# Description: Legacy error logged event handler with medic gating (deprecated)
|
||||
# Version: 2.0.0
|
||||
# Description: Legacy error logged event handler — monitor-only (no dispatch)
|
||||
# Version: 3.0.0
|
||||
# Created: 2026-01-31
|
||||
# Modified: 2026-02-25
|
||||
# Modified: 2026-04-10
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Error Logged Event Handler (DEPRECATED)
|
||||
Error Logged Event Handler (Monitor-Only)
|
||||
|
||||
Legacy handler for error_logged events. The primary error dispatch pipeline
|
||||
is now error_detected.py (Medic v2) which provides circuit breaker, per-fingerprint
|
||||
Legacy handler for error_logged events. All dispatch now goes through
|
||||
error_detected.py (Medic v2) which provides circuit breaker, per-fingerprint
|
||||
backoff, and registry-based deduplication.
|
||||
|
||||
This handler remains for backward compatibility with code that fires error_logged
|
||||
events directly. It now includes full medic gating (medic_enabled, branch_muted,
|
||||
rate limiting, devpulse protection) to prevent bypass.
|
||||
|
||||
Event data expected:
|
||||
- branch: Branch where error occurred (e.g., FLOW)
|
||||
- message: Error message text
|
||||
- error_hash: Unique hash for deduplication
|
||||
- timestamp: When the error occurred
|
||||
- log_file: Path to log file
|
||||
- source_module: Module that logged the error
|
||||
- level: Log level (always 'error' for this handler)
|
||||
This handler logs event metadata for monitoring. No email, no wake_branch.
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any
|
||||
from aipass.trigger.apps.config import TRIGGER_ROOT
|
||||
from aipass.trigger.apps.handlers.json import json_handler
|
||||
|
||||
@@ -49,229 +35,33 @@ def _log_warning(message: str) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def _find_repo_root() -> Path:
|
||||
"""Walk up from this file to find the repo root (contains AIPASS_REGISTRY.json)."""
|
||||
current = Path(__file__).resolve().parent
|
||||
for parent in [current] + list(current.parents):
|
||||
if (parent / "AIPASS_REGISTRY.json").exists():
|
||||
return parent
|
||||
return Path.cwd()
|
||||
|
||||
_REPO_ROOT = _find_repo_root()
|
||||
|
||||
TRIGGER_CONFIG_FILE = TRIGGER_ROOT / "trigger_json" / "trigger_config.json"
|
||||
BRANCH_REGISTRY_FILE = _REPO_ROOT / "AIPASS_REGISTRY.json"
|
||||
SUPPRESSED_LOG = TRIGGER_ROOT / "logs" / "medic_suppressed.log"
|
||||
|
||||
# Legacy rate limiting
|
||||
_dispatch_timestamps: Dict[str, List[float]] = {}
|
||||
MAX_DISPATCHES_PER_WINDOW = 3
|
||||
RATE_LIMIT_WINDOW_SECONDS = 600 # 10 minutes
|
||||
|
||||
|
||||
def _is_medic_enabled() -> bool:
|
||||
"""Check if medic dispatch is enabled globally.
|
||||
|
||||
Reads medic_enabled from trigger_config.json.
|
||||
Defaults to True if config is missing or unreadable.
|
||||
|
||||
Returns:
|
||||
True if medic dispatch is enabled
|
||||
"""
|
||||
try:
|
||||
if TRIGGER_CONFIG_FILE.exists():
|
||||
data = json.loads(TRIGGER_CONFIG_FILE.read_text(encoding='utf-8'))
|
||||
return bool(data.get('config', {}).get('medic_enabled', True))
|
||||
except Exception as exc:
|
||||
_log_warning(f"_is_medic_enabled config read failed: {exc}")
|
||||
return True
|
||||
return True
|
||||
|
||||
|
||||
def _is_branch_muted(branch_name: str) -> bool:
|
||||
"""Check if a specific branch is muted for medic dispatch.
|
||||
|
||||
Reads muted_branches list from trigger_config.json.
|
||||
|
||||
Args:
|
||||
branch_name: Branch name (case-insensitive)
|
||||
|
||||
Returns:
|
||||
True if branch is in the muted list
|
||||
"""
|
||||
try:
|
||||
if TRIGGER_CONFIG_FILE.exists():
|
||||
data = json.loads(TRIGGER_CONFIG_FILE.read_text(encoding='utf-8'))
|
||||
muted = data.get('config', {}).get('muted_branches', [])
|
||||
return branch_name.lower() in [b.lower() for b in muted]
|
||||
except Exception as exc:
|
||||
_log_warning(f"_is_branch_muted config read failed: {exc}")
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def _get_registered_emails() -> set:
|
||||
"""Read registered branch emails from AIPASS_REGISTRY.json.
|
||||
|
||||
Returns:
|
||||
Set of registered email addresses (e.g., {'@flow', '@drone'})
|
||||
"""
|
||||
try:
|
||||
if BRANCH_REGISTRY_FILE.exists():
|
||||
data = json.loads(BRANCH_REGISTRY_FILE.read_text(encoding='utf-8'))
|
||||
return {b["email"] for b in data.get("branches", [])}
|
||||
except Exception as exc:
|
||||
_log_warning(f"_get_registered_emails registry read failed: {exc}")
|
||||
return set()
|
||||
return set()
|
||||
|
||||
|
||||
def _is_rate_limited(branch_email: str) -> bool:
|
||||
"""Check if a branch has exceeded the dispatch rate limit.
|
||||
|
||||
Args:
|
||||
branch_email: Target branch email (e.g., '@flow')
|
||||
|
||||
Returns:
|
||||
True if branch has hit the limit (3 dispatches in 10 minutes)
|
||||
"""
|
||||
now = time.time()
|
||||
cutoff = now - RATE_LIMIT_WINDOW_SECONDS
|
||||
|
||||
if branch_email not in _dispatch_timestamps:
|
||||
_dispatch_timestamps[branch_email] = []
|
||||
|
||||
_dispatch_timestamps[branch_email] = [
|
||||
ts for ts in _dispatch_timestamps[branch_email] if ts > cutoff
|
||||
]
|
||||
|
||||
return len(_dispatch_timestamps[branch_email]) >= MAX_DISPATCHES_PER_WINDOW
|
||||
|
||||
|
||||
def _record_dispatch(branch_email: str) -> None:
|
||||
"""Record a dispatch timestamp for rate limiting.
|
||||
|
||||
Args:
|
||||
branch_email: Target branch email (e.g., '@flow')
|
||||
"""
|
||||
if branch_email not in _dispatch_timestamps:
|
||||
_dispatch_timestamps[branch_email] = []
|
||||
_dispatch_timestamps[branch_email].append(time.time())
|
||||
|
||||
|
||||
def _log_suppression(reason: str, branch: str, source_module: str, message: str) -> None:
|
||||
"""Log a suppressed dispatch to medic_suppressed.log.
|
||||
|
||||
Args:
|
||||
reason: Why dispatch was suppressed
|
||||
branch: Target branch name
|
||||
source_module: Module that logged the error
|
||||
message: Error message (truncated to 100 chars)
|
||||
"""
|
||||
try:
|
||||
SUPPRESSED_LOG.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(SUPPRESSED_LOG, 'a', encoding='utf-8') as f:
|
||||
f.write(
|
||||
f"{datetime.now().isoformat()} | "
|
||||
f"{reason} - suppressed dispatch for {branch}: "
|
||||
f"{source_module} - {message[:100]}\n"
|
||||
)
|
||||
except Exception as exc:
|
||||
_log_warning(f"_log_suppression write failed: {exc}")
|
||||
return
|
||||
|
||||
|
||||
def _build_notification_message(
|
||||
error_hash: str,
|
||||
source_module: str,
|
||||
message: str,
|
||||
timestamp: str,
|
||||
log_file: str
|
||||
) -> str:
|
||||
"""Build error notification message with investigation instructions.
|
||||
|
||||
Args:
|
||||
error_hash: Unique error identifier
|
||||
source_module: Module that logged the error
|
||||
message: Error message text
|
||||
timestamp: When error occurred
|
||||
log_file: Path to source log file
|
||||
|
||||
Returns:
|
||||
Formatted message string
|
||||
"""
|
||||
return f"""Error detected - investigate and respond.
|
||||
|
||||
Error ID: {error_hash}
|
||||
Module: {source_module}
|
||||
Timestamp: {timestamp}
|
||||
Log file: {log_file}
|
||||
|
||||
Error message:
|
||||
{message}
|
||||
|
||||
---
|
||||
INVESTIGATION STEPS:
|
||||
1. Check the log file for context around this error
|
||||
2. Identify root cause
|
||||
|
||||
DECISION TREE:
|
||||
- SIMPLE FIX (typo, missing import, config issue):
|
||||
-> Fix it yourself, then report what you did to @devpulse
|
||||
- COMPLEX/UNCLEAR (needs research, affects multiple files):
|
||||
-> Report findings only to @devpulse, recommend action, don't fix
|
||||
- CRITICAL (data loss risk, security, system stability):
|
||||
-> STOP immediately, escalate to @devpulse with full context
|
||||
|
||||
REPORT TO @devpulse:
|
||||
ai_mail email @devpulse "ERROR {error_hash[:8]} - [STATUS]" "Findings..."
|
||||
"""
|
||||
|
||||
|
||||
def handle_error_logged(
|
||||
branch: str | None = None,
|
||||
message: str | None = None,
|
||||
error_hash: str | None = None,
|
||||
timestamp: str | None = None,
|
||||
log_file: str | None = None,
|
||||
source_module: str | None = None,
|
||||
module_name: str | None = None,
|
||||
level: str | None = None, # noqa: ARG001
|
||||
**kwargs: Any # noqa: ARG001
|
||||
**_kwargs: Any
|
||||
) -> None:
|
||||
"""Handle error_logged event with full medic gating.
|
||||
"""Handle error_logged event — monitor-only, no dispatch.
|
||||
|
||||
DEPRECATED: This is the legacy error notification handler. The primary
|
||||
pipeline is error_detected.py (Medic v2). This handler remains for
|
||||
backward compatibility with code that fires error_logged events.
|
||||
|
||||
Gating (matches error_detected.py):
|
||||
1. medic_enabled check (global toggle)
|
||||
2. branch_muted check (per-branch suppression)
|
||||
3. devpulse protection (never auto-trigger)
|
||||
4. Branch validation (unknown branches logged + skipped)
|
||||
5. Rate limiting (3 per 10 minutes per branch)
|
||||
All dispatch now goes through error_detected.py (Medic v2).
|
||||
This handler logs event metadata for monitoring only.
|
||||
|
||||
Args:
|
||||
branch: Branch where error occurred - REQUIRED
|
||||
message: Error message text - REQUIRED
|
||||
error_hash: Unique error identifier - REQUIRED
|
||||
timestamp: When error occurred (defaults to now)
|
||||
log_file: Path to source log file
|
||||
branch: Branch where error occurred
|
||||
message: Error message text
|
||||
error_hash: Unique error identifier
|
||||
source_module: Module that logged the error
|
||||
module_name: Deprecated alias for source_module
|
||||
level: Log level (for reference, unused)
|
||||
**kwargs: Additional event data (ignored)
|
||||
**_kwargs: Additional event data (ignored)
|
||||
"""
|
||||
try:
|
||||
if not branch or not message or not error_hash:
|
||||
return
|
||||
|
||||
# Resolve source_module from either parameter name
|
||||
effective_module = source_module or module_name or "unknown"
|
||||
|
||||
# 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,
|
||||
|
||||
Reference in New Issue
Block a user