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>
This commit is contained in:
2026-06-14 20:59:41 +08:00
commit efe8e165cf
62 changed files with 7671 additions and 0 deletions
@@ -0,0 +1,145 @@
# 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。
@@ -0,0 +1,38 @@
# KBDB-graph 抽出 — Requirements
> 建立:2026-06-14
> 來源:InkStoneCo 頂層 `matrix-rearrange` R2 + 本目錄 `docs/HANDOFF-kbdb-plugin.md`
> 定調:leo 2026-06-13
---
## 背景
本目錄(`matrix/kbdb-graph-plugin`,原 `matrix/kbdb`)原是「整套 KBDB」。leo 2026-06-13 拍板拆分:
- **基本盤** = `arcrun/kbdb`D1 三表(blocks/templates/slots)的基本存儲讀寫,已併進 arcrun。
- **本目錄(KBDB-graph** = 掛在基本盤之上的 triplet 採集 + graph 查詢插件,**類比 Apache AGE 之於 Postgres**。
為何獨立:graph 能力較龐大、非基本存儲、leo 產權較複雜 → 獨立成 repo 不留 arcrun。
## 需求
1. **R-EXT-1 確認邊界**:把現有 `src/actions/``src/routes/``migrations/``contracts/` 逐一分類為「插件(triplet/graph/entity/search)」或「基本盤(block CRUD/template/tag/profile)」。基本盤的歸 arcrun/kbdb,插件的留本目錄。順便裁剪 CLAUDE.md(移除基本盤規範,只留 graph 插件相關)。
2. **R-EXT-2 獨立成 repo**:改名 KBDB-graph`git init` + 設 remote(帳號問 leo+ 推 GitHub。由本 CC 自己推,不經總管。
3. **R-EXT-3 定義掛載介面**KBDB-graph 如何掛在 arcrun/kbdb 基本盤上(AGE-on-Postgres 模式)——插件怎麼讀基本盤的 blocks 表、怎麼宣告自己的 triplet/entity/graph schema。
## 約束(硬性)
- **修改不是重建**:在現有實作上改,不重寫。
- **部署繞開 GitHub**wrangler 直推 Cloudflare,禁跨 repo 同步 Actions(當初害帳號被 flag 的模式)。新 repo 預設不開 Actions。
- **本目錄現無獨立 git**matrix 降級後脫離、被 InkStoneCo 頂層 gitignore)→ R-EXT-2 才 git init。在那之前用普通 mv 不是 git mv。
- **API-as-Wall / 萬物皆 Block** 仍適用於基本盤;插件對 graph 資料同樣經 API。
- **樂高法**`src/actions/` 單檔 < 100 行、一檔一事、無狀態。
## leo 已拍板(2026-06-14
- 獨立 repo = **新 repo `uncle6me-web/kbdb-graph-plugin`**(沿用現目錄名,非「KBDB-graph」字面)。
- 灰色地帶處理方式 = 先 grep 查引用再提建議(已完成,見 design.md)。
## 仍待確認(與 arcrun 對齊,非 leo
- 0005/0007 等基本盤 migration 歸屬與 HANDOFF 清單有出入,移交前對齊 arcrun。
@@ -0,0 +1,60 @@
# KBDB-graph 抽出 — Tasks
> 唯一進度來源,不靠對話記憶。完成即時更新。
> 狀態:[ ] 未開始 [🔄] 進行中 [x] 完成 [⏸] 卡住/待確認
---
## R-EXT-1 確認邊界
- [x] 1.1 inventory:列出現有 actions/routes/migrations/contracts2026-06-14 完成,見 design.md
- [x] 1.2 初步分類:插件 / 基本盤 / 灰色地帶(2026-06-14,見 design.md 邊界分類表)
- [x] 1.3 灰色地帶 grep 調查:證實插件 action 層零耦合、耦合只在 DB 層;逐檔附證據歸屬(2026-06-14,見 design.md 灰色地帶結論)。⚠️ 0005/0007 歸屬與 HANDOFF 有出入,仍需與 arcrun 對齊(屬 1.4
- [x] 1.4a 讀 arcrun 端真身對齊(2026-06-14):**發現 arcrun/kbdb 還是 v2entries,無 blocks/0005/0007/block-crud),且兩 repo 是不同 D1 庫**。v3 基本盤真身其實在本目錄。見 design.md「全局核對發現」
- [x] 1.4b 前置議題**總管已答覆**leo 2026-06-14):→ `InkStoneCo/docs/3-specs/matrix-rearrange/DECISION-kbdb-v3-baseplane.md`。三問消解:基本盤已在 arcrun/kbdb 且設計正確、掛載走 API(非共用 D1)、插件零 migration。**阻擋解除。**
- [x] 1.4c 不需移交/升級 arcrun——基本盤已正確。插件改寫成走 API 即可(見 R-EXT-4
- [ ] 1.5 裁剪 CLAUDE.md:移除基本盤規範,只留 graph 插件相關
- [ ] 1.6 清掉殘留:`blocks.ts.bak`、誤入 repo 的 `ruvector.db`(根 + src/routes/ 各一份)、`finally.click` 空檔、`.swarm`
## R-EXT-3 定義掛載介面(已定案 2026-06-14
- [x] 3.1 確認基本盤 API 契約(讀 arcrun/kbdb src,見 design.md「基本盤 API 契約」表)
- [x] 3.2 掛載方式定案:**API-as-Wall**HTTP API,非共用 D1、非 VIEW、非附加表)。圖在插件層記憶體組裝
- [x] 3.3 寫進 design.md 定稿(「掛載介面 = 基本盤 API」節)
## R-EXT-4 改寫成走 API(核心,2026-06-14 新增)
> 鐵律:插件零建表、零 migration、零 SQL,只用 API/CLI/MCP。
- [ ] 4.1 寫 `src/lib/kbdb-client.ts`:封裝基本盤 HTTP APIentries/templates/records),指向 `KBDB_BASE_URL` env var。零 `.prepare`
- [ ] 4.2 wrangler.toml:移除 D1/Vectorize 綁定(插件不碰 DB),加 `KBDB_BASE_URL` var(先留空)
- [ ] 4.3 改寫 `triplet-crud`(拆 < 100 行):create/query/get/update/delete/stats → API
- [ ] 4.4 改寫 `triplet-extract`/`triplet-entities`/`triplet-stats`/`triplet-update` → API
- [ ] 4.5 改寫 `graph-{nodes,path,traverse}`:取 triplet records → 插件層組圖
- [ ] 4.6 改寫 `entity-{crud,normalize,graph-embed}`template='entity' + records API;無 vectorize 時降級 exact
- [ ] 4.7 改寫 `search-query`/`search-*``GET /entries/search`(keyword);語意搜尋標記待 embed 模組
- [ ] 4.8 刪違規 migrations0001/0002/0005/0007 等含 CREATE TABLE+ 清基本盤 action/routeblock-*/entry-crud/record-crud/tag/profile/admin/...
- [ ] 4.9 改測試走 mock client;標記 base 缺口(PUT/DELETE record、vectorize)為 `[→arcrun]`
## R-EXT-2 獨立成 repo(最後做,依賴 1.4/1.5 完成)
- [x] 2.1 GitHub 帳號 + repo 名:**leo 拍板 = 新 repo `uncle6me-web/kbdb-graph-plugin`**2026-06-14
- [ ] 2.2 `git init` + `.gitignore`(排除 ruvector.db、.env、node_modules、.wrangler、*.bak
- [ ] 2.3 設 remote、首次 commit、推 GitHub(本 CC 自己推,不經總管)
- [ ] 2.4 部署驗證:wrangler 直推 CF,確認不開 Actions
---
## 阻擋項彙整(更新 2026-06-14
1. ✅ repo 已定:`uncle6me-web/kbdb-graph-plugin`(解除 2.1
2. ✅ 灰色地帶已 grep 調查完,附證據建議(解除 1.3)
3.**前置議題(讀 arcrun 後升級為主阻擋)**arcrun/kbdb 還是 v2、與插件不同 D1 庫。三問待 leo/arcrun 定案:
- (1) v3 基本盤(blocks/0005/0007/block-crud)由誰、怎麼進 arcrun?(arcrun 升 v3 vs 本目錄整理好再交)
- (2) 掛載形態:共用同一 D1(需合庫)還是插件透過基本盤 **API** 取 block(不共用 D1)?
- (3) `0005_universal_table` 歸基本盤(它定義 entry_values)——與 HANDOFF 列為插件矛盾,需 arcrun 確認。
## 注意
- arcrun 端對應交棒:`arcrun docs/HANDOFF-matrix-rearrange.md §2`,移交基本盤前先與其對齊。
- 在 2.2 git init 前,本目錄無版控 → 搬檔用 `mv` 不是 `git mv`