anusuyaganguly93-web/incident-auto-remediation
GitHub: anusuyaganguly93-web/incident-auto-remediation
一套基于 Temporal 编排的 AI 故障响应系统,通过告警去重、并行诊断、RAG 对话和策略门控的命令执行,在保证人工审批的前提下实现从告警到修复的自动化闭环。
Stars: 0 | Forks: 0
# 故障自动修复系统
这是一个故障自动修复系统,其设计理念类似于 incident.io:接收告警,对其进行去重和分流,并行收集诊断证据,发布综合摘要,并在获得人工批准后,实际执行修复并验证其效果。所有操作均在 90 秒的 p99 首次响应时间内完成,稳态负载为每天 5,000 条告警。
本项目作为作品集构建,旨在展示在真实约束条件(重协调、实时性、对正确性要求极高)下的系统设计能力,而不仅仅是“把 LLM 连到几个工具上”。请参阅 [`docs/architecture.md`](docs/architecture.md) 获取完整的设计原理,以及 [`docs/DEBUGGING_LOG.md`](docs/DEBUGGING_LOG.md) 获取按时间顺序记录的实现过程中所有崩溃故障及其修复过程的日志——包括一个在开发时被实时发现并修复的真实逻辑 bug。
本 README 按**阶段**组织。每个阶段部分都是独立的:包含各自的架构图、运行方法以及确认其正常工作的具体输出。各阶段按顺序相互依赖构建——阶段 2 需要阶段 1 的接收功能运行,阶段 3 需要阶段 2 的证据存在,阶段 4 需要阶段 2 的 runbook 匹配存在。
## 状态
| 组件 | 状态 |
|---|---|
| 阶段 1 — Webhook 接收、去重/upsert、条件性 Jira 工单创建 | ✅ 已构建并验证 |
| 阶段 2 — 诊断子代理、并行扇出、Temporal 编排、实时目标服务 | ✅ 已构建并验证 |
| 阶段 3 — IAR 聊天(RAG,单事件上下文 + 对话记忆) | ✅ 已构建并验证 |
| 阶段 4 — 策略门控 + 确定性命令分发 + 验证 | ✅ 已构建并验证 |
| 将命令执行接入 Temporal(目前为独立 CLI) | ⬜ 未构建(见路线图) |
| 跨事件语义搜索(embeddings + pgvector) | ⬜ 未构建(见路线图) |
| 置信度评分 + 自动提升 | ⬜ 刻意推迟(见路线图) |
## 全系统架构(所有阶段)
```
PagerDuty/ZenDuty webhook
│
▼
Ingestion: normalize → fingerprint → dedup/upsert (Postgres) ── Phase 1
│
├── new fingerprint ──► create Jira ticket ──► start Temporal workflow
└── existing open fingerprint ──► bump alert_count (no duplicate ticket)
│
▼
IncidentWorkflow (Temporal, durable) ── Phase 2
│
resolve_infra_metadata (Postgres service_registry)
│
run_diagnostics_activity — PARALLEL fan-out:
┌──────────┬──────────┬────────────────┬──────────┐
metrics logs deploy_history runbook
(live) (live) (fixture) (fixture)
└──────────┴──────────┴────────────────┴──────────┘
│
propose_commands_activity ── Phase 4
(binds runbook's suggested_commands
to fully-specified tool calls)
│
┌───────────────────────────────────┴──────────────┐
generate_and_post_comment (LLM) store_evidence (Postgres)
— runs concurrently, not sequentially —
═══════════════════════ no 90s SLA below this line ═══════════════════════
IAR chat (Postgres RAG, conversational, multi-turn) ── Phase 3
reads: incidents + incident_events + incident_chat_messages
read-only / advisory — never executes anything itself
Human approves a proposed command (simulated Jira tag) ── Phase 4
│
▼
policy gate (reversibility × blast radius) → dispatcher (table lookup,
never an LLM decision) → real MCP-style tool call → target_app
│
▼
verification (reuses anomaly_detection.py) → command_executions
```
“实时” = 通过 HTTP 从真实的 `target_app` 玩具服务中查询。
“夹具” = 刻意保持为静态 JSON —— 伪装的 CI/CD pipeline 或替代 embedding 搜索的关键字评分器对于演示来说不会增加真实信号,因此精力被投入到了真正有用的部分。
## 项目结构
```
ingestion/ webhook → normalize → dedup/upsert → conditional Jira ticket
diagnostics/ subagents (metrics/logs/deploy_history/runbook), scripts,
LLM client, fixtures, standalone execute() entrypoint
orchestrator/ Temporal workflow, activities, worker, client helper
iar_chat/ retrieval, LLM chat client, conversation orchestration, CLI
policy/ rule-based policy gate (reversibility × blast radius)
command_executor/ MCP-style tools, dispatcher, verification, approval CLI
target_app/ toy service standing in for checkout-api, chaos injection
shared/ data contracts, fingerprinting, DB repos
migrations/ Postgres schema (001-006, one per phase's new tables)
tests/ pytest suites, one file per phase's logic
docs/ architecture.md, DEBUGGING_LOG.md
```
## 前置条件
- Docker Desktop
- Python 3.11+
- [Temporal CLI](https://docs.temporal.io/cli#install)(在 macOS 上使用 `brew install temporal`)
- 可选:用于真实 LLM 合成的 `ANTHROPIC_API_KEY`,涵盖分流评论(阶段 2)和 IAR 聊天(阶段 3)——否则将回退到确定性的模拟实现。整个系统在没有 API key 的情况下完全可以运行和测试;下文每一个“验证结果”块都是在模拟模式下截取的。
### 一次性设置(在任何阶段之前)
```
git clone && cd incident-auto-remediation
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
# 阶段 1 — 接收、去重、条件性 Jira 工单创建
## 架构
```
PagerDuty / ZenDuty / AlertManager
│ webhook: POST /webhooks/alerts/{source}
▼
Ingestion service (FastAPI)
│
▼
normalize() → StandardizedAlert
│
▼
compute_fingerprint(service, alert_type, env)
│
▼
Dedup/upsert against Postgres `incidents` table
partial unique index: at most ONE open incident per fingerprint
│
┌────┴─────────────────────────────┐
▼ ▼
new fingerprint existing open fingerprint
│ │
▼ ▼
create Jira ticket (mocked) bump alert_count
enqueue for Phase 2 triage re-escalation comment at 10x/100x/1000x
```
去重/upsert 步骤是整个系统中负载最重的核心部分:没有它,一次糟糕的部署在一分钟内触发的 50-200 条相关告警就会用 50-200 个工单淹没值班看板,而不是合并为一个。
## 运行说明
```
docker-compose up --build
```
启动 Postgres(在全新的 volume 上自动应用 `migrations/001_create_incidents.sql`)以及运行在 `localhost:8000` 的接收服务。
向其发送一阵相关的告警风暴:
```
python3 ingestion/simulate_alert_storm.py --count 50
```
## 检查内容
**告警风暴应合并为恰好一个事件、一个工单:**
```
Fired 50 correlated alerts.
Distinct incidents created : 1
Distinct Jira tickets : 1
'is_new' True count : 1
✅ Dedup working as designed: storm collapsed into ONE incident.
```
**通过直接查询 DB 独立确认:**
```
docker exec -it incident-auto-remediation-postgres-1 psql -U postgres -d incidents -c \
"SELECT id, fingerprint, alert_count, jira_ticket_id, status FROM incidents;"
```
```
id | fingerprint | alert_count | jira_ticket_id | status
--------------------------------------+-------------------------------------------------+-------------+----------------+--------
c3177366-c723-433a-a793-f98d940d4231 | checkout-api:high_latency:prod:80b7642a02ea1e59 | 55 | INC-B5951A | new
```
(是 `55`,而不是 `50` —— 该行积累了几天内多次测试会话的告警,这本身就是去重即使在容器重启后也能正确工作的证据,而不仅仅是在单次运行中有效。详见调试日志 #8。)
**运行测试套件:**
```
pytest tests/test_dedup.py -v
```
```
tests/test_dedup.py::test_first_alert_creates_new_incident PASSED
tests/test_dedup.py::test_correlated_storm_collapses_to_one_incident PASSED
tests/test_dedup.py::test_different_services_create_separate_incidents PASSED
tests/test_dedup.py::test_different_alert_types_on_same_service_are_separate_incidents PASSED
tests/test_dedup.py::test_reescalation_threshold_crossing PASSED
```
# 阶段 2 — 诊断子代理 + Temporal 编排 + 实时目标服务
## 架构
```
[Phase 1: new incident created]
│
▼
Temporal Client starts IncidentWorkflow — workflow_id = incident_id
(idempotent: a duplicate enqueue can't double-trigger triage)
│
▼
┌──────────────────────────── IncidentWorkflow ────────────────────────────┐
│ │
│ resolve_infra_metadata ──► Postgres `service_registry` │
│ │ │
│ ▼ │
│ run_diagnostics_activity — PARALLEL fan-out (asyncio.gather): │
│ this is load-bearing for the 90s budget, not an optimization │
│ │
│ ┌───────────┬───────────┬─────────────────┬───────────┐ │
│ │ metrics │ logs │ deploy_history │ runbook │ │
│ │ query → │ query → │ one-hop │ keyword │ │
│ │ target_app│ target_app│ dependency │ match │ │
│ │ /metrics │ /logs │ walk (fixture) │ (fixture) │ │
│ │ anomaly_ │ filter + │ │ │ │
│ │ detection │ cluster │ │ │ │
│ │ .py │ │ │ │ │
│ └───────────┴───────────┴─────────────────┴───────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────┴──────────────────────┐ │
│ generate_and_post_comment (LLM) store_evidence (Postgres) │
│ — run CONCURRENTLY, not sequentially — │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
target_app (toy checkout-api stand-in, real FastAPI service)
GET /metrics — live rolling window of p99_latency_ms / error_rate_pct
GET /logs — live rolling window of structured log lines
POST /chaos — inject_chaos.py toggles simulated latency/error spikes
```
## 运行说明
**终端 1 — Temporal 开发服务器**(保持运行):
```
temporal server start-dev
```
Web UI 位于 http://localhost:8233。
**终端 2 — Docker Compose**(保持运行 —— 与阶段 1 命令相同,现在也会构建 `target-app`):
```
docker-compose up --build
```
**一次性种子数据**(任意终端,Postgres 启动后执行一次):
```
python3 shared/service_registry_seed.py
```
应该打印 `seeded 3 service_registry rows`。
**终端 3 — Temporal worker**(保持运行):
```
source venv/bin/activate
python3 -m orchestrator.worker
```
应该打印 `Worker started. Polling task queue 'incident-triage' on localhost:7233...`
⚠️ **任何代码更改后都必须重启此进程** —— Python 不会热重载(调试日志 #13)。
**终端 4 — 实际演示:**
```
source venv/bin/activate
curl http://localhost:8080/metrics # confirm target_app is healthy
python3 target_app/inject_chaos.py --latency on # inject a latency spike
python3 ingestion/simulate_alert_storm.py --count 5 # fire the alert
```
若要重置以进行干净的重新运行:
```
python3 target_app/inject_chaos.py --latency off --errors off
docker-compose restart target-app # gives a clean baseline window — see gotcha #5
```
## 检查内容
**终端 2(接收)应显示:**
```
[MOCK JIRA] Created INC-530881 for service=checkout-api severity=P1
[TEMPORAL] started workflow 8b361b1a-... for incident 8b361b1a-...
```
**终端 3(worker)应显示实时的 HTTP 调用、并行扇出计时以及合成的评论:**
```
INFO:httpx:HTTP Request: GET http://localhost:8080/logs "HTTP/1.1 200 OK"
INFO:httpx:HTTP Request: GET http://localhost:8080/metrics "HTTP/1.1 200 OK"
INFO:temporalio.activity:diagnostics parallel fan-out wall time: 612.7ms
INFO:temporalio.activity:stored 4 evidence rows for incident 8b361b1a-4acf-4e26-8275-dda7caa68649
[MOCK JIRA] Comment on INC-530881: **Triage summary — checkout-api (high_latency)**
- [metrics] p99_latency_ms anomalous: 117.01 -> 906.43 (+674.7%, z=163.95); error_rate_pct anomalous: 0.16 -> 4.57 (+2704.7%, z=86.44)
- [logs] [ERROR] 'upstream call to payment-api timed out after Nms' x16
- [deploy_history] payment-api deployed v2.8.3 at 2026-07-25T14:01:45Z (1m ago)
- [runbook] best match: 'High latency due to downstream dependency timeout' (score=2)
Suggested next step per runbook: restart-pods, rollback-deploy
```
`117.01 -> 906.43` 这些数字是由 `target_app` (`random.gauss()`) **实时**随机生成的 —— 每次运行都不同,不像静态夹具值(总是精确的 `121.8 -> 895.17`)。正是这个细节证实了实时的 HTTP 路径确实在被调用,而不是默默地回退到夹具数据。
**跨服务根因归属** —— 这是部署历史一跳依赖遍历专门构建以捕捉的场景:`checkout-api` 触发了告警,但证据正确地将其归因于一分钟前对其*依赖项* `payment-api` 的一次部署。系统绝不会建议“重启 checkout-api”,因为那是错误的修复方式。
**确认证据持久化:**
```
docker exec -it incident-auto-remediation-postgres-1 psql -U postgres -d incidents -c \
"SELECT event_type, created_at FROM incident_events WHERE incident_id = '' ORDER BY created_at;"
```
```
event_type | created_at
-------------------------+-------------------------------
evidence_metrics | 2026-07-25 10:53:15.557862+00
evidence_logs | 2026-07-25 10:53:15.557862+00
evidence_deploy_history | 2026-07-25 10:53:15.557862+00
evidence_runbook | 2026-07-25 10:53:15.557862+00
```
**运行测试套件:**
```
pytest tests/test_diagnostics.py -v
```
```
tests/test_diagnostics.py::test_anomaly_detection_catches_injected_latency_spike PASSED
tests/test_diagnostics.py::test_anomaly_detection_does_not_flag_flat_series PASSED
tests/test_diagnostics.py::test_log_filter_clusters_repeated_errors_and_drops_info PASSED
tests/test_diagnostics.py::test_deploy_history_attributes_checkout_incident_to_payment_api_dependency PASSED
tests/test_diagnostics.py::test_runbook_subagent_picks_dependency_timeout_runbook_for_checkout PASSED
tests/test_diagnostics.py::test_inventory_api_shows_no_anomalies_no_false_positive PASSED
tests/test_diagnostics.py::test_parallel_fanout_is_actually_parallel_not_sequential PASSED
tests/test_diagnostics.py::test_execute_end_to_end_produces_comment_referencing_all_evidence PASSED
```
# 阶段 3 — IAR 聊天 (RAG)
## 架构
```
On-call engineer
│
▼
python3 -m iar_chat.cli
│
▼
PostgresIARChatRepo
reads: incidents, incident_events (Phase 2's evidence), incident_chat_messages
│
▼
retrieval.build_context()
assembles incident metadata + all evidence findings into one context block
│
▼
llm_chat_client.generate_chat_reply()
read-only / advisory system prompt — never claims to execute anything
(mock fallback if no ANTHROPIC_API_KEY)
│
▼
save_chat_message() × 2 → incident_chat_messages (Postgres)
conversation persists across separate CLI invocations, not just in-session
═══ no 90-second SLA — this is the "slow path" subsystem ═══
```
## 运行说明
需要阶段 1 + 2 已经运行(Temporal、Docker Compose、worker),外加一个可以讨论的已分流事件。
新的迁移(不会自动应用到现有的 Postgres volume —— 见注意事项 #2):
```
docker exec -i incident-auto-remediation-postgres-1 psql -U postgres -d incidents \
< migrations/004_create_incident_chat_messages.sql
```
获取一个 incident_id:
```
docker exec -it incident-auto-remediation-postgres-1 psql -U postgres -d incidents -c \
"SELECT id, service, status FROM incidents ORDER BY created_at DESC LIMIT 5;"
```
然后:
```
source venv/bin/activate
python3 -m iar_chat.cli
```
## 检查内容
**基于真实证据的真实对话**(这是实际截取的输出,事件 `3b73beec-...`,`checkout-api`):
```
IAR chat — incident 3b73beec-3be7-4bbf-9b25-2a1fda3e7c8f (checkout-api, status=new)
Type 'exit' to quit.
you> what's going on with this service?
iar-chat> (mock reply — set ANTHROPIC_API_KEY for real chat)
You asked: "what's going on with this service?"
Based on the retrieved evidence:
Incident: 3b73beec-3be7-4bbf-9b25-2a1fda3e7c8f
Service: checkout-api
Status: new
Severity: P1
Alert count: 5
Diagnostic evidence gathered during automated triage:
- [evidence_metrics] p99_latency_ms anomalous: 120.09 -> 242.53 (+102.0%, z=19.85); error_rate_pct anomalous: 0.19 -> 0.91 (+369.1%, z=14.37)
- [evidence_logs] [ERROR] 'upstream call to payment-api timed out after Nms' x1
- [evidence_deploy_history] payment-api deployed v2.8.3 at 2026-07-25T14:01:45Z (1m ago)
- [evidence_runbook] best match: 'High latency due to downstream dependency timeout' (score=2) — ...
I'm read-only and advisory — I can't execute anything here. To take action, tag the agent on the Jira ticket with an approved command.
```
**即使被明确要求执行操作,只读护栏依然有效:**
```
you> can you restart it for me?
iar-chat> (mock reply — set ANTHROPIC_API_KEY for real chat)
...
I'm read-only and advisory — I can't execute anything here. To take action, tag the agent on the Jira ticket with an approved command.
```
**对话在不同的进程调用之间持久存在** —— 退出 CLI(`Ctrl+C` 或 `exit`)并针对同一个 incident_id 启动一个全新的会话:
```
python3 -m iar_chat.cli 3b73beec-3be7-4bbf-9b25-2a1fda3e7c8f
```
在让你进入新的提示符之前,它会重播**之前两次**的提问和回复——这证实了历史记录存在于 Postgres(`incident_chat_messages`)中,而不是内存中,并且能在完全独立的进程运行之间保留,而不仅仅是在单个会话内有效。
**运行测试套件**(无需任何外部依赖 —— 纯内存 repo + 模拟 LLM):
```
pytest tests/test_iar_chat.py -v
```
```
tests/test_iar_chat.py::test_context_includes_service_and_evidence_findings PASSED
tests/test_iar_chat.py::test_context_raises_for_missing_incident PASSED
tests/test_iar_chat.py::test_ask_returns_reply_referencing_retrieved_evidence PASSED
tests/test_iar_chat.py::test_ask_persists_both_user_and_assistant_messages PASSED
tests/test_iar_chat.py::test_multi_turn_history_accumulates_across_calls PASSED
tests/test_iar_chat.py::test_ask_raises_for_missing_incident_without_persisting_orphan_message PASSED
tests/test_iar_chat.py::test_separate_incidents_have_independent_conversation_history PASSED
```
# 阶段 4 — 策略门控 + 命令执行器
## 架构
```
[Phase 2 workflow, extended]
│
▼
propose_commands_activity
reads the runbook evidence's suggested_commands (e.g. ["restart-pods","rollback-deploy"])
binds each to a FULLY-SPECIFIED tool call — no placeholders left downstream
│
▼
Postgres `proposed_commands` (TTL, unconsumed)
│
▼
Jira comment now includes:
**Available actions** (tag the agent with the label to run one):
- `restart-pods`
- `rollback-deploy`
│
▼
Human approves — simulated via CLI (no real Jira webhook in this project):
python3 -m command_executor.approve_command restart-pods
│
▼
policy/rules.py — evaluate_policy(tool_name)
reversible? blast_radius? → APPROVE / DENY
runs even AFTER human approval — a second safety check, not a rubber stamp
│
┌────┴─────┐
▼ ▼
DENIED APPROVED
│ │
│ ▼
│ command_executor/dispatcher.py
│ tool_name → function, a plain dict lookup — NEVER an LLM decision
│ │
│ ▼
│ MCP-style tool (modify_infra / deploy_service)
│ real HTTP call → target_app's /chaos endpoint
│ │
│ ▼
│ command_executor/verify.py
│ re-query target_app's live /metrics, re-run anomaly_detection.py,
│ check DIRECTION of change (not just magnitude)
│ │
└──────────┴──► outcome recorded in Postgres `command_executions`
resolved | regressed | denied_by_policy | insufficient_data
```
## 运行说明
需要阶段 1 + 2 已经运行,外加一个带有 runbook 匹配且生成了建议命令的已分流事件。
新的迁移:
```
docker exec -i incident-auto-remediation-postgres-1 psql -U postgres -d incidents \
< migrations/005_create_proposed_commands.sql
docker exec -i incident-auto-remediation-postgres-1 psql -U postgres -d incidents \
< migrations/006_create_command_executions.sql
```
⚠️ 应用这些迁移后重启 Temporal worker —— workflow 本身已更改,包含了 `propose_commands_activity`(调试日志 #13)。
触发一个新事件(与阶段 2 的演示相同),然后批准一条命令:
```
source venv/bin/activate
python3 -m command_executor.approve_command restart-pods
```
## 检查内容
**Jira 评论应包含一个操作菜单**(终端 3,worker 日志):
```
Suggested next step per runbook: restart-pods, rollback-deploy
_(mock LLM output — set ANTHROPIC_API_KEY for real synthesis)_
**Available actions** (tag the agent with the label to run one):
- `restart-pods`
- `rollback-deploy`
```
**批准和执行应显示完整的真实链条** —— 这是来自实时运行的实际截取输出,包括更正后的验证结果(关于此过程捕捉并修复的方向性 bug,请参阅 `docs/DEBUGGING_LOG.md` #15):
```
Policy check: APPROVED — 'modify_infra' is reversible with low blast radius — within auto-dispatch policy
Dispatching modify_infra({'action': 'restart', 'service': 'checkout-api', 'target_url': 'http://localhost:8080'}) ...
Action result: {'action': 'restart', 'service': 'checkout-api', 'result': {'chaos': {'latency': False, 'errors': False}}}
Waiting to verify resolution...
Verification: {'outcome': 'resolved', 'baseline_mean': 908.63, 'recent_mean': 260.83, 'pct_change': -71.3}
Outcome recorded: resolved
```
`-71.3%` 证实了这是真实的:`target_app` 的 `/chaos` endpoint 确实被调用了,延迟确实下降了,并且验证步骤确实重新查询了实时数据并正确地对改善情况进行了分类。
**确认结果已持久化:**
```
docker exec -it incident-auto-remediation-postgres-1 psql -U postgres -d incidents -c \
"SELECT tool_name, outcome, executed_at FROM command_executions ORDER BY executed_at DESC LIMIT 5;"
```
**证明真实诊断证据能提出正确命令的端到端证据** —— 真实的 `checkout-api` runbook 匹配通过绑定逻辑处理:
```
Runbook suggested_commands: ['restart-pods', 'rollback-deploy']
PASS: real runbook evidence -> exactly 2 correctly-bound proposed commands:
{'command_label': 'restart-pods', 'tool_name': 'modify_infra', 'params': {'action': 'restart', 'service': 'checkout-api', 'target_url': 'http://localhost:8080'}}
{'command_label': 'rollback-deploy', 'tool_name': 'deploy_service', 'params': {'action': 'rollback', 'service': 'checkout-api', 'target_url': 'http://localhost:8080'}}
```
**运行测试套件:**
```
pytest tests/test_policy.py tests/test_command_executor.py -v
```
```
tests/test_policy.py::test_modify_infra_approved PASSED
tests/test_policy.py::test_deploy_service_approved PASSED
tests/test_policy.py::test_update_database_denied_not_reversible PASSED
tests/test_policy.py::test_unknown_tool_denied PASSED
tests/test_policy.py::test_criticality_tier_accepted_but_not_currently_restrictive PASSED
tests/test_command_executor.py::test_bind_restart_pods_to_modify_infra PASSED
tests/test_command_executor.py::test_bind_rollback_deploy_to_deploy_service PASSED
tests/test_command_executor.py::test_bind_returns_none_when_no_live_target PASSED
tests/test_command_executor.py::test_bind_returns_none_for_escalate_only PASSED
tests/test_command_executor.py::test_bind_returns_none_for_unrecognized_label PASSED
tests/test_command_executor.py::test_repo_propose_then_get_active PASSED
tests/test_command_executor.py::test_repo_consumed_command_no_longer_active PASSED
tests/test_command_executor.py::test_repo_expired_command_no_longer_active PASSED
tests/test_command_executor.py::test_dispatcher_registry_has_all_three_tools PASSED
tests/test_command_executor.py::test_dispatch_raises_for_unknown_tool PASSED
tests/test_command_executor.py::test_classify_verification_healthy_series_is_resolved PASSED
tests/test_command_executor.py::test_classify_verification_spiking_series_is_regressed PASSED
tests/test_command_executor.py::test_classify_verification_recovering_series_is_resolved_not_regressed PASSED
tests/test_command_executor.py::test_classify_verification_short_series_is_insufficient_data PASSED
```
## 完整测试套件(全部 4 个阶段,39 个测试)
```
pytest tests/ -v
```
所有 39 个测试均通过:5(去重)+ 8(诊断)+ 7(IAR 聊天)+ 5(策略)+ 14(命令执行器)。
## 已知注意事项(详情请参阅 `docs/DEBUGGING_LOG.md`)
1. **Postgres 数据在 `docker-compose up` 重启之间持久存在。** 如果去重测试意外显示 `is_new: False`,这很可能是匹配到了之前会话中仍然开启的事件,而不是 bug。可以使用 `docker-compose down -v` 或者手动执行
`UPDATE incidents SET status = 'resolved' WHERE status = 'new';`。
2. **新的迁移不会自动应用到现有的 Postgres volume** —— Docker 只在全新的 volume 上运行初始化脚本。通过 `psql` 手动应用,或使用 `down -v` 重置。
3. **worker 进程不会热重载。** 任何代码更改后都要重启它,包括新的 activity(阶段 4 的 `propose_commands_activity` 直接遇到了这个问题 —— 见调试日志 #13)。
4. **`ingestion` 在 Docker 中运行,Temporal 在宿主机上运行** —— 接收容器需要 `host.docker.internal:7233`,而不是 `localhost:7233`,才能连接到 Temporal。这在 `docker-compose.yml` 中已经处理好了;只有在你要修改该配置时才需要注意。
5. **异常检测器需要一个干净的基准窗口。** 如果 chaos 开启时间足够长,使得整个 30 个样本的滚动窗口达到饱和,就没有留下可供检测的对比度。`docker-compose restart target-app` 可以瞬间提供一个干净的基准。
6. **始终在每个新终端中激活 venv** —— `source venv/bin/activate`。
7. **验证结果关乎方向,而不仅仅是变化的幅度。** 统计上的巨大改善和巨大恶化都会被记录为“异常”——分类逻辑必须检查指标移动的*方向*,而不仅仅是它是否发生了大幅度变化。这是一个真实的 bug(调试日志 #15),而不仅仅是一个需要配置规避的坑。
## 路线图(未构建)
- **跨事件语义搜索**:IAR 聊天(阶段 3)目前仅检索*当前*事件的证据。跨*过去*已解决事件检索“我们以前见过这种模式吗”将需要 embeddings + pgvector 相似性搜索 —— 这是一个自然的扩展,但尚未构建。
- **将命令执行接入 Temporal**:`approve_command.py`(阶段 4)目前是一个独立的 CLI,而不是 Temporal 的工作流/activity —— 真正的部署会希望这也具备持久性(重试、通过 workflow 历史记录进行审计)。此外:真实的 Jira webhook 触发器(目前通过 CLI 模拟,因为 Jira 本身是被模拟的),以及超越当前可逆性/影响范围检查的基于层级的策略限制(接受了 `service_criticality_tier` 参数,但目前尚无限制作用 —— 见架构文档)。
- **置信度评分 + 自动提升**:`command_executions` 现在跟踪真实的结果(已解决/已退化/已拒绝),这是实现该功能所需的原始素材 —— 但基于置信度评分的自动提升被刻意推迟,直到有足够真实的执行历史记录使评分变得有意义。见架构文档,§7。
## 设计原理(简短版)
去重/upsert 步骤是本系统最重要的组成部分。
没有它,在稳态下每天 5,000 条告警在任何东西真正崩溃的那一刻就会变得无法管理——一次糟糕的部署可以在不到一分钟内触发 50-200 条相关告警,而简单的“1 条告警 = 1 个单”的接收逻辑,在值班工程师最需要信号而不是噪音的时候,恰恰会淹没他们的看板。`incidents(fingerprint) WHERE status NOT IN ('resolved','escalated')` 上的部分唯一索引在数据库层面强制执行了这一点,而不仅仅是在应用代码中,因此它在并发 webhook 投放下是竞态安全的。
写入路径(阶段 4)绝不让 LLM 决定*执行什么* —— 每个建议的命令在提议时都已完全绑定,分发是一次表查找,并且基于规则的策略门控即使在人类批准了某些操作之后依然会运行。完整的原理说明,包括 LLM 调用预算的推导以及为何刻意推迟置信度评分,请参阅 [`docs/architecture.md`](docs/architecture.md)。
标签:AI智能体, RAG, Temporal, 告警分发, 模块化设计, 自动化修复, 请求拦截, 运维与DevOps, 逆向工具