#!/usr/bin/env bash # Telegram dedup 修復驗證 — commit b3a0f0d7 (fingerprint dedup + 24h TTL) # 部署時間: 2026-05-02 16:25 Asia/Taipei # 用法: ssh wooo@192.168.0.121 'bash -s' < verify_telegram_dedup_b3a0f0d7.sh # 或 scp 上去後 sudo bash verify_telegram_dedup_b3a0f0d7.sh # 純讀,不寫任何 prod 資料 set -e POD=$(sudo kubectl get pods -n awoooi-prod -l app=awoooi-api -o jsonpath='{.items[0].metadata.name}') echo "=== Pod: $POD ===" echo "=== Image SHA (應含 b3a0f0d7) ===" sudo kubectl get pod -n awoooi-prod "$POD" -o jsonpath='{.spec.containers[0].image}' echo echo echo "=== A. 過去 1h Telegram 發送 top(部署後)===" sudo kubectl exec -n awoooi-prod "$POD" -- python -c " import asyncio, os, asyncpg async def q(): conn = await asyncpg.connect(os.environ['DATABASE_URL']) await conn.execute(\"SELECT set_config('app.project_id', 'awoooi', FALSE)\") rows = await conn.fetch(\"\"\" SELECT COALESCE(i.title, 'unknown') AS alertname, COALESCE(i.affected_services[1], 'unknown') AS target, COUNT(t.id) AS msg_count, MIN(t.created_at) AS first_sent, MAX(t.created_at) AS last_sent FROM notification_outcomes t JOIN approval_records a ON t.approval_id = a.id JOIN incidents i ON a.incident_id = i.id WHERE t.channel='telegram' AND t.created_at > now() - interval '1 hour' GROUP BY 1,2 ORDER BY 3 DESC LIMIT 10 \"\"\") for r in rows: print(f\" {r['msg_count']:>3} | {r['alertname'][:40]:<40} | {r['target'][:30]:<30} | first={r['first_sent']:%H:%M} last={r['last_sent']:%H:%M}\") await conn.close() asyncio.run(q()) " echo echo "=== B. 過去 24h(含部署前對照)===" sudo kubectl exec -n awoooi-prod "$POD" -- python -c " import asyncio, os, asyncpg async def q(): conn = await asyncpg.connect(os.environ['DATABASE_URL']) await conn.execute(\"SELECT set_config('app.project_id', 'awoooi', FALSE)\") rows = await conn.fetch(\"\"\" SELECT COALESCE(i.title, 'unknown') AS alertname, COALESCE(i.affected_services[1], 'unknown') AS target, COUNT(t.id) AS msg_count FROM notification_outcomes t JOIN approval_records a ON t.approval_id = a.id JOIN incidents i ON a.incident_id = i.id WHERE t.channel='telegram' AND t.created_at > now() - interval '24 hours' GROUP BY 1,2 ORDER BY 3 DESC LIMIT 10 \"\"\") for r in rows: print(f\" {r['msg_count']:>3} | {r['alertname'][:40]:<40} | {r['target'][:30]:<30}\") await conn.close() asyncio.run(q()) " echo echo "=== C. 截圖兩 INC 最後發送時刻 ===" sudo kubectl exec -n awoooi-prod "$POD" -- python -c " import asyncio, os, asyncpg async def q(): conn = await asyncpg.connect(os.environ['DATABASE_URL']) await conn.execute(\"SELECT set_config('app.project_id', 'awoooi', FALSE)\") rows = await conn.fetch(\"\"\" SELECT i.id, i.title, COUNT(t.id) AS total_24h, MAX(t.created_at) AS last_sent, COUNT(t.id) FILTER (WHERE t.created_at > '2026-05-02 16:25 Asia/Taipei'::timestamptz) AS post_deploy FROM notification_outcomes t JOIN approval_records a ON t.approval_id = a.id JOIN incidents i ON a.incident_id = i.id WHERE i.id IN ('INC-20260501-6FE3BD','INC-20260502-FD6E21') AND t.channel='telegram' AND t.created_at > now() - interval '24 hours' GROUP BY 1,2 ORDER BY 1 \"\"\") for r in rows: print(f\" {r['id']} | {r['title'][:40]:<40} | 24h={r['total_24h']} 部署後={r['post_deploy']} last={r['last_sent']:%H:%M}\") await conn.close() asyncio.run(q()) " echo echo "=== D. Redis dedup key 結構(fingerprint 應已建立)===" sudo kubectl exec -n awoooi-prod "$POD" -- python -c " import asyncio, os from redis.asyncio import Redis async def q(): r = Redis.from_url(os.environ['REDIS_URL']) fp_keys = await r.keys('telegram_sent:fp:*') inc_keys = await r.keys('telegram_sent:INC-*') print(f' telegram_sent:fp:* (新格式) = {len(fp_keys)} (應 > 0)') print(f' telegram_sent:INC-* (舊格式) = {len(inc_keys)} (應 = 0 或減少中)') if fp_keys: print(f' 範例 fp key: {fp_keys[0].decode() if isinstance(fp_keys[0], bytes) else fp_keys[0]}') sweeper_keys = await r.keys('sweeper_done:*') print(f' sweeper_done:* = {len(sweeper_keys)} (24h TTL,整個 INVESTIGATING 集合)') asyncio.run(q()) " echo echo "=== 驗收標準 ===" echo "✅ A 段任何 fingerprint msg_count ≤ 2 → 修復生效" echo "✅ C 段兩 INC 部署後 ≤ 1 → 鐵證生效" echo "✅ D 段 telegram_sent:fp:* 已建立 → 新 dedup 邏輯有跑" echo "❌ 任何 fingerprint 部署後仍 ≥ 5 → 未生效,回報 Claude"