---
status: draft
note: leo 2026-07-19「藏書地圖可以走」＝design confirmed 可動工（M1/M2 起跑）；active 標記待對照 Arcrun 現行 active SDD 收斂。
---
# library-map（藏書地圖）— Design

## 1. 資料結構：`library_map` Template（照 leo spec §3）

每庫一個 map block。slots：`library`(text)／`narrative`(text，抽自該庫頂層 wiki 首段)／`top_entities`(array，degree 排序 top-N)／`relation_profile`(array，predicate 分布＝庫的性格)／`bridges`(array，同 entity 跨庫 join)／`triplet_count`(number)／`commit_hash`(text)／`status`(active|superseded)。

三元組按庫過濾：先核實現況——rag-ingest-cards v2 的 triplet 已帶 `source_uri`、entries 已有 `metadata.library`（portal-auth P1）；若 triplet 定位庫仍不足，在 Triplet **Template schema** 加 optional `library` slot（改 template 不動表）。

> **核實結果（2026-07-19，M1 對 prod 實查）**：triplet template（prod 名 `triplet`，非 MCP 註解寫的 `graph_triplet`）slots＝subject/predicate/object/…/source_uri/…，**無 `library` slot**；entries 的 `metadata.library` 機制在但既有資料未標記（`?library=kb` → 0 筆）→ **定位庫不足，走預案**：M1 於 recompute 時冪等地在 triplet template 加 optional `library` slot（`ensureTripletLibrarySlot`，改 template 不動表）；slot 值由 ingest 端補寫（M3）。過渡期 `/map/recompute` 收 optional `source_prefix` 參數，對「無 library 值的舊 triplet」以 `source_uri` 前綴歸庫（如 `gitea:Leo/kb@`）——前綴由 caller 提供，base 不寫死 URI 語意；bridges 的「對面庫」只認 library slot 標記值，M3 backfill 前會偏稀疏（誠實限制）。

## 2. 重算的家：SQL 只能住基本盤（D6 推論，本 SDD 關鍵歸屬裁定）

degree 排序／predicate 統計／跨庫 join 是聚合 SQL——**D6 鐵律：插件與 workflow 全程禁 SQL**。故重算實作＝**kbdb base 新增內建端點 `POST /map/recompute?library=`**（基本盤內 SQL 合法，一段交易：算→建新 block→舊標 superseded）。ingest workflow（A 類）尾端用 `http_request` 呼此端點——A 類接 B 類 API，牆不破。
讀取端點 `GET /map`（全館，每庫一行）／`GET /map/:library`（詳圖），MCP/GUI 共用。

## 3. 更新機制（leo spec §4）

ingest 完成 → 取本次 commit diff 涉及的庫集合 → 逐庫呼 `/map/recompute`。無 cron 全量。首次 backfill＝對每個既有庫手動各呼一次（installer/腳本一行）。

> **2026-08-08 更正（matrix/arcrun CC）**：上面這條「ingest 尾端接鏈」的路徑本身沒錯（`arcrun-rag`
> 那條管線也確實接了，`670f38a`），但它**只覆蓋接了鏈的那一條 ingest**——這個 repo 內（kbdb/
> cypher-executor/mcp）從沒有任何呼叫點會打 `/map/recompute`，導致沒手動 backfill 過的租戶
> （絕大多數）恆空，且拖了三週沒人發現／接上（見 `system-dev/wiki/mistakes.md` 08-08 段）。
> **改法**（leo 否決「降級成即時聚合、不維護快取」的提案，因為那會丟失 narrative 這類摘要
> 本體）：`GET /map`／`GET /map/:library` 讀端自己核對即時三元組數，落差就地呼叫既有的
> `recomputeLibraryMap` 補算（`kbdb/src/actions/library-map.ts` `ensureFreshLibraryMaps`）。
> 聚合 SQL 仍只住 kbdb base（沒有違反 §2 的歸屬裁定），只是觸發時機從「等外部呼叫」改成
> 「讀的當下順手核對」——這條讀端機制本身就是「無 cron 全量」的自動 backfill，取代了
> 「首次 backfill＝對每個既有庫手動各呼一次」這句手動步驟。細節見
> `system-dev/docs/3-specs/library-map/tasks.md` M3 段。

## 4. 注入（leo spec §5，本功能重點）

- **MCP instructions**：arcrun-mcp 啟動組 instructions 時拉 `GET /map` 嵌入（快取＋TTL，或每次連線現拉——量數百 token，現拉可接受）。
- **`get_map` 工具**：薄殼呼 `/map`／`/map/:library`。與 #68 的 kbdb_graph_neighbors 同族（D17 KBDB MCP 面），可同 PR 或緊接。
- **GUI**：console/portal 首頁 render `GET /map`——庫卡片（narrative＋top entities＋規模）＋跨庫 bridges 一覽；點庫進該庫搜尋。

## 5. 與 D30 檢索治本的連動（本 SDD 新增，超出原 spec）

leo 終景「補充的只 vectorize entities＋一句話」與本件是同一素材的兩用：map block 的 narrative＋top_entities＝卡級/庫級摘要嵌入單位。落法：map block 的 content 欄寫成可嵌人話（`{library}：{narrative}。核心：{top_entities}`），標 embed → **semantic 第一跳打 map 層做庫路由**，第二跳才進庫內（D30「向量做路由、定稿給 LLM 讀」）。#58/#59 的殘影/模型問題在 map 層天然緩解（block 少、可整層重刷）。

## 6. Retrieval 流程（改造後，leo spec §6 原文）

session 啟動 → instructions 已含全館地圖（push 零查詢）→ 需細節 get_map(library) → graph query 找 entity/關係 → 依 index 取 repo wiki 定稿（真相源不變）。

## 7. 歸屬

kbdb base 端點＝B 類（PR）；ingest 尾端接鏈＝A 類（workflow 改版，arcrun-rag/個人庫同款）；MCP＝B 類；GUI＝B 類（console 路由）。全框架件：寫一次，arcrun-rag／Mira／未來客戶全實例受益（bundle 更新分發）。
