From ef79edd872c49af26da9597a8e94bf106dd87fd9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 2 Jul 2026 13:52:22 +0800 Subject: [PATCH] fix(awooop): expose telegram automation coverage gaps --- ...agent_report_truth_actionability_review.py | 89 ++++++++++++++++++- ...agent_report_truth_actionability_review.py | 40 +++++++++ ...t_report_truth_actionability_review_api.py | 21 +++++ apps/web/messages/en.json | 7 +- apps/web/messages/zh-TW.json | 7 +- .../tabs/automation-inventory-tab.tsx | 21 ++++- apps/web/src/lib/api-client.ts | 29 ++++++ docs/LOGBOOK.md | 18 ++++ 8 files changed, 227 insertions(+), 5 deletions(-) diff --git a/apps/api/src/services/ai_agent_report_truth_actionability_review.py b/apps/api/src/services/ai_agent_report_truth_actionability_review.py index 8187c574b..9aab9fdf3 100644 --- a/apps/api/src/services/ai_agent_report_truth_actionability_review.py +++ b/apps/api/src/services/ai_agent_report_truth_actionability_review.py @@ -79,6 +79,13 @@ def _with_live_telegram_egress_guard(payload: dict[str, Any]) -> dict[str, Any]: summary = guard.get("summary") if isinstance(guard.get("summary"), dict) else {} live_files = sorted({str(item.get("path")) for item in live_calls if item.get("path")}) new_bypass_count = int(summary.get("new_bypass_count") or 0) + surface_counts = _telegram_direct_surface_counts(live_calls) + receipt_missing_count = sum( + 1 for item in live_calls if item.get("receipt_state") != "awooop_outbound_receipt_recorded" + ) + ai_route_missing_count = sum( + 1 for item in live_calls if item.get("ai_controlled_route_state") != "ai_controlled_gateway_route_ready" + ) updated["telegram_egress_guard"] = { "schema_version": "telegram_notification_egress_no_new_bypass_guard_v1", @@ -94,6 +101,45 @@ def _with_live_telegram_egress_guard(payload: dict[str, Any]) -> dict[str, Any]: "live_scan_matches_committed_guard_count": ( len(live_calls) == int(summary.get("current_direct_bot_api_call_count") or -1) ), + "gitea_workflow_direct_bot_api_call_count": surface_counts.get( + "gitea_workflow_direct_bot_api", 0 + ), + "ops_script_direct_bot_api_call_count": surface_counts.get( + "ops_script_direct_bot_api", 0 + ), + "ci_script_direct_bot_api_call_count": surface_counts.get("ci_script_direct_bot_api", 0), + "api_direct_bot_api_call_count": surface_counts.get("api_direct_bot_api", 0), + "direct_bot_api_awooop_db_receipt_count": len(live_calls) - receipt_missing_count, + "direct_bot_api_awooop_db_receipt_missing_count": receipt_missing_count, + "direct_bot_api_ai_controlled_route_count": len(live_calls) - ai_route_missing_count, + "direct_bot_api_ai_controlled_route_missing_count": ai_route_missing_count, + }, + "telegram_receipt_coverage": { + "coverage_status": "partial_gateway_receipts_direct_routes_missing" + if live_calls + else "complete_no_direct_routes_detected", + "gateway_alert_notification_db_receipt_enabled": True, + "gateway_alert_notification_receipt_source": "TelegramGateway.send_alert_notification", + "direct_bot_api_total_count": len(live_calls), + "direct_bot_api_awooop_db_receipt_count": len(live_calls) - receipt_missing_count, + "direct_bot_api_awooop_db_receipt_missing_count": receipt_missing_count, + "direct_bot_api_ai_controlled_route_count": len(live_calls) - ai_route_missing_count, + "direct_bot_api_ai_controlled_route_missing_count": ai_route_missing_count, + "all_direct_routes_have_awooop_db_receipt": receipt_missing_count == 0, + "all_direct_routes_have_ai_controlled_route": ai_route_missing_count == 0, + "all_telegram_alerts_have_db_or_log_receipts": receipt_missing_count == 0, + "all_telegram_alerts_ai_controlled": ai_route_missing_count == 0, + "evidence_persistence_states": sorted( + { + str(item.get("evidence_persistence_state")) + for item in live_calls + if item.get("evidence_persistence_state") + } + ), + "ai_agent_next_action": ( + "create_controlled_migration_work_items_for_direct_bot_api_routes_with_db_receipt_" + "verifier_and_km_playbook_writeback" + ), }, "current_direct_bot_api_calls": live_calls, "ai_agent_interpretation": { @@ -101,6 +147,8 @@ def _with_live_telegram_egress_guard(payload: dict[str, Any]) -> dict[str, Any]: "existing_direct_routes_need_migration": len(live_calls) > 0, "db_receipt_required": True, "ai_controlled_route_required": True, + "all_direct_routes_have_awooop_db_receipt": receipt_missing_count == 0, + "all_direct_routes_have_ai_controlled_route": ai_route_missing_count == 0, "manual_default_outcome_allowed": False, "next_action": "migrate_direct_bot_api_calls_to_awoooi_api_or_telegram_gateway_receipts", }, @@ -146,6 +194,38 @@ def _sanitize_telegram_egress_excerpt(line: str) -> str: return excerpt[:180] +def _telegram_direct_surface_kind(relative_path: str) -> str: + if relative_path.startswith(".gitea/workflows/"): + return "gitea_workflow_direct_bot_api" + if relative_path.startswith("scripts/ops/"): + return "ops_script_direct_bot_api" + if relative_path.startswith("scripts/ci/"): + return "ci_script_direct_bot_api" + if relative_path.startswith("apps/api/src/"): + return "api_direct_bot_api" + return "unknown_direct_bot_api" + + +def _telegram_direct_persistence_state(surface_kind: str) -> str: + if surface_kind == "gitea_workflow_direct_bot_api": + return "gitea_actions_log_only_no_awooop_db_receipt" + if surface_kind == "ops_script_direct_bot_api": + return "host_script_stdout_only_no_awooop_db_receipt" + if surface_kind == "ci_script_direct_bot_api": + return "ci_script_log_only_no_awooop_db_receipt" + if surface_kind == "api_direct_bot_api": + return "api_direct_send_no_awooop_outbound_receipt_contract" + return "direct_send_no_awooop_outbound_receipt_contract" + + +def _telegram_direct_surface_counts(calls: list[dict[str, Any]]) -> dict[str, int]: + counts: dict[str, int] = {} + for item in calls: + surface_kind = str(item.get("surface_kind") or "unknown_direct_bot_api") + counts[surface_kind] = counts.get(surface_kind, 0) + 1 + return counts + + def _scan_current_direct_telegram_endpoints(root: Path) -> list[dict[str, Any]]: findings: list[dict[str, Any]] = [] for scan_root in _telegram_egress_scan_roots(root): @@ -158,13 +238,19 @@ def _scan_current_direct_telegram_endpoints(root: Path) -> list[dict[str, Any]]: text = path.read_text(encoding="utf-8", errors="replace") for line_number, line in enumerate(text.splitlines(), start=1): for match in _BOT_ENDPOINT_RE.finditer(line): + surface_kind = _telegram_direct_surface_kind(relative_path) findings.append( { "path": relative_path, "line": line_number, "method": match.group("method"), + "surface_kind": surface_kind, "sanitized_excerpt": _sanitize_telegram_egress_excerpt(line), "receipt_state": "missing_awooop_outbound_receipt_contract", + "evidence_persistence_state": _telegram_direct_persistence_state( + surface_kind + ), + "ai_controlled_route_state": "missing_ai_controlled_gateway_route", } ) return findings @@ -174,6 +260,7 @@ def _telegram_direct_call_route_finding(item: dict[str, Any]) -> dict[str, str]: path = str(item.get("path") or "unknown") line = int(item.get("line") or 0) method = str(item.get("method") or "sendMessage") + surface_kind = str(item.get("surface_kind") or "unknown_direct_bot_api") route_key = re.sub(r"[^a-z0-9]+", "_", f"{path}_{line}_{method}".lower()).strip("_") return { "route_id": f"telegram_direct_bot_api_{route_key}", @@ -182,7 +269,7 @@ def _telegram_direct_call_route_finding(item: dict[str, Any]) -> dict[str, str]: "current_state": "direct_send_path_present", "target_state": "gateway_db_receipt_required", "risk": ( - "這條 Telegram 直送路徑不保證寫入 awooop_outbound_message;" + f"{surface_kind} 不保證寫入 awooop_outbound_message 或 AI controlled route;" f"sanitized excerpt: {item.get('sanitized_excerpt', '')}" ), "required_fix": ( diff --git a/apps/api/tests/test_ai_agent_report_truth_actionability_review.py b/apps/api/tests/test_ai_agent_report_truth_actionability_review.py index 35e8def90..e7fc0991f 100644 --- a/apps/api/tests/test_ai_agent_report_truth_actionability_review.py +++ b/apps/api/tests/test_ai_agent_report_truth_actionability_review.py @@ -39,6 +39,39 @@ def test_load_latest_ai_agent_report_truth_actionability_review(): assert data["telegram_egress_guard"]["status"] == "pass_no_new_bypass" assert data["telegram_egress_guard"]["summary"]["live_direct_bot_api_call_count"] == 18 assert data["telegram_egress_guard"]["summary"]["new_bypass_count"] == 0 + assert data["telegram_egress_guard"]["summary"]["gitea_workflow_direct_bot_api_call_count"] == 13 + assert data["telegram_egress_guard"]["summary"]["ops_script_direct_bot_api_call_count"] == 4 + assert data["telegram_egress_guard"]["summary"]["api_direct_bot_api_call_count"] == 1 + assert ( + data["telegram_egress_guard"]["summary"]["direct_bot_api_awooop_db_receipt_missing_count"] + == 18 + ) + assert ( + data["telegram_egress_guard"]["summary"]["direct_bot_api_ai_controlled_route_missing_count"] + == 18 + ) + assert ( + data["telegram_egress_guard"]["telegram_receipt_coverage"]["coverage_status"] + == "partial_gateway_receipts_direct_routes_missing" + ) + assert ( + data["telegram_egress_guard"]["telegram_receipt_coverage"][ + "gateway_alert_notification_db_receipt_enabled" + ] + is True + ) + assert ( + data["telegram_egress_guard"]["telegram_receipt_coverage"][ + "all_direct_routes_have_awooop_db_receipt" + ] + is False + ) + assert ( + data["telegram_egress_guard"]["telegram_receipt_coverage"][ + "all_direct_routes_have_ai_controlled_route" + ] + is False + ) assert data["rollups"]["telegram_route_finding_count"] == 22 assert data["rollups"]["legacy_or_direct_route_count"] == 22 assert sum( @@ -46,6 +79,13 @@ def test_load_latest_ai_agent_report_truth_actionability_review(): for route in data["telegram_route_findings"] if route["route_id"].startswith("telegram_direct_bot_api_") ) == 18 + assert { + item["surface_kind"] for item in data["telegram_egress_guard"]["current_direct_bot_api_calls"] + } == { + "gitea_workflow_direct_bot_api", + "ops_script_direct_bot_api", + "api_direct_bot_api", + } assert data["rollups"]["all_zero_weekly_report_confidence"] == "low_trust_actionable_anomaly" diff --git a/apps/api/tests/test_ai_agent_report_truth_actionability_review_api.py b/apps/api/tests/test_ai_agent_report_truth_actionability_review_api.py index c7230a5b1..ba591969d 100644 --- a/apps/api/tests/test_ai_agent_report_truth_actionability_review_api.py +++ b/apps/api/tests/test_ai_agent_report_truth_actionability_review_api.py @@ -36,6 +36,27 @@ def test_get_ai_agent_report_truth_actionability_review_api(): assert data["telegram_routing_consolidation"]["direct_telegram_api_send_allowed"] is False assert data["telegram_egress_guard"]["summary"]["live_direct_bot_api_call_count"] == 18 assert data["telegram_egress_guard"]["summary"]["new_bypass_count"] == 0 + assert data["telegram_egress_guard"]["summary"]["gitea_workflow_direct_bot_api_call_count"] == 13 + assert data["telegram_egress_guard"]["summary"]["ops_script_direct_bot_api_call_count"] == 4 + assert data["telegram_egress_guard"]["summary"]["api_direct_bot_api_call_count"] == 1 + assert ( + data["telegram_egress_guard"]["summary"]["direct_bot_api_awooop_db_receipt_missing_count"] + == 18 + ) + assert ( + data["telegram_egress_guard"]["summary"]["direct_bot_api_ai_controlled_route_missing_count"] + == 18 + ) + assert ( + data["telegram_egress_guard"]["telegram_receipt_coverage"][ + "all_telegram_alerts_have_db_or_log_receipts" + ] + is False + ) + assert ( + data["telegram_egress_guard"]["telegram_receipt_coverage"]["all_telegram_alerts_ai_controlled"] + is False + ) assert data["rollups"]["telegram_route_finding_count"] == 22 assert data["rollups"]["legacy_or_direct_route_count"] == 22 assert data["rollups"]["operator_action_count"] == 5 diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 1af8a3e48..275d67420 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -6017,7 +6017,12 @@ "ttl": "TTL: {value}", "requiredAssets": "必填資產 {count} 項", "guardCalls": "Direct scan {count}", - "newBypass": "New bypass {count}" + "newBypass": "New bypass {count}", + "dbReceiptMissing": "DB receipt gap {count}", + "aiRouteMissing": "AI route gap {count}", + "workflowDirect": "workflow direct {count}", + "opsDirect": "ops direct {count}", + "apiDirect": "API direct {count}" } }, "reportAutomationReview": { diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 552621a37..3c1de8b08 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -6017,7 +6017,12 @@ "ttl": "TTL: {value}", "requiredAssets": "必填資產 {count} 項", "guardCalls": "直送掃描 {count} 條", - "newBypass": "新增旁路 {count}" + "newBypass": "新增旁路 {count}", + "dbReceiptMissing": "DB receipt 缺口 {count}", + "aiRouteMissing": "AI route 缺口 {count}", + "workflowDirect": "workflow 直送 {count}", + "opsDirect": "ops 直送 {count}", + "apiDirect": "API 直送 {count}" } }, "reportAutomationReview": { diff --git a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx index 62b135aab..36fbebbf4 100644 --- a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx +++ b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx @@ -5496,8 +5496,20 @@ export function AutomationInventoryTab() { const reportTruthZeroWorkItems = reportTruthActionabilityReview.rollups.zero_signal_work_item_count const reportTruthWeeklyScore = reportTruthActionabilityReview.rollups.all_zero_weekly_report_actionability_score const reportTruthLegacyRoutes = reportTruthActionabilityReview.rollups.legacy_or_direct_route_count - const reportTruthTelegramGuardCalls = reportTruthActionabilityReview.telegram_egress_guard?.summary.live_direct_bot_api_call_count ?? 0 - const reportTruthTelegramNewBypass = reportTruthActionabilityReview.telegram_egress_guard?.summary.new_bypass_count ?? 0 + const reportTruthTelegramGuardSummary = reportTruthActionabilityReview.telegram_egress_guard?.summary + const reportTruthTelegramGuardCalls = reportTruthTelegramGuardSummary?.live_direct_bot_api_call_count ?? 0 + const reportTruthTelegramNewBypass = reportTruthTelegramGuardSummary?.new_bypass_count ?? 0 + const reportTruthTelegramDbReceiptMissing = ( + reportTruthTelegramGuardSummary?.direct_bot_api_awooop_db_receipt_missing_count ?? 0 + ) + const reportTruthTelegramAiRouteMissing = ( + reportTruthTelegramGuardSummary?.direct_bot_api_ai_controlled_route_missing_count ?? 0 + ) + const reportTruthTelegramWorkflowDirect = ( + reportTruthTelegramGuardSummary?.gitea_workflow_direct_bot_api_call_count ?? 0 + ) + const reportTruthTelegramOpsDirect = reportTruthTelegramGuardSummary?.ops_script_direct_bot_api_call_count ?? 0 + const reportTruthTelegramApiDirect = reportTruthTelegramGuardSummary?.api_direct_bot_api_call_count ?? 0 const reportTruthApprovals = reportTruthActionabilityReview.rollups.approval_required_action_ids.length const reportTruthBlockedActions = reportTruthActionabilityReview.rollups.blocked_runtime_action_count const ownerDryRunOverall = ownerDryRunPackage.program_status.overall_completion_percent @@ -18027,6 +18039,11 @@ export function AutomationInventoryTab() { + + + + + diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index f7351766d..e91c5e8db 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -4986,21 +4986,50 @@ export interface AiAgentReportTruthActionabilityReviewSnapshot { live_direct_bot_api_call_count: number live_direct_bot_api_file_count: number live_scan_matches_committed_guard_count: boolean + gitea_workflow_direct_bot_api_call_count: number + ops_script_direct_bot_api_call_count: number + ci_script_direct_bot_api_call_count: number + api_direct_bot_api_call_count: number + direct_bot_api_awooop_db_receipt_count: number + direct_bot_api_awooop_db_receipt_missing_count: number + direct_bot_api_ai_controlled_route_count: number + direct_bot_api_ai_controlled_route_missing_count: number new_bypass_count: number sendMessage_call_count: number } + telegram_receipt_coverage: { + coverage_status: string + gateway_alert_notification_db_receipt_enabled: boolean + gateway_alert_notification_receipt_source: string + direct_bot_api_total_count: number + direct_bot_api_awooop_db_receipt_count: number + direct_bot_api_awooop_db_receipt_missing_count: number + direct_bot_api_ai_controlled_route_count: number + direct_bot_api_ai_controlled_route_missing_count: number + all_direct_routes_have_awooop_db_receipt: boolean + all_direct_routes_have_ai_controlled_route: boolean + all_telegram_alerts_have_db_or_log_receipts: boolean + all_telegram_alerts_ai_controlled: boolean + evidence_persistence_states: string[] + ai_agent_next_action: string + } current_direct_bot_api_calls: Array<{ path: string line: number method: string + surface_kind: string sanitized_excerpt: string receipt_state: string + evidence_persistence_state: string + ai_controlled_route_state: string }> ai_agent_interpretation: { no_new_bypass: boolean existing_direct_routes_need_migration: boolean db_receipt_required: boolean ai_controlled_route_required: boolean + all_direct_routes_have_awooop_db_receipt: boolean + all_direct_routes_have_ai_controlled_route: boolean manual_default_outcome_allowed: false next_action: string } diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index e72485279..3b852af29 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,21 @@ +## 2026-07-02 — 13:55 Telegram 告警 receipt / AI route coverage 缺口讀回 + +**完成內容**: +- `agent-report-truth-actionability-review` 的 `telegram_egress_guard` 現在不只顯示 18 條 direct Bot API 掃描與 0 新旁路,還會逐條標示 `surface_kind`、`evidence_persistence_state`、`receipt_state` 與 `ai_controlled_route_state`。 +- 新增 `telegram_receipt_coverage`:明確讀回 gateway alert notification DB receipt 已啟用,但 direct Bot API 路徑仍是 `direct_bot_api_awooop_db_receipt_missing_count=18`、`direct_bot_api_ai_controlled_route_missing_count=18`,不得被誤解為所有 Telegram 告警都已完整入庫或 AI controlled。 +- Governance / Automation Inventory UI 新增 `DB receipt 缺口 18`、`AI route 缺口 18`、`workflow 直送 13`、`ops 直送 4`、`API 直送 1`,讓「是否全面清查 / 是否完整寫入 DB / 是否符合 AI 自動化」可直接在產品頁讀回。 + +**驗證**: +- `DATABASE_URL=... PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_ai_agent_report_truth_actionability_review.py apps/api/tests/test_ai_agent_report_truth_actionability_review_api.py apps/api/tests/test_awoooi_priority_work_order_readback_api.py ops/runner/test_cd_controlled_runtime_profile.py -q -p no:cacheprovider`:`68 passed`。 +- 本地 API loader readback:`route_count=22`、`direct_total=18`、`new_bypass=0`、workflow / ops / API = `13 / 4 / 1`、DB receipt missing `18`、AI route missing `18`。 +- JSON parse / i18n mirror:`zh=14895 en=14895 onlyZh=0 onlyEn=0`。 +- `python3 scripts/security/telegram-notification-egress-no-new-bypass-guard.py --root .`:`current=18 baseline=18 new=0 sendDocument=0 runtime_gate=0`。 +- `pnpm --dir apps/web typecheck`:通過。 +- `git diff --check`:通過。 + +**仍維持**: +- 未使用 GitHub / `gh` / GitHub API;未讀 secret / token / `.env` / raw sessions / SQLite / auth;未送 Telegram;未對 production DB 寫入。 + ## 2026-07-02 — 13:35 Telegram 告警 egress 全量 live readback **完成內容**: