# KBDB-graph 抽出 — Design > 建立:2026-06-14 > 大改:2026-06-14(leo 拍板鐵律後,推翻原「共用 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_values`,`entry_type='block'`)**是刻意設計的基本盤**,明寫 plugin model(core + 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_type`**(block/value/...)、**`owner_id`**;回 `{success, entry}` | | `GET /entries` | list(filter: 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` | 建 template(name+slots[])= **替代建表** | | | `GET /templates`、`GET /templates/:name`、`PUT /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`/`alias`、`entity_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 embed,base 沒有就降級 exact match) | | entity_type | 不再是 blocks 欄位(基本盤無此欄)→ 收進 entity record 的 slot | 「插件自建獨立 D1(triplet_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-normalize`、`search-{embed,query,suggest}` (3) | | routes | `triplets.ts`、`graph.ts`、`entities.ts`、`search.ts` | | migrations | `0003_triplet_user_id`、`0005_universal_table`、`0006_triplet_clusters`、`0008_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-crud`、`profile-crud` | | routes | `blocks.ts`(+ `blocks.ts.bak` 清掉)、`tags.ts`、`templates.ts`、`profiles.ts` | | migrations | `0001_init`、`0002_block_indexing` | ### 灰色地帶 — grep 調查結論(2026-06-14) 調查方法:`src/index.ts` route 掛載、route↔action import、plugin action 的相依、DB 表引用。 **關鍵發現(耦合面)**: - **插件與基本盤在 action 層完全解耦**:所有 plugin action(triplet/graph/entity/search)**不 import 任何**基本盤或灰色地帶 action;plugin route 也只 import plugin action。`src/lib`、`src/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` | **無人 import**(route/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-documents`、`convertPdf` + `convert.ts` | 被 `blocks.ts` 用 / `/convert` 掛載;PDF 轉換 | **基本盤/arcrun**(非 graph) | | `partner-auth` + `partners.ts` | 被 admin/partners/index 用;partner API key 認證 | **基本盤/arcrun 認證層** | | `admin.ts`、`personality.ts` | `/admin`、`/personality` 掛載;與 graph 無關 | **arcrun 其他子系統**(非本插件) | **結論**:graph 插件邊界乾淨(action 層零耦合)。耦合**只在 DB 層**——而那層正是要拆掉的違規。掛載介面不是「DB 掛載」,是 **API 掛載**(見下)。 ## 掛載介面(R-EXT-3)= 基本盤 API(API-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 var(leo 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/:id`;update/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`;語意比對降級 exact(base 無 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」區塊。 ## 獨立成 repo(R-EXT-2) 1. 確認 R-EXT-1 邊界、清掉基本盤檔案(移交 arcrun)後,本目錄只剩插件。 2. 改名 KBDB-graph、`git init`、設 remote(帳號問 leo)。 3. 部署繞開 GitHub:wrangler 直推 CF;不開 Actions。 4. 推 GitHub(由本 CC 自己推)。 ## CLAUDE.md 裁剪 移除整套 KBDB v3 基本盤規範(萬物皆 Block 全文、50 endpoints、Block CRUD 細節),保留:樂高法、graph 插件定位、掛載介面、上游約束、wiki 讀取順序。基本盤規範移交 arcrun/kbdb 的 CLAUDE.md。