Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

112 lines
3.9 KiB
Python

"""Normalize investigation state into report-context inputs."""
from __future__ import annotations
import time
from typing import Any
from core.state import InvestigationState
def safe_get(data: dict[str, Any] | None, *keys: str, default: Any = None) -> Any:
"""Safely navigate nested dictionaries without raising."""
if data is None:
return default
current: Any = data
for key in keys:
if not isinstance(current, dict):
return default
current = current.get(key)
if current is None:
return default
return current
def as_snippet(value: str | None, max_len: int = 140) -> str | None:
"""Compact a value to a short, brace-free snippet for display."""
if not value:
return None
compact = " ".join(str(value).split())
compact = compact.replace("{", "").replace("}", "").replace("[", "").replace("]", "")
return compact[:max_len]
def filter_valid_claims(claims: list[dict]) -> list[dict]:
"""Drop claims with empty text or the NON_ artifact prefix."""
return [
c
for c in claims
if c.get("claim", "").strip() and not c.get("claim", "").strip().startswith("NON_")
]
class NormalizedState:
"""All raw data extracted from InvestigationState in one place."""
def __init__(self, state: InvestigationState) -> None:
evidence = state.get("evidence", {}) or {}
available_sources = state.get("available_sources", {}) or {}
raw_alert_value = state.get("raw_alert", {})
self.evidence: dict[str, Any] = evidence
self.raw_alert: dict[str, Any] = (
raw_alert_value if isinstance(raw_alert_value, dict) else {}
)
self.s3: dict[str, Any] = evidence.get("s3", {}) or {}
self.available_sources: dict[str, dict[str, Any]] = available_sources
self.grafana_endpoint: str | None = (available_sources.get("grafana") or {}).get(
"grafana_endpoint"
)
self.datadog_site: str = (available_sources.get("datadog") or {}).get(
"site"
) or "datadoghq.com"
self.validated_claims: list[dict] = filter_valid_claims(state.get("validated_claims", []))
self.non_validated_claims: list[dict] = state.get("non_validated_claims", [])
(
self.cloudwatch_url,
self.cloudwatch_group,
self.cloudwatch_stream,
self.cloudwatch_region,
self.alert_id,
) = extract_cloudwatch_info(self.raw_alert)
started_at = state.get("investigation_started_at")
self.duration_seconds: int | None = (
max(0, int(round(time.monotonic() - float(started_at))))
if isinstance(started_at, int | float)
else None
)
self.state = state
def extract_cloudwatch_info(
raw_alert: dict[str, Any],
) -> tuple[str | None, str | None, str | None, str | None, str | None]:
"""Pull CloudWatch metadata from an alert dict.
Returns: (url, log_group, log_stream, region, alert_id)
"""
annotations = raw_alert.get("annotations", {}) or raw_alert.get("commonAnnotations", {})
if not annotations and raw_alert.get("alerts"):
first_alert = raw_alert.get("alerts", [{}])[0]
if isinstance(first_alert, dict):
annotations = first_alert.get("annotations", {}) or {}
url = (
raw_alert.get("cloudwatch_logs_url")
or raw_alert.get("cloudwatch_url")
or safe_get(annotations, "cloudwatch_logs_url")
or safe_get(annotations, "cloudwatch_url")
)
group = raw_alert.get("cloudwatch_log_group") or safe_get(annotations, "cloudwatch_log_group")
stream = raw_alert.get("cloudwatch_log_stream") or safe_get(
annotations, "cloudwatch_log_stream"
)
region = raw_alert.get("cloudwatch_region") or safe_get(annotations, "cloudwatch_region")
alert_id = raw_alert.get("alert_id")
return url, group, stream, region, alert_id