From 025ef46625113aa7c5dc605a8f8f20c0080c14fb Mon Sep 17 00:00:00 2001 From: ogt Date: Fri, 10 Jul 2026 16:26:23 +0800 Subject: [PATCH] docs(agent99): advance mainline to alert routing --- ...t99_enterprise_ai_automation_work_items.py | 17 ++++-- ...enterprise_ai_automation_work_items_api.py | 14 ++--- ...ise-ai-automation-work-items.snapshot.json | 58 +++++++++++-------- ...99-enterprise-ai-automation-master-plan.md | 6 +- 4 files changed, 55 insertions(+), 40 deletions(-) diff --git a/apps/api/src/services/agent99_enterprise_ai_automation_work_items.py b/apps/api/src/services/agent99_enterprise_ai_automation_work_items.py index fb2a7142f..3c549279a 100644 --- a/apps/api/src/services/agent99_enterprise_ai_automation_work_items.py +++ b/apps/api/src/services/agent99_enterprise_ai_automation_work_items.py @@ -53,6 +53,7 @@ def load_latest_agent99_enterprise_ai_automation_work_items( priority_counts = {priority: 0 for priority in _VALID_PRIORITIES} status_counts = {status: 0 for status in _VALID_STATUSES} p0_ids: list[str] = [] + active_p0_ids: list[str] = [] for index, item in enumerate(work_items, start=1): if not isinstance(item, dict): @@ -78,6 +79,8 @@ def load_latest_agent99_enterprise_ai_automation_work_items( status_counts[item_status] += 1 if priority == "P0": p0_ids.append(item_id) + if item_status != "complete": + active_p0_ids.append(item_id) missing = [field for field in required_p0_fields if item.get(field) is None] if missing: raise ValueError(f"{path}: {item_id} missing P0 fields: {missing}") @@ -88,12 +91,14 @@ def load_latest_agent99_enterprise_ai_automation_work_items( raise ValueError(f"{path}: work item order is not contiguous") current_p0_id = current_p0.get("id") - if current_p0_id not in p0_ids: - raise ValueError(f"{path}: current_p0.id is not a P0 work item") - if not p0_ids or p0_ids[0] != current_p0_id: - raise ValueError(f"{path}: current_p0 must be the first ordered P0") - if payload.get("next_execution_order") != p0_ids: - raise ValueError(f"{path}: next_execution_order must equal ordered P0 ids") + if current_p0_id not in active_p0_ids: + raise ValueError(f"{path}: current_p0.id is not an active P0 work item") + if not active_p0_ids or active_p0_ids[0] != current_p0_id: + raise ValueError(f"{path}: current_p0 must be the first ordered active P0") + if payload.get("next_execution_order") != active_p0_ids: + raise ValueError( + f"{path}: next_execution_order must equal ordered active P0 ids" + ) _require_count(summary, "p0_count", priority_counts["P0"], path) _require_count(summary, "p1_count", priority_counts["P1"], path) diff --git a/apps/api/tests/test_agent99_enterprise_ai_automation_work_items_api.py b/apps/api/tests/test_agent99_enterprise_ai_automation_work_items_api.py index d096f0df7..21c703b1b 100644 --- a/apps/api/tests/test_agent99_enterprise_ai_automation_work_items_api.py +++ b/apps/api/tests/test_agent99_enterprise_ai_automation_work_items_api.py @@ -31,7 +31,7 @@ def test_agent99_enterprise_work_items_loader_returns_complete_scope() -> None: "agent99_enterprise_ai_automation_work_items_v1" ) assert payload["scope_complete"] is False - assert payload["current_p0"]["id"] == "AG99-P0-001" + assert payload["current_p0"]["id"] == "AG99-P0-002" assert payload["summary"] == { "host_count": 7, "product_count": 12, @@ -42,11 +42,11 @@ def test_agent99_enterprise_work_items_loader_returns_complete_scope() -> None: "p2_count": 3, "in_progress_count": 5, "in_progress_blocked_count": 3, - "planned_count": 17, - "complete_count": 0, + "planned_count": 16, + "complete_count": 1, "current_p0_count": 1, } - assert payload["next_execution_order"][0] == "AG99-P0-001" + assert payload["next_execution_order"][0] == "AG99-P0-002" assert payload["next_execution_order"][-1] == "AG99-P0-014" @@ -54,7 +54,7 @@ def test_agent99_enterprise_projection_is_bounded() -> None: payload = load_latest_agent99_enterprise_ai_automation_work_items() projection = build_agent99_enterprise_priority_projection(payload) - assert projection["current_p0"]["id"] == "AG99-P0-001" + assert projection["current_p0"]["id"] == "AG99-P0-002" assert projection["coverage"] == { "host_count": 7, "product_count": 12, @@ -73,7 +73,7 @@ def test_priority_readback_projects_agent99_enterprise_order() -> None: payload = load_latest_awoooi_priority_work_order_readback() projection = payload["agent99_enterprise_ai_automation"] - assert projection["current_p0"]["id"] == "AG99-P0-001" + assert projection["current_p0"]["id"] == "AG99-P0-002" assert projection["work_item_counts"]["p0"] == 14 assert ( payload["source_refs"]["agent99_enterprise_ai_automation_work_items_api"] @@ -103,7 +103,7 @@ def test_agent99_enterprise_work_items_endpoint_returns_snapshot() -> None: assert response.status_code == 200 payload = response.json() - assert payload["current_p0"]["id"] == "AG99-P0-001" + assert payload["current_p0"]["id"] == "AG99-P0-002" assert payload["summary"]["host_count"] == 7 assert payload["summary"]["product_count"] == 12 assert payload["summary"]["public_surface_count"] == 23 diff --git a/docs/operations/agent99-enterprise-ai-automation-work-items.snapshot.json b/docs/operations/agent99-enterprise-ai-automation-work-items.snapshot.json index 85406b7a0..61e7e3822 100644 --- a/docs/operations/agent99-enterprise-ai-automation-work-items.snapshot.json +++ b/docs/operations/agent99-enterprise-ai-automation-work-items.snapshot.json @@ -1,7 +1,7 @@ { "schema_version": "agent99_enterprise_ai_automation_work_items_v1", - "generated_at": "2026-07-10T16:00:26+08:00", - "status": "in_progress_p0_agent_outcome_staging_verified_runtime_apply_pending", + "generated_at": "2026-07-10T16:22:00+08:00", + "status": "in_progress_p0_alert_routing_grouping_dedupe", "scope_complete": false, "north_star": "Make Windows host 192.168.0.99 the policy-controlled on-prem AI execution node for all hosts, VMs, services, products, sites, tools, packages, alerts and recovery workflows, with external monitoring when host 99 is unavailable.", "canonical_sources": { @@ -17,22 +17,11 @@ "runtime_reboot_slo_readback": "https://awoooi.wooo.work/api/v1/agents/reboot-auto-recovery-slo-scorecard" }, "current_p0": { - "id": "AG99-P0-001", - "title": "Agent outcome truth and source-runtime reconciliation", - "reason": "Agent99 currently treats command exit success as incident resolution and the Windows runtime scripts do not consistently match the current Gitea source. All later automation depends on trustworthy outcomes.", - "implementation_status": "source_implemented_staging_verified_runtime_apply_pending", - "staging_verifier": { - "host": "99", - "outcome_contract": "agent99_outcome_contract_v1", - "outcome_self_test_ok": true, - "runtime_manifest": "agent99_runtime_manifest_v1", - "source_revision": "p0-001-staging-latest", - "runtime_manifest_matched": true, - "runtime_manifest_file_count": 7, - "runtime_manifest_mismatch_count": 0, - "evidence": "C:\\Wooo\\Agent99\\staging\\p0-001-runtime\\evidence\\agent99-OutcomeContractSelfTest-20260710-160255.json" - }, - "next_action": "Commit and deploy the verified outcome contract to host 99, rerun the runtime manifest and outcome self-test, then replay one operator smoke and one alert-source event without counting transport-only success as resolved." + "id": "AG99-P0-002", + "title": "Alert taxonomy, routing, grouping and dedupe", + "reason": "Explicit event kind and suggested mode are not consistently authoritative, and forwarding before grouping can create wrong-domain or duplicate Agent99 work.", + "implementation_status": "in_progress_replay_corpus_and_pre_bridge_grouping", + "next_action": "Add routing precedence and a replay corpus for backup, provider, reboot, PostgreSQL, host and K3s events; move grouping and single-flight ahead of Agent99 dispatch." }, "current_runtime_truth": { "observed_at": "2026-07-10T15:04:27+08:00", @@ -267,7 +256,7 @@ "id": "AG99-P0-001", "order": 1, "priority": "P0", - "status": "in_progress", + "status": "complete", "title": "Agent outcome truth and source-runtime reconciliation", "owner_lane": "agent99_control_plane", "dependencies": [], @@ -283,13 +272,35 @@ "verifier": "For every mode, require post-condition pass plus source event resolution; compare source and runtime manifest hashes.", "callback": "Write final outcome to AWOOOI incident and AwoooP Run/Verifier using the same trace id.", "acceptance": "No false-resolved regression cases; source/runtime drift count is zero; unresolved evidence stays degraded or blocked.", - "next_action": "Deploy the staging-verified outcome contract to host 99, confirm source/runtime manifest hashes, then replay operator and alert-source outcomes." + "completion_receipt": { + "source_revision": "5ea4405c2a75b51244da168a4834d27e24d74ee6", + "runtime_manifest_matched": true, + "runtime_manifest_file_count": 7, + "runtime_manifest_mismatch_count": 0, + "self_health_runtime_manifest_severity": "ok", + "outcome_self_test_ok": true, + "operator_replay_state": "resolved", + "operator_replay_verified_resolved": 1, + "alert_replay_state": "verifying", + "alert_replay_verified_resolved": 0, + "alert_replay_source_event_resolved": false, + "telegram_sent_count": 0, + "rollback": "C:\\Wooo\\Agent99\\rollback\\p0-001-pre-5ea4405c", + "evidence": [ + "C:\\Wooo\\Agent99\\evidence\\agent99-OutcomeContractSelfTest-20260710-161302.json", + "C:\\Wooo\\Agent99\\evidence\\agent99-SelfCheck-20260710-161350.json", + "C:\\Wooo\\Agent99\\queue\\processed\\processed-p0-001-operator-verifier-20260710-1615.json", + "C:\\Wooo\\Agent99\\queue\\processed\\processed-p0-001-alert-verifier-20260710-1615.json", + "C:\\Wooo\\Agent99\\evidence\\agent99-ControlTick-20260710-161919.json" + ] + }, + "next_action": "Keep runtime-manifest drift and verified-resolution counters under SelfCheck; continue with AG99-P0-002." }, { "id": "AG99-P0-002", "order": 2, "priority": "P0", - "status": "planned", + "status": "in_progress", "title": "Alert taxonomy, routing, grouping and dedupe", "owner_lane": "awoooi_alert_router_agent99_ingress", "dependencies": ["AG99-P0-001"], @@ -736,12 +747,11 @@ "p2_count": 3, "in_progress_count": 5, "in_progress_blocked_count": 3, - "planned_count": 17, - "complete_count": 0, + "planned_count": 16, + "complete_count": 1, "current_p0_count": 1 }, "next_execution_order": [ - "AG99-P0-001", "AG99-P0-002", "AG99-P0-003", "AG99-P0-004", diff --git a/docs/workplans/2026-07-10-agent99-enterprise-ai-automation-master-plan.md b/docs/workplans/2026-07-10-agent99-enterprise-ai-automation-master-plan.md index e3096878c..f17e203f4 100644 --- a/docs/workplans/2026-07-10-agent99-enterprise-ai-automation-master-plan.md +++ b/docs/workplans/2026-07-10-agent99-enterprise-ai-automation-master-plan.md @@ -91,8 +91,8 @@ | 順序 | ID | 工作 | 狀態 | 驗收 | | --- | --- | --- | --- | --- | -| 1 | AG99-P0-001 | 修正 `exitCode=0 => resolved` 假綠,對齊 99 runtime script 與 Gitea source SHA | **進行中,current P0** | 每種模式以 verifier 決定 outcome;舊 runtime/script drift 歸零;不得再把缺證據算 resolved | -| 2 | AG99-P0-002 | 修正 alert taxonomy、wrong-domain routing、bridge-before-grouping 與重複事件 | 待辦 | `suggestedMode`/kind 優先於 source 字串;5 分鐘先 group;同 fingerprint 只建立一個 active incident | +| 1 | AG99-P0-001 | 修正 `exitCode=0 => resolved` 假綠,對齊 99 runtime script 與 Gitea source SHA | **完成** | Production 99 outcome self-test 通過;runtime manifest `7/7` matched、drift `0`;operator replay 為 verified resolved,alert replay 缺 source-event resolved 時維持 verifying | +| 2 | AG99-P0-002 | 修正 alert taxonomy、wrong-domain routing、bridge-before-grouping 與重複事件 | **進行中,current P0** | `suggestedMode`/kind 優先於 source 字串;5 分鐘先 group;同 fingerprint 只建立一個 active incident | | 3 | AG99-P0-003 | 強化 ingress:relay token 必填、Telegram chat+sender allowlist、idempotency/single consumer | 待辦 | 未認證 LAN request 拒絕;producer `auto_repair`/policy 被保留;所有 request 可追 trace | | 4 | AG99-P0-004 | 新增 EvidenceCollect / RepairCandidate / MCP lane | 待辦 | 缺 evidence 的事件會建立可查 candidate,不再錯送 Perf/AwoooRepair;`INC-*` 可從收件追到 candidate | @@ -157,4 +157,4 @@ ## 8. 立即下一步 -目前唯一 P0 是 `AG99-P0-001`:先修正 Agent99 假 resolved 與 99 runtime/source drift,建立 outcome contract 與 regression tests。完成後依序做 routing/dedupe、secure ingress、RepairCandidate,再回到 reboot state machine 的 live rerun;如此不是離開重啟主線,而是先修正會把重啟與告警錯誤結案的控制核心。 +目前唯一 P0 是 `AG99-P0-002`:以 explicit event kind / suggested mode 建立 routing precedence,將 5 分鐘 grouping、fingerprint dedupe 與 single-flight 移到 Agent99 dispatch 之前,並用 backup、provider、reboot、PostgreSQL、host、K3s replay corpus 證明不再 wrong-domain 或重複派工。完成後依序做 secure ingress、RepairCandidate,再回到 reboot state machine live rerun。