From 0b61b6fe4dbec37102759de485eb94d311b13db5 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 2 Jul 2026 18:56:36 +0800 Subject: [PATCH] fix(telegram): route channel hub interim via gateway --- .gitea/workflows/cd.yaml | 16 ++ .../awoooi_priority_work_order_readback.py | 7 +- apps/api/src/services/channel_hub.py | 26 +- .../telegram_alert_ai_automation_matrix.py | 133 +++++++--- ...agent_report_truth_actionability_review.py | 19 +- ...t_report_truth_actionability_review_api.py | 12 +- .../test_channel_hub_grouped_alert_events.py | 52 ++++ ...telegram_alert_ai_automation_matrix_api.py | 26 +- docs/LOGBOOK.md | 12 + .../TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md | 7 +- ...otification-egress-inventory.snapshot.json | 107 +------- ...-egress-migration-plan-draft.snapshot.json | 126 +--------- ...n-egress-owner-request-draft.snapshot.json | 169 +------------ ...ss-owner-response-acceptance.snapshot.json | 233 +----------------- .../test_cd_controlled_runtime_profile.py | 12 + 15 files changed, 271 insertions(+), 686 deletions(-) diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml index b4161979..06d88492 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/cd.yaml @@ -261,6 +261,14 @@ jobs: ;; docs/awooop/TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md) ;; + docs/security/telegram-notification-egress-inventory.snapshot.json) + ;; + docs/security/telegram-notification-egress-owner-request-draft.snapshot.json) + ;; + docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json) + ;; + docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json) + ;; docs/workplans/2026-06-04-reboot-cold-start-backup-recovery-workplan.md) ;; docs/workplans/2026-07-02-commander-inserted-requirements-priority-ledger.md) @@ -425,6 +433,8 @@ jobs: ;; apps/api/src/services/backup_dr_readiness_matrix.py) ;; + apps/api/src/services/channel_hub.py) + ;; apps/api/src/services/decision_manager.py) ;; apps/api/src/services/delivery_closure_workbench.py) @@ -461,6 +471,8 @@ jobs: ;; apps/api/tests/test_awooop_truth_chain_service.py) ;; + apps/api/tests/test_channel_hub_grouped_alert_events.py) + ;; apps/api/tests/test_shadow_auto_approve.py) ;; apps/api/tests/test_destructive_patterns.py) @@ -834,6 +846,7 @@ jobs: src/services/awoooi_onboarding_source_contracts.py \ src/services/awoooi_priority_work_order_readback.py \ src/services/awoooi_product_onboarding_guard.py \ + src/services/channel_hub.py \ src/services/p0_cicd_baseline_source_readiness.py \ src/services/product_awoooi_manifest_standard.py \ src/services/platform_operator_service.py \ @@ -900,7 +913,10 @@ jobs: tests/test_ai_agent_log_controlled_writeback_consumer_readback_api.py \ tests/test_ai_agent_log_controlled_writeback_consumer_apply_api.py \ tests/test_ai_agent_autonomous_runtime_control.py \ + tests/test_ai_agent_report_truth_actionability_review.py \ + tests/test_ai_agent_report_truth_actionability_review_api.py \ tests/test_awooop_truth_chain_service.py \ + tests/test_channel_hub_grouped_alert_events.py \ tests/test_shadow_auto_approve.py \ tests/test_destructive_patterns.py \ tests/test_approval_pending_visibility.py \ diff --git a/apps/api/src/services/awoooi_priority_work_order_readback.py b/apps/api/src/services/awoooi_priority_work_order_readback.py index 17d33516..8fe86f7d 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -335,11 +335,12 @@ _COMMANDER_INSERTED_REQUIREMENT_WORK_ITEMS: list[dict[str, Any]] = [ "post verifier 與 learning writeback;direct send 或 manual default 都列缺口。" ), "current_state": ( - "Telegram alert AI automation matrix endpoint 已實作;目前明確讀回 workflow direct send 0、" - "ops script direct gap 4、API direct gap 1,下一步收斂 direct Bot API 旁路。" + "Telegram alert AI automation matrix endpoint 已實作;channel_hub API direct sender 已改走 " + "TelegramGateway / AwoooP outbound receipt path;目前明確讀回 workflow direct send 0、" + "ops script direct gap 4、API direct gap 0。" ), "acceptance": "Telegram alert matrix 顯示 total、DB receipt、AI route、controlled queue、manual/default gap 與 direct send gap。", - "next_action": "先把 apps/api/src/services/channel_hub.py direct sender 改走 TelegramGateway / AwoooP outbound receipt path。", + "next_action": "收斂 scripts/ops 內剩餘 4 條 direct Bot API sendMessage 到 AWOOI API / TelegramGateway receipt path。", "mapped_workplan_id": "P0-006", }, { diff --git a/apps/api/src/services/channel_hub.py b/apps/api/src/services/channel_hub.py index a88753c8..83fc28a8 100644 --- a/apps/api/src/services/channel_hub.py +++ b/apps/api/src/services/channel_hub.py @@ -1122,26 +1122,16 @@ async def _send_telegram_interim( content: str, run_id: UUID, ) -> None: - """實際發送 Telegram interim 訊息(non-shadow 專用)""" + """透過 TelegramGateway 發送 interim 訊息(non-shadow 專用)。""" try: - import os + from src.services.telegram_gateway import get_telegram_gateway - import httpx - - bot_token = os.environ.get("TELEGRAM_BOT_TOKEN") - if not bot_token: - logger.warning("interim_telegram_no_token", run_id=str(run_id)) - return - - async with httpx.AsyncClient(timeout=10) as client: - await client.post( - f"https://api.telegram.org/bot{bot_token}/sendMessage", - json={ - "chat_id": channel_chat_id, - "text": content, - "parse_mode": "HTML", - }, - ) + await get_telegram_gateway().send_text( + content, + chat_id=channel_chat_id, + parse_mode="HTML", + disable_web_page_preview=True, + ) except Exception as exc: logger.warning( "interim_telegram_send_failed", diff --git a/apps/api/src/services/telegram_alert_ai_automation_matrix.py b/apps/api/src/services/telegram_alert_ai_automation_matrix.py index 0fd1fd7a..4e7db4f4 100644 --- a/apps/api/src/services/telegram_alert_ai_automation_matrix.py +++ b/apps/api/src/services/telegram_alert_ai_automation_matrix.py @@ -84,6 +84,8 @@ def load_latest_telegram_alert_ai_automation_matrix( direct_gap_count = int(egress_summary["direct_bot_api_call_count"]) direct_gap_file_count = int(egress_summary["direct_bot_api_file_count"]) + api_direct_gap_count = int(egress_summary["api_direct_bot_api_call_count"]) + ops_script_gap_count = int(egress_summary["ops_script_direct_bot_api_call_count"]) direct_gap_refs = [ { "surface_id": item["egress_surface_id"], @@ -189,6 +191,12 @@ def load_latest_telegram_alert_ai_automation_matrix( }, ] + next_priority_action = _next_priority_action( + api_direct_gap_count=api_direct_gap_count, + ops_script_gap_count=ops_script_gap_count, + direct_gap_count=direct_gap_count, + ) + next_controlled_actions = _ordered_controlled_actions(next_priority_action) summary = _build_summary( matrix=matrix, direct_gap_count=direct_gap_count, @@ -199,6 +207,7 @@ def load_latest_telegram_alert_ai_automation_matrix( readability_summary=readability_summary, digest_rollups=digest_rollups, source_proof=source_proof, + next_priority_action=next_priority_action, ) return { @@ -232,43 +241,57 @@ def load_latest_telegram_alert_ai_automation_matrix( "summary": summary, "matrix": matrix, "direct_send_gap_refs": direct_gap_refs, - "next_controlled_actions": [ - { - "action_id": "migrate_api_direct_sender_to_gateway", - "scope": "apps/api/src/services/channel_hub.py", - "priority": "P0", - "why": "API direct Bot API path is the remaining high-blast-radius bypass.", - "requires_secret": False, - "requires_runtime_send": False, - "post_verifier": "telegram-notification-egress-inventory direct gap count decreases and TelegramGateway mirror tests pass.", - }, - { - "action_id": "migrate_ops_script_direct_senders_to_receipt_path", - "scope": "scripts/ops direct Bot API sendMessage surfaces", - "priority": "P0", - "why": "Ops script direct sends can bypass DB/log receipt and AI automation card metadata.", - "requires_secret": False, - "requires_runtime_send": False, - "post_verifier": "egress inventory and owner acceptance matrix show no unreviewed direct sends.", - }, - { - "action_id": "bind_ai_alert_cards_to_learning_writeback", - "scope": "ai_automation_alert_card_v1 outbound mirror metadata", - "priority": "P0", - "why": "Telegram must become an AI automation learning input, not a manual terminal state.", - "requires_secret": False, - "requires_runtime_send": False, - "post_verifier": "/api/v1/platform/runs/ai-alert-cards readback shows delivery_receipt_readback_required.", - }, - ], + "next_controlled_actions": next_controlled_actions, "operator_interpretation": [ "TelegramGateway AI alert cards already expose AI route, controlled queue, final-exit formatter, outbound mirror metadata, and readback endpoint support.", - "Known direct Bot API surfaces remain the concrete AI automation gap; they must not be treated as completed delivery receipts.", + "Known direct Bot API surfaces remain the concrete AI automation gap; API direct senders are clear when api_direct_send_gap_count is 0.", "Manual/default semantics are only acceptable for critical/break-glass or historical evidence; direct send gaps remain controlled migration candidates.", ], } +def _ordered_controlled_actions(next_priority_action: dict[str, Any]) -> list[dict[str, Any]]: + actions = [ + next_priority_action, + { + "action_id": "migrate_api_direct_sender_to_gateway", + "scope": "apps/api/src/services/channel_hub.py", + "priority": "P0", + "why": "API direct Bot API path is the remaining high-blast-radius bypass.", + "requires_secret": False, + "requires_runtime_send": False, + "post_verifier": "telegram-notification-egress-inventory direct gap count decreases and TelegramGateway mirror tests pass.", + }, + { + "action_id": "migrate_ops_script_direct_senders_to_receipt_path", + "scope": "scripts/ops direct Bot API sendMessage surfaces", + "priority": "P0", + "why": "Ops script direct sends can bypass DB/log receipt and AI automation card metadata.", + "requires_secret": False, + "requires_runtime_send": False, + "post_verifier": "egress inventory and owner acceptance matrix show no unreviewed direct sends.", + }, + { + "action_id": "bind_ai_alert_cards_to_learning_writeback", + "scope": "ai_automation_alert_card_v1 outbound mirror metadata", + "priority": "P0", + "why": "Telegram must become an AI automation learning input, not a manual terminal state.", + "requires_secret": False, + "requires_runtime_send": False, + "post_verifier": "/api/v1/platform/runs/ai-alert-cards readback shows delivery_receipt_readback_required.", + }, + ] + ordered: list[dict[str, Any]] = [] + seen: set[str] = set() + for action in actions: + action_id = str(action["action_id"]) + if action_id in seen: + continue + seen.add(action_id) + ordered.append(action) + return ordered + + def _load_required_json(path: Path) -> dict[str, Any]: if not path.exists(): raise FileNotFoundError(f"required Telegram matrix source missing: {path}") @@ -366,6 +389,53 @@ def _inspect_source_contract(repo_root: Path) -> dict[str, int | bool]: return result +def _next_priority_action( + *, + api_direct_gap_count: int, + ops_script_gap_count: int, + direct_gap_count: int, +) -> dict[str, Any]: + if api_direct_gap_count > 0: + return { + "action_id": "migrate_api_direct_sender_to_gateway", + "scope": "apps/api/src/services/channel_hub.py", + "priority": "P0", + "why": "API direct Bot API path is the remaining high-blast-radius bypass.", + "requires_secret": False, + "requires_runtime_send": False, + "post_verifier": "telegram-notification-egress-inventory direct gap count decreases and TelegramGateway mirror tests pass.", + } + if ops_script_gap_count > 0: + return { + "action_id": "migrate_ops_script_direct_senders_to_receipt_path", + "scope": "scripts/ops direct Bot API sendMessage surfaces", + "priority": "P0", + "why": "Only ops script direct Bot API paths remain after API sender convergence.", + "requires_secret": False, + "requires_runtime_send": False, + "post_verifier": "telegram-notification-egress-inventory reports ops=0 and owner acceptance candidates=0 for direct Bot API surfaces.", + } + if direct_gap_count > 0: + return { + "action_id": "migrate_remaining_direct_senders_to_receipt_path", + "scope": "remaining direct Bot API sendMessage surfaces", + "priority": "P0", + "why": "Direct Bot API paths still bypass the controlled receipt path.", + "requires_secret": False, + "requires_runtime_send": False, + "post_verifier": "telegram-notification-egress-inventory direct gap count reaches 0.", + } + return { + "action_id": "bind_ai_alert_cards_to_learning_writeback", + "scope": "ai_automation_alert_card_v1 outbound mirror metadata", + "priority": "P0", + "why": "All direct Bot API gaps are clear; close the learning/writeback loop.", + "requires_secret": False, + "requires_runtime_send": False, + "post_verifier": "/api/v1/platform/runs/ai-alert-cards readback shows delivery_receipt_readback_required and learning writeback refs.", + } + + def _build_summary( *, matrix: list[dict[str, Any]], @@ -377,6 +447,7 @@ def _build_summary( readability_summary: dict[str, Any], digest_rollups: dict[str, Any], source_proof: dict[str, int | bool], + next_priority_action: dict[str, Any], ) -> dict[str, Any]: ready = lambda key, prefix: sum(1 for item in matrix if str(item[key]).startswith(prefix)) return { @@ -409,6 +480,6 @@ def _build_summary( "ai_alert_card_delivery_readback_endpoint_count": source_proof[ "ai_alert_card_delivery_readback_endpoint_count" ], - "next_priority_action_id": "migrate_api_direct_sender_to_gateway", + "next_priority_action_id": next_priority_action["action_id"], "next_priority_work_item_id": "CIR-P0-TG-001", } 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 2f6f9cfd..68b58e4e 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 @@ -37,18 +37,18 @@ def test_load_latest_ai_agent_report_truth_actionability_review(): assert data["telegram_routing_consolidation"]["direct_telegram_api_send_allowed"] is False assert data["rollups"]["telegram_route_finding_count"] == len(data["telegram_route_findings"]) assert data["telegram_egress_guard"]["status"] == "pass_no_new_bypass" - assert data["telegram_egress_guard"]["summary"]["live_direct_bot_api_call_count"] == 5 + assert data["telegram_egress_guard"]["summary"]["live_direct_bot_api_call_count"] == 4 assert data["telegram_egress_guard"]["summary"]["new_bypass_count"] == 0 assert data["telegram_egress_guard"]["summary"]["gitea_workflow_direct_bot_api_call_count"] == 0 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"]["api_direct_bot_api_call_count"] == 0 assert ( data["telegram_egress_guard"]["summary"]["direct_bot_api_awooop_db_receipt_missing_count"] - == 5 + == 4 ) assert ( data["telegram_egress_guard"]["summary"]["direct_bot_api_ai_controlled_route_missing_count"] - == 5 + == 4 ) assert ( data["telegram_egress_guard"]["telegram_receipt_coverage"]["coverage_status"] @@ -72,19 +72,16 @@ def test_load_latest_ai_agent_report_truth_actionability_review(): ] is False ) - assert data["rollups"]["telegram_route_finding_count"] == 9 - assert data["rollups"]["legacy_or_direct_route_count"] == 9 + assert data["rollups"]["telegram_route_finding_count"] == 8 + assert data["rollups"]["legacy_or_direct_route_count"] == 8 assert sum( 1 for route in data["telegram_route_findings"] if route["route_id"].startswith("telegram_direct_bot_api_") - ) == 5 + ) == 4 assert { item["surface_kind"] for item in data["telegram_egress_guard"]["current_direct_bot_api_calls"] - } == { - "ops_script_direct_bot_api", - "api_direct_bot_api", - } + } == {"ops_script_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 1d4e7a05..b14458d0 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 @@ -34,18 +34,18 @@ def test_get_ai_agent_report_truth_actionability_review_api(): assert data["telegram_routing_consolidation"]["canonical_room_env"] == "SRE_GROUP_CHAT_ID" assert data["telegram_routing_consolidation"]["other_bot_or_group_alerts_allowed"] is False assert data["telegram_routing_consolidation"]["direct_telegram_api_send_allowed"] is False - assert data["telegram_egress_guard"]["summary"]["live_direct_bot_api_call_count"] == 5 + assert data["telegram_egress_guard"]["summary"]["live_direct_bot_api_call_count"] == 4 assert data["telegram_egress_guard"]["summary"]["new_bypass_count"] == 0 assert data["telegram_egress_guard"]["summary"]["gitea_workflow_direct_bot_api_call_count"] == 0 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"]["api_direct_bot_api_call_count"] == 0 assert ( data["telegram_egress_guard"]["summary"]["direct_bot_api_awooop_db_receipt_missing_count"] - == 5 + == 4 ) assert ( data["telegram_egress_guard"]["summary"]["direct_bot_api_ai_controlled_route_missing_count"] - == 5 + == 4 ) assert ( data["telegram_egress_guard"]["telegram_receipt_coverage"][ @@ -57,6 +57,6 @@ def test_get_ai_agent_report_truth_actionability_review_api(): data["telegram_egress_guard"]["telegram_receipt_coverage"]["all_telegram_alerts_ai_controlled"] is False ) - assert data["rollups"]["telegram_route_finding_count"] == 9 - assert data["rollups"]["legacy_or_direct_route_count"] == 9 + assert data["rollups"]["telegram_route_finding_count"] == 8 + assert data["rollups"]["legacy_or_direct_route_count"] == 8 assert data["rollups"]["operator_action_count"] == 5 diff --git a/apps/api/tests/test_channel_hub_grouped_alert_events.py b/apps/api/tests/test_channel_hub_grouped_alert_events.py index bb50d6bc..9f874ff5 100644 --- a/apps/api/tests/test_channel_hub_grouped_alert_events.py +++ b/apps/api/tests/test_channel_hub_grouped_alert_events.py @@ -1,9 +1,12 @@ from __future__ import annotations import json +import inspect +from uuid import uuid4 from src.services.channel_hub import ( _db_timestamp_now, + _send_telegram_interim, build_alertmanager_provider_event_id, build_alertmanager_run_id, build_external_alert_provider_event_id, @@ -23,6 +26,55 @@ def test_db_timestamp_now_is_naive_utc_for_asyncpg() -> None: assert _db_timestamp_now().tzinfo is None +async def test_send_telegram_interim_uses_gateway_mirror_path(monkeypatch) -> None: + calls: list[dict[str, object]] = [] + + class _FakeGateway: + async def send_text( + self, + text: str, + *, + chat_id: str | int | None = None, + parse_mode: str = "HTML", + disable_web_page_preview: bool = True, + ) -> dict[str, object]: + calls.append( + { + "text": text, + "chat_id": chat_id, + "parse_mode": parse_mode, + "disable_web_page_preview": disable_web_page_preview, + } + ) + return {"ok": True, "result": {"message_id": 42}} + + monkeypatch.setattr( + "src.services.telegram_gateway.get_telegram_gateway", + lambda: _FakeGateway(), + ) + + await _send_telegram_interim( + channel_chat_id="123456", + content="AI 正在分析中,請稍候...", + run_id=uuid4(), + ) + + assert calls == [ + { + "text": "AI 正在分析中,請稍候...", + "chat_id": "123456", + "parse_mode": "HTML", + "disable_web_page_preview": True, + } + ] + + +def test_channel_hub_does_not_embed_direct_telegram_bot_api() -> None: + source = inspect.getsource(_send_telegram_interim) + assert "TELEGRAM_BOT_TOKEN" not in source + assert "api.telegram.org" not in source + + def test_build_grouped_alert_provider_event_id_is_deterministic() -> None: event_id = build_grouped_alert_provider_event_id( "INC-20260507-ABCD12", diff --git a/apps/api/tests/test_telegram_alert_ai_automation_matrix_api.py b/apps/api/tests/test_telegram_alert_ai_automation_matrix_api.py index 78dc2cf1..f0075d20 100644 --- a/apps/api/tests/test_telegram_alert_ai_automation_matrix_api.py +++ b/apps/api/tests/test_telegram_alert_ai_automation_matrix_api.py @@ -32,26 +32,26 @@ def test_telegram_alert_ai_automation_matrix_loader_returns_gap_matrix(): summary = payload["summary"] assert summary["telegram_alert_surface_count"] == 5 - assert summary["known_direct_send_gap_count"] == 5 - assert summary["known_direct_send_gap_file_count"] == 5 + assert summary["known_direct_send_gap_count"] == 4 + assert summary["known_direct_send_gap_file_count"] == 4 assert summary["workflow_direct_send_gap_count"] == 0 assert summary["ops_script_direct_send_gap_count"] == 4 - assert summary["api_direct_send_gap_count"] == 1 + assert summary["api_direct_send_gap_count"] == 0 assert summary["gateway_normalized_callsite_count"] >= 50 assert summary["db_or_log_receipt_ready_surface_count"] >= 2 assert summary["ai_route_ready_surface_count"] >= 3 assert summary["controlled_queue_ready_surface_count"] >= 2 assert summary["post_verifier_ready_surface_count"] >= 3 assert summary["learning_writeback_ready_surface_count"] >= 3 - assert summary["manual_default_gap_count"] == 5 - assert summary["direct_gap_migration_candidate_count"] == 5 - assert summary["owner_acceptance_candidate_count"] == 5 + assert summary["manual_default_gap_count"] == 4 + assert summary["direct_gap_migration_candidate_count"] == 4 + assert summary["owner_acceptance_candidate_count"] == 4 assert summary["telegram_send_performed_count"] == 0 assert summary["bot_api_call_performed_count"] == 0 assert summary["secret_value_read_count"] == 0 assert summary["runtime_write_performed_count"] == 0 assert summary["ai_alert_card_delivery_readback_endpoint_count"] == 1 - assert summary["next_priority_action_id"] == "migrate_api_direct_sender_to_gateway" + assert summary["next_priority_action_id"] == "migrate_ops_script_direct_senders_to_receipt_path" matrix = {item["surface_id"]: item for item in payload["matrix"]} assert matrix["telegram_gateway_ai_alert_cards"]["status"] == ( @@ -63,14 +63,18 @@ def test_telegram_alert_ai_automation_matrix_loader_returns_gap_matrix(): assert matrix["telegram_direct_bot_api_egress_gap"]["status"] == ( "gap_open_requires_controlled_migration" ) - assert matrix["telegram_direct_bot_api_egress_gap"]["direct_send_gap_count"] == 5 + assert matrix["telegram_direct_bot_api_egress_gap"]["direct_send_gap_count"] == 4 assert matrix["telegram_action_required_digest_policy"]["known_item_count"] == 8 assert matrix["telegram_ai_alert_card_delivery_readback_endpoint"][ "db_or_log_receipt" ] == "ready_awooop_outbound_message_query" - assert len(payload["direct_send_gap_refs"]) == 5 - assert any( + assert len(payload["direct_send_gap_refs"]) == 4 + assert all( + ref["surface_kind"] == "ops_script_direct_bot_api" + for ref in payload["direct_send_gap_refs"] + ) + assert not any( ref["path"] == "apps/api/src/services/channel_hub.py" for ref in payload["direct_send_gap_refs"] ) @@ -96,7 +100,7 @@ def test_telegram_alert_ai_automation_matrix_endpoint_returns_readback(): assert response.status_code == 200 data = response.json() assert data["schema_version"] == "telegram_alert_ai_automation_matrix_v1" - assert data["summary"]["known_direct_send_gap_count"] == 5 + assert data["summary"]["known_direct_send_gap_count"] == 4 assert data["summary"]["next_priority_work_item_id"] == "CIR-P0-TG-001" assert data["operation_boundaries"]["telegram_send_performed"] is False assert data["operation_boundaries"]["secret_value_read"] is False diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 578d4806..987d2f95 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,15 @@ +## 2026-07-02 — 18:53 CIR-P0-TG-001 channel_hub direct Telegram sender 收斂 + +**完成內容**: +- `apps/api/src/services/channel_hub.py` 的 non-shadow interim feedback sender 已由直接讀 `TELEGRAM_BOT_TOKEN` / 拼 `api.telegram.org/bot.../sendMessage` 改為 `TelegramGateway.send_text()`,讓訊息進入既有 final-exit formatter、thread reply、錯誤 sanitizer 與 AwoooP outbound mirror receipt path。 +- `docs/security/telegram-notification-egress-inventory.snapshot.json` 重新讀回:direct Bot API `sendMessage` 從 `5` 降為 `4`;workflow `0`、ops script `4`、API direct `0`。 +- `telegram-notification-egress-owner-request-draft`、migration plan、owner-response acceptance snapshots 已同步重建為 `4` 份 candidate,下一個 P0 action 改為收斂 `scripts/ops` 內 4 條 direct Bot API path。 +- `telegram_alert_ai_automation_matrix` 與 `awoooi_priority_work_order_readback` 已同步:`api_direct_send_gap_count=0`、`next_priority_action_id=migrate_ops_script_direct_senders_to_receipt_path`。 +- 新增 channel_hub regression test,確認 interim feedback 走 Gateway mirror path,且函式 source 不再含 `TELEGRAM_BOT_TOKEN` / `api.telegram.org`。 + +**仍維持**: +- 未送 Telegram、未呼叫 Bot API、未讀 secret / token / `.env` / raw sessions / SQLite / auth;未寫 production DB、未改 receiver route、未 workflow_dispatch、未重啟主機 / VM / Docker / Nginx / K3s / DB / firewall。 + ## 2026-07-02 — 18:35 CIR-P0-TG-001 Telegram matrix CD profile 修正 **完成內容**: diff --git a/docs/awooop/TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md b/docs/awooop/TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md index 8b03d94e..58fbfcb7 100644 --- a/docs/awooop/TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md +++ b/docs/awooop/TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md @@ -87,9 +87,10 @@ Host / runner 資源告警的第一版落地: 通知出口旁路清冊: -- `docs/security/telegram-notification-egress-inventory.snapshot.json` 目前固定 repo 內 `5` 個 direct Bot API `sendMessage`,分布為 Gitea workflow `0`、ops script `4`、API direct path `1`。 -- `docs/security/telegram-notification-egress-owner-request-draft.snapshot.json` 已將上述 direct egress 檔案聚成 `5` 份 metadata-only candidate;owner response 欄位只能作為 evidence,不得阻擋低爆炸半徑 controlled apply。 -- `docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json` 已將 5 份 candidate 排成 ops script、API sender 兩個遷移波次;migration runtime send / Bot API call / secret read 仍維持 `0`。 +- `docs/security/telegram-notification-egress-inventory.snapshot.json` 目前固定 repo 內 `4` 個 direct Bot API `sendMessage`,分布為 Gitea workflow `0`、ops script `4`、API direct path `0`。 +- `apps/api/src/services/channel_hub.py` 的 interim feedback sender 已改走 `TelegramGateway.send_text()`,讓 non-shadow interim 訊息進入既有 final-exit formatter、thread reply、錯誤 sanitizer 與 AwoooP outbound mirror path;不再直接讀 `TELEGRAM_BOT_TOKEN` 或拼 Bot API URL。 +- `docs/security/telegram-notification-egress-owner-request-draft.snapshot.json` 已將上述 direct egress 檔案聚成 `4` 份 metadata-only candidate;owner response 欄位只能作為 evidence,不得阻擋低爆炸半徑 controlled apply。 +- `docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json` 已將 4 份 candidate 排成 ops script 遷移波次;migration runtime send / Bot API call / secret read 仍維持 `0`。 - `GET /api/v1/agents/telegram-alert-ai-automation-matrix` 會把上述清冊與 readability guard、AI digest policy、AwoooP outbound mirror source contract 合併,讀回 DB/log receipt、AI route、controlled queue、work item、post verifier、learning writeback、manual/default gap 與 direct send gap。 - 這些 direct egress 可能繞過 TelegramGateway formatter、dedup、outbound mirror、KM / PlayBook / Verifier 與 no-false-green gate;因此只能視為已知待治理 surface,不能視為已完成收斂。 - 清冊階段只允許保存 path、line、redacted excerpt、owner 欄位與 reviewer gate;不送 Telegram、不讀 Bot token、不保存 chat secret、不改 workflow / script、不開 runtime gate。 diff --git a/docs/security/telegram-notification-egress-inventory.snapshot.json b/docs/security/telegram-notification-egress-inventory.snapshot.json index 9e0c6b67..e7d409cf 100644 --- a/docs/security/telegram-notification-egress-inventory.snapshot.json +++ b/docs/security/telegram-notification-egress-inventory.snapshot.json @@ -1,7 +1,7 @@ { "schema_version": "telegram_notification_egress_inventory_v1", - "generated_at": "2026-07-02T14:22:12+08:00", - "git_commit": "f9469bcc2", + "generated_at": "2026-07-02T18:47:50+08:00", + "git_commit": "ae5844733", "status": "inventory_ready_no_runtime_action", "mode": "repo_only_scan_no_secret_value_no_telegram_send", "scan_roots": [ @@ -11,13 +11,13 @@ "apps/api/src" ], "summary": { - "scanned_file_count": 636, - "direct_bot_api_file_count": 5, - "direct_bot_api_call_count": 5, + "scanned_file_count": 637, + "direct_bot_api_file_count": 4, + "direct_bot_api_call_count": 4, "workflow_direct_bot_api_call_count": 0, "ops_script_direct_bot_api_call_count": 4, "ci_script_direct_bot_api_call_count": 0, - "api_direct_bot_api_call_count": 1, + "api_direct_bot_api_call_count": 0, "gateway_normalized_callsite_count": 57, "gateway_final_exit_formatter_present_count": 1, "required_owner_field_count": 18, @@ -58,101 +58,6 @@ "not_authorization": true }, "direct_bot_api_calls": [ - { - "egress_surface_id": "telegram_egress:api_direct_bot_api:apps/api/src/services/channel_hub.py:1138", - "surface_kind": "api_direct_bot_api", - "path": "apps/api/src/services/channel_hub.py", - "line": 1138, - "line_hash": "9e418fa74c83815a", - "sanitized_excerpt": "f\"https://api.telegram.org/bot/sendMessage\",", - "required_owner_fields": [ - "egress_surface_id", - "owner_role_or_team", - "routing_purpose", - "current_sender", - "target_chat_route", - "message_shape_contract", - "redaction_contract", - "formatter_convergence_plan", - "delivery_receipt_ref", - "dedup_or_fingerprint_plan", - "fallback_or_degraded_mode", - "migration_or_exception_reason", - "maintenance_window", - "rollback_owner", - "postcheck_evidence_ref", - "no_secret_value_attestation", - "no_raw_payload_attestation", - "no_false_green_attestation" - ], - "reviewer_checks": [ - "direct_bot_api_surface_identified", - "owner_role_present", - "target_route_is_sre_owned", - "message_shape_is_ai_automation_card_or_documented_exception", - "redaction_contract_present", - "formatter_convergence_path_present", - "delivery_receipt_metadata_only", - "dedup_or_fingerprint_present", - "fallback_mode_does_not_leak_raw_payload", - "secret_name_only_no_value", - "workflow_or_script_change_requires_separate_approval", - "telegram_send_not_executed_by_inventory", - "no_false_green_claim", - "runtime_gate_stays_zero" - ], - "outcome_lanes": [ - "waiting_owner_response", - "request_owner_route_supplement", - "request_formatter_convergence_plan", - "request_redaction_contract", - "request_delivery_receipt_metadata", - "quarantine_secret_or_raw_payload", - "reject_false_green_claim", - "ready_for_notification_egress_review", - "waiting_runtime_gate" - ], - "blocked_actions": [ - "telegram_send", - "bot_api_call", - "workflow_modification", - "script_modification_without_owner", - "secret_value_collection", - "secret_hash_collection", - "partial_token_collection", - "chat_id_collection_without_owner", - "store_raw_message_payload", - "store_unredacted_workflow_log", - "change_chat_route", - "change_bot_token", - "rotate_secret", - "workflow_dispatch", - "production_deploy", - "accept_route_200_as_delivery_receipt", - "accept_cd_success_as_notification_acceptance", - "accept_ui_visible_as_notification_acceptance", - "skip_formatter_convergence", - "skip_redaction_review", - "open_runtime_gate", - "add_action_button" - ], - "owner_response_received": false, - "owner_response_accepted": false, - "formatter_convergence_accepted": false, - "redaction_contract_accepted": false, - "delivery_receipt_accepted": false, - "direct_bot_api_migration_authorized": false, - "telegram_send_authorized": false, - "bot_api_call_authorized": false, - "workflow_modification_authorized": false, - "script_modification_authorized": false, - "secret_value_collection_allowed": false, - "raw_payload_storage_allowed": false, - "production_write_authorized": false, - "runtime_gate": false, - "action_buttons_allowed": false, - "not_authorization": true - }, { "egress_surface_id": "telegram_egress:ops_script_direct_bot_api:scripts/ops/backup-from-110.sh:64", "surface_kind": "ops_script_direct_bot_api", diff --git a/docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json b/docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json index 270d6495..b0dd5135 100644 --- a/docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json +++ b/docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json @@ -1,29 +1,29 @@ { "schema_version": "telegram_notification_egress_migration_plan_draft_v1", - "generated_at": "2026-07-02T14:24:46+08:00", - "git_commit": "f9469bcc2", + "generated_at": "2026-07-02T18:48:33+08:00", + "git_commit": "ae5844733", "status": "migration_plan_draft_ready_no_runtime_action", "mode": "metadata_only_no_workflow_script_api_change_no_telegram_send", "source_snapshot": "docs/security/telegram-notification-egress-owner-request-draft.snapshot.json", "source_schema_version": "telegram_notification_egress_owner_request_draft_v1", "source_status": "owner_request_draft_ready_no_dispatch_no_runtime_action", "summary": { - "source_request_draft_count": 5, - "source_direct_bot_api_call_count": 5, - "migration_candidate_count": 5, + "source_request_draft_count": 4, + "source_direct_bot_api_call_count": 4, + "migration_candidate_count": 4, "workflow_migration_candidate_count": 0, "ops_script_migration_candidate_count": 4, - "api_direct_migration_candidate_count": 1, - "proposed_wave_count": 2, + "api_direct_migration_candidate_count": 0, + "proposed_wave_count": 1, "plan_field_count": 17, "reviewer_check_count": 15, "outcome_lane_count": 9, "blocked_action_count": 21, - "owner_response_required_count": 5, - "maintenance_window_required_count": 5, - "rollback_owner_required_count": 5, - "postcheck_required_count": 5, - "delivery_receipt_required_count": 5, + "owner_response_required_count": 4, + "maintenance_window_required_count": 4, + "rollback_owner_required_count": 4, + "postcheck_required_count": 4, + "delivery_receipt_required_count": 4, "owner_response_received_count": 0, "owner_response_accepted_count": 0, "migration_authorized_count": 0, @@ -52,109 +52,9 @@ "not_authorization": true }, "proposed_waves": [ - "wave_2_ops_notification_wrapper", - "wave_3_api_sender_gateway" + "wave_2_ops_notification_wrapper" ], "migration_candidates": [ - { - "migration_candidate_id": "telegram_notification_egress_migration:apps/api/src/services/channel_hub.py", - "source_request_draft_id": "telegram_notification_egress_owner_request:apps_api_src_services_channel_hub_py", - "source_path": "apps/api/src/services/channel_hub.py", - "surface_kind": "api_direct_bot_api", - "direct_call_count": 1, - "proposed_wave": "wave_3_api_sender_gateway", - "proposed_target": "TelegramGateway final-exit formatter", - "proposed_change_summary": "Route API interim sender through TelegramGateway or equivalent final-exit normalization and mirror contract.", - "plan_fields": [ - "migration_candidate_id", - "source_request_draft_id", - "source_path", - "surface_kind", - "direct_call_count", - "proposed_wave", - "proposed_target", - "proposed_change_summary", - "required_owner_response_ref", - "required_maintenance_window", - "required_rollback_owner", - "required_postcheck_ref", - "required_delivery_receipt_ref", - "required_no_secret_value_attestation", - "required_no_raw_payload_attestation", - "required_no_false_green_attestation", - "not_authorization" - ], - "reviewer_checks": [ - "source_owner_request_draft_current", - "owner_response_required_before_change", - "maintenance_window_required_before_change", - "rollback_owner_required_before_change", - "delivery_receipt_plan_required", - "postcheck_plan_required", - "redaction_contract_required", - "break_glass_fallback_explicit", - "no_secret_value_required", - "no_raw_payload_required", - "no_false_green_required", - "workflow_changes_separate_from_docs", - "script_changes_separate_from_docs", - "api_sender_refactor_separate_from_docs", - "runtime_gate_stays_zero" - ], - "outcome_lanes": [ - "draft_waiting_owner_response", - "ready_for_workflow_migration_review", - "ready_for_ops_script_migration_review", - "ready_for_api_sender_migration_review", - "request_missing_owner_response", - "request_missing_maintenance_or_rollback", - "reject_secret_or_raw_payload", - "reject_false_green_claim", - "waiting_runtime_gate" - ], - "blocked_actions": [ - "modify_workflow", - "modify_ops_script", - "refactor_api_sender", - "send_telegram", - "call_bot_api", - "dispatch_workflow", - "trigger_cd", - "deploy_production", - "read_secret_store", - "collect_secret_value", - "collect_secret_hash", - "collect_partial_token", - "store_raw_payload", - "store_unredacted_log", - "change_chat_route", - "change_bot_token", - "rotate_secret", - "accept_cd_success_as_delivery_receipt", - "accept_route_200_as_notification_delivery", - "open_runtime_gate", - "add_action_button" - ], - "owner_response_required": true, - "maintenance_window_required": true, - "rollback_owner_required": true, - "postcheck_required": true, - "delivery_receipt_required": true, - "owner_response_received": false, - "owner_response_accepted": false, - "migration_authorized": false, - "workflow_modification_authorized": false, - "script_modification_authorized": false, - "api_sender_refactor_authorized": false, - "telegram_send_authorized": false, - "bot_api_call_authorized": false, - "secret_value_collection_allowed": false, - "raw_payload_storage_allowed": false, - "production_write_authorized": false, - "runtime_gate": false, - "action_buttons_allowed": false, - "not_authorization": true - }, { "migration_candidate_id": "telegram_notification_egress_migration:scripts/ops/backup-from-110.sh", "source_request_draft_id": "telegram_notification_egress_owner_request:scripts_ops_backup_from_110_sh", diff --git a/docs/security/telegram-notification-egress-owner-request-draft.snapshot.json b/docs/security/telegram-notification-egress-owner-request-draft.snapshot.json index ff772ec7..5feb26d7 100644 --- a/docs/security/telegram-notification-egress-owner-request-draft.snapshot.json +++ b/docs/security/telegram-notification-egress-owner-request-draft.snapshot.json @@ -1,20 +1,20 @@ { "schema_version": "telegram_notification_egress_owner_request_draft_v1", - "generated_at": "2026-07-02T14:24:46+08:00", - "git_commit": "f9469bcc2", + "generated_at": "2026-07-02T18:48:33+08:00", + "git_commit": "ae5844733", "status": "owner_request_draft_ready_no_dispatch_no_runtime_action", "mode": "metadata_only_no_secret_value_no_telegram_send_no_workflow_change", "source_snapshot": "docs/security/telegram-notification-egress-inventory.snapshot.json", "source_schema_version": "telegram_notification_egress_inventory_v1", "source_status": "inventory_ready_no_runtime_action", "summary": { - "source_direct_bot_api_call_count": 5, - "source_direct_bot_api_file_count": 5, - "request_draft_count": 5, + "source_direct_bot_api_call_count": 4, + "source_direct_bot_api_file_count": 4, + "request_draft_count": 4, "workflow_request_draft_count": 0, "ops_script_request_draft_count": 4, "ci_script_request_draft_count": 0, - "api_direct_request_draft_count": 1, + "api_direct_request_draft_count": 0, "request_field_count": 27, "required_owner_field_count": 19, "preflight_check_count": 16, @@ -59,163 +59,6 @@ "not_authorization": true }, "request_drafts": [ - { - "request_draft_id": "telegram_notification_egress_owner_request:apps_api_src_services_channel_hub_py", - "source_inventory_schema_version": "telegram_notification_egress_inventory_v1", - "source_path": "apps/api/src/services/channel_hub.py", - "surface_kind": "api_direct_bot_api", - "direct_call_count": 1, - "line_refs": [ - 1138 - ], - "line_hash_refs": [ - "9e418fa74c83815a" - ], - "request_fields": [ - "request_draft_id", - "source_inventory_schema_version", - "source_path", - "surface_kind", - "direct_call_count", - "line_refs", - "line_hash_refs", - "owner_role_or_team", - "routing_purpose", - "current_sender", - "target_chat_route", - "message_shape_contract", - "redaction_contract", - "formatter_convergence_decision", - "gateway_or_alertmanager_target", - "break_glass_fallback_decision", - "delivery_receipt_ref", - "dedup_or_fingerprint_plan", - "fallback_or_degraded_mode", - "migration_or_exception_reason", - "maintenance_window", - "rollback_owner", - "postcheck_evidence_ref", - "no_secret_value_attestation", - "no_raw_payload_attestation", - "no_false_green_attestation", - "not_authorization" - ], - "required_owner_fields": [ - "owner_role_or_team", - "routing_purpose", - "current_sender", - "target_chat_route", - "message_shape_contract", - "redaction_contract", - "formatter_convergence_decision", - "gateway_or_alertmanager_target", - "break_glass_fallback_decision", - "delivery_receipt_ref", - "dedup_or_fingerprint_plan", - "fallback_or_degraded_mode", - "migration_or_exception_reason", - "maintenance_window", - "rollback_owner", - "postcheck_evidence_ref", - "no_secret_value_attestation", - "no_raw_payload_attestation", - "no_false_green_attestation" - ], - "preflight_checks": [ - "source_inventory_current", - "owner_role_present", - "route_purpose_present", - "message_shape_contract_present", - "redaction_contract_present", - "formatter_convergence_decision_present", - "break_glass_fallback_explicit", - "delivery_receipt_metadata_present", - "dedup_or_fingerprint_present", - "maintenance_window_present_for_change", - "rollback_owner_present", - "postcheck_evidence_present", - "no_secret_value_attested", - "no_raw_payload_attested", - "no_false_green_attested", - "runtime_gate_stays_zero" - ], - "outcome_lanes": [ - "draft_waiting_owner_dispatch", - "request_owner_route_supplement", - "request_formatter_convergence_supplement", - "request_break_glass_fallback_supplement", - "request_redaction_or_receipt_supplement", - "quarantine_secret_or_raw_payload", - "reject_false_green_claim", - "ready_for_manual_dispatch", - "waiting_runtime_gate" - ], - "forbidden_payloads": [ - "bot_token_value", - "chat_secret_value", - "secret_hash", - "partial_token", - "masked_token", - "authorization_header", - "raw_message_payload", - "raw_workflow_log", - "raw_action_log", - "raw_screenshot_with_secret", - "internal_work_window_transcript", - "private_namespace", - "unredacted_internal_path", - "unredacted_private_ip" - ], - "blocked_actions": [ - "send_owner_request", - "confirm_recipient", - "emit_audit_event", - "telegram_send", - "bot_api_call", - "workflow_modification", - "script_modification", - "api_sender_refactor", - "change_chat_route", - "change_bot_token", - "read_secret_store", - "collect_secret_value", - "collect_secret_hash", - "collect_partial_token", - "collect_chat_id_secret", - "store_raw_message_payload", - "store_unredacted_log", - "workflow_dispatch", - "production_deploy", - "accept_cd_success_as_delivery_receipt", - "accept_route_200_as_notification_delivery", - "accept_ui_visible_as_notification_acceptance", - "skip_formatter_convergence", - "skip_redaction_contract", - "open_runtime_gate", - "add_action_button" - ], - "request_sent": false, - "recipient_confirmed": false, - "audit_event_emitted": false, - "owner_response_received": false, - "owner_response_accepted": false, - "formatter_convergence_accepted": false, - "redaction_contract_accepted": false, - "delivery_receipt_accepted": false, - "break_glass_fallback_accepted": false, - "direct_bot_api_migration_authorized": false, - "telegram_send_authorized": false, - "bot_api_call_authorized": false, - "workflow_modification_authorized": false, - "script_modification_authorized": false, - "api_sender_refactor_authorized": false, - "secret_value_collection_allowed": false, - "raw_payload_storage_allowed": false, - "production_write_authorized": false, - "runtime_gate": false, - "action_buttons_allowed": false, - "not_authorization": true - }, { "request_draft_id": "telegram_notification_egress_owner_request:scripts_ops_backup_from_110_sh", "source_inventory_schema_version": "telegram_notification_egress_inventory_v1", diff --git a/docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json b/docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json index 14f0f2f7..3caa6f27 100644 --- a/docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json +++ b/docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json @@ -1,7 +1,7 @@ { "schema_version": "telegram_notification_egress_owner_response_acceptance_v1", - "generated_at": "2026-07-02T14:24:46+08:00", - "git_commit": "f9469bcc2", + "generated_at": "2026-07-02T18:48:33+08:00", + "git_commit": "ae5844733", "status": "owner_response_acceptance_ledger_ready_no_runtime_action", "mode": "metadata_only_no_secret_value_no_telegram_send_no_workflow_script_api_change", "source_owner_request_snapshot": "docs/security/telegram-notification-egress-owner-request-draft.snapshot.json", @@ -12,13 +12,13 @@ "source_migration_plan_status": "migration_plan_draft_ready_no_runtime_action", "message_readability_guard_snapshot": "docs/security/telegram-alert-readability-guard.snapshot.json", "summary": { - "source_request_draft_count": 5, - "source_migration_candidate_count": 5, - "source_direct_bot_api_call_count": 5, - "acceptance_candidate_count": 5, + "source_request_draft_count": 4, + "source_migration_candidate_count": 4, + "source_direct_bot_api_call_count": 4, + "acceptance_candidate_count": 4, "workflow_acceptance_candidate_count": 0, "ops_script_acceptance_candidate_count": 4, - "api_direct_acceptance_candidate_count": 1, + "api_direct_acceptance_candidate_count": 0, "acceptance_field_count": 33, "required_owner_field_count": 19, "reviewer_check_count": 23, @@ -75,225 +75,6 @@ "not_authorization": true }, "acceptance_candidates": [ - { - "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:apps/api/src/services/channel_hub.py", - "status": "waiting_owner_response", - "source_request_draft_id": "telegram_notification_egress_owner_request:apps_api_src_services_channel_hub_py", - "source_migration_candidate_id": "telegram_notification_egress_migration:apps/api/src/services/channel_hub.py", - "source_path": "apps/api/src/services/channel_hub.py", - "surface_kind": "api_direct_bot_api", - "direct_call_count": 1, - "line_refs": [ - 1138 - ], - "line_hash_refs": [ - "9e418fa74c83815a" - ], - "proposed_wave": "wave_3_api_sender_gateway", - "proposed_target": "TelegramGateway final-exit formatter", - "proposed_change_summary": "Route API interim sender through TelegramGateway or equivalent final-exit normalization and mirror contract.", - "owner_response_ref": null, - "owner_role_or_team": "pending_owner_response", - "decision": "pending_owner_response", - "decision_reason": "pending_owner_response", - "affected_scope": "pending_owner_response", - "redacted_evidence_refs": [], - "message_shape_contract_ref": null, - "message_readability_guard_ref": "docs/security/telegram-alert-readability-guard.snapshot.json", - "redaction_contract_ref": null, - "formatter_convergence_decision": "pending_owner_response", - "gateway_or_alertmanager_target": "pending_owner_response", - "break_glass_fallback_decision": "pending_owner_response", - "delivery_receipt_ref": null, - "dedup_or_fingerprint_plan": "pending_owner_response", - "fallback_or_degraded_mode": "pending_owner_response", - "migration_or_exception_reason": "pending_owner_response", - "maintenance_window": "pending_owner_response", - "rollback_owner": "pending_owner_response", - "postcheck_evidence_ref": null, - "no_secret_value_attestation": "pending_owner_response", - "no_raw_payload_attestation": "pending_owner_response", - "no_false_green_attestation": "pending_owner_response", - "reviewer_outcome": "waiting_owner_response", - "followup_owner": "pending_owner_response", - "acceptance_fields": [ - "acceptance_candidate_id", - "source_request_draft_id", - "source_migration_candidate_id", - "source_path", - "surface_kind", - "direct_call_count", - "proposed_wave", - "proposed_target", - "owner_response_ref", - "owner_role_or_team", - "decision", - "decision_reason", - "affected_scope", - "redacted_evidence_refs", - "message_shape_contract_ref", - "message_readability_guard_ref", - "redaction_contract_ref", - "formatter_convergence_decision", - "gateway_or_alertmanager_target", - "break_glass_fallback_decision", - "delivery_receipt_ref", - "dedup_or_fingerprint_plan", - "fallback_or_degraded_mode", - "migration_or_exception_reason", - "maintenance_window", - "rollback_owner", - "postcheck_evidence_ref", - "no_secret_value_attestation", - "no_raw_payload_attestation", - "no_false_green_attestation", - "reviewer_outcome", - "followup_owner", - "not_authorization" - ], - "required_owner_fields": [ - "owner_role_or_team", - "routing_purpose", - "current_sender", - "target_chat_route", - "message_shape_contract", - "redaction_contract", - "formatter_convergence_decision", - "gateway_or_alertmanager_target", - "break_glass_fallback_decision", - "delivery_receipt_ref", - "dedup_or_fingerprint_plan", - "fallback_or_degraded_mode", - "migration_or_exception_reason", - "maintenance_window", - "rollback_owner", - "postcheck_evidence_ref", - "no_secret_value_attestation", - "no_raw_payload_attestation", - "no_false_green_attestation" - ], - "reviewer_checks": [ - "source_owner_request_current", - "source_migration_plan_current", - "owner_identity_present", - "decision_reason_present", - "affected_scope_matches_source", - "redacted_refs_only", - "no_secret_or_token_value", - "no_raw_message_payload", - "message_shape_contract_present", - "message_readability_guard_present", - "redaction_contract_present", - "formatter_convergence_explicit", - "gateway_or_alertmanager_target_valid", - "break_glass_fallback_explicit", - "delivery_receipt_metadata_only", - "dedup_or_fingerprint_present", - "maintenance_window_present", - "rollback_owner_present", - "postcheck_evidence_present", - "no_false_green_attested", - "migration_authorization_separate", - "counts_transition_safe", - "runtime_gate_stays_zero" - ], - "outcome_lanes": [ - "waiting_owner_response", - "quarantine_secret_or_raw_payload", - "reject_execution_request", - "request_owner_route_supplement", - "request_formatter_convergence_supplement", - "request_redaction_or_receipt_supplement", - "request_maintenance_or_rollback_supplement", - "ready_for_migration_review", - "owner_review_only_update", - "waiting_runtime_gate" - ], - "forbidden_payloads": [ - "bot_token_value", - "chat_secret_value", - "secret_hash", - "partial_token", - "masked_token", - "authorization_header", - "raw_message_payload", - "raw_workflow_log", - "raw_action_log", - "raw_screenshot_with_secret", - "internal_work_window_transcript", - "private_namespace", - "unredacted_internal_path", - "unredacted_private_ip" - ], - "blocked_actions": [ - "mark_owner_response_received_without_record", - "mark_owner_response_accepted_without_reviewer_record", - "send_telegram", - "call_bot_api", - "modify_workflow", - "modify_ops_script", - "refactor_api_sender", - "dispatch_workflow", - "trigger_cd", - "deploy_production", - "change_chat_route", - "change_bot_token", - "rotate_secret", - "read_secret_store", - "collect_secret_value", - "collect_secret_hash", - "collect_partial_token", - "collect_chat_id_secret", - "store_raw_message_payload", - "store_unredacted_log", - "store_internal_work_window_transcript", - "accept_cd_success_as_delivery_receipt", - "accept_route_200_as_notification_delivery", - "accept_ui_visible_as_notification_acceptance", - "accept_telegram_sent_without_delivery_receipt", - "skip_formatter_convergence", - "skip_redaction_contract", - "skip_dedup_or_fingerprint_review", - "skip_break_glass_fallback_review", - "authorize_migration", - "authorize_workflow_modification", - "authorize_script_modification", - "authorize_api_sender_refactor", - "open_runtime_gate", - "add_action_button" - ], - "not_authorization": true, - "request_sent": false, - "recipient_confirmed": false, - "audit_event_emitted": false, - "owner_response_received": false, - "owner_response_accepted": false, - "owner_response_rejected": false, - "owner_response_quarantined": false, - "supplement_requested": false, - "formatter_convergence_accepted": false, - "redaction_contract_accepted": false, - "delivery_receipt_accepted": false, - "break_glass_fallback_accepted": false, - "maintenance_window_accepted": false, - "rollback_owner_accepted": false, - "postcheck_evidence_accepted": false, - "dedup_or_fingerprint_accepted": false, - "no_false_green_accepted": false, - "direct_bot_api_migration_authorized": false, - "workflow_modification_authorized": false, - "script_modification_authorized": false, - "api_sender_refactor_authorized": false, - "telegram_send_authorized": false, - "bot_api_call_authorized": false, - "workflow_dispatch_authorized": false, - "production_deploy_authorized": false, - "secret_value_collection_allowed": false, - "raw_payload_storage_allowed": false, - "production_write_authorized": false, - "runtime_gate": false, - "action_buttons_allowed": false - }, { "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:scripts/ops/backup-from-110.sh", "status": "waiting_owner_response", diff --git a/ops/runner/test_cd_controlled_runtime_profile.py b/ops/runner/test_cd_controlled_runtime_profile.py index a0c198f6..fe6d1d6e 100644 --- a/ops/runner/test_cd_controlled_runtime_profile.py +++ b/ops/runner/test_cd_controlled_runtime_profile.py @@ -189,9 +189,21 @@ def test_telegram_alert_ai_automation_matrix_stays_on_controlled_runtime_profile text = _workflow_text() expected_sources = [ "docs/awooop/TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md)", + "docs/security/telegram-notification-egress-inventory.snapshot.json)", + "docs/security/telegram-notification-egress-owner-request-draft.snapshot.json)", + "docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json)", + "docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json)", + "apps/api/src/services/channel_hub.py)", "apps/api/src/services/telegram_alert_ai_automation_matrix.py)", + "apps/api/tests/test_channel_hub_grouped_alert_events.py)", + "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_telegram_alert_ai_automation_matrix_api.py)", + "src/services/channel_hub.py", "src/services/telegram_alert_ai_automation_matrix.py", + "tests/test_channel_hub_grouped_alert_events.py", + "tests/test_ai_agent_report_truth_actionability_review.py", + "tests/test_ai_agent_report_truth_actionability_review_api.py", "tests/test_telegram_alert_ai_automation_matrix_api.py", ] for source in expected_sources: