From aef8982cbbe1df6418c86ef7d7f5f69bfd807cf8 Mon Sep 17 00:00:00 2001 From: ogt Date: Mon, 20 Apr 2026 04:50:28 +0800 Subject: [PATCH] fix: add Incident/Playbook/HealLog to autoheal_models.py (was never committed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR-013 AIOps classes Incident, Playbook, HealLog existed locally but were missing from git. manager.py imports them → ImportError on every scheduler restart. Also fixes transitive MetaData conflict with ai_models.py. Co-Authored-By: Claude Sonnet 4.6 --- database/autoheal_models.py | 116 +++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 3 deletions(-) diff --git a/database/autoheal_models.py b/database/autoheal_models.py index 4bb9140..cd3716c 100644 --- a/database/autoheal_models.py +++ b/database/autoheal_models.py @@ -15,13 +15,14 @@ class AgentContext(Base): session_id = Column(String(64), nullable=False, index=True) agent_name = Column(String(50), nullable=False, index=True) context_key = Column(String(100), nullable=False) - context_val = Column(Text) # JSON 字串 + context_val = Column(Text) # JSON string created_at = Column(DateTime, default=datetime.now) ttl_minutes = Column(Integer, default=60) __table_args__ = ( Index('idx_agent_context_session_key', 'session_id', 'agent_name', 'context_key'), Index('idx_agent_context_session_ttl', 'session_id', 'created_at'), + {'extend_existing': True}, ) @@ -45,6 +46,7 @@ class ActionPlan(Base): __table_args__ = ( Index('idx_action_plan_sku_status', 'sku', 'status'), Index('idx_action_plan_created', 'created_at'), + {'extend_existing': True}, ) @@ -53,6 +55,7 @@ class ActionOutcome(Base): 行動結果追蹤(閉環學習核心)。 """ __tablename__ = 'action_outcomes' + __table_args__ = {'extend_existing': True} id = Column(Integer, primary_key=True, autoincrement=True) plan_id = Column(Integer, ForeignKey('action_plans.id'), nullable=False) @@ -68,8 +71,8 @@ class ActionOutcome(Base): class AgentStrategyWeights(Base): """ - Agent 策略權重(OpenClaw 學習累積)。 - 索引:strategy_key 以便快速更新與查詢。 + Agent strategy weights (OpenClaw learning accumulation). + Index: strategy_key for fast updates/query. """ __tablename__ = 'agent_strategy_weights' @@ -82,4 +85,111 @@ class AgentStrategyWeights(Base): __table_args__ = ( Index('idx_strategy_key', 'strategy_key'), + {'extend_existing': True}, ) + + +class Incident(Base): + """ + Incident tracking for AIOps auto-healing. + """ + __tablename__ = 'incidents' + + id = Column(Integer, primary_key=True, autoincrement=True) + task_name = Column(String(100), nullable=False) + error_type = Column(String(50), nullable=False) + error_message = Column(Text, nullable=False) + traceback_str = Column(Text) + severity = Column(String(20), default='medium') + status = Column(String(20), default='open') # open/healing/closed/escalated + retry_count = Column(Integer, default=0) + matched_playbook_id = Column(Integer, ForeignKey('playbooks.id'), nullable=True) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now) + + # Relationship + playbook = relationship("Playbook", backref="incidents") + + +class Playbook(Base): + """ + Playbook definitions for auto-healing actions. + """ + __tablename__ = 'playbooks' + + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(String(100), nullable=False) + description = Column(Text) + error_type = Column(String(50), nullable=False) + match_pattern = Column(Text) # JSON array of patterns + action_type = Column(String(50), nullable=False) # DOCKER_RESTART/SSH_CMD/ALERT_ONLY/WAIT_RETRY + action_params = Column(Text) # JSON params + max_retries = Column(Integer, default=3) + cooldown_min = Column(Integer, default=30) + is_active = Column(Boolean, default=True) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now) + + def get_match_patterns(self): + """Parse match_pattern JSON to list""" + if self.match_pattern: + try: + import json + return json.loads(self.match_pattern) + except: + return [] + return [] + + def get_action_params(self): + """Parse action_params JSON to dict""" + if self.action_params: + try: + import json + return json.loads(self.action_params) + except: + return {} + return {} + + +class HealLog(Base): + """ + Healing execution logs. + """ + __tablename__ = 'heal_logs' + + id = Column(Integer, primary_key=True, autoincrement=True) + incident_id = Column(Integer, ForeignKey('incidents.id'), nullable=False) + playbook_id = Column(Integer, ForeignKey('playbooks.id'), nullable=False) + action_type = Column(String(50), nullable=False) + action_detail = Column(Text) + result = Column(String(20), default='unknown') # success/failed/skipped + result_output = Column(Text) + duration_ms = Column(Float) + created_at = Column(DateTime, default=datetime.now) + + # Relationships + incident = relationship("Incident", backref="heal_logs") + playbook = relationship("Playbook", backref="heal_logs") + + +# Seed playbooks for common issues +SEED_PLAYBOOKS = [ + { + 'name': 'Database Connection Recovery', + 'error_type': 'database', + 'match_pattern': '["connection", "timeout", "unreachable"]', + 'action_type': 'WAIT_RETRY', + 'action_params': '{"wait_minutes": 5}', + 'max_retries': 3, + 'cooldown_min': 15 + }, + { + 'name': 'Disk Space Alert', + 'error_type': 'disk_space', + 'match_pattern': '["disk", "space", "full", "no space"]', + 'action_type': 'ALERT_ONLY', + 'action_params': '{"message": "Disk space critical - manual intervention required"}', + 'max_retries': 1, + 'cooldown_min': 60 + } +]