feat(telegram): extract incident refs from callback buttons
All checks were successful
CD Pipeline / tests (push) Successful in 1m21s
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / build-and-deploy (push) Successful in 3m35s
CD Pipeline / post-deploy-checks (push) Successful in 1m19s

This commit is contained in:
Your Name
2026-05-25 18:55:28 +08:00
parent c792f37440
commit 23fc499b97
3 changed files with 112 additions and 1 deletions

View File

@@ -951,10 +951,31 @@ def _reply_markup_summary(payload: dict) -> dict[str, object]:
}
def _reply_markup_incident_ids(payload: dict) -> list[str]:
"""Extract safe incident refs from button callback_data without storing it."""
reply_markup = payload.get("reply_markup")
if not isinstance(reply_markup, dict):
return []
incident_ids: set[str] = set()
for row in reply_markup.get("inline_keyboard") or []:
if not isinstance(row, list):
continue
for button in row:
if not isinstance(button, dict):
continue
callback_data = str(button.get("callback_data") or "")
incident_ids.update(_INCIDENT_ID_RE.findall(callback_data))
return sorted(incident_ids)
def _outbound_source_envelope(method: str, payload: dict) -> dict[str, object]:
"""Build a redaction-friendly source envelope for Channel Hub replay."""
text = str(payload.get("text") or payload.get("caption") or "")
incident_ids = sorted(set(_INCIDENT_ID_RE.findall(text)))
incident_ids = sorted(
set(_INCIDENT_ID_RE.findall(text))
| set(_reply_markup_incident_ids(payload))
)
code_refs = sorted(set(match.group(1) for match in _CODE_REF_RE.finditer(text)))
return {
"adapter": "legacy_telegram_gateway",

View File

@@ -49,3 +49,30 @@ def test_outbound_source_envelope_keeps_replay_context_without_raw_payload() ->
assert "approval-id-secret" not in str(envelope)
assert "1234567890:" not in str(envelope)
assert "ACTION REQUIRED" not in str(envelope)
def test_outbound_source_envelope_reads_incident_refs_from_buttons() -> None:
payload = {
"chat_id": "-100123",
"text": "ACTION REQUIRED without incident id in visible text",
"reply_markup": {
"inline_keyboard": [
[
{"text": "詳情", "callback_data": "detail:INC-20260525-ABC123"},
{"text": "歷史", "callback_data": "history:INC-20260525-ABC123"},
],
[
{"text": "重診", "callback_data": "reanalyze:INC-20260525-DEF456"},
],
],
},
}
envelope = _outbound_source_envelope("sendMessage", payload)
assert envelope["source_refs"]["incident_ids"] == [
"INC-20260525-ABC123",
"INC-20260525-DEF456",
]
assert envelope["reply_markup"]["buttons"][0]["callback_prefix"] == "detail"
assert "detail:INC-20260525-ABC123" not in str(envelope)

View File

@@ -20531,3 +20531,66 @@ GET /api/v1/health:
- KM governance約 84.6%。
- AI Provider lane visibility約 92.2%。
- 完整 AI 自動化管理產品化:約 97.35%。
---
## 2026-05-25 T186 — Telegram Outbound Source Refs Future Coverage
**背景**
- T183-T185 已把 callback evidence / snapshot status 做到可觀測。
- 下一個 source matching 缺口是 Telegram outbound `source_refs.incident_ids` 覆蓋率偏低:
```text
production read-only aggregate:
message_type triggered_by total source_refs incident_refs reply_markup callback_reply
final legacy_gateway 2465 2174 229 56 2
approval_request legacy_gateway 1477 1259 678 1261 0
error legacy_gateway 1307 1271 20 5 1
reply_markup first callback prefix:
approve 382 total / 332 with incident refs
silence 370 total / 95 with incident refs
detail 104 total / 99 with incident refs
drift_view 144 total / 0 with incident refs
```
- 既有 mirror 只從 Telegram visible text 抽 `INC-*`;若卡片文字沒有 incident但按鈕 callback_data 有 `detail:INC-*` / `history:INC-*`,未來 source matching 會漏掉。
- 既有 DB 只保存 redacted `callback_prefix`,不保存 raw callback_data因此舊資料不能安全反推補齊本輪先補「未來出站鏡像」。
**本輪修正**
- `_outbound_source_envelope()` 新增安全抽取:
- 從 `reply_markup.inline_keyboard[*].callback_data` 只抽 `INC-*`
- 仍只保存 `source_refs.incident_ids` 與既有 `callback_prefix` 摘要。
- 不保存 raw callback_data、approval nonce、Telegram token 或原始訊息文字。
- 新增 regression test確保
- visible text 沒 incident 時,也能從 `detail:INC-*` / `history:INC-*` / `reanalyze:INC-*` 抽 incident refs。
- envelope 不含 raw callback_data。
**local validation完成**
```text
python3 -m py_compile apps/api/src/services/telegram_gateway.py apps/api/tests/test_telegram_gateway_error_sanitizer.py
git diff --check
PYTHONPATH=. DATABASE_URL='postgresql+asyncpg://test:test@localhost/test' /Users/ogt/.pyenv/shims/pytest tests/test_telegram_gateway_error_sanitizer.py -q
3 passed in 0.56s
PYTHONPATH=. DATABASE_URL='postgresql+asyncpg://test:test@localhost/test' /Users/ogt/.pyenv/shims/pytest tests/test_telegram_message_templates.py tests/test_telegram_button_consistency.py -q
64 passed in 0.63s
```
**目前整體進度**
- AwoooP 告警可觀測鏈:約 99.60%。
- 低風險自動修復閉環:約 95.8%。
- 前端 AI 自動化管理介面同步:約 99.15%。
- 首頁 KPI / 小龍蝦流程 truth alignment約 96.5%。
- Telegram 詳情 / 歷史可追溯:約 99.0%。
- Telegram outbound / callback DB coverage 可視化:約 99.0%。
- callback / DB replayability約 98.5%。
- MCP / 自建 MCP 可視化:約 95.1%。
- Sentry / SigNoz source correlation約 93.9%。
- Ansible / PlayBook 可視化:約 92.6%。
- KM governance約 84.6%。
- AI Provider lane visibility約 92.2%。
- 完整 AI 自動化管理產品化:約 97.4%。