Files
ewoooc/docs/phase1_final_critic_signoff_20260503.md
OoO 4648673423 db(p1): ai_calls/mcp_calls/budgets schema + bge-m3 signature
migrations 024/025/026 — 統一 LLM 遙測 + 預算告警 + RAG 一致性護欄
- 024: ai_calls 表 + 5 索引 + 6 CHECK constraint(H1/H2/M3/L3)
- 025: mcp_calls + ai_call_budgets + 10 種子預算(含 ollama_secondary)
- 026: ai_insights.embedding_signature + pgcrypto + CONCURRENTLY index

A11 critic 三輪審查記錄完整保留:
- Phase 1 schema review: 2 BLOCKER + 4 HIGH + 6 MEDIUM 全處理
- Phase 1 final sign-off: 0 BLOCKER + 2 HIGH + 4 MEDIUM
- Phase 6 ADR review: 5 BLOCKER + 6 HIGH 全修

Operation Ollama-First v5.0 / Phase 0+1+6 護欄

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:04:42 +08:00

20 KiB
Raw Blame History

Phase 1 Final Critic Sign-off — Operation Ollama-First v5.0

日期2026-05-03 / critic-A11第二輪 / 收尾) 審查範圍A3 / A4 / A5 / 第一輪 A11 修補的全部產出 基準文件docs/phase1_critic_review_20260503.md


Verdict

  • APPROVED — Phase 1 deploy ready
  • APPROVED WITH NOTES — 接受 deploy4 項 NOTE 統帥部署前/後處理即可
  • CONDITIONAL — 修以下後 deploy
  • REJECTED

理由:本輪沒有發現新 BLOCKER。前一輪 BLOCKER 中 B2pgcryptomigration 端已修補B1ai_usage_tracking ORM 落後 schema屬於既有技術債、不阻擋 v5.0 觀測層上線。Logger 與 token report 行為正確、52/52 unit test 通過、失敗安全與 PII 紀律執行到位。NOTE 主要是文件對齊與已知盲區Bot main path token=0 / chat_id 進 meta不影響 P1 觀測層收尾。


前一輪 BLOCKER / HIGH 處理確認

編號 等級 描述 本輪驗證
B1 🔴 ai_usage_tracking ORM 與實際欄位脫鉤(雙寫並存) ⚠️ 未動。ORM database/ai_models.py:72-109 仍是過時版;屬既有技術債,不影響 v5.0 觀測層A4/A5 一律走 raw SQL → ai_calls未碰 ai_usage_tracking。建議列入 Phase 12 deprecate roadmap。
B2 🔴 migration 026 DIGEST() 需 pgcrypto 已驗證migrations/026_add_embedding_signature.sql 頂部已加 CREATE EXTENSION IF NOT EXISTS pgcrypto;
H1 🟠 provider 白名單 CHECK 已驗證migrations/024:88-91 chk_ai_calls_provider 列出 7 個 provider 與 _PROVIDER_DISPLAY 完全一致。
H2 🟠 meta/error 大小護欄 已驗證024:104-109 meta ≤ 8192 / error ≤ 4096 octet。Python 端 set_error 也截 2000 字(ai_call_logger.py:168),雙保險。
H3 🟠 budgets 漏 nim/nim_via_elephant 已驗證025:170-199 7 個 provider × monthly + 3 條全供應商總額daily/weekly/monthly共 10 筆種子。
H4 🟠 idx 非 coveringlatency 樂觀) ⚠️ 文件層 — 不影響部署。
M1-M6/L3 🟡🔵 細節修補 全數在 024/025 内驗證通過。
M4 🟡 manager.py 未 import 新 model ⚠️ A4/A5 全走 raw SQL未建立 AICall ORM class所以 import 缺口不會引發 Base.metadata.create_all 漏表migrations 直接建表)。不阻擋
L1 🔵 ewoooc migration 編號衝突 🟡 未驗證 — 統帥 deploy 前手動 git fetch ewoooc && git log ewoooc/main --oneline -- migrations/ 即可。

小結:第一輪 BLOCKER 中可由 critic 自動修補的 1/2 已修B1 為設計文資料漂移,本輪確認 v5.0 觀測層完全不依賴 ai_usage_tracking所以解耦處理不阻擋 deploy


本輪新發現 Findings

BLOCKER

HIGH

H5. caller 欄位沒有 CHECK 白名單 — 本輪實際比對下發現先前 H1 修補只覆蓋 provider 不含 caller

  • 位置migrations/024_create_ai_calls_table.sql:55caller VARCHAR(64) NOT NULL+ :86-110 所有 CHECKchk_ai_calls_caller
  • 證據grep chk_ 在 024 共 7 條 constraint僅針對 provider/status/fallback/duration/meta/errorcaller 完全沒護欄
  • 影響A4 接入的 13 個 caller 名(hermes_intent/hermes_analyst/hermes_rule_engine/code_review_hermes/code_review_openclaw/code_review_elephant/openclaw_qa/openclaw_qa_nim/openclaw_weekly/openclaw_daily/openclaw_monthly/openclaw_meta/nemotron_dispatch/openclaw_bot_main/openclaw_bot_gemini/openclaw_bot_nim)若未來打字打錯(例如 openclae_qaDB 不會擋token 報表 GROUP BY caller 會出現假名稱,污染統計
  • 緩解:戰役 v5.0 收尾才標到的問題,本輪先 NOTE 不阻 deploy但建議 Phase 5 跑保留任務時加:
    ALTER TABLE ai_calls ADD CONSTRAINT chk_ai_calls_caller_known
      CHECK (caller ~ '^[a-z][a-z0-9_]{2,63}$') NOT VALID;
    
    (格式約束而非完整白名單,避免每次擴 caller 都改 schema
  • 嚴重度判定:本來 BLOCKER但因 v5.0 上線前 caller 名是集中於 ai_call_logger 的固定字串13 個全部 grep 過typo 風險可控 → 降為 HIGH。

H6. chat_id 寫入 ai_calls.meta — 屬 PIITelegram 用戶識別)

  • 位置routes/openclaw_bot_routes.py:6832, 6892, 6959, 7034
  • 證據4 個 Bot Q&A 入口的 meta={'chat_id': chat_id, ...} 全部把 Telegram chat_id 直接落地進 ai_calls.meta JSONB
  • 規格牴觸
    • services/token_report_service.py:18「PII 保護: 報表訊息不含 prompt 原文ai_insights metadata 只存統計 meta不存 username
    • feedback_user_input_html_injectionTelegram 用戶識別屬 user-controllable 欄位,不該以明文落地 90 天
  • 影響90 天保留期 + 萬一報表程式換成 raw query 可被反查;雖然 ai_calls.caller 維度不會直接顯示 chat_id但稽核時違反「Telegram username/chat_id 進 DB 必雜湊」原則
  • 建議修法(不阻 deploy可在 Phase 2 同步):
    meta={'chat_id_hash': hashlib.sha256(str(chat_id).encode()).hexdigest()[:12], ...}
    
    或乾脆改成 meta={'has_chat_id': bool(chat_id), ...}(只記是否屬聊天會話)
  • 嚴重度判定HIGH。短期內統帥即唯一 Telegram operator即所有 chat_id 來源,外洩面向小;但 PII 紀律一致性必須維持,所以列入 deploy 後 Phase 2 第一波 patch 清單。

MEDIUM

M7. openclaw_bot_main token 永遠記為 0 — Section 5「Ollama Tokens」會嚴重低估

  • 位置routes/openclaw_bot_routes.py:6834 ollama_service.generate(...) 回傳 OllamaResponse,但 OllamaResponse 沒有 prompt_eval_count/eval_count 欄位(services/ollama_service.py:88-95 dataclass 只有 success/content/model/error/total_duration/host
  • 影響
    • Bot 主鏈走 GCP Ollama 的所有 Q&A token 記為 0
    • Section 5「今日 Ollama Tokens vs 7 日均」失真
    • Section 1 ollama_pct 計算分子下偏 → 報表會顯示「Ollama 失守」假警報
  • 建議修法Phase 2 A6 修 ollama_service.py 時順便):
    @dataclass
    class OllamaResponse:
        success: bool
        content: str
        model: str
        error: Optional[str] = None
        total_duration: Optional[float] = None
        host: Optional[str] = None
        prompt_tokens: int = 0      # ← 新增
        completion_tokens: int = 0  # ← 新增
    
    並在 generate()data.get('prompt_eval_count')/eval_count 帶入。
  • 暫行處置A4 應在 routes/openclaw_bot_routes.py:6834 加 TODO 標記「待 ollama_service 補 token 欄位後接回」。本輪不阻 deploy。

M8. caller='openclaw_qa_nim'_call_nvidia_nim 動態組成,與 logger 端命名習慣脫鉤

  • 位置services/openclaw_strategist_service.py:737 nim_caller = f"{caller}_nim"
  • 影響Section 3 TOP caller 報表會出現 5 個 NIM 變體(openclaw_qa_nim/openclaw_weekly_nim/openclaw_daily_nim/openclaw_monthly_nim/openclaw_meta_nim
  • 判定:這其實是好設計(清楚標示 NIM 路徑由哪個原 caller fallback 來的),但與 H5 中提到「未來加 caller 白名單」邏輯衝突 → 若加 CHECK constraint 必須允許 _nim 後綴
  • 建議:不修;但 ADR-028 撰寫時要明文聲明此命名慣例。

M9. ai_insights INSERT 缺 confidence 欄位 — 走 default 0.5,但 token report 是規則引擎產出,理論該標 1.0

  • 位置services/token_report_service.py:790-799
  • 影響:未來 RAG 檢索時token report 的洞察會被當「中信度」混入;其實這是規則引擎死硬產出,應該標高信度
  • 建議修法INSERT 加 confidence 欄位設 1.0;或將 avg_quality 從 0.9 改為 1.0
  • 嚴重度MEDIUM不阻 deploy。

M10. daily_token_report 截斷邏輯雙重保險,但截斷點落在 HTML tag 中間會壞 parse_mode='HTML'

  • 位置
    • services/token_report_service.py:130 report_html[: _TELEGRAM_MAX_CHARS - 80]
    • services/telegram_templates.py:566 body[: _DAILY_TOKEN_REPORT_MAX_CHARS - 80]
  • 風險:若截斷剛好落在 <b>...</b> 之間(例如卡在 <b 後 1 字元Telegram sendMessage 會回 400 can't parse entities → 整則訊息 failscheduler log 出現 telegram_send error
  • 緩解:實務上 4000 字截斷觸發時,落在 HTML tag 中間機率 < 5%tag 密度低),但仍是已知 corner case
  • 建議修法:截斷後跑 re.sub(r'<[^>]*$', '', truncated) 把不完整的開 tag 砍掉
  • 嚴重度MEDIUM建議 Phase 2 修;不阻 deploy萬一觸發只是該日報表掉而已scheduler 不爆)。

LOW

L5. qwen3:14b 在戰役 v5.0 Frontier 升級表中提到,但 COST_TABLE 未列

  • 位置services/ai_call_logger.py:43-62
  • 驗證grep -n "qwen3" services/ai_call_logger.py services/token_report_service.py → 0 hit
  • 影響:未來 P2 把 NemoTron 改用 qwen3:14b 時,第一波寫入會走 _calc_costunknown model 路徑log warning 但成本回 0因為 qwen3 是本地 Ollama 也應為 0→ 行為正確但 noisy log
  • 建議:在 COST_TABLE 加:
    'qwen3:14b':         {'in': 0.0, 'out': 0.0},
    'qwen3:14b-q4_K_M':  {'in': 0.0, 'out': 0.0},  # 視 v5.0 量化版
    
  • 嚴重度LOWPhase 2 會修),不阻 deploy。

L6. total_cost_usd SUM 無 NUMERIC 上限保險

  • 位置services/token_report_service.py:195 COALESCE(SUM(cost_usd), 0)
  • 思考:單筆 cost_usd 是 NUMERIC(10,6)(上限 9999.999999)。若一日內呼叫 100,000 筆且每筆 ~$0.01SUM 仍 < 10K 安全
  • 判定v5.0 規模下不會炸;但 Phase 9 預算守門需重新評估 — 統帥可忽略。

L7. total_duration (Decimal) 隱式轉 float 可能損失精度

  • 位置services/token_report_service.py:30 from decimal import Decimal 但未使用
  • 影響dead import無功能影響
  • 建議:刪 line 30 import。

L8. Section 4 預算列在無預算時用 _pad('未設定預算', 10) 寬度可能斷行

  • 位置services/token_report_service.py:843-844
  • 驗證「未設定預算」5 個中文 = 10 寬度,剛好;無 padding 餘量。寫死 OK。
  • 嚴重度LOWFYI。

Unit test 實測

$ /opt/anaconda3/bin/python3 -m pytest tests/test_ai_call_logger.py tests/test_token_report_service.py -v 2>&1 | tail -30

tests/test_token_report_service.py::TestQueriesViaMock::test_query_top_callers_orders_by_tokens PASSED [ 76%]
tests/test_token_report_service.py::TestQueriesViaMock::test_query_cost_breakdown_filters_zero_cost PASSED [ 78%]
tests/test_token_report_service.py::TestSendDailyReport::test_send_happy_path PASSED [ 80%]
tests/test_token_report_service.py::TestSendDailyReport::test_send_truncates_oversized_message PASSED [ 82%]
tests/test_token_report_service.py::TestSendDailyReport::test_send_resilient_to_telegram_failure PASSED [ 84%]
tests/test_token_report_service.py::TestSendDailyReport::test_generate_returns_failure_msg_when_db_dies PASSED [ 86%]
tests/test_token_report_service.py::TestTelegramTemplate::test_daily_token_report_appends_footer PASSED [ 88%]
tests/test_token_report_service.py::TestTelegramTemplate::test_daily_token_report_truncates_to_4096 PASSED [ 90%]
tests/test_token_report_service.py::TestTelegramTemplate::test_daily_token_report_escapes_footer_url PASSED [ 92%]
tests/test_token_report_service.py::TestFormatHelpers::test_fmt_kb PASSED [ 94%]
tests/test_token_report_service.py::TestFormatHelpers::test_esc_handles_none PASSED [ 96%]
tests/test_token_report_service.py::TestFormatHelpers::test_budget_line_zero_budget PASSED [ 98%]
tests/test_token_report_service.py::TestFormatHelpers::test_trend_line_handles_zero_baseline PASSED [100%]

============================== 52 passed in 0.21s ==============================

結論52/52 全綠22 ai_call_logger + 30 token_report_service覆蓋:

  • happy/exception/explicit-fallback/set-error 三種 context manager 路徑
  • decorator + model_extractor + 例外 reraise
  • DB 失敗 swallow / async dispatch 失敗 swallow
  • COST_TABLE 各 provider 計算 + 未知 model + NIM 前綴自動 0 + 負數安全
  • AI_CALL_LOGGING_ENABLED 開關 + kill-switch 連續失敗
  • 6 條告警規則spike/gemini share/error rate/budget/gcp hit/cache+ insights 規則
  • 報表 6 段落齊全 + 4096 截斷 + HTML escape + DB fail 路徑
  • format helpersfmt_kb/esc/budget_line/trend_line邊界

通過 ai_call_logger 「DB 失敗永不影響主流程」鐵律test_db_failure_does_not_break_main_flow + test_async_dispatch_failure_swallowed 兩條測試直接證明。

通過「meta 不洩露 prompt 原文」鐵律test_meta_does_not_leak_raw_prompt_into_call_state + test_set_prompt_hash_truncates_to_12 兩條測試。


安全/PII 審計(六大類深審)

類別 結論 證據
A. logger 安全 失敗安全到位 _write_to_db 全 try/except / kill-switch 在連續 10 次失敗觸發 (_record_failure:80-89) / _async_write 走 daemon thread 不阻塞
B. PII 保護 ⚠️ 新發現 chat_id 進 metaH6 logger 本身只用 set_prompt_hash 雜湊;但 4 個 Bot 入口直接灌 chat_id 進 meta — 違反規格
C. SQL Injection 全參數化 _exec_query 強制走 SQLAlchemy text(sql), params7 條報表 SQL 全用 named param
D. HTML escape 對齊既有風格 _esc()&<> 三字元與 telegram_templates._html_escape 一致;所有 user-controlled (caller/model/error/insight text) 進 HTML 前都 escape
E. 路由 / cron 衝突 23:55 唯一 17 條既有 cron 中無 23:5x 區段資源競爭風險低DB query 預估 < 30s
F. 預算 0 / 除 0 全數防護 7 處潛在除 0 全部有 if X else default 守衛

與既有系統整合風險

1. A4 修改 vs Phase 2 A6 即將修改 — conflict 風險評估

檔案 A4Phase 1做了什麼 A6Phase 2將做什麼 Conflict 風險
services/ollama_service.py + get_host_label() / OllamaResponse.host 欄位 / host=self.host 4 處 預期擴 GCP/111 切換邏輯B2、補 token 欄位M7 🟡 中 — 都動同一個 OllamaResponse dataclass + generate() 主體;建議 A6 先 rebase 再開工
services/code_review_pipeline_service.py 3 處包 log_ai_callhermes/openclaw/elephant 修補 B3 / 接入 ad-hoc retry 🟡 中 — 都動 _hermes_scan / _openclaw_assess 主流程rebase 前先讀 A4 區塊
services/aider_heal_executor.py A4 未動 A6 將動 低 — 完全分離
routes/openclaw_bot_routes.py 4 處包 log_ai_callmain/gemini×2/nim 預期不會碰
services/hermes_analyst_service.py 2 處包 log_ai_call + 修 commit 00591c5 殘留 bug 預期不碰
services/nemoton_dispatcher_service.py 1 處包 log_ai_call 預期不碰
services/openclaw_strategist_service.py _call_gemini/_call_nvidia_nim 加 caller= 參數 預期不碰

建議

  • A6 開工前先 git pull origin main 拉到 A4 的 commit
  • ollama_service.py 時優先新增而非改既有 dataclass 欄位minimize merge conflict
  • code_review_pipeline_service.py 時保留現有 with log_ai_call(...) 包裝層,僅在内層修補

2. commit 00591c5 殘留 bug 修復確認

services/hermes_analyst_service.py:194-200:原本 commit 00591c5 動到 except Exception as e: 區塊時,誤把 logger.warning 抹除留下孤立 f-string。本輪 A4 順手修補:

except Exception as e:
    # NOTE: 修補 commit 00591c5 殘留的孤立 f-string原 logger.warning 被誤刪)
    logger.warning(
        f"[Hermes.intent] Ollama 連線失敗,降級規則引擎"
        f"model={HERMES_MODEL} error={type(e).__name__}: {e}"
    )
    _ctx.set_error(f"{type(e).__name__}: {e}")
    _ctx.fallback_to_caller('hermes_rule_engine')
    return None

已驗證logger.warning 完整呼叫 + ctx.set_error + fallback_to_caller 三件齊全。原 silent failure 反模式已破。


Phase 2 銜接建議

統帥批准 Phase 2 A6 開工前,建議先 commit Phase 1 的所有變動到 mainA6 才有乾淨 baseline。順序

git add migrations/024 migrations/025 migrations/026                  # A3
git add services/ai_call_logger.py services/token_report_service.py   # A4/A5
git add tests/test_ai_call_logger.py tests/test_token_report_service.py  # A4/A5 tests
git add services/hermes_analyst_service.py services/nemoton_dispatcher_service.py
git add services/openclaw_strategist_service.py services/code_review_pipeline_service.py
git add services/ollama_service.py routes/openclaw_bot_routes.py
git add run_scheduler.py services/telegram_templates.py
git add docs/phase0_audit_report_20260503.md docs/phase1_db_design_20260503.md
git add docs/phase1_critic_review_20260503.md docs/phase1_final_critic_signoff_20260503.md
git commit -m "[Phase 1] Operation Ollama-First v5.0 觀測層落地 (A3 migration / A4 logger 13 callers / A5 token report)"

部署後第一波驗證Phase 2 啟動前):

-- 1. CHECK constraint 全在
SELECT conname FROM pg_constraint WHERE conrelid='ai_calls'::regclass ORDER BY conname;

-- 2. 種子預算 10 筆
SELECT period, provider, budget_usd, alert_pct FROM ai_call_budgets ORDER BY period, provider NULLS FIRST;

-- 3. logger 寫入煙測A4 接入後第一次 LLM 呼叫應出現)
SELECT caller, provider, model, status, input_tokens, output_tokens, duration_ms
FROM ai_calls ORDER BY called_at DESC LIMIT 20;

-- 4. caller 分布(驗證 13 個白名單值)
SELECT caller, COUNT(*) FROM ai_calls GROUP BY caller ORDER BY 2 DESC;

-- 5. 失敗率(觀察 kill-switch 是否誤觸發)
SELECT status, COUNT(*) FROM ai_calls
WHERE called_at >= NOW() - INTERVAL '24h' GROUP BY status;

Phase 2 進度第 1 天觀察點:

  • SELECT count(*) FROM ai_calls; 應 ≥ 100戰役前審計 34 個呼叫點 / 一日 ~200 calls
  • SELECT count(DISTINCT caller) FROM ai_calls; 應 ≥ 13
  • 23:55 cron 第一次跑完應有 1 筆 ai_insights WHERE insight_type='daily_token_report'

NOTE 清單Sign-off 條件)

deploy 後 7 天内由統帥決策處理:

  • NOTE-1H5:考慮加 caller 格式 CHECK constraintNOT VALID 不阻既存資料)
  • NOTE-2H64 個 Bot 入口的 chat_id 改成 hash 後存Phase 2 第一波 patch
  • NOTE-3M7OllamaResponseprompt_tokens/completion_tokens 欄位 → 修復 openclaw_bot_main token=0 黑洞(與 Phase 2 A6 ollama_service 改動合併)
  • NOTE-4L1deploy 前手動驗 git fetch ewoooc && git log ewoooc/main --oneline -- migrations/

deploy 後 30 天可選優化:

  • M9 ai_insights confidence 標 1.0 / M10 HTML tag 截斷修補 / L5 qwen3:14b 進 COST_TABLE / L7Decimal dead import
  • B1 ai_usage_tracking ORM 對齊真實 schema雙寫 deprecate roadmap與 ADR-028 合併)

Final Sign-off

critic-A11 / 2026-05-03 / Phase 1 final closure
Verdict: APPROVED WITH NOTES
Tests:   52/52 PASSED (22 ai_call_logger + 30 token_report)
Findings (本輪新發現): 0 BLOCKER / 2 HIGH (H5/H6) / 4 MEDIUM (M7-M10) / 4 LOW (L5-L8)
Findings (前輪殘留):   1 HIGH (H4 文件) / 1 MEDIUM (M4 解耦) / 1 LOW (L1 統帥手驗)

簽署critic-A11 (Operation Ollama-First v5.0 / Phase 1 sign-off)