0d49989c19
真因(不是重裝洗掉,是從來沒存進去):前端唯一寫入路徑 POST /portal/admin/ai
**後端從來沒有這條 route** ⇒ 用戶填 key → 404 → 什麼都沒存,畫面卻像成功了(藍字=假綠)。
產物層鐵證:bundle tier2/ui grep 'portal/admin/ai'=1、tier2/cypher=0;兩實例實打皆 404。
反證(別改不壞的東西):走正確端點寫入 → 完整重裝 24/24 → credential 仍在、
rag_chat verdict failed→success ⇒ **重裝不洗 credential,資料層本來就是保留式的**。
① cypher 補 GET|POST /portal/admin/ai(routes/portal.ts)
- POST 內部轉呼 credentials.ts 既有 storeCredential()=**唯一寫入路徑**,不另造第二套
- GET 只回 has_key 布林,**永不回傳 key 本身**(D36)
- credential 的 api_key 欄=租戶 slug(portalTenant),與安裝器 seedCredential 寫
kbdb_internal_token 用的 ns 同值 ⇒ 同租戶分區,彼此查得到
- use_claude_for_extract 存 KV(單一布林偏好,不為它開 KBDB template)
- claude_available 目前無訊號來源(該由小幫手回報)⇒ **誠實回 false 不假綠**
- 寫入失敗回 502 帶原因——本 bug 的教訓就是「不能讓前端以為存好了」
- 兩者皆未提供 → 400,避免「看起來成功但什麼都沒做」
② 前端(console-ui/public/portal/index.html)
- 拔掉寫死 `|| "https://cypher.arcrun.dev"` fallback ⇒ 缺 config.js 就顯示紅色橫幅明顯報錯。
**寧可明顯失敗,不要靜默把用戶金鑰送去中央實例。**
⚠️ 更正我先前的錯誤診斷:config.js **有**正確注入(實查 leo 實例指向自己的 cypher),
跨租戶錯置未發生;這條是未爆彈不是現行災情。
- leo 規格:已存 → 唯讀「已輸入」+「修改」鈕(不顯示 key);按修改才變回輸入框;
未存 → 一般輸入框。舊版只改 placeholder=讀起來像臨時提示,是它像 UI bug 的原因。
- 只有真的送了新 key 才切回「已輸入」(單純改 Claude 勾選不誤報)
tsc 綠;三個 script 區塊 node --check 全過。
卷:system-dev/docs/3-specs/journeys/gemini-key-lost-on-reinstall.md