Files
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

146 lines
11 KiB
Markdown
Raw Permalink 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.
# 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 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_type`**block/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 /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 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-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 actiontriplet/graph/entity/search**不 import 任何**基本盤或灰色地帶 actionplugin 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= 基本盤 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/: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`;語意比對降級 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。