Files
awoooi/apps/api/src/services/agent_replay_fixture.py
Your Name cfb866d055
Some checks failed
Ansible Lint / lint (push) Successful in 35s
CD Pipeline / tests (push) Failing after 13s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Code Review / ai-code-review (push) Failing after 11s
feat(governance): add agent market automation surfaces
2026-06-04 21:50:55 +08:00

225 lines
7.3 KiB
Python

"""
Agent Replay Fixture Builder
============================
Builds sanitized incident fixtures for OpenClaw replacement candidate replay.
Fixtures separate the input context shown to candidate Agents from evaluation
labels used by the offline scoring harness. This prevents candidates from
self-grading against the answer key while keeping replay runs reproducible.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
REDACTED = "[REDACTED]"
SENSITIVE_KEY_MARKERS = (
"authorization",
"cookie",
"password",
"passwd",
"secret",
"token",
"api_key",
"apikey",
"private_key",
)
SENSITIVE_VALUE_MARKERS = (
"bearer ",
"basic ",
"-----begin private key-----",
)
@dataclass(frozen=True)
class AgentReplayFixture:
"""One sanitized incident fixture for candidate Agent offline replay."""
run_id: str
incident_id: str
schema_version: str = "agent_replay_fixture_v1"
incident_context: dict[str, Any] = field(default_factory=dict)
evaluation_labels: dict[str, Any] = field(default_factory=dict)
source_metadata: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
return {
"schema_version": self.schema_version,
"run_id": self.run_id,
"incident_id": self.incident_id,
"incident_context": dict(self.incident_context),
"evaluation_labels": dict(self.evaluation_labels),
"source_metadata": dict(self.source_metadata),
}
def build_agent_replay_fixture(
*,
run_id: str,
incident,
evidence=None,
execution=None,
agent_turn_count: int = 0,
) -> AgentReplayFixture:
"""Build a sanitized fixture from DB model objects."""
incident_context = {
"severity": _scalar_value(getattr(incident, "severity", None)),
"status": _scalar_value(getattr(incident, "status", None)),
"alertname": getattr(incident, "alertname", None),
"alert_category": getattr(incident, "alert_category", None),
"notification_type": getattr(incident, "notification_type", None),
"affected_services": list(getattr(incident, "affected_services", None) or []),
"signals": _sanitize_for_fixture(getattr(incident, "signals", None) or []),
"frequency_snapshot": _sanitize_for_fixture(
getattr(incident, "frequency_snapshot", None)
),
"evidence_summary": _sanitize_for_fixture(
getattr(evidence, "evidence_summary", None) if evidence else None
),
"mcp_health": _sanitize_for_fixture(
getattr(evidence, "mcp_health", None) if evidence else None
),
"sensors_attempted": getattr(evidence, "sensors_attempted", None)
if evidence
else None,
"sensors_succeeded": getattr(evidence, "sensors_succeeded", None)
if evidence
else None,
"historical_context": _sanitize_for_fixture(
getattr(evidence, "historical_context", None) if evidence else None
),
"dependency_topology": _sanitize_for_fixture(
getattr(evidence, "dependency_topology", None) if evidence else None
),
"business_metrics": _sanitize_for_fixture(
getattr(evidence, "business_metrics", None) if evidence else None
),
}
expected_action_markers = _expected_action_markers(
incident_context=incident_context,
execution=execution,
)
evaluation_labels = {
"verification_result": getattr(evidence, "verification_result", None)
if evidence
else None,
"self_healing_score": getattr(evidence, "self_healing_score", None)
if evidence
else None,
"execution_success": getattr(execution, "success", None) if execution else None,
"execution_error": _sanitize_for_fixture(
getattr(execution, "error_message", None) if execution else None
),
"resolved_at": _iso_or_none(getattr(incident, "resolved_at", None)),
"closed_at": _iso_or_none(getattr(incident, "closed_at", None)),
}
if expected_action_markers:
evaluation_labels["expected_action_markers"] = expected_action_markers
source_metadata = {
"created_at": _iso_or_none(getattr(incident, "created_at", None)),
"updated_at": _iso_or_none(getattr(incident, "updated_at", None)),
"agent_turn_count": agent_turn_count,
"source": "awoooi_incident_replay_fixture",
}
return AgentReplayFixture(
run_id=run_id,
incident_id=str(incident.incident_id),
incident_context=_drop_none(incident_context),
evaluation_labels=_drop_none(evaluation_labels),
source_metadata=_drop_none(source_metadata),
)
def _sanitize_for_fixture(value: Any) -> Any:
if isinstance(value, dict):
sanitized: dict[str, Any] = {}
for key, nested in value.items():
key_text = str(key)
if _is_sensitive_key(key_text):
sanitized[key_text] = REDACTED
else:
sanitized[key_text] = _sanitize_for_fixture(nested)
return sanitized
if isinstance(value, list):
return [_sanitize_for_fixture(item) for item in value]
if isinstance(value, tuple):
return [_sanitize_for_fixture(item) for item in value]
if isinstance(value, str):
return _sanitize_string(value)
if isinstance(value, datetime):
return value.isoformat()
return value
def _sanitize_string(value: str) -> str:
lowered = value.lower()
if any(marker in lowered for marker in SENSITIVE_VALUE_MARKERS):
return REDACTED
return value
def _is_sensitive_key(key: str) -> bool:
lowered = key.lower()
return any(marker in lowered for marker in SENSITIVE_KEY_MARKERS)
def _drop_none(payload: dict[str, Any]) -> dict[str, Any]:
return {key: value for key, value in payload.items() if value is not None}
def _iso_or_none(value: Any) -> str | None:
if value is None:
return None
if isinstance(value, datetime):
return value.isoformat()
return str(value)
def _scalar_value(value: Any) -> Any:
return getattr(value, "value", value)
def _expected_action_markers(
*,
incident_context: dict[str, Any],
execution: Any,
) -> list[str]:
if execution is None:
return []
parts = [
getattr(execution, "playbook_name", None),
_sanitize_for_fixture(getattr(execution, "executed_steps", None) or []),
]
haystack = " ".join(
json_part.lower()
for json_part in (_json_text(part) for part in parts)
if json_part
)
markers: list[str] = []
if "rollout restart" in haystack or ("rollout" in haystack and "restart" in haystack):
markers.append("rollout restart")
else:
for marker in ("restart", "rollback", "scale", "describe", "logs", "delete"):
if marker in haystack:
markers.append(marker)
for service in incident_context.get("affected_services") or []:
service_marker = str(service).strip().lower()
if service_marker:
markers.append(service_marker)
break
return list(dict.fromkeys(markers))
def _json_text(value: Any) -> str:
if value is None:
return ""
if isinstance(value, str):
return value
return str(value)