4b6cc159b8
實作 pending-changes.md「認證儲存要不要搬回 D1/KV」提案(commit 8286c8a): D61 的病根「重裝時 binding 被安裝器照名字重新指到新建空資源」已被更通用的 shared/resource-rule(Arcrun#97,2026-08-13)解掉,故不再需要繞開 binding 去躲這個病——而繞開的代價正是這次要收的債:Workers Secrets 寫入需要外部 CF_SECRETS_API_TOKEN,這把 token 從安裝那天起就沒被種過,止血版只解掉 「建第一個帳號」這一格,之後的每一次寫入(換密碼/加帳號/改權限)仍卡死。 改動: - console 管理員帳密:家改回 SESSIONS_KV(binding,console-auth.ts) - portal 多人帳號:家改回 KBDB(binding,走 base HTTP API,D38 零 SQL,portal.ts) - D61 認證儲存(CF Workers Secrets)留為舊實例的唯讀回退路徑:讀取零成本、 零外部憑證需求(只有寫入才要 token);登入成功即 best-effort 自動搬進新家, 且**這次登入發出的 session 就直接指向新 record_id**(不必等下一次登入) - D61 的三項「明顯失敗」語意全部保留:auth_store_empty(讀不到不算密碼錯、 不計入鎖定)、/console/setup 遇既有帳號說清楚密碼沒被採用、/health 與 /console/auth-status 吐儲存狀態 - 移除止血版的 x-arcrun-install-token 表頭傳遞機制(installToken 參數)—— 帳號寫入從此不需要任何外部 CF token,這個結構性缺口已從根拔除 測試:cypher-executor 全套 vitest 439/453(14 個既存失敗與本改動無關,已用 git stash 對照 clean checkout 逐一比對檔名確認完全相同);tsc --noEmit 無新增錯誤(3 個既存錯誤同上核實無關)。已跑 build-worker-artifacts.mjs 重打 tier2 bundle,grep 複驗 createKbdbUserRecord/promoteToKbdb 進了成品、 promoteLegacyUser/x-arcrun-install-token 完全從成品消失。 未覆蓋:POST /credentials(一般 workflow API 金鑰儲存)仍依賴 CF_SECRETS_API_TOKEN——這是 01-tech-stack.md 既有的、獨立於 D61 之外的 credential 儲存架構(D19「擁有目錄不擁有內容物」),本提案範圍只涵蓋「認證」 (登入帳密),不涵蓋一般 credential 儲存;07-29 已知缺口仍待另案處理。 不准 merge 進 main(SDD 鐵律③,等總管審過再併);不准部署(D20 出貨閘)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
3.9 KiB
TypeScript
65 lines
3.9 KiB
TypeScript
import { Hono } from 'hono';
|
||
import type { Bindings } from '../types';
|
||
import { readAuthStore } from '../lib/portal-auth-store';
|
||
|
||
export const healthRouter = new Hono<{ Bindings: Bindings }>();
|
||
|
||
/**
|
||
* 認證儲存狀態(2026-08-14 起:console 帳密住 SESSIONS_KV、portal 帳號住 KBDB,
|
||
* 兩者皆 binding-based,不再需要外部 CF token 才寫得進去——見 console-auth.ts/portal.ts
|
||
* 檔頭「D61 補充」說明)。`legacy_*_present` 只回是否還有 D61 時代留在認證儲存(CF Workers
|
||
* Secrets)裡尚未搬遷的資料,不洩漏任何 email/雜湊內容。
|
||
*/
|
||
function authStoreStatus(env: Bindings): {
|
||
console: { home: 'sessions-kv'; writable: true; legacy_secrets_present: boolean };
|
||
portal_users: { home: 'kbdb'; writable: true; legacy_secrets_present: boolean };
|
||
} {
|
||
const legacy = readAuthStore(env);
|
||
return {
|
||
console: { home: 'sessions-kv', writable: true, legacy_secrets_present: legacy.console !== null },
|
||
portal_users: { home: 'kbdb', writable: true, legacy_secrets_present: legacy.users.length > 0 },
|
||
};
|
||
}
|
||
|
||
// t162(leo 07-31 實撞:「小幫手一直顯示知識庫需要更新…重新更新後並不會消失」):
|
||
// daemon cloudVersionStale() 讀 /health 的 `bundle_version` 判斷是否過舊——
|
||
// 但本端點過去只回 {ok:true},**從沒吐這個欄位** ⇒ daemon 恆讀到空字串
|
||
// ⇒ 恆判 stale ⇒ 假警報永遠不消失(安裝器其實一直有注入 ARCRUN_BUNDLE_VERSION var,
|
||
// 只是沒有人把它吐出來)。修=誠實回報本實例的 bundle 版本。
|
||
// 未注入(本地 dev/很舊的實例)就省略該欄——daemon 對空字串仍判 stale,
|
||
// 那是**正確的**(真的是老實例,該更新)。
|
||
// D61(ADR D61 / Leo/arcrun-rag#55):多吐一個 `auth_store`——「認證住哪、寫不寫得進去」
|
||
// 要在實例自己這一側就看得出來,不是等用戶登不進去才發現(#10「寧可明顯失敗」)。
|
||
// 只回統計不回內容(帳號數/有沒有 console 帳密/分片數),不洩漏任何 email 或雜湊。
|
||
// bundle_version 的既有行為不動(未注入就省略該欄——daemon 對空字串判 stale 是正確的)。
|
||
// Arcrun#106(leo 08-12 實撞:更新完設定頁變成「無法讀取目前版本」):
|
||
// `bundle_version` 只在部署時被注入,而**只有安裝器會注入**——CLI 更新那條路重部署
|
||
// 等於把這個標籤洗掉(wrangler deploy 整份覆蓋,toml 沒寫的 var 直接消失)。
|
||
// 修在 CLI 那側(cli/src/lib/deploy.ts:既有 var 沿用 + 版本標籤每趟重烙)。
|
||
// 這裡只多吐一個 `bundle_commit`:版號是「發行頻道的編號」,commit 才是「真的部了哪份碼」——
|
||
// 兩個一起看才有辦法查「標籤有沒有跟成品漂掉」。沒注入就省略該欄(同 bundle_version 的既有行為)。
|
||
healthRouter.get('/health', (c) => {
|
||
const bundleVersion = c.env.ARCRUN_BUNDLE_VERSION;
|
||
const bundleCommit = c.env.ARCRUN_BUNDLE_COMMIT;
|
||
return c.json({
|
||
ok: true,
|
||
...(bundleVersion ? { bundle_version: bundleVersion } : {}),
|
||
...(bundleCommit ? { bundle_commit: bundleCommit } : {}),
|
||
auth_store: authStoreStatus(c.env),
|
||
// arcrun-rag#38/#69/#25(2026-08-11):安裝器判斷「要不要重推」只比 bundle_version——
|
||
// 但這次要修的洞是「installer 從沒注入過 PORTAL_MAIL_RELAY_BASE」,跟 bundle 內容
|
||
// 版本無關(同一個 cypher 版本,有的實例有這個 var、有的沒有)。純比版本號的話,
|
||
// 已經在最新版的實例(如 leo 自己那台)永遠不會因為「按更新」而重推,這個 var
|
||
// 就永遠補不進去。只回布林(有沒有設,不回值本身)——不洩漏郵差網址。
|
||
mail_relay_configured: Boolean(String(c.env.PORTAL_MAIL_RELAY_BASE ?? '').trim()),
|
||
});
|
||
});
|
||
|
||
healthRouter.get('/', (c) =>
|
||
c.json({
|
||
service: 'arcrun-cypher-executor',
|
||
version: '1.0.0',
|
||
status: 'ok',
|
||
})
|
||
);
|