5668d48d37
- portal.ts:export 共用 helpers(requirePortalUser/parseLibraries/kbdbFetch/run…) +graphSourceLibraries/hasGraphAccess/workflowsVisible;/portal/session 補 graph_allowed、workflows_visible(顯示提示,真閘在 /portal/data/* 路由層) - portal_library seed 加 graph_source slot;ensurePortalTemplates 冪等補舊 template 缺的 slots(updateRecord 對 template 外 slot 會 reject,不補就寫不進標記) - admin libraries PATCH 收 graph_source(boolean);toPublicLibrary 回 graph_source - kbdb-proxy /kbdb/search 與 /kbdb/entries 白名單加 library(tasks.md P1 順延一行) - PORTAL_SHOW_WORKFLOWS var(D-8,預設 admin) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8.0 KiB
8.0 KiB
portal-auth — Tasks(實作切分,每階段可獨立 PR)
狀態:全部未動工——本 SDD 待總管/leo 審後才開工(B 類流程:SDD 審 → 實作 PR → component-pr-review-standard → 總管 review → gated 部署 leo21c) 對應:Gitea #24/#25;design.md 各 §
依賴鏈:P1 → P2 → P3 → P4(P2 不依賴 P1 可並行起,但 P3 要兩者都齊)。
P1 — KBDB「庫」filter 地基(design §3.2/§3.3)|觸碰:kbdb/
- 開工第一件事:實測 Vectorize metadata filter
$in支援與否(決定主路徑 vs fan-out fallback)- 核實結論(2026-07-14):支援,走主路徑
$in,不需 fan-out fallback。證據:① 官方文件 developers.cloudflare.com/vectorize/reference/metadata-filtering/ 明列 8 運算子$eq/$ne/$in/$nin/$lt/$lte/$gt/$gte,「For $in and $nin, filter object values can be arrays of string, number, boolean, or null values」;② 本 repo@cloudflare/workers-types@4.20260702.1原生 typingVectorizeVectorMetadataFilterCollectionOp = "$in" | "$nin"(index.d.ts:15782), wrangler 4.98.0。限制:metadata filtering 只對 2023-12-06 之後建的 index 有效(本專案 index 皆是)。 本機無 Vectorize runtime,$in的線上實跑併入部署排練驗(見部署清單)。
- 核實結論(2026-07-14):支援,走主路徑
/entries/search+/entries加library多值參數(D1 json_extract IN+NULL→general fallback)- 實作註:SQL 用
COALESCE(json_extract(metadata_json,'$.library'),'general') IN (…)——與 design §3.3 的 OR 形狀語意完全等價(單組佔位符較簡)。本機 miniflare+local D1 十案例實跑全過(PR 附證據)。
- 實作註:SQL 用
- embed upsert metadata 加
library欄;semanticSearch filter 支援 library($in 主路徑,fan-out 不需)- 實作註:Vectorize 端在寫入時正規化(未標記→
'general')——Vectorize filter 做不了 COALESCE, 寫入端蓋章後查詢端單純$in;與 D1 查詢端 fallback 語意對齊。
- 實作註:Vectorize 端在寫入時正規化(未標記→
- Vectorize
librarymetadata index 建立步驟+reindex backfill 寫進部署清單- 部署清單(待雲端排練驗——本機無 Vectorize runtime,semantic 路徑 code 完成、線上實跑未驗):
wrangler vectorize create-metadata-index arcrun-kbdb-embed --property-name library --type stringPOST /embed/backfill {"reindex":true}分批到 remaining=0(index 只收建立後 upsert 的向量)- 驗收抽查:semantic 帶
library=過濾命中/不帶行為不變 - 掛號:
cli/src/lib/deploy.tsensureVectorizeMetadataIndexes()待補library(cli/ 屬 P1 派工範圍外「只動 kbdb」,隨 P3 或部署 PR 補——kbdb/wrangler.toml 註解已標)
- 部署清單(待雲端排練驗——本機無 Vectorize runtime,semantic 路徑 code 完成、線上實跑未驗):
- cypher
kbdb-proxy透傳library參數(供 owner/admin 面用;portal 面走 P3 的注入,不經這)- ⚠️ 範圍註(2026-07-14):P1 派工紅線「只動 kbdb、不碰 cypher-executor」與本項矛盾——照窄範圍 執行,本項順延(一行 query 透傳,隨 P3 動 cypher 時一併)。
- 測試:D1 filter 單元測、NULL fallback、多值、semantic filter(mock VECTORIZE)
kbdb/tests/library-filter.test.ts11 項(SQL 形狀/route 解析/向後相容/降級仍 enforce/$in 構造/ 寫入端正規化)+既有 6 項全綠(17/17);tsc exit 0。
- 驗收:curl
/entries/search?q=&library=finance只回 finance+未標記條目歸 general 可驗;semantic 同 - 工程量:小-中(0.5–1 個 CC 工作天)
P2 — portal_user 模型+認證 API(design §2/§4)|觸碰:cypher-executor/(新 route 檔)
- seed template
portal_user+portal_library(init-seed 慣例)- 實作註(2026-07-14):種子資料
cypher-executor/src/lib/portal-seeds.ts(*-seeds.ts 慣例類別); 冪等 ensure 在routes/portal.tsensurePortalTemplates(),掛進POST /init/seed(回應加portal_templates區塊)+ bootstrap 前自動 ensure(沒跑過 seed 也能 bootstrap)。
- 實作註(2026-07-14):種子資料
- PBKDF2-SHA256 雜湊模組(600k iters、常數時間比對、
pbkdf2-sha256$…格式)cypher-executor/src/lib/portal-auth.ts(WebCrypto deriveBits;verify 從儲存格式解析 iterations → 換 KDF/參數可共存漸進遷移;constantTimeEqual 同 mcp PR#15 實作)。
POST /portal/admin/bootstrap(console owner session 閘)- login/logout/session/改密碼(KV
portal_sess:、TTL var、每請求回讀 record 驗 status)PORTAL_SESSION_TTL進 wrangler.toml [vars]+types.ts(預設 604800)。
- admin 用戶 CRUD+libraries 授權+庫目錄 CRUD(role=admin 閘)
- 安全註:admin 操作目標 record 需過「email head entry 指回同 record_id」成員驗證(防拿任意 record_id 改到不相干 KBDB record);列表/回應一律剝除 password_hash;未帶密碼建帳號 → server 產一次性密碼隨回應回傳一次(不落地明碼)。
- 登入失敗節流(5 次/15 分鐘,KV TTL)
- 順手修
record-crud.tsupdateRecord grow 路徑漏 owner_id(design §2.2 附帶)- 修法:首查 JOIN entries 帶回 record 既有 owner_id,grow 的新 entry 沿用。回歸測試
kbdb/tests/record-grow-owner.test.ts(3 項:帶 owner/原無 owner 維持 null/非 grow 不 INSERT)。
- 修法:首查 JOIN entries 帶回 record 既有 owner_id,grow 的新 entry 沿用。回歸測試
- 測試:bootstrap 閘、登入對錯、停用即拒(session 立即失效)、role 閘、雜湊格式、
{tenant}::portal子 namespace 隔離(搜 email 搜不到)- 單元:
cypher-executor/tests/portal-auth.test.ts20 項(fetchMock 假 KBDB host,絕不外連); cypher 全套 133/134(唯一失敗=executor「不存在的零件」pre-existing,stash 複驗);kbdb 20/20; 兩包 tsc exit 0。端到端:本機雙 worker(隔離 persist)33/33 全綠,含搜尋隔離實測 (租戶 ns 搜 email/雜湊 0 筆+對照組 leo::portal 搜得到=非假綠)與明碼落地抽查 0 筆。
- 單元:
- 驗收(=#25 驗收):新增用戶能登入;停用登入被拒+既有 session 失效;KBDB 無新表;密碼抽查非明碼
- 工程量:中(約 1 個 CC 工作天)
P3 — /portal UI:登入殼+搜尋頁+設定頁+scope enforce(design §1/§3.3/§5/§6)|觸碰:cypher-executor/
- [🔄]
/portalHTML 殼(重用 console 樣式/搜尋 view 抽共用 helper;CONSOLE_BRAND品牌;零 Mira 字樣) - [🔄] 未登入只見登入殼;登入後兩頁:搜尋(keyword/semantic/graph 三模式+source 溯源+卡片詳頁)+設定(改密碼/看自己權限/主題)
- [🔄]
/portal/data/*server-side enforce:session→record→注入owner_id+library;前端絕不下發租戶字串 - [🔄] 卡片詳頁逐筆驗 library(越庫 id 直讀 → 404)
- [🔄] graph 粗閘(D-4:無 graph 來源庫權限 → 模式不顯示+API 403)
- [🔄] 測試:curl 帶 user session 直打 data API 驗 filter 繞不過(=#24 驗收 3 的 server-side 證明)
- 驗收(=#24 驗收):leo21c
/portal可開;未登入只見登入殼;三模式+溯源可用;A 用戶(僅 general)搜不到 finance 內容——UI 與 curl 雙驗 - 工程量:大(1–1.5 個 CC 工作天,UI 是最大件)
P4 — admin 頁+工作流顯示(design §6)|觸碰:cypher-executor/
- 帳號管理頁(admin-only nav):列表/新增/停用/重設密碼+每帳號庫權限勾選
- 庫目錄管理(登記/停用庫)
- 工作流頁(唯讀 list+最近執行;
PORTAL_SHOW_WORKFLOWS預設 admin;不開 trigger) - 驗收:admin 全流程「發帳號→授庫→同仁登入查詢→停用」在 UI 走通;一般用戶看不到 admin 頁與(預設下)工作流頁
- 工程量:中(約 1 個 CC 工作天)
第二波(不在本 SDD 動工範圍,掛號)
- MCP token 綁庫集合(design §9;PR#15 擴充,只動
mcp/) - graph 逐節點/逐邊細粒度過濾
- Google OAuth2 fast-follow、審計 log、忘記密碼 email 自助、舊資料 library 回填清潔工