Files
kbdb-graph-plugin/docs/3-specs/ingest-contract/design.md
T
Leo 613071f41d feat(graph): get_source + refresh 端點 + keyword 收斂 (T3.6-3.7)
對應 issue #1 T3 C 段(圖工具 HTTP API 備好,MCP 註冊薄殼待 arcrun)。

- get_source (3.7): graph-source.ts + GET /graph/source/:name —
  回節點的 active triplet 來源指標(uri/anchor/block_id/content_hash),去重。
  連帶加 source_anchor slot,ingest 從 source.anchor 帶入
- refresh (3.6/3.6b): graph-refresh.ts + POST /graph/refresh —
  純被動代轉 ingest(KBDB_INGEST_URL),只人發起、無排程/webhook(fan-out 紅線)。
  未設 URL → 誠實 forwarded:false,不假綠
- 3.6d: POST /search 移除公開 keyword 模式(重複 KBDB MCP),收斂 suggest-only;
  keywordSearch helper 留作 suggest 內部建構塊
- 3 新測試(get_source uri+anchor / active-only / refresh 未就緒誠實回報)

gates: vitest 19 passed / zero SQL / 無新綁定 / dry-run bundle 乾淨
待接:MCP 註冊薄殼併 arcrun u6u-mcp-server;refresh 端到端待 ingest(T4) 部署

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:24:04 +08:00

88 lines
6.7 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.
# ingest-contract — 設計
> **藍圖在頂層**:本 SDD 只放 **kbdb-graph-plugin 內部實作細節**。跨專案脈絡(為什麼拆 ingest/graph、mira 蒸發、整體資料流)見 InkStoneCo `docs/3-specs/mira-dissolve/`design + requirements)。
> **對應交辦**[kbdb-graph-plugin #1](https://github.com/uncle6me-web/kbdb-graph-plugin/issues/1)T3)。
> **鐵律**API-as-Wall / 零建表 / 零 SQL / 零 migration。embedding/語義 normalize 屬 base 模組,graph 只調不自算。圖在插件層記憶體組裝。
---
## 1. 範圍
graph 插件補上**寫入端**:收 ingest 送來的 `ingest-candidate` envelope,正規化後以「翻 triplet 的 `status` slot」做 **deprecate-then-append** 取代,並讓查詢面 active-only。查詢面(search/traverse/neighbors)已成熟,本批只補 active 過濾。
不在本批:圖工具併入 KBDB MCP 的**註冊薄殼**(需 arcrun 配合,見 §6);semantic normalize 端到端(依賴 base embed 部署)。
## 2. 邊界契約
唯一入口契約 = `contracts/ingest-candidate.json`(已搬入,T3.1)。要點見 `contracts/README.md`
graph 收件後負責的領域欄位(ingest 禁送):`id` / `clusters` / `bridge_score` / `created_at` / triplet 上的 `*_entity_type``additionalProperties:false` → 違規欄位 422。
## 3. template slot 變更(T3.2 / 3.2b
現況(`src/lib/templates.ts`,已核實):
- `TRIPLET_SLOTS``status` / `superseded_by`
- `ENTITY_SLOTS``gloss`
要加:
- triplet`status``active` | `deprecated`,預設 `active`)、`superseded_by`(指向取代它的新 record id,空=未被取代)。
- entity`gloss`(一句話描述,供「詞+gloss」語義 normalize 的 embedding 對象)。
### ⚠️ ensureTemplate early-return 陷阱(前置警示 1,已核實)
`KbdbClient.ensureTemplate``src/lib/kbdb-client.ts:120`)命中既有 template 即 `return`**不會把新 slot 補進已 seed 的 template**。只改 `TRIPLET_SLOTS` 陣列對既有環境無效。
**對策**`ensureTemplate` 改為「命中既有 → 比對 slot 差集 → 缺的走 base `PATCH /templates/:id` 補上」,而非 early-return。新環境(template 不存在)走原 `POST /templates` 路徑不變。如此既有 + 全新環境都正確收斂,不需另跑一次性遷移腳本。
## 4. KbdbClient.updateRecord(前置警示 2,已核實)
client 現有 `createRecord` / `getRecord` / `listRecordsByTemplate`**無 record 層 PATCH**`updateEntry` 是 entry 層,不是 record)。
新增 `updateRecord(recordId, values)` → 呼叫 base `PATCH /records/:id`base #6 已就緒)。deprecate(翻 status)與 rollback(翻回 status)都靠它。
## 5. POST /triplets/ingest(核心,T3.3
純函式 action`src/actions/triplet-ingest.ts`<100 行,第一參數收 `KbdbClient`),route 只驗證 + 呼叫。流程:
1. **驗證 envelope**Zod schema 鏡射 `ingest-candidate.json``additionalProperties:false` → 禁送欄位(如 `bridge_score`)回 422。
2. **idempotency**:以 `source.uri` 為鍵查該來源現存 active triplet 的 `content_hash`(存進 triplet 的某 slot,見下)。同 hash → no-op 回 `{skipped:true}`
3. **deprecate-then-append**:新 hash → 查該 uri 所有 active triplet,逐筆 `updateRecord(id, {status:'deprecated', superseded_by:<新批暫不知 id,二階段或留空>})`,再 append 新批 active。
4. **回應**`{ ingested: N, deprecated: M, skipped: false }`
### content_hash / source.uri 存哪
triplet record 需記住它來自哪個 snapshot 才能做 idempotency 與 active-only。沿用既有 `source_block_id` 不夠(那是 Logseq block)。設計:triplet template 增記 `source_uri` + `content_hash` slot(屬 §3 同批 slot 變更,非建表)。active-only 與 deprecate 都按 `source_uri` 分組。
> 註:`superseded_by` 指向「取代它的新 record id」。新批 record 是 append 後才有 id → 若要精確回填,deprecate 分兩步(先 append 拿 id,再翻舊批 status + superseded_by)。先 append 後 deprecate 比較安全(中途失敗不會留下「全無 active」的空窗)。
## 6. 查詢 active-onlyT3.5
traverse / search / neighbors 從 records 組鄰接表前,先 `filter(status === 'active')`(預設值缺省視為 active,相容舊資料)。active 集合永遠乾淨,deprecated 仍可查(rollback / 考古)但不進圖遍歷。
## 7. 跨 repo 協調點:圖工具併入 KBDB MCPT3.6,需 arcrun 配合)
圖工具(traverse / neighbors / get_source+ `refresh` 要**併進 arcrun 的 KBDB MCP**`u6u-mcp-server`),**不另起 graph 獨立 MCP**。
- **graph repo 端能備好的**:圖查詢的 HTTP API + 邏輯、`get_source` 端點(T3.7)、`refresh` 代轉 ingest 的端點。
- **需 arcrun 端動的**:MCP 工具註冊薄殼那層 → 標清在 issue #1,由總管協調。本批不擅自開獨立 graph MCP。
- **`refresh` 紅線**(T3.6b):只能人發起的 MCP 調用觸發,**禁掛排程/webhook 自動 refresh**(否則變回 fan-out,踩 flag 紅線)。
- **T3.6d**:整合時移除 `search-query.ts` 代理 base 關鍵字那條(重複,關鍵字歸 KBDB MCP)。
## 7.5 get_source / refresh 落地(C 段,已實作)
- **get_source**`graph-source.ts` + `GET /graph/source/:name`):給節點名 → 回觸及它的 active triplet 的來源指標(`uri` / `anchor` / `block_id` / `content_hash`),按 uri+anchor 去重。為此 triplet template 增 `source_anchor` slotingest 從 `source.anchor` 帶入)。
- **refresh**`graph-refresh.ts` + `POST /graph/refresh`):純被動代轉 ingest 重抓+萃。graph 自己不抓不萃(ingest 純餵食器職責)。
- 🚫 紅線:只人發起 MCP 調用觸發,無排程/webhook。
- ingest 對象 = `KBDB_INGEST_URL`(env,T4 就緒前留空)。未設 → 誠實回 `{forwarded:false}`,不假綠。
- **search keyword 收斂**T3.6d):`POST /search` 移除公開 keyword 模式(重複 KBDB MCP `kbdb_search`),收斂為 suggest-only。`keywordSearch` helper 保留為 suggest 內部建構塊。
## 8. 不做 / 延後
- **graph CLI**T3.7b):延後。人少在命令行 traverse、AI 用不到 → 不做(非省工,是不誤導 AI 以為有這條路)。
- **semantic normalize**T3.2c):本批先 exact-onlysemantic 留接口。base embedArcrun #7)部署後才端到端,屆時標「待 base embed 部署驗」。
## 9. 樂高法遵循
- 每個 action <100 行、一檔一事、無狀態(狀態走 base API 持久化)。
- route 不含業務邏輯,只 `makeKbdbClient(c.env)` + 驗證 + 呼叫 action。
- 測試走 `tests/mock-client.ts`,不打真網路。