建議 model:opus/effort:medium——跨三個既有功能改金鑰讀法+installer 寫入,規格已定但要確保 AI 小幫手/Dashboard 行為零改變。
本卡屬 FR-107(母卡 CM-1843)第 .5 棒(子需求卡 CM-1848),子任務 T-5.5,決策者 2026-09-17 裁(D13:原廠 AI 金鑰)。依賴 T-5.4(守門常數)與 T-5.1。
這張卡做什麼(白話)
現在系統裡有三個地方會用到 AI:AI 小幫手(右下角聊天)、AI Dashboard(統計分析問答)、證據自動分類(本案)。前兩個直接讀伺服器的環境變數 ANTHROPIC_API_KEY/OPENAI_API_KEY/GOOGLE_API_KEY,證據分類已經改成優先讀資料庫設定、讀不到才退回環境變數,但餵給分類容器的那把鑰匙目前還是直接抓環境變數,沒有真的走資料庫那條路。
決策者定案:原廠(我們自己)會給客戶一組 AI 金鑰,這組鑰匙放進 ROOT 租戶(tenant_id=1)的系統設定表,用現有的加密機制存起來,不再要求裝機人員手動把明文金鑰貼進伺服器的 guidant.env 檔。三個 AI 功能都要改成統一的判斷順序:先看這個租戶自己有沒有填金鑰 → 沒有就用 ROOT(原廠)那把 → ROOT 也沒有就退回環境變數(開發機才會走到這層)。
第一版客戶自己不能在畫面上填自己的金鑰(那顆設定頁鎖給 ROOT 平台管理員),全部客戶共用原廠那把。開放客戶自填是以後的事,只改一個守門旗標就能打開,這次不用先做。
為什麼這樣做
- 落地版客戶機是原廠人員裝機,讓客戶機開箱就能用 AI 功能,不必額外去申請自己的金鑰
- 明文金鑰放在 .env 檔案裡風險比放加密的 DB 欄位高——.env 檔案容易被整包複製、备份、傳來傳去,DB 欄位至少多一層加密
- 三個 AI 功能各自一套讀法,以後要換金鑰或想開放客戶自填,要改三個地方、還可能漏改,統一成一份共用邏輯只需要改一次
怎麼做(逐步)
- ① 先查現況:AI 小幫手在 core/plugins/ai_bot.py 的 build_config(),AI Dashboard 在 di_containers/ai_dashboard/ai_dashboard_containers.py 的 _ai_api_keys(),證據分類在 core/plugins/evidence_classification.py 的 SystemConfigAiProviderAdapter.configured_providers()(只回傳「有沒有配置」的 bool,還沒有回傳實際金鑰值給誰用)——三處讀法完全不同,逐一確認接線點
- ② 查現有 AI_PROVIDER_CONFIG 的寫入是不是明文:實查 app/system_config/service/guarded_system_config_service.py 與底層 system_configs 表,目前只有「遮罩」(讀出去變成 is_set 旗標)沒有真正加密——這是本卡要補的第一件事。抓既有 Fernet 用法當範本:infra/cloud_integration/crypto/fernet_crypto.py(DRIVE_TOKEN_ENCRYPTION_KEY 那套),新加密鑰另開一個環境變數名(例如 AI_PROVIDER_ENCRYPTION_KEY),不要沿用 DRIVE_TOKEN_ENCRYPTION_KEY——不同用途共用一把加密鑰,其中一把外洩會牽連另一邊
- ③ 把 core/plugins/evidence_classification.py 的 SystemConfigAiProviderAdapter 抽成三功能共用的解析器(先查有沒有更合適的既有位置,找不到就放 common/ 或 app/system_config/ 下):輸入 tenant_id + provider,回傳「這個 provider 該用哪把金鑰」,內部邏輯=租戶自己的設定 → ROOT(tenant_id=1)的設定 → 環境變數,找不到回 None
- ④ AI 小幫手 core/plugins/ai_bot.py 的 build_config() 與 AI Dashboard di_containers/ai_dashboard/ai_dashboard_containers.py 的 _ai_api_keys() 改接這支共用解析器,取代直接 os.environ.get(...)。改完這兩個功能的行為必須零改變——沒有設定 ROOT 金鑰時退回 env,跟現在完全一樣
- ⑤ 證據分類容器的 container_env(同檔 :518-529 一帶 _CONTAINER_ENV_KEYS)改成從解析結果注入,不再直接讀 os.environ
- ⑥ install.sh 裝機流程加一步:把原廠給的金鑰寫進 ROOT 租戶的 AI_PROVIDER_CONFIG(呼叫既有的寫入服務,走加密,不寫進 guidant.env)。金鑰的來源是 install.conf(裝機人員填),比照現有 install.conf 處理 S3/物件儲存那段憑證的寫法。install.conf.example 只留欄位名與註解說明,值留空,不可入版控
- ⑦ install.sh 的 --upgrade 模式要跳過這步,不可覆寫客戶機上已經存在的 ROOT 金鑰設定(升級不能把裝機後可能已經被覆寫或客戶自己動過的設定蓋回去)
不要做的事
- 不寫進 guidant.env 或任何 .env 檔——原廠鑰只落 DB