Files
kbdb-graph-plugin/docs/3-specs/kbdb-graph-extraction/design.md
T
Leo efe8e165cf feat: KBDB-graph 插件獨立 — 全面改寫成走基本盤 API(API-as-Wall)
按 leo 鐵律(2026-06-14)把插件從「直接 SQL 操作基本盤表」改寫成
「只透過基本盤 arcrun/kbdb HTTP API 讀寫」。零建表、零 migration、零 SQL。

- 新增 src/lib/kbdb-client.ts:唯一對外通道,封裝 entries/templates/records API
- 新增 src/lib/templates.ts:triplet/entity template 定義(替代建表)
- 改寫 21 個違規 action(triplet/graph/entity/search)→ 走 client,圖在插件層記憶體組裝
- 移除所有 migrations、D1/Vectorize/AI 綁定;embedding/語意搜尋歸基本盤 optional 模組
- index.ts 只掛 triplets/graph/entities/search 路由;基本盤路由歸 arcrun/kbdb
- 測試改走 mock client(純 node);裁剪 CLAUDE.md 只留 graph 插件 + 鐵律
- 修正 SDD design.md「讀現狀推翻鐵律」的錯誤判斷(共用 D1 → API-as-Wall)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 20:59:41 +08:00

11 KiB
Raw Blame History

KBDB-graph 抽出 — Design

建立:2026-06-14 大改:2026-06-14leo 拍板鐵律後,推翻原「共用 D1 / 直接 SQL」判斷) 對應 requirements.md 的 R-EXT-1/2/3


⚠️ 修正:原 design 的錯誤判斷(2026-06-14

本檔原版犯了「讀現狀推翻鐵律」的錯,必須記下來避免重犯:

  • 原版看到插件現狀「直接 SQL 讀 blocks/entry_values」(28×/31× SQL 引用),把它當成「AGE-on-Postgres 訊號」當設計依據,跑去問「要不要共用同一個 D1、直接 SQL 掛在基本盤表上」。
  • 這是錯的:現狀那 21 個直接 SQL 的 action 是違規的歷史產物(違反本 repo CLAUDE.md「禁止繞過 API 直接 D1 操作 — API-as-Wall」),不是設計依據。讀違規現狀去推翻規則,正是 leo 點名的反例。
  • leo 2026-06-14 拍板 KBDB 鐵律(見 InkStoneCo/.../DECISION-kbdb-v3-baseplane.md):插件絕不碰表,讀寫全走基本盤 API。零建表、零 migration、零 SQL。 比 AGE-on-Postgres 更嚴——AGE 能讀 Postgres 表,KBDB 插件連表都不許碰。

連帶修正的次要誤判

  • 「v3 基本盤真身在本目錄」:原版以為 arcrun/kbdb 是「v2 落後版」、本目錄的 blocks 表才是 v3 真身。讀 arcrun/kbdb/0001_base.sql 註釋確認前提倒反arcrun 的 3 表(entries/templates/entry_valuesentry_type='block'是刻意設計的基本盤,明寫 plugin modelcore + AGE)、"Table never changes"。本目錄帶獨立 blocks 表 + 0001/0005 CREATE TABLE 的那套「v3」才是長歪的違規殘留,要刪
  • 「共用 D1 vs 走 API」:選走 API(鐵律 3)。兩 repo 不同 D1 庫不是問題——插件本來就不該碰基本盤的 D1。
  • 「0005 歸屬」:問題消解。插件不該有任何 migration。entry_values 屬基本盤、基本盤已有。

基本盤 API 契約(arcrun/kbdb,已存在,不動)

arcrun/kbdb/src/ 確認的真實端點(插件改寫的目標介面):

端點 用途 備註
POST /entries 建 atomic entry body 用 entry_typeblock/value/...)、owner_id;回 {success, entry}
GET /entries listfilter: entry_type/owner_id/parent_id/page_name/limit/offset
GET /entries/search?q=&owner_id= D1 LIKE keyword search base 只有 keyword;語意搜尋是 optional embed module
GET/PATCH/DELETE /entries/:id 單筆 CRUD
POST /templates 建 templatename+slots[]= 替代建表
GET /templatesGET /templates/:namePUT /templates/:name template CRUD
POST /records 建 record{template, values:{slot:content}, owner_id?}= 填 slot {success, record}
GET /records/by-template/:template?owner_id= 列某 template 的所有 record
GET /records/:recordId 取單筆 record 的 slot values

⚠️ 與插件本地舊 copy 的差異(改寫時務必對齊):欄位是 entry_type/owner_id(不是本地的 type/user_id);回應包在 {success, ...};基本盤 PUT/DELETE /records/:id entity_type 欄位、 vectorize 綁定(語意搜尋與 embedding 屬 optional 模組,不在 base)。

插件專屬狀態怎麼存(leo 2026-06-14 釘正:不准建表)

triplet 的 clusters/bridge_score/confidence/source_block_id、entity 正規化的 canonical/aliasentity_type——全部是「新資料類型 = 建 template + 填 slot」,不是建表:

插件狀態 存法(純 API
triplet template='triplet'slots: subject/predicate/object/source_block_id/confidence/clusters_json/bridge_score → POST /records
entity 正規化 template='entity'slots: canonical/aliases_json/entity_type/owner → POST /records;查重靠 GET /records/by-template/entity + (語意比對走 optional embedbase 沒有就降級 exact match
entity_type 不再是 blocks 欄位(基本盤無此欄)→ 收進 entity record 的 slot

「插件自建獨立 D1triplet_clusters 等)」不是選項——那仍是建表,違反鐵律 1。問「狀態存哪」時若想到建表,只准 template/slot。

邊界分類(R-EXT-1

依 HANDOFF 資產清單 + 實際 ls 比對(2026-06-14)。三類:插件留 / 基本盤走 / 灰色地帶待確認。 (註:下表「基本盤走」的前提是 arcrun 升 v3——見上「前置議題」未解前不要實際搬。)

插件(graph)— 留本目錄

類型 檔案
actions triplet-{crud,embed,entities,extract,stats,syntax,update} (7)、graph-{nodes,path,traverse} (3)、entity-{crud,graph-embed,normalize} (3)、predicate-normalizesearch-{embed,query,suggest} (3)
routes triplets.tsgraph.tsentities.tssearch.ts
migrations 0003_triplet_user_id0005_universal_table0006_triplet_clusters0008_entity_type
contracts triplet.json

註:HANDOFF 同時列 search-*entity-graph-embed 為插件資產,與此處一致。0005_universal_table 雖名「universal」(看似基本盤),但 HANDOFF 明列為 triplet/graph 相關 → 暫歸插件,待 R-EXT-3 釐清(見灰色地帶)。

基本盤(block CRUD)— 走 arcrun/kbdb

類型 檔案
actions block-{crud,embed,import,ingest,process,update} (6)、tag-crudprofile-crud
routes blocks.ts+ blocks.ts.bak 清掉)、tags.tstemplates.tsprofiles.ts
migrations 0001_init0002_block_indexing

灰色地帶 — grep 調查結論(2026-06-14

調查方法:src/index.ts route 掛載、route↔action import、plugin action 的相依、DB 表引用。

關鍵發現(耦合面)

  • 插件與基本盤在 action 層完全解耦:所有 plugin actiontriplet/graph/entity/search不 import 任何基本盤或灰色地帶 actionplugin route 也只 import plugin action。src/libsrc/models 都是空的,無共用程式碼耦合。
  • 耦合只在 DB 層plugin action 直接以 SQL 讀 blocks(28×)、entry_values(31×)、triplets(23×)。→ 這正是 AGE-on-Postgres 訊號:插件靠共用 D1 掛在基本盤表之上。
  • triplets 演進:0001 是 TABLE → 0005/0006/0007 改成 VIEW(疊在 universal table 上)。entry_values 是 0005 定義的 universal 儲存

逐檔歸屬建議(附證據)

檔案 證據 建議歸屬
0005_universal_table 定義 entry_values(v3 slots 儲存表)= 基本盤核心基礎設施,非純插件 基本盤(arcrun⚠️ 與 HANDOFF 列為插件相反——需與 arcrun 對齊
0007_v3_rename_and_cleanup 同時 rename 基本盤 blocks 表 + 重建 triplets/user_profiles VIEW **基本盤(arcrun**做 rename;插件只依賴「基本盤已是 v3 schema + 有 triplets VIEW」
entry-crud 無人 importroute/index 都沒引用)= dead code v2 legacy
record-crud + records.ts 被 admin/templates/records 用,非任何 graph route 基本盤/arcrun(非 graph
0004_task_status + tasks.ts /tasks 掛載,與 block 儲存/graph 都無關 arcrun 其他子系統(非本插件)
block-documentsconvertPdf + convert.ts blocks.ts 用 / /convert 掛載;PDF 轉換 基本盤/arcrun(非 graph
partner-auth + partners.ts 被 admin/partners/index 用;partner API key 認證 基本盤/arcrun 認證層
admin.tspersonality.ts /admin/personality 掛載;與 graph 無關 arcrun 其他子系統(非本插件)

結論graph 插件邊界乾淨(action 層零耦合)。耦合只在 DB 層——而那層正是要拆掉的違規。掛載介面不是「DB 掛載」,是 API 掛載(見下)。

掛載介面(R-EXT-3= 基本盤 APIAPI-as-Wall,非共用 D1

推翻原「AGE-on-Postgres 共用 D1 + triplets VIEW」設計。leo 鐵律:插件不碰表。

  • 掛載 = HTTP API:插件不共用 D1、不自建表、不建 VIEW。插件只能用基本盤 API / CLI / MCP 去建與讀leo 2026-06-14 釘正),與 AI/人同一條路。連 triplets VIEW 都不做——「圖」在插件層的記憶體裡從 record 組裝,不靠 DB VIEW。
  • 插件依賴的基本盤介面 = 上節「基本盤 API 契約」那張表(已存在於 arcrun/kbdb,不需 arcrun 升級、不需合庫)。
  • base URL = KBDB_BASE_URL env varleo 2026-06-14:做成可設定,先留空)。插件透過 src/lib/kbdb-client.ts 打它。本地測試用 mock / 本地 base worker,部署時填真網址。
  • 禁止繞道:不准把 SQL 藏在 helper 裡假裝走 API。client 只發 HTTP,零 .prepare。hook 擋語法層,這條是設計層補強。

改寫對照(21 個違規 action → API

現狀(違規 SQL 改寫成(基本盤 API
triplet-crud INSERT INTO entry_values + value blocks POST /templates(確保 triplet template 存在) + POST /records(template=triplet, 填 slot)
triplet-crud queryTriplets 大 JOIN GET /records/by-template/triplet?owner_id= → 插件層 filter/組裝
triplet-crud getTriplet/updateTriplet/deleteTriplet GET /records/:idupdate/delete 受限於 base 無 PUT/DELETE record(見「缺口」)
triplet-stats 聚合 SQL GET /records/by-template/triplet 後在插件層 reduce 統計
triplet-extract/triplet-entities 讀 blocks GET /entries/GET /entries/search
graph-{nodes,path,traverse}triplets GET /records/by-template/triplet 取全部 triplet → 插件層建鄰接表跑圖演算法
entity-crud/entity-normalize 讀寫 entity 表 template='entity' + POST/GET records;語意比對降級 exactbase 無 vectorize
search-query SQL GET /entries/search?q=keyword);語意搜尋待 optional embed 模組
predicate-normalize 純函式(若有 SQL 一併改 API

基本盤缺口(改寫時誠實標記,不偷建表補)

base 目前 PUT/DELETE /records/:id entity_type 欄位、 vectorize。影響:

  • triplet/entity 的 update/delete → base 缺端點。對策:(a) 標記為 [→arcrun] 缺口待基本盤補端點;(b) 暫以「建新 record + 標記舊 record 作廢」soft-delete仍走 API。不得為此自建表或直連 D1。
  • 語意搜尋 / entity embedding 比對 → 屬 optional embed 模組(不在 base)。base 沒有時降級成 exact match / keyword。embedding 不是插件職責,不在插件建 vectorize。

改寫 task(落到 tasks.md R-EXT-4

見 tasks.md 新增的 R-EXT-4「改寫成走 API」區塊。

獨立成 repoR-EXT-2

  1. 確認 R-EXT-1 邊界、清掉基本盤檔案(移交 arcrun)後,本目錄只剩插件。
  2. 改名 KBDB-graph、git init、設 remote(帳號問 leo)。
  3. 部署繞開 GitHubwrangler 直推 CF;不開 Actions。
  4. 推 GitHub(由本 CC 自己推)。

CLAUDE.md 裁剪

移除整套 KBDB v3 基本盤規範(萬物皆 Block 全文、50 endpoints、Block CRUD 細節),保留:樂高法、graph 插件定位、掛載介面、上游約束、wiki 讀取順序。基本盤規範移交 arcrun/kbdb 的 CLAUDE.md。