liveTripletCountsByLibrary 沒有過濾 status,recomputeLibraryMap 只算 COALESCE(status,'active')='active'——兩邊判準不一致,只要庫裡有一筆 superseded triplet 就永遠判定 stale,導致每次讀地圖(GET /map、GET /map/:library、 kbdb_get_map MCP 工具)都觸發重算並新建一筆 library_map record,無止盡寫 D1, 且加劇既有的非原子 supersede 競態(kb 44 筆全 superseded、notes 兩筆同時 active 即 arcrun-rag#50 的共同根因之一)。 修法:liveTripletCountsByLibrary 的 SQL 改成 pivot 出 status 再套用與 recomputeLibraryMap 逐字一致的過濾,兩邊判準對齊。 新增迴歸測試釘住此 bug(反向驗證:跑在修前的 SQL 上會失敗,非空氣測試); 獨立用 leo21c MCP 連線連讀兩次 kbdb_get_map() 重現修前症狀 (general 庫 updated_at 從 1786457080 前進到 1786457114,中間無寫入)。 kbdb/tests/library-map.test.ts 19/19 全綠。既有殘骸(100 筆 library_map record) 未清——目前沒有可用的 DELETE 通道,待部署後另行處理。 kbdb-sql-ok:liveTripletCountsByLibrary 的 .prepare 呼叫是牆內本體 (kbdb/src/actions/),本次 checkout 開在巢狀 worktree matrix/arcrun/.worktree-fix-87/(避免打斷另一 session 佔用中的 matrix/arcrun 主 checkout),kbdb-api-wall-guard.sh 的字面路徑比對 *matrix/arcrun/kbdb/src/* 吃不到中間多出的 worktree 目錄層,非真的繞牆。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.2 KiB
library-map — Tasks
進度真相源。依 design §7 歸屬;動工前總管審本 SDD(#39 明令)。
| # | 任務 | 類 | 依賴 | 狀態 | 備註 |
|---|---|---|---|---|---|
| M1 | library_map Template+slots 定義(含 triplet 按庫過濾現況核實;不足則 Triplet template 加 optional library slot) |
B | — | ✅ 07-19(PR#72 merge) | D6 零建表。核實:triplet 無 library slot → 已走預案(design §1 核實結果) |
| M2 | kbdb base POST /map/recompute?library=+GET /map/GET /map/:library(聚合 SQL 住基本盤;交易式 supersede) |
B | M1 | ✅ 07-19(PR#72 merge;leo21c 33d88016+demo 699018af 已部署+backfill:leo21c kb 111/notes 108、demo general 23) |
PR+測試(真 SQLite 驗聚合);merge 後 gated 部署(leo 閘)+逐庫 backfill recompute |
| M3 | 讓地圖跟得上資料、全租戶自動 backfill(原訂做法:ingest 尾端接鏈逐庫呼 recompute) | B | M2 | 🔁 07-19 標的 ✅ 是誤報,08-08 更正並改法重做 | 見下方 08-08 段 |
| M4 | MCP:instructions 注入全館地圖+get_map 工具 |
B | M2 | ✅ 07-19(PR#73 merge;leo21c 1e73da90 部署,MCP instructions 實載地圖) |
與 #68 同族薄殼;kbdb_get_map+connect 時注入(isolate TTL 快取,失敗靜默略過不擋連線);merge 後 gated redeploy arcrun-mcp(leo 閘) |
| M5 | GUI 首頁:全館地圖 render(console+portal) | B | M2 | ✅ 07-19(PR#74 merge;leo21c cypher 9bc3a1f3 部署) |
取代空白搜尋框 |
| M6 | D30 連動:map 層 embed+semantic 庫路由第一跳 | B | M2 | ⬜ | #58/#59/#60 家族的第一片治本 |
| M7 | dogfood:leo 庫(leo21c)首個實例 backfill+驗收(requirements 驗收段全項) | A | M3-M5 | 🔄 demo 側實質驗過;leo21c 正式驗收單(requirements 全項)待做 | 過了才進 demo/客戶 |
M3 更正(2026-08-08,matrix/arcrun CC,總管交辦)
07-19 標 ✅ 是誤報:arcrun-rag 670f38a 只接了 rag-ingest-cards 那一條管線,repo 內
(grep -rn "map/recompute" --include=*.ts 排除 node_modules/.github-public)查無任何呼叫點,
三週來沒手動 backfill 過的租戶(絕大多數、含 youlin 與所有新租戶)GET /map 恆回空,
kbdb_get_map 的 MCP 說明文字還宣稱「地圖由 ingest 尾端自動重算(M3)」——那是假話(詳見
system-dev/wiki/mistakes.md 08-08 段「藏書地圖 kbdb_get_map 對多數租戶永遠是空的」)。
leo 裁示:總管原提兩案(①接上 M3 原訂設計/②降級成只算 count 的即時聚合,不維護快取), leo 否決②——「藏書地圖就是 arcrun 的最重要功能,讓 AI 一眼看到所有庫的摘要」, 即時聚合算得出 count、算不出 narrative,降級等於砍功能。
改法(非①非②,第三案):不再依賴任何外部呼叫者(ingest workflow)記得呼
POST /map/recompute——那條線跨 repo/跨租戶,已證實三週沒人接上,天生脆弱。改成
GET /map/GET /map/:library 讀端自己核對即時三元組數,落差就地呼叫既有的
recomputeLibraryMap(kbdb/src/actions/library-map.ts ensureFreshLibraryMaps)補算。
聚合 SQL 沒有第二套、narrative/relation_profile/bridges 這些摘要欄位原封不動——不是砍成
只算數字,只是觸發時機從「等外部呼叫」改成「讀的當下順手核對」。同時解掉:
① 全租戶自動 backfill(不需要任何人做任何事)② 跟得上資料(下一筆 ingest 進來,
下一次讀就反映)③ 不再依賴跨 repo 的 ingest 接鏈。
narrative 欄位有一個已知誠實限制:這個機制只保留既有 narrative(不會被自動重算洗掉),
但不會生成新的 narrative——沒人手動填過、也沒有 ingest 端寫過的庫,narrative 仍是空的
(content 顯示「(narrative 待 ingest 補寫)」)。這是 design §1 本來就承認的缺口
(narrative 抽自 wiki 首段,屬內容語意萃取,非聚合 SQL 能生出來),非本次新增。
驗證:kbdb/tests/library-map.test.ts 新增 6 案(全綠,18/18)涵蓋:從未手動 recompute
即自動補齊/跟得上新資料(不手動重算,數字自動更新)/narrative 不被靜默洗掉/
GET /map/:library 誠實分辨查無此庫(404) vs 已知空庫(200)/owner 隔離/無 triplet template
不報錯。mcp/tests/unit/tools/kbdb-map.test.ts 新增 1 案釘住舊謊言不再出現(18/18 全綠)。
tsc 兩包乾淨。實測:yuga3bse 租戶(從未 backfill 過、真實 triplet 資料橫跨 5 個庫)改前
kbdb_get_map 回 {libraries:[],count:0}——改動待部署後需重新實測驗證非空。
M3 止血(2026-08-11,Arcrun#87,總管交辦「動工前的量測」comment 第四節)
08-08 那次改法本身留了一個判準缺口,這次補上:ensureFreshLibraryMaps 比對
「即時三元組數」(liveTripletCountsByLibrary)與「快取的地圖數」(recomputeLibraryMap
算出來寫進去的),但兩邊的 status 過濾不一致——recomputeLibraryMap 只算
COALESCE(status,'active')='active',liveTripletCountsByLibrary 完全不濾 status。
只要一個庫裡混了任何一筆 superseded/deprecated triplet,兩邊數字就永遠對不上,
ensureFreshLibraryMaps 就永遠判定 stale ⇒ 每次讀地圖都觸發重算,每次都新建一筆
library_map record(superseded 舊的),無止盡寫 D1——且加劇 recomputeLibraryMap
本身非原子 supersede 的既有競態(更高重算頻率 = 更高並發重算機率),是 kb 庫
全部 44 筆被標 superseded、notes 庫兩筆同時 active(arcrun-rag#50)這兩個症狀的
共同根因之一。
修法:liveTripletCountsByLibrary(kbdb/src/actions/library-map.ts)的 SQL 改成
先 pivot 出每筆 triplet record 的 status,再套用與 recomputeLibraryMap 逐字一致的
COALESCE(status,'active')='active' 過濾,兩邊判準對齊後,資料未變動時兩個計數必然相等,
stale 判定回歸「真的有資料變動才 stale」。
驗證:新增迴歸案「Arcrun#87 迴歸:superseded triplet 存在時,連讀兩次地圖不會再次
觸發重算」(kbdb/tests/library-map.test.ts,19/19 全綠);反向驗證過——把同一顆測試跑在
修前的舊 SQL 上會失敗(library_map record 數 2 vs 期望 1),證明測試真的釘住這個 bug、
不是空氣測試。另外用 leo21c MCP 連線(bfezv28v)連讀兩次 kbdb_get_map()(無中間寫入)
獨立重現修前症狀:general 庫 updated_at 從 1786457080 前進到 1786457114。
尚待:改動只在分支 fix/library-map-recompute-loop-87-v3(未 push、未部署 leo21c);
既有 100 筆 library_map 殘骸(kb 44 筆 superseded/general 41/notes 2)未清——
清除需要一個目前不存在的 DELETE 通道(cypher-executor 的 /kbdb/records/:id proxy 只有
GET/POST/PATCH,無 DELETE;kbdb base 自己雖有 DELETE /records/:recordId 但走 leo21c
需要 KBDB_INTERNAL_TOKEN,非 CC 可持有的機密)——待總管部署本修法+視情況補一支
DELETE proxy 後再清。