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
**完成內容**: