Files
Arcrun/system-dev/docs/3-specs/portal-auth/tasks.md
T
uncle6me-web 0860e84d22 feat(t135): 庫目錄可自主移除+標示還在不在同步
leo:「需要加移除按鈕。因為別人裝錯我沒辦法幫他弄,需要可以自主」
+「你應該要顯示這個庫沒有本地對應的 folder,那就不容易刪錯」
(兩態非三態——leo 二修:「分兩種沒意義」,那是內部狀態不是用戶分類)。
- DELETE /portal/admin/libraries/:id(登記簿)與 by-name/:name(auto 庫需輸入庫名確認)
- kbdb 加 deprecate-by-library(auto 庫移除=標 deprecated,資料保留可還原)
- daemon/libraries 存 active 清單 → 卡片標 🟢同步中/灰目前沒有在同步
- 不自動刪(daemon 可能沒開機);daemon 從未回報時整列不標
vitest 24 passed(1 紅=console HTML 搬遷陳舊測試,非本案)。
(實作=子 CC;驗證+commit=總管。含 t116/t117 先前未 commit 的 graph-executor/wasi-shim 修正)
2026-07-29 14:45:44 +08:00

298 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# portal-auth — Tasks(實作切分,每階段可獨立 PR)
> 狀態:**全部未動工——本 SDD 待總管/leo 審後才開工**(B 類流程:SDD 審 → 實作 PR → component-pr-review-standard → 總管 review → gated 部署 leo21c
> 對應:Gitea #24/#25design.md 各 §
依賴鏈:P1 → P2 → P3 → P4(P2 不依賴 P1 可並行起,但 P3 要兩者都齊)。
---
## P1 — KBDB「庫」filter 地基(design §3.2/§3.3)|觸碰:`kbdb/`
- [x] 開工第一件事:實測 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`
原生 typing `VectorizeVectorMetadataFilterCollectionOp = "$in" | "$nin"`index.d.ts:15782),
wrangler 4.98.0。限制:metadata filtering 只對 2023-12-06 之後建的 index 有效(本專案 index 皆是)。
本機無 Vectorize runtime`$in` 的**線上實跑**併入部署排練驗(見部署清單)。
- [x] `/entries/search``/entries``library` 多值參數(D1 json_extract INNULL→general fallback
- 實作註:SQL 用 `COALESCE(json_extract(metadata_json,'$.library'),'general') IN (…)`——與 design §3.3
的 OR 形狀語意完全等價(單組佔位符較簡)。本機 miniflare+local D1 十案例實跑全過(PR 附證據)。
- [x] embed upsert metadata 加 `library` 欄;semanticSearch filter 支援 library**$in 主路徑**fan-out 不需)
- 實作註:Vectorize 端在**寫入時正規化**(未標記→`'general'`)——Vectorize filter 做不了 COALESCE
寫入端蓋章後查詢端單純 `$in`;與 D1 查詢端 fallback 語意對齊。
- [x] Vectorize `library` metadata index 建立步驟+reindex backfill 寫進部署清單
- **部署清單(待雲端排練驗——本機無 Vectorize runtimesemantic 路徑 code 完成、線上實跑未驗)**:
1. `wrangler vectorize create-metadata-index arcrun-kbdb-embed --property-name library --type string`
2. `POST /embed/backfill {"reindex":true}` 分批到 remaining=0index 只收建立後 upsert 的向量)
3. 驗收抽查:semantic 帶 `library=` 過濾命中/不帶行為不變
4. 掛號:`cli/src/lib/deploy.ts` `ensureVectorizeMetadataIndexes()` 待補 `library`cli/ 屬 P1
派工範圍外「只動 kbdb」,隨 P3 或部署 PR 補——kbdb/wrangler.toml 註解已標)
- [x] cypher `kbdb-proxy` 透傳 `library` 參數(供 owner/admin 面用;portal 面走 P3 的注入,不經這)
- ⚠️ 範圍註(2026-07-14):P1 派工紅線「只動 kbdb、不碰 cypher-executor」與本項矛盾——照窄範圍
執行,本項順延(一行 query 透傳,隨 P3 動 cypher 時一併)。
- ✅ 已隨 P3 補上(2026-07-14 P3 PR):`/kbdb/search``/kbdb/entries` 透傳白名單各加 `library`
- [x] 測試:D1 filter 單元測、NULL fallback、多值、semantic filtermock VECTORIZE
- `kbdb/tests/library-filter.test.ts` 11 項(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 模型+認證 APIdesign §2/§4)|觸碰:`cypher-executor/`(新 route 檔)
- [x] seed template `portal_user``portal_library`init-seed 慣例)
- 實作註(2026-07-14):種子資料 `cypher-executor/src/lib/portal-seeds.ts`*-seeds.ts 慣例類別);
冪等 ensure 在 `routes/portal.ts` `ensurePortalTemplates()`,掛進 `POST /init/seed`(回應加
`portal_templates` 區塊)+ bootstrap 前自動 ensure(沒跑過 seed 也能 bootstrap)。
- [x] PBKDF2-SHA256 雜湊模組(600k iters、常數時間比對、`pbkdf2-sha256$…` 格式)
- `cypher-executor/src/lib/portal-auth.ts`WebCrypto deriveBitsverify 從儲存格式解析 iterations
→ 換 KDF/參數可共存漸進遷移;constantTimeEqual 同 mcp PR#15 實作)。
- [x] `POST /portal/admin/bootstrap`console owner session 閘)
- [x] login/logout/session/改密碼(KV `portal_sess:`、TTL var、每請求回讀 record 驗 status
- `PORTAL_SESSION_TTL` 進 wrangler.toml [vars]types.ts(預設 604800)。
- [x] admin 用戶 CRUDlibraries 授權+庫目錄 CRUDrole=admin 閘)
- 安全註:admin 操作目標 record 需過「email head entry 指回同 record_id」成員驗證(防拿任意
record_id 改到不相干 KBDB record);列表/回應一律剝除 password_hash;未帶密碼建帳號 →
server 產一次性密碼隨回應回傳一次(不落地明碼)。
- [x] 登入失敗節流(5 次/15 分鐘,KV TTL
- [x] 順手修 `record-crud.ts` updateRecord grow 路徑漏 owner_iddesign §2.2 附帶)
- 修法:首查 JOIN entries 帶回 record 既有 owner_idgrow 的新 entry 沿用。回歸測試
`kbdb/tests/record-grow-owner.test.ts`3 項:帶 owner/原無 owner 維持 null/非 grow 不 INSERT)。
- [x] 測試:bootstrap 閘、登入對錯、停用即拒(session 立即失效)、role 閘、雜湊格式、`{tenant}::portal` 子 namespace 隔離(**搜 email 搜不到**
- 單元:`cypher-executor/tests/portal-auth.test.ts` 20 項(fetchMock 假 KBDB host,絕不外連);
cypher 全套 133/134(唯一失敗=executor「不存在的零件」pre-existingstash 複驗);kbdb 20/20
兩包 tsc exit 0。端到端:本機雙 worker(隔離 persist33/33 全綠,含**搜尋隔離實測**
(租戶 ns 搜 email/雜湊 0 筆+對照組 leo::portal 搜得到=非假綠)與明碼落地抽查 0 筆。
- **驗收**=#25 驗收):新增用戶能登入;停用登入被拒+既有 session 失效;KBDB 無新表;密碼抽查非明碼
- **工程量**:中(約 1 個 CC 工作天)
## P3 — `/portal` UI:登入殼+搜尋頁+設定頁+scope enforcedesign §1/§3.3/§5/§6)|觸碰:`cypher-executor/`
- [x] `/portal` HTML 殼(重用 console 樣式/搜尋 view 抽共用 helper`CONSOLE_BRAND` 品牌;零 Mira 字樣)
- 實作註(2026-07-14):`routes/portal-ui.ts` 單檔 HTML+原生 JSconsole 同形態)。**UI 重用取捨=
複製 console 樣式子集而非抽共用模組**——抽共用要動 console.ts(違「console 現狀不動」鐵律),
代價=樣式雙份、改版要兩邊同步(PR 說明誠實記)。零 Mira 字樣由測試機械斷言。
- [x] 未登入只見登入殼;登入後兩頁:搜尋(keyword/semantic/graph 三模式+source 溯源+卡片詳頁)+設定(改密碼/看自己權限/主題)
- graph 模式按 session `graph_allowed` 顯示(D-4);工作流頁 admin 額外可見(D-8,
`PORTAL_SHOW_WORKFLOWS` 預設 admin,唯讀不開 trigger)——前端藏只是 UX,真閘在路由層。
- [x] `/portal/data/*` server-side enforcesession→record→注入 `owner_id``library`**前端絕不下發租戶字串**
- `routes/portal-data.ts`(安全核心):caller 自帶 owner_id/library 一律被 server 值靜默覆蓋
e2e 攻擊實測);`["*"]`=不注 library;HTML 殼經測試機械斷言零租戶字串/零
X-Arcrun-API-Key/不打 /kbdb/*。
- [x] 卡片詳頁逐筆驗 library(越庫 id 直讀 → 404
- 雙重驗證:owner_id=CONSOLE_TENANT(防拿跨租戶 id 直讀)+library∈用戶集合(NULL→general);
越庫/跨租戶/不存在**同一句 404**(不洩存在性,e2e 逐字比對)。
- [x] graph 粗閘(D-4:無 graph 來源庫權限 → 模式不顯示+API 403)
- 來源庫=portal_library `graph_source='true'` 集合(seed 補 slotensurePortalTemplates 冪等
grow 舊 template 缺的 slotsadmin libraries PATCH 可標記/取消),**沒有任何標記→預設
['general']**D-4 定案);無權 403、前端模式鈕不顯示;停用庫不算來源。
- [x] 測試:curl 帶 user session 直打 data API 驗 filter 繞不過(=#24 驗收 3 的 server-side 證明)
- 單元:`cypher-executor/tests/portal-data.test.ts` 21 項(fetchMock 假 host,絕不外連);
cypher 全套 154/155(唯一失敗=executor「不存在的零件」pre-existingstash 複驗);
kbdb 20/20;兩包 tsc exit 0。端到端:本機隔離雙 worker9790/9791demo 8787/8788 不碰)
**47/47 全綠**——含越庫 id 直讀 404、攻擊參數被覆蓋、graph 403→授 general 後放行、
admin 全庫+跨租戶不可見、權限 PATCH 即時生效、HTML 殼斷言+inline JS node --check、
改密碼全鏈。`wrangler deploy --dry-run` 打包過。
- **驗收**=#24 驗收):leo21c `/portal` 可開;未登入只見登入殼;三模式+溯源可用;A 用戶(僅 general)搜不到 finance 內容——UI 與 curl 雙驗
- 本機端到端已全數驗過(上項證據);**leo21c 線上面=部署後驗**gated wrangler 直推,B 類流程;
semantic 線上路徑仍依 P1 部署清單的 Vectorize indexreindex)。
- **工程量**:大(1–1.5 個 CC 工作天,UI 是最大件)
## P4 — admin 頁+工作流顯示(design §6)|觸碰:`cypher-executor/`
- [x] 帳號管理頁(admin-only nav):列表/新增/停用/重設密碼+每帳號庫權限勾選
- 實作註(2026-07-14):`portal-ui.ts` 加「管理」頁(v-adminnav 只對 role=admin 顯示,
真閘=/portal/admin/* role 閘 403)。新增未帶密碼 → server `generatePassword` 一次性
密碼**只在畫面顯示一次**(otpbox,關閉即逝;server 只存 hash);每帳號庫勾選含
`["*"]` 全庫選項(勾選連動停用個別庫框);可勾清單=庫目錄啟用中 ∪ general ∪ 該帳號
既有授權(目錄外舊授權不憑空消失)。admin API P2 已交付不重做;本波補
**last-admin 鎖死保護**(總管派工明定,design 未明寫——PATCH 停用/降級「最後一個
active admin」→ 409;只在目標是 active admin 且 patch 會使其失格時多打一次 list)。
- [x] 庫目錄管理(登記/停用庫)
- 同頁第二區:登記(name/display_name/description)+停用/啟用+ `graph_source`
圖譜來源標記(D-4,P3 已有 API,本波補 UI)。
- [x] 工作流頁(唯讀 list+最近執行;`PORTAL_SHOW_WORKFLOWS` 預設 admin;不開 trigger)
- **P3 已交付**`/portal/data/workflows` routev-workflows 頁+D-8 閘),P4 沿用零改動。
- **驗收**:admin 全流程「發帳號→授庫→同仁登入查詢→停用」在 UI 走通;一般用戶看不到 admin 頁與(預設下)工作流頁
- 單元:`tests/portal-admin.test.ts` 14 項(鎖死保護含「另一 admin 是 disabled 不算數」
「停用一般 user 不多打 list」;一次性密碼/reset 明碼不進 KBDB bodyHTML 殼加 admin 頁後
仍零租戶字串/零 /kbdb//零 API key/零 Mira)。cypher 全套 168/169(唯一失敗=executor
「不存在的零件」pre-existing);kbdb 20/20;兩包 tsc 0dry-run 打包過;inline JS
node --check 過(2 段)。
- 端到端:本機隔離雙 worker9790/9791demo 8787/8788 不碰)**41/41 全綠**——bootstrap→
admin 建帳號拿一次性密碼→新 user 登入→勾庫後**同一 session 搜尋範圍即時變化**(1 筆→2 筆、
越庫 404→200)→停用 session 立即失效→重設密碼舊密 401 新密 200→last-admin 409(含降級
即時生效:原 admin session 打 admin API 變 403)→一般 user 打 admin API 403。
- leo21c 線上面=部署後驗(gated wrangler 直推,B 類流程)。
- **工程量**:中(約 1 個 CC 工作天)
---
## BugfixP1-P4 交付後)
- [x] **PBKDF2 600k → 100kT6-cloud 部署抓到的框架蟲,2026-07-14,分支 `fix-pbkdf2-cf-limit`**
CF Workers 正式 runtime PBKDF2 上限 100,000 iterations600k 時 `crypto.subtle.deriveBits`
直接拒絕 → `/portal/admin/bootstrap` 真雲 500uncle6 實撞,證據 arcrun-rag
`docs/manual/uncle6-deploy-record.md`);miniflare 無此限制=本機測試全綠假象。
修:`portal-auth.ts` `PBKDF2_ITERATIONS` 100_000+註明平台封頂;design D-5 已加修訂註。
verify 相容性:迭代數從儲存值解析,補測試「600k 舊 hash 仍通」(miniflare 造);誠實邊界
=真雲 deriveBits 同樣封頂、600k hash 真雲仍拒,但真雲 bootstrap 從未成功過 → 無存量 hash、
無遷移面。同 PR 順手:`.component-builds/http_request/wrangler.toml`
`global_fetch_strictly_public` flagself-hosted 同帳號 fetch 1042 正解,Arcrun#33 同族,
官方行為不變原理同 cypher 前例 rules/03 1042 段)。
- [x] **Portal 來源回溯超連結(2026-07-18,任務層小改,分支 `feat/portal-source-link`
來源=leo RAG demo 客戶測試回饋「來源回溯 gitea:// 沒辦法點」)**
新增可選 var `PORTAL_SOURCE_WEB_BASE`types.ts 註解含語意);portal-ui 卡片詳頁「來源回溯」
與 AI 問答出處列在「var 有設+source 為 gitea://」時渲染 `<a target=_blank rel=noopener>`
gitea://<path>#n → {base}/<path>chunk 錨點捨棄、路徑逐段 encodeURIComponent、href 過
esc());未設 var 行為一字不變(Mira 零影響)。驗證:tsc 0、portal-auth/portal-data 測試
48/48 綠、渲染後 client JS new Function 語法檢查過。部署:uncle6 重部+設 var(leo 閘)。
- [x] **搜尋濾內部型別+總圖頁(2026-07-18 晚,任務層小改,分支 `feat/portal-library-overview`
來源=leo 客戶測試回饋「無標題雜項列」+「書庫總圖」指示(原案=Arcrun#39 藏書地圖的 GUI 切片))**
`filterDeprecatedEntries` 加內部型別過濾(value=slot 值外漏雜項列、workflow=工作流定義),
#46 上游修好後隨整段拔掉;測試補一案(49/49 綠)。
② 新增 `GET /portal/data/graph/overview`(全租戶 active 三元組 → nodes/edgesD-4 graph
粗閘同 neighborskbdbFetch 直讀不經 plugin,邊數上限 500 誠實截斷)+portal「總圖」頁
nav 隨 graph_allowed 顯示;手刻力導向佈局、固定種子確定性、紙感樣式、點節點跳圖譜搜尋;
頁尾連 00-MAP.md#39「人機共用同一份地圖」的文字版)。
#39 本體(library_map Template/ingest 重算/MCP instructions+get_map)不在本次範圍,仍歸 #39 SDD。
- [x] **t95 搜尋 CJK 正規化(2026-07-28,任務層小改)**
來源=leo 實測 geek6688 實例「搜『AI協作』(無空白)0 結果,搜『AI 協作』有結果,中文習慣不是每個人都會加空白」。
修:`portal-data.ts` 新增 `normalizeCjkQuery()`(CJK/ASCII 邊界自動插空白,純函式可 export 單測);
`/portal/data/search` 路由的 `q` 參數先過正規化再查 KBDB;只動查詢端不動索引端。
驗證:pure function 6 案(純 CJK/ASCII 不動、邊界插空白、已有空白不重複、全形符號不觸發)+
integration 2 案(`AI%E5%8D%94%E4%BD%9C` → KBDB 收到 `AI 協作`)。
- [x] **t96 圖譜節點模糊比對 fuzzy fallback2026-07-28,任務層小改)**
來源=leo 實測「graph 模式搜『AI 協作』回鄰居 0 關聯 0,但總圖上明明有節點『AI 協作規範書』—精確比對太嚴」。
修:`portal-data.ts` 新增 `findBestNodeMatch()`(contains 比對+最短名優先,純函式)+
`fuzzyFindNode()`(查 KBDB triplet records 找最佳節點名);
graph neighbors 路由 plugin fallback 路徑(②):精確命中 0 鄰居+0 邊 → 以 fuzzyFindNode 找最佳節點名重查;
工作流路徑(①)套 CJK 正規化但不加 fuzzy fallbackworkflow 自管節點解析)。
B5 分支(work/b5-graph-library-filter-0726)只動同一行的 `libraries` 欄位,衝突面最小。
驗證:findBestNodeMatch 6 案(空清單/無命中/精確子字串/多命中取最短/CJK 未正規化/大小寫)+
integration 4 案(有鄰居直接回、0 鄰居 fuzzy 命中、0 鄰居 fuzzy 無命中誠實回 0、t95+t96 連動)。
- [x] **t97 庫目錄只顯示用戶同步進來的(2026-07-28,任務層小改)**
來源=leo 裁定「用戶沒加上的庫,不要自作主張給它加上」。
t97abootstrap 後不再預埋 `kb` 庫(index.html firstsetup 移除 POST /portal/admin/libraries 種子 call)。
t97bGET /portal/admin/libraries auto 段新增 `n === 'general'` 過濾——general 是系統「未標庫」
fallback 桶,不是用戶加的,不在目錄露臉;資料照舊、B5 權限語意不動。
驗證:portal-admin.test.ts 新增「auto 過濾 general」1 案(mock KBDB /entries/libraries → ['kb','general','notes']
回應 names 含 kb/notes,不含 general)。
- [x] **t114 拿掉「登記到目錄」兩段式(2026-07-28,任務層小改)**
來源=leo 裁定「掃進來的就是要進目錄…加入目錄這件小事還要分兩段做?是在攻打用戶嗎」。
修:`renderAdminLibs()` 拿掉 auto/已登記視覺分岔,auto 庫直接以完整卡片顯示(保留「同步自動出現」tag,
去掉「登記到目錄後可以改顯示名」描述文字);移除「登記到目錄」按鈕與 lib-adopt event handler
空狀態文字改「同步小幫手還沒送來任何庫…庫會自動出現在這裡」。
顯示名:此輪 auto 庫唯讀(與已登記庫現行行為一致,實作成本最低;後續若需可補 auto-adopt on PATCH)。
搜尋不依賴登記(搜尋走 /portal/data/search → server 注入 library filter,與庫目錄登記簿無關)。
驗證:HTML 殼測試補「無 lib-adopt、無登記到目錄」斷言;既有 HTML 斷言(零租戶字串等)不回退。
- [x] **t115 kbdb 全域認證中介層(2026-07-28,安全洞熱修;二修 2026-07-28**
實證:不帶任何憑證直打 `arcrun-kbdb.<sub>.workers.dev/entries` → 200 回真實知識;POST 直寫
成功。B5 權限做在 cypher/portal 層,繞過 portal 直打 kbdb 全破,隱私賣點(原文不出機)形同虛設。
一修(已在樹上)兩個缺陷:① `if (env.KBDB_INTERNAL_TOKEN)` 才擋 = 沒設 secret 洞照開(fail-open);
② workflows/rag-ingest-card.local.yamlarcrun-rag repo)有 3 處 `__KBDB_BASE__`
post_block/post_triplet 走 http_request 直打 kbdb),rag-chat/graph-neighbors/takedown 同樣;
token 生效後收卡與查詢全 401。
二修(本次):
`kbdb/src/index.ts` middleware 改 fail-closed
- 未設 token → POST/PATCH/DELETE/PUT 直接 401fail-closed for writes);
GET 記 console.warn 後放行(讀取升級窗口,老實例不整個炸)。
- 已設 token → 非 health 路由全部要求 Bearer(行為同一修)。
`kbdb/tests/auth.test.ts` 改 12 項(含 PATCH/DELETE 無 token→401;原「無 token POST 過」改為 401)。
`kbdb/src/types.ts``kbdb/wrangler.toml` 說明改為 fail-closed 語意。
④ workflow http_request 節點(arcrun-rag 側):規格見本項末「給安裝器的規格」段,由總管派 arcrun-rag。
cypher 不需改(五處已有 `if (KBDB_INTERNAL_TOKEN) headers[Authorization]=Bearer`)。
老實例升級路徑:安裝器部署時自動生成同一把 token → `wrangler secret put KBDB_INTERNAL_TOKEN` 注入
kbdb 與 cypher 兩個 workerworkflow yaml 同批替換 `__KBDB_TOKEN__`(見規格)即封口。
kbdb_upsert_block WASM 指向死路由 `/blocks`(設 token 前後都壞,不新增破壞)。
- [x] **t116 圖搜尋靜默失敗——portal 補傳 kbdb_base2026-07-29,任務層小改)**
診斷:graph_neighbors workflow 的 fetch_triplets url 含 `{{input.kbdb_base}}/records/...`
但 portal 呼叫 executeWebhookGraph 時未傳 kbdb_base → URL 解析失敗 → `HTTP request failed`
修法(b 案 雙保險):portal-data.ts 補傳 `kbdb_base: env.KBDB_BASE_URL ?? ''`
yaml 端(`__KBDB_BASE__` 安裝期替換)由總管派 arcrun-rag,詳見本項末規格段。
執行範圍:`cypher-executor/src/routes/portal-data.ts`(補傳 kbdb_base)。
- [x] **t117 三元組寫入靜默失敗——FOREACH 全失敗浮出水面(2026-07-29,任務層小改)**
診斷:youlin 實例 triplet=0,懷疑 post_triplet http_request 401 被靜默吞掉;
FOREACH 收集到全 `success:false` 的 iterResults 後回 `{success:true, data:{results:[...]}}` —— 呼叫端不知失敗。
兩層修法:
① wasi-shim.ts http_request catch:改寫錯誤 envelope(含 fetch 原始 message)到 WASM 輸出,
取代只 return 1WASM 寫 "HTTP request failed");
② graph-executor.ts FOREACH:若全部 iterResults 均 `success===false`,拋 Error(含首項 status code
→ catch 轉 ExecutionError → executeWebhookGraph 回 `{success:false, error:"..."}`
執行範圍:`cypher-executor/src/lib/wasi-shim.ts``cypher-executor/src/graph-executor.ts`
測試:vitest 新增兩條(FOREACH 全失敗→error 含 statuswasi-shim catch 路徑)。
- [x] **t122 萃取引擎金鑰雲端下發(2026-07-29**
來源=總管確認:daemon/config 回傳 extractor:'claude' 寫死且不含金鑰,封測者萃取全滅。
修:① `POST /portal/admin/extractor`admin 閘)存 `{engine, gemini_api_key?, llm_model?}` 到 KV
key=`{tenant}:portal:extractor_config`,金鑰不落 log
`GET /portal/admin/extractor`(回 engine + has_key:bool,不回明文);
`POST /portal/daemon/config` 回傳改讀上述設定(未設→預設 gemma;gemma+有 key → 含金鑰下發);
`console-ui/public/portal/index.html` 設定頁新增「萃取引擎」區塊(引擎選擇+金鑰輸入+提示重連)。
測試:vitest 新增 3 案(未設定→下發 gemma 無金鑰;設定後→下發含金鑰;GET 不回明文)。
- [x] **t128 圖搜尋仍回 0——graph_neighbors 補傳 template2026-07-29,任務層小改)**
診斷:t116 只補了 kbdb_base,但 fetch_triplets.url 同時含 `{{input.template}}`
portal 呼叫 executeWebhookGraph 未傳 template → URL 變 `/records/by-template/?owner_id=...` → count=0。
總管實測:手動補 `"template":"triplet"` → count=1(企業版功能解鎖)。
修:portal-data.ts graph_neighbors 呼叫補 `template: 'triplet'`
執行範圍:`cypher-executor/src/routes/portal-data.ts` 一行。
測試:portal-data.test.ts 新增整合案(t128 describeworkflow 執行不崩即代表 template 進到 context)。
workflow input 完整核對清單(portal-data.ts 所有 executeWebhookGraph 呼叫):
- graph_neighborsnode ✅、depth ✅、namespace ✅、owner ✅、kbdb_base ✅(t116)、template ✅(本次)
- rag_chatquestion ✅;namespace/kbdb_base 未核實(rag_chat workflow yaml 在 arcrun-rag,非本 repo
- takedown:不在 portal-data.ts,在 arcrun-rag 端(dashboard/安裝器),超出本 repo 範圍
- [x] **t130 新實例缺 triplet template → 總圖永遠空(2026-07-29,任務層小改)**
真因:`PORTAL_TEMPLATE_SEEDS` 只有 `portal_user``portal_library``rag_ingest_card.post_triplet`
`POST /records {template:'triplet'}` → 新實例回 400「template not found: triplet」→ 三元組全滅。
修:`portal-seeds.ts``triplet` seedslots 來源:2026-07-19 kbdb_list_templates 核實 15 槽 + library)。
呼叫路徑驗證(`ensurePortalTemplates` 全四掛點):
`POST /init/seed`(acr init 觸發)← 主線,新裝必經
`POST /portal/admin/bootstrap`(首次 admin 設定)
`POST /portal/daemon/libraries`daemon 登記資料夾→庫)
`POST /portal/admin/libraries`admin 手動新增庫)
既有壞實例補救:任一掛點重跑即補建(冪等),e.g. 重跑 acr init 或 bootstrap。
執行範圍:`cypher-executor/src/lib/portal-seeds.ts`(新增 triplet seed);
`cypher-executor/tests/portal-auth.test.ts`mockTemplatesExist 補 triplet + t130 3 條新測試);
`cypher-executor/tests/portal-admin.test.ts`mockTemplatesExist 補 triplet)。
測試:純資料驗證(seed 含 triplet + 必要 slots)+冪等驗(已存→existing)+自動補建(404→POST→created)。
- [x] **t129 AI 問答出處重複一整頁——後端按 page_name 去重(2026-07-29,任務層小改)**
診斷:一張卡拆成 3-5 block,每 block 各回一筆 source(同 page_name)→ 前端列一整頁重複。
選後端修(/portal/data/chat)理由:出處去重是資料清潔,跟前端渲染無關;改後端不需動 HTML。
修:portal-data.ts 新增純函式 `dedupeSourcesByPage()`Map 按 page_name/page 去重,hit_count > 1 時附計數);
chat 路由 rawSources 先過去重再回。
執行範圍:`cypher-executor/src/routes/portal-data.ts`(新增 export 函式 + chat route)。
測試:portal-data.test.ts 新增 5 案(t129 describe:合併計數/各保一筆/page 備用/空陣列/page_name 優先)。
- [x] **t135 庫目錄移除(2026-07-29,任務層小改)**
來源=leo 裁定「需要加移除按鈕,別人裝錯我沒辦法幫他弄,需要可以自主」。
後端:`DELETE /portal/admin/libraries/:id`(刪登記簿 record,資料不動)+
`DELETE /portal/admin/libraries/by-name/:name`auto 庫;body `confirm:<庫名>` 才執行;
呼叫 KBDB `PATCH /entries/deprecate-by-library`entries 標 deprecated → 從 auto 清單消失)。
KBDB`deleteRecord` action + `DELETE /records/:id` route
`deprecateEntriesByLibrary` action + `PATCH /entries/deprecate-by-library` route(此路由在 `/:id` 之前)。
Daemon hint(根治只增不減):`POST /portal/daemon/libraries` 存 active lib names 到 KVTTL 48h);
`GET /portal/admin/libraries` 讀 KV 後對登記庫標 `daemon_watching: false`(若 daemon 有回報但未含該庫)。
前端:每張庫卡片加「移除」按鈕(登記庫=`confirm()` 對話框;auto 庫=`prompt()` 輸入庫名確認);
移除後即時從列表消失(不必重整);`daemon_watching=false` → 顯示灰字「小幫手目前沒有在同步這個資料夾」。
測試:portal-admin.test.ts 新增 7 案(DELETE/:id 成功/404/非admin403by-name 成功/無confirm/confirm不符/非admin403)。
## 第二波(不在本 SDD 動工範圍,掛號)
- MCP token 綁庫集合(design §9PR#15 擴充,只動 `mcp/`
- graph 逐節點/逐邊細粒度過濾
- Google OAuth2 fast-follow、審計 log、忘記密碼 email 自助、舊資料 library 回填清潔工