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
288 lines
10 KiB
Python
288 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
from tools.investigation.reporting.context import build_report_context
|
|
from tools.investigation.reporting.formatters.report import (
|
|
build_slack_blocks,
|
|
format_slack_message,
|
|
format_telegram_message,
|
|
)
|
|
|
|
|
|
def _make_state() -> dict:
|
|
return {
|
|
"pipeline_name": "checkout-service",
|
|
"alert_name": "Checkout latency spike",
|
|
"root_cause": "Checkout service was throttled by the upstream API cluster.",
|
|
"root_cause_category": "dependency_failure",
|
|
"validity_score": 0.91,
|
|
"validated_claims": [
|
|
{
|
|
"claim": "Grafana logs show repeated 500 responses.",
|
|
"evidence_sources": ["grafana_logs"],
|
|
}
|
|
],
|
|
"non_validated_claims": [],
|
|
"investigation_recommendations": [],
|
|
"remediation_steps": [],
|
|
"available_sources": {
|
|
"grafana": {
|
|
"grafana_endpoint": "https://myorg.grafana.net",
|
|
"service_name": "checkout-api",
|
|
"pipeline_name": "checkout-service",
|
|
},
|
|
"eks": {
|
|
"cluster_name": "prod-cluster",
|
|
"namespace": "payments",
|
|
"region": "us-east-1",
|
|
},
|
|
},
|
|
"evidence": {
|
|
"grafana_logs": [
|
|
{"message": "service unavailable"},
|
|
],
|
|
},
|
|
}
|
|
|
|
|
|
def test_build_report_context_adds_source_provenance() -> None:
|
|
ctx = build_report_context(_make_state())
|
|
|
|
assert ctx["source_provenance"]["grafana"]["summary"] == (
|
|
"instance=myorg.grafana.net, service=checkout-api, pipeline=checkout-service"
|
|
)
|
|
assert ctx["evidence_catalog"]["evidence/grafana/loki"]["provenance"] == (
|
|
"instance=myorg.grafana.net, service=checkout-api, pipeline=checkout-service"
|
|
)
|
|
|
|
|
|
def test_format_telegram_message_does_not_treat_lonely_asterisk_as_bold() -> None:
|
|
state = _make_state()
|
|
state["severity"] = "warning"
|
|
state["alert_name"] = "Unit"
|
|
state["pipeline_name"] = "pipe"
|
|
state["root_cause"] = "Check 2 * 3 = 6 before scaling"
|
|
ctx = build_report_context(state)
|
|
body = format_telegram_message(ctx)
|
|
assert "2 * 3" in body
|
|
assert "<b>3</b>" not in body
|
|
|
|
|
|
def test_format_telegram_message_renders_double_star_bold_in_root_cause() -> None:
|
|
state = _make_state()
|
|
state["severity"] = "high"
|
|
state["alert_name"] = "HighMemory"
|
|
state["pipeline_name"] = "checkout"
|
|
state["root_cause"] = "The pod **api-server** exhausted its memory limit"
|
|
ctx = build_report_context(state)
|
|
body = format_telegram_message(ctx)
|
|
assert "<b>api-server</b>" in body
|
|
# Internal bold placeholders must never leak into the rendered message.
|
|
assert "«B" not in body
|
|
assert "**" not in body
|
|
|
|
|
|
def test_format_telegram_message_omits_banner_only_root_cause() -> None:
|
|
state = _make_state()
|
|
state["severity"] = "info"
|
|
state["alert_name"] = "[synthetic-k8s] Scheduled Health Check — payments-api"
|
|
state["pipeline_name"] = "k8s-eks-synthetic"
|
|
state["root_cause"] = (
|
|
"[synthetic-k8s] Scheduled Health Check — payments-api on k8s-eks-synthetic "
|
|
"(severity: info)"
|
|
)
|
|
ctx = build_report_context(state)
|
|
body = format_telegram_message(ctx)
|
|
assert body.count("k8s-eks-synthetic") == 1
|
|
assert "Scheduled Health Check — payments-api on k8s-eks-synthetic (severity: info)" not in body
|
|
|
|
|
|
def test_format_telegram_message_uses_html_and_severity_header() -> None:
|
|
state = _make_state()
|
|
state["severity"] = "critical"
|
|
state["alert_name"] = "KubernetesJobFailed"
|
|
ctx = build_report_context(state)
|
|
body = format_telegram_message(ctx)
|
|
assert "🔴" in body
|
|
assert "<b>KubernetesJobFailed</b>" in body
|
|
assert "CRITICAL" in body
|
|
assert "##" not in body
|
|
assert "*Cited Evidence" not in body
|
|
|
|
|
|
def test_format_slack_message_shows_incident_command_section() -> None:
|
|
state = _make_state()
|
|
state["triage_summary"] = "Triage complete: Critical errors isolated to payments_etl."
|
|
state["incident_status"] = (
|
|
"Status — confirmed: alert critical | open: deploy time | next: verify DB | owner: on-call"
|
|
)
|
|
state["investigation_hypotheses"] = [
|
|
"H1: Database outage — confirm: DB error logs; rule out: caller-only misconfig"
|
|
]
|
|
state["verification_summary"] = ["Datadog logs (H1): connection refused errors in payments_etl"]
|
|
state["follow_up_questions"] = [
|
|
"Was there a deploy of payments_etl around 14:32 UTC?",
|
|
]
|
|
state["remediation_tradeoffs"] = (
|
|
"Rollback is fastest; scaling DB is slower but safer. Recommend rollback first."
|
|
)
|
|
message = format_slack_message(build_report_context(state))
|
|
|
|
assert "## Incident Command" in message
|
|
assert "Triage complete: Critical errors isolated to payments_etl." in message
|
|
assert "Triage complete: Triage complete:" not in message
|
|
assert "*Hypotheses:*" in message
|
|
assert "*Verification:*" in message
|
|
assert "*Follow-up questions:*" in message
|
|
assert "Remediation trade-offs:" in message
|
|
|
|
|
|
def test_format_slack_message_shows_provenance() -> None:
|
|
ctx = build_report_context(_make_state())
|
|
message = format_slack_message(ctx)
|
|
|
|
assert "*Provenance:*" in message
|
|
assert "Grafana: instance=myorg.grafana.net" in message
|
|
assert "AWS EKS: cluster=prod-cluster, namespace=payments, region=us-east-1" in message
|
|
|
|
|
|
def test_format_slack_message_shows_recommended_actions() -> None:
|
|
state = _make_state()
|
|
state["remediation_steps"] = [
|
|
"Increase memory limit for payments-api deployment",
|
|
"Add Datadog monitor for memory usage at 80% threshold",
|
|
]
|
|
ctx = build_report_context(state)
|
|
message = format_slack_message(ctx)
|
|
|
|
assert "## Recommended Actions" in message
|
|
assert "• Increase memory limit for payments-api deployment" in message
|
|
assert "• Add Datadog monitor for memory usage at 80% threshold" in message
|
|
|
|
|
|
def test_format_slack_message_hides_tool_call_inputs_and_outputs_by_default() -> None:
|
|
state = _make_state()
|
|
state["evidence_entries"] = [
|
|
{
|
|
"tool_name": "query_grafana_metrics",
|
|
"tool_args": {
|
|
"metric_name": "pipeline_runs_total",
|
|
"grafana_api_key": "secret-key",
|
|
},
|
|
"data": {"available": True, "total_series": 0},
|
|
"loop_iteration": 0,
|
|
}
|
|
]
|
|
|
|
ctx = build_report_context(state)
|
|
message = format_slack_message(ctx)
|
|
|
|
assert "*Tool Calls and Responses:*" not in message
|
|
assert "query_grafana_metrics" not in message
|
|
assert "pipeline_runs_total" not in message
|
|
assert "total_series" not in message
|
|
assert "secret-key" not in message
|
|
|
|
|
|
def test_format_slack_message_omits_recommended_actions_when_empty() -> None:
|
|
ctx = build_report_context(_make_state()) # remediation_steps=[] by default
|
|
message = format_slack_message(ctx)
|
|
|
|
assert "## Recommended Actions" not in message
|
|
assert (
|
|
"provenance: instance=myorg.grafana.net, service=checkout-api, pipeline=checkout-service"
|
|
in message
|
|
)
|
|
|
|
|
|
def test_build_report_context_adds_additional_source_provenance() -> None:
|
|
state = _make_state()
|
|
state["available_sources"].update(
|
|
{
|
|
"datadog": {
|
|
"site": "datadoghq.eu",
|
|
"default_query": "service:checkout",
|
|
"kubernetes_context": {"namespace": "payments"},
|
|
},
|
|
"github": {
|
|
"owner": "myorg",
|
|
"repo": "checkout",
|
|
"ref": "main",
|
|
},
|
|
"vercel": {
|
|
"project_name": "checkout-web",
|
|
"deployment_id": "dpl_123",
|
|
},
|
|
"s3": {
|
|
"bucket": "tracer-artifacts",
|
|
"prefix": "runs/checkout",
|
|
},
|
|
}
|
|
)
|
|
state["evidence"]["datadog_logs"] = [{"message": "5xx spike"}]
|
|
|
|
ctx = build_report_context(state)
|
|
|
|
assert ctx["source_provenance"]["datadog"]["summary"] == (
|
|
"site=datadoghq.eu, query=service:checkout, namespace=payments"
|
|
)
|
|
assert ctx["source_provenance"]["github"]["summary"] == "repo=myorg/checkout, ref=main"
|
|
assert (
|
|
ctx["source_provenance"]["vercel"]["summary"]
|
|
== "project=checkout-web, deployment_id=dpl_123"
|
|
)
|
|
assert (
|
|
ctx["source_provenance"]["s3"]["summary"] == "bucket=tracer-artifacts, prefix=runs/checkout"
|
|
)
|
|
assert (
|
|
ctx["evidence_catalog"]["evidence/datadog/logs"]["provenance"]
|
|
== "site=datadoghq.eu, query=service:checkout, namespace=payments"
|
|
)
|
|
|
|
|
|
def test_build_report_context_drops_empty_provenance_summaries() -> None:
|
|
state = _make_state()
|
|
state["available_sources"]["github"] = {}
|
|
state["available_sources"]["coralogix"] = {"application_name": ""}
|
|
|
|
ctx = build_report_context(state)
|
|
|
|
assert "github" not in ctx["source_provenance"]
|
|
assert "coralogix" not in ctx["source_provenance"]
|
|
|
|
|
|
def test_format_slack_message_sanitizes_provenance_content() -> None:
|
|
state = _make_state()
|
|
state["available_sources"]["grafana"]["service_name"] = "**checkout-api**"
|
|
|
|
ctx = build_report_context(state)
|
|
message = format_slack_message(ctx)
|
|
|
|
assert "service=*checkout-api*" in message
|
|
assert "service=**checkout-api**" not in message
|
|
|
|
|
|
def test_build_slack_blocks_shows_recommended_actions() -> None:
|
|
state = _make_state()
|
|
state["remediation_steps"] = [
|
|
"Increase memory limit for payments-api deployment",
|
|
"Add Datadog monitor for memory usage at 80% threshold",
|
|
]
|
|
ctx = build_report_context(state)
|
|
blocks = build_slack_blocks(ctx)
|
|
|
|
block_texts = [
|
|
b.get("text", {}).get("text", "") if isinstance(b.get("text"), dict) else "" for b in blocks
|
|
]
|
|
header_texts = [b.get("text", {}).get("text", "") for b in blocks if b.get("type") == "header"]
|
|
assert any("Recommended Actions" in t for t in header_texts)
|
|
assert any("Increase memory limit" in t for t in block_texts)
|
|
assert any(b.get("type") == "divider" for b in blocks)
|
|
|
|
|
|
def test_build_slack_blocks_omits_recommended_actions_when_empty() -> None:
|
|
ctx = build_report_context(_make_state()) # remediation_steps=[] by default
|
|
blocks = build_slack_blocks(ctx)
|
|
|
|
header_texts = [b.get("text", {}).get("text", "") for b in blocks if b.get("type") == "header"]
|
|
assert not any("Recommended Actions" in t for t in header_texts)
|