Merge branch 'main' into fix/merge-main-into-batch-t173

# Conflicts:
#	console-ui/public/portal/index.html
#	cypher-executor/src/routes/health.ts
#	cypher-executor/src/routes/portal.ts
#	registry/components/kbdb_upsert_block/component.contract.yaml
#	registry/examples/km-wiki-ingest/workflow.yaml
This commit is contained in:
2026-08-02 23:43:16 +08:00
34 changed files with 2039 additions and 33 deletions
@@ -161,6 +161,170 @@
頁尾連 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)。
- [x] **t131 金鑰設定合併(2026-07-29,任務層小改)**
來源=leo 裁定「一律規定先輸入 gemini api key,聊天和萃都一次設好;如果有 claude,勾選加強版」。
後端:新增 `POST /portal/admin/ai`(同時做 chat-keyextractor 的事)+
`GET /portal/admin/ai`(回 has_key/use_claude_for_extract/claude_available)+
`POST /portal/daemon/report-capabilities`(收 daemon 回報的 has_claude);
`/portal/admin/chat-key``/portal/admin/extractor` 保留相容。
前端:設定頁兩區塊合一,Gemini Key 必填欄+Claude 選填 checkbox(依 daemon 回報 enable/disable)。
⚠️ daemon 端(arcrun-rag repo)需實作 `report-capabilities` 呼叫,規格:
- 連線成功後 POST `{cypher_url}/portal/daemon/report-capabilities`
- body: `{email, password, has_claude: bool, daemon_version: string, os: string}`
- has_claude 由現有 `FindClaudeBin()` 決定(t92 已實作)
- 時機:連線成功後一次+每次啟動時一次(不加新 timer)
KV key 設計:`{tenant}:portal:ai_config`(合併設定)`{tenant}:portal:daemon_caps`(能力回報,TTL 7 天)
測試:portal-admin.test.ts 新增 7 案(全通;全套 238 tests 229 passed9 failed 皆 pre-existing)。
- [x] **t142 庫目錄顯示同步張數+關聯數(2026-07-29,任務層小改)**
來源=leo 裁定(政府專案驗收)「雲端子庫顯示同步了幾個 wiki+幾個三元組,一眼看出這個庫真的有東西」。
後端(kbdb):
`GET /entries/library-stats?owner_id=``{stats: [{library, card_count}]}`
SQL`COUNT(DISTINCT page_name)` GROUP BY library(僅 entry_type='block',排 deprecated)。
⚠️ 卡數=distinct page_name(一張卡 3-5 個 block),不是 COUNT(*)(防膨脹 3-5 倍)。
路由在 `/libraries` 之後、`/` 之前(避免被 `/:id` 吃掉)。
`GET /records/triplet-stats?owner_id=``{stats: [{library, triplet_count}]}`
SQLsubquery DISTINCT triplet record IDs + LEFT JOIN library slot(無 slot→general),
一次 SQL 完成,不 N+1。路由在 `/by-template/:template` 之前。
後端(portal.ts):`GET /portal/admin/libraries` 改並行撈三端點(Promise.all + .catch(→null));
已登記庫與 auto 庫各補 card_count + triplet_countMap O(1) 查找),任一失敗不炸主流程。
前端(index.html renderAdminLibs):每張庫卡片補一行
「N 張知識卡・M 條關聯」(只顯示非零項);兩者均 0 → 顯示「還沒有內容」,不顯示「0 張」。
測試:`kbdb/tests/library-stats.test.ts`14 新案:SQL 形狀/COUNT DISTINCT/entry_type filter/
deprecated filter/COALESCE general fallback/參數傳入/回傳結構/空陣列不炸);
`portal-admin.test.ts`(t97 既有測試補三端點 mock + t142 describe 3 案:
已登記庫帶 stats/auto 庫帶 stats/空庫 card_count=triplet_count=0)。
vitest + node --check 待 leo 驗收環境跑(本機無 Workers runtime)。
## 第二波(不在本 SDD 動工範圍,掛號)
- MCP token 綁庫集合(design §9PR#15 擴充,只動 `mcp/`
+7
View File
@@ -15,6 +15,13 @@ metadata:
## 📍 當前位置
> **2026-07-28t95+t96 搜尋缺陷修復,main)**portal 搜尋兩缺陷修復——t95 CJK/ASCII
> 邊界自動補空白(`normalizeCjkQuery`,查詢端,不動索引);t96 graph 節點精確 0 鄰居
> → fuzzy fallback`findBestNodeMatch`/`fuzzyFindNode`contains 比對+最短名優先)。
> 純函式 + integration 各 6/2/4 案,tasks.md Bugfix 已標 [x]。
> B5 分支衝突面:同一行 `libraries` 欄位(可一行解)。待 leo 本機跑 vitest 驗收
>sandbox symlink 封鎖,靜態分析確認邏輯正確)+ commit+部署。
>
> **2026-07-19#39 藏書地圖 M5,分支 `feat/console-library-map-home`****library-map SDD M5GUI
> 首頁)PR 已開,等審+gated 部署(merge 後需 leo 閘 redeploy cypher-executor**。R4 落點裁定=
> console **總庫搜尋頁搜尋框上方**rag profile 該頁即首頁;full profile 它是全館入口——駕駛艙是