# 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 後再清。
