Files
awoooi/scripts/verify/verify_telegram_dedup_b3a0f0d7.sh
Your Name 8629ac709b
Some checks failed
run-migration / migrate (push) Failing after 59s
Code Review / ai-code-review (push) Successful in 1m8s
Type Sync Check / check-type-sync (push) Successful in 2m27s
feat(awooop): Phase 1-8 完整實作 — AwoooP Agent Platform 六平面架構
## Phase 1-3: Control Plane + Contract System
- awooop_phase1_control_plane_2026-05-04.sql: 12 張核心表 + RLS
- awooop_phase1_batch1_rls_2026-05-04.sql: 全部 FORCE RLS + GRANT
- packages/awooop-contracts/: 六合約 JSON Schema + golden fixtures
- src/models/awooop_contracts.py: Pydantic v2 contract models(extra=forbid)
- src/repositories/contract_repository.py: contract lifecycle(draft→published→active)
- src/services/contract_service.py: HMAC publish sig + Redis multi-sig activate
- src/services/schema_validator.py: LLM output validator(retry×3, E-SCHEMA-001)

## Phase 2: Tenant Isolation
- awooop_phase2_budget_ledger_2026-05-04.sql: budget_ledger + RLS
- src/services/budget_service.py: Token Budget Hard Kill 三層防線
- src/core/context.py: PROJECT_ID ContextVar(31 background loop 自動繼承)
- src/db/base.py + models.py: project_id 欄位 + RLS set_config 注入
- src/hermes/nl_gateway.py: project_id Redis key 前綴(Phase A 雙寫)
- src/services/anomaly_counter.py: per-project 改造(Phase A fallback)

## Phase 4: Platform Shell in Shadow Mode
- awooop_phase4_run_state_2026-05-04.sql: run_state + step_journal + idempotency
- src/services/run_state_machine.py: 8-state FSM + SKIP LOCKED + stale reaper
- src/services/platform_runtime.py: UUID v7 + W3C trace_id + shadow_execute
- src/services/audit_sink.py: PII/secret redaction 9 patterns
- src/api/v1/platform/runs.py: POST/GET /v1/platform/runs(Router→Service 架構)
- src/workers/platform_worker.py: SKIP LOCKED worker + heartbeat + reaper loop
- src/main.py: platform router + lifespan worker start/stop

## Phase 5: MCP Gateway 五閘門
- awooop_phase5_mcp_gateway_2026-05-04.sql: 4 表 + RLS
- src/plugins/mcp/gateway.py: McpGateway(Gate 1~5, E-MCP-GATE-001~009)
- src/plugins/mcp/redaction_middleware.py: 雙層 redaction + 16K 截斷
- src/plugins/mcp/registry.py: __provider name mangling(ADR-116)
- src/plugins/mcp/credential_resolver.py: k8s secret ref 解析
- tests/test_mcp_credential_isolation.py: 10 個迴歸測試(secret leak 防再現)

## Phase 6-8: EwoooC + Channel Hub + Approval Token
- awooop_phase6_ewoooc_onboarding_2026-05-04.sql: ewoooc tenant + 4 read-only MCP tools
- awooop_phase7_channel_hub_2026-05-04.sql: conversation_event + outbound_message
- src/services/provider_proxy.py: ProviderProxy + PlatformEnvelope(ADR-115)
- src/services/channel_hub.py: Telegram inbound mirror + Progressive Feedback(30s)
- src/services/awooop_approval_token.py: HS256 + jti NX replay 防護 + suggest mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:31:53 +08:00

111 lines
4.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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'])
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'])
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'])
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"