77 lines
2.6 KiB
Python
77 lines
2.6 KiB
Python
"""
|
|
Autopsy API — post-hoc root cause analysis for failed actions.
|
|
"""
|
|
|
|
from fastapi import APIRouter
|
|
|
|
from backend.analysis.failure_autopsy import FailureAutopsy
|
|
from backend.analysis.timeline_store import STORE as TIMELINE_STORE
|
|
from backend.api.replay import SESSION_DB
|
|
|
|
router = APIRouter(prefix="/autopsy", tags=["autopsy"])
|
|
|
|
|
|
# -----------------------------
|
|
# RUN AUTOPSY
|
|
# -----------------------------
|
|
@router.get("/{session_id}/{action}")
|
|
def run_autopsy(session_id: str, action: str):
|
|
"""Run a failure autopsy for `action` against the replay session `session_id`."""
|
|
|
|
# Try to fetch events for this session from SessionDB
|
|
events = SESSION_DB.get_replay_stream(session_id)
|
|
|
|
if not events:
|
|
# Fallback: use the live timeline store
|
|
journal = TIMELINE_STORE
|
|
else:
|
|
# Wrap the session's event list in a tiny shim that quacks like
|
|
# the TimelineStore API the FailureAutopsy expects.
|
|
journal = _JournalShim(events)
|
|
|
|
autopsy = FailureAutopsy(journal)
|
|
return autopsy.report(action)
|
|
|
|
|
|
# -----------------------------
|
|
# Legacy wrapper for api.py facade
|
|
# -----------------------------
|
|
def autopsy_endpoint(session_id, action=None):
|
|
"""Legacy entrypoint — caller passed only session_id; we look up
|
|
failures across all known sessions."""
|
|
events = SESSION_DB.get_replay_stream(session_id)
|
|
if not events:
|
|
return {"error": "session not found"}
|
|
journal = _JournalShim(events)
|
|
autopsy = FailureAutopsy(journal)
|
|
# If no action given, report on the first failure we find
|
|
if action is None:
|
|
failures = autopsy.find_failures()
|
|
if not failures:
|
|
return {"status": "no_failures_in_session", "session_id": session_id}
|
|
action = failures[0].get("data", failures[0]).get("action")
|
|
if not action:
|
|
return {"status": "no_actionable_failure", "session_id": session_id}
|
|
return autopsy.report(action)
|
|
|
|
|
|
class _JournalShim:
|
|
"""Minimal adapter that lets FailureAutopsy work against a plain list
|
|
of events (from a replay session) instead of a full TimelineStore."""
|
|
|
|
def __init__(self, events):
|
|
self.events = events
|
|
|
|
def find_failures(self):
|
|
return [
|
|
e for e in self.events
|
|
if e.get("type") in ("failure", "task_update", "pipeline_update")
|
|
and (e.get("state") == "failed" or e.get("data", {}).get("state") == "failed")
|
|
]
|
|
|
|
def trace_action(self, action_name):
|
|
return [
|
|
e for e in self.events
|
|
if (e.get("action") == action_name) or (e.get("data", {}).get("action") == action_name)
|
|
]
|