按 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>
11 KiB
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/人同一條路。連
tripletsVIEW 都不做——「圖」在插件層的記憶體裡從 record 組裝,不靠 DB VIEW。 - 插件依賴的基本盤介面 = 上節「基本盤 API 契約」那張表(已存在於 arcrun/kbdb,不需 arcrun 升級、不需合庫)。
- base URL =
KBDB_BASE_URLenv 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)
- 確認 R-EXT-1 邊界、清掉基本盤檔案(移交 arcrun)後,本目錄只剩插件。
- 改名 KBDB-graph、
git init、設 remote(帳號問 leo)。 - 部署繞開 GitHub:wrangler 直推 CF;不開 Actions。
- 推 GitHub(由本 CC 自己推)。
CLAUDE.md 裁剪
移除整套 KBDB v3 基本盤規範(萬物皆 Block 全文、50 endpoints、Block CRUD 細節),保留:樂高法、graph 插件定位、掛載介面、上游約束、wiki 讀取順序。基本盤規範移交 arcrun/kbdb 的 CLAUDE.md。