Files
Arcrun/system-dev/docs/3-specs/arcrun/kbdb-base/design.md
uncle6me-web 7bd7b4b26a SDD 生命週期鐵律遷移:單一活性制度上線(portal-auth=active,其餘 paused/draft)
- 鋪檔(自 system-dev-template v1.15.0):SDD-LIFECYCLE.md+pending-changes.md
  +sdd-guard.sh(覆蓋舊版,加單一活性檢查)+sdd-check.md+sdd-active-check.sh
- settings.json PreToolUse(Write|Edit|MultiEdit)掛上 sdd-guard.sh
- 全部 SDD design.md 掛 frontmatter:portal-auth=active(現行 portal 線,
  #61 demo 四件套剛 merge);artifact-sharing=draft(零任務動工);
  其餘 16 份=paused(皆有未完成任務,無明顯死件,不硬 close)
- CLAUDE.md 加「SDD 生命週期鐵律」段(指向 SDD-LIFECYCLE.md+濃縮五條)
  +SDD 速查表改以 frontmatter status: active 為現行判準
- 驗證:sdd-active-check exit 0(恰 1 份 active);guard pipe-test code 檔
  exit 0 帶現行 SDD 提示;反向測試(造 2 份 active)guard exit 2/check exit 1 全擋

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:03:16 +08:00

329 lines
26 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.
---
status: paused
superseded_by: ""
---
# Design: KBDB Base —— 原子化萬用表(self-hosted 資料底座 + 官方核心)
> 2026-06-07 richblack 拍板。來源:壓測報告(`test_arcrun/docs/壓測報告.md`)暴露 self-hosted 無資料層 →
> richblack 決定把 KBDB 的「基礎儲存」開源進 arcrun,向量/三元組走插件模式(如 PostgreSQL 的 PGVector / Apache AGE)。
> **架構同時影響官方版**:官方也改成「基礎核心 + 可選模組」,故開源與官方共用同一基礎,不維護兩套。
---
## 0. 為什麼(壓測暴露的缺口)
壓測(第一次完整壓測,6 輪回歸)跑通了「表單 → workflow → Google Sheets」,但暴露 **self-hosted 沒有資料層**
| 缺口 | 現況 | KBDB Base 如何解 |
|---|---|---|
| **無「專案」維度** | KV key 只有 `{namespace}:{type}:{name}`,無 projectrecipe 甚至全局共享 | entries 樹狀(entry_type=project/workflowparent_id)→ 可列「某專案的所有工作流」 |
| **D1 完全空置** | self-hosted init 只建 8 KV,無 D1`grep d1_databases` 全 repo 零結果 | KBDB Base 用 D1 → 填上閒置的 D1 |
| **recipe 成功無記錄** | push 時 probe 2xx 但不持久化 | 成功記錄存 entries/records → 可監控、可當投稿依據 |
| **資料碎(像 n8n** | 散落 KV,無結構 | templates 做虛擬表 → 結構化資料形態 |
> recipe 成功記錄 + 投稿(帶記錄免驗證)本次一起做(見 7)。recipe 信譽/市場/排序是後續,不在本次。
---
## 1. 核心設計:原子化萬用表(萬年不動)
> richblack 設計原則:「基礎表是 truth,萬年不動。新技術(embed/triplet/未來)一直增加,
> 它們把衍生結果記在**各自的地方**,不回頭改基礎表。」這正是 PostgreSQL 核心 + PGVector/AGE 插件的模型。
### 三張表(沿用 KBDB v3 `0005_universal_table`,已驗證可用)
| 表 | 作用 |
|---|---|
| `entries` | 原子資料。`entry_type``block`/`value`/`template`/`slot`+ 本 SDD 擴充 `project`/`workflow`)。樹狀 `parent_id`、多租戶 `owner_id` |
| `templates` | 定義虛擬表的 slots`slots_json`,如 `["display_name","gender"]`)→ 同一份 entries 用不同 template 呈現不同資料形態 |
| `entry_values` | 把多個 entries 按 template 組成一筆結構化記錄(`record_id` + `slot_name` + `entry_id` |
### 萬年不動原則(本 SDD 要修正 KBDB 現況的地方)
KBDB 現況**大致已符合**,但有違反處要修:
- ✅ embed`block-embed.ts` 把向量 upsert 到 **Vectorize**(獨立),不動 blocks 表。
- ✅ triplet 主體:`triplet-crud.ts` 主要 `INSERT INTO entry_values`(獨立記錄)。
- 🔴 **違反處**`triplet-crud.ts:106/110` `UPDATE blocks SET entity_type = ?` —— 抽 triplet 後回頭改基礎表。
**修法**:衍生屬性(entity_type 等)移出基礎表,記到衍生層(entry_values 或獨立 template)。基礎表只存 truth。
---
## 2. 插件模式:一套 code,三層 binding 開關(不維護兩套)
> richblack:「像 PGVector/AGE 是插件。基礎模組提供原子化萬用表(這也是賣點);要向量、要圖資料庫另外裝。
> 我還是希望只維持一套。」
| 層 | 能力 | 依賴 | 開源 / 費用 |
|---|---|---|---|
| **基礎 KBDB Base** | entries/templates/entry_values CRUD + **D1 LIKE 關鍵字搜尋** | D1 only | ✅ 開源,免費,**不綁卡** |
| **+ embed 模組** | 語義搜尋(search 升級) | CF **Vectorize** binding + AI | embed 是 **CF 內建**(程式薄)→ **不拆 repo,給「開/關」**:有 Vectorize binding 就啟用,沒有就降級 LIKE。Vectorize 需用戶自開(自付費) |
| **+ triplet 模組** | 三元組抽取 / 知識圖譜 / 圖查詢 | AI + triplet-*.ts8 個 action,較重) | triplet 是 **richblack 的 IP、程式量大****獨立 repo / 獨立 worker**,基礎不依賴它,要才裝 |
### Q1 拍板(richblack 2026-06-07):embed 不拆、triplet 拆
- **embed → 不拆 repobinding 開/關**:因為 embed 靠 CF 內建 Vectorize,程式薄(呼叫 `env.VECTORIZE.upsert`),沒 binding 就降級。一套 code、開關即可。
- **triplet → 獨立 repo/worker**:非 CF 內建、是 richblack IP、action 多(triplet-extract/normalize/embed/crud/stats/syntax/entities/update)。基礎 KBDB **不 import triplet**,要才裝。
### search 分層(壓測者問「search 還是要有,只是沒語義」→ 對)
KBDB `search.ts` 已有兩層骨架:
- 基礎版:D1 `LIKE` 全文搜尋(KBDB 現有 fallback 邏輯,不需 Vectorize)。
- + embed:語義搜尋(Vectorize + AI)。
**API 不變,能力分層**:基礎用 LIKE,裝 embed 升級語義。
---
## 3. 解耦工作(把寫死的觸發改成可選)
KBDB 現況:`blocks.ts``ingestText`/`createTriplet`/`deleteBlockVector` **寫死 import 進寫入流程**(寫 block 順手 embed+抽 triplet)。
解耦目標:
- 寫 block = 只寫 block(純基礎)。
- embed = 可選 hookVectorize binding 在才掛)。
- triplet = 可選(triplet 模組裝了才掛)。
- 基礎 `blocks.ts` / `templates.ts` / `records.ts` **不 import** vectorize/triplet。
> ✅ 已查證 `templates.ts` / `records.ts` 本就乾淨(只 import record-crud);主要工作在 `blocks.ts`。
---
## 4. 落地形態:import 進 arcrun(如 MCP 的 pattern
> richblack:「像先前把 MCP import 進本專案一樣,也把 KBDB import 進來。」對,同一 pattern。
- **基礎 KBDB Base 搬進 `arcrun/kbdb/`**(如 `arcrun/mcp/`)。形態:獨立 Worker + D1。納入 deploy 掃描(rule 05:新目錄 + wrangler.toml 自動部署)。
- **init 建 D1**`cli/src/lib/cf-api.ts` 加 D1 建立(如現有 KV 建立),`deploy.ts` 注入 D1 id。**self-hosted 從只建 KV → 建 KV + D1**。
- **cypher 改用 KBDB Base 存專案/工作流**webhook/credential/recipe 的「專案歸屬」走 KBDB entries(漸進,不一次搬完)。
- **embed**:隨基礎進 arcrunVectorize binding 開關。
- **triplet**:獨立 repoself-host 用戶要才另裝(不進 arcrun 基礎)。
### 共用同一基礎(官方也改)
richblack 決定官方版也改成「基礎核心 + 可選模組」→ 官方與 self-hosted **共用同一份基礎 KBDB code**,差別只在 binding(官方全開、self-host 基礎免費可選開)。**不維護兩套。**
---
## 5. 邊界(本 SDD 不做)
- **recipe 投稿入口 + 帶成功記錄免驗證**:✅ **本次要做**,見新增 §7(與 KBDB 成功記錄嵌在一起)。
- **recipe 信任 = 市場機制(靠量/星數模型)**:拍板用市場優先、暫不做強防偽(見 §7.3)。本次(5.2)做投稿端點(submit 私庫 / submit-p 公共庫覆蓋同 canonical_id+ stat 存證;**跨環境「靠量同步回市場」聚合層另排 task**;強防偽(官方重跑/多方回報)只在市場失靈才做。
- **triplet 模組本身**:獨立 repo,不在本 SDD(本 SDD 只確保基礎不依賴它、可選掛上)。
- **vectorize 的進階用法**suggest/entities-graph-embed):隨 triplet/進階模組,不在基礎。
- **官方版的搬遷細節**:本 SDD 聚焦「基礎 KBDB 進 arcrun self-hosted」,官方共用基礎是方向,搬遷另排。
## 7. recipe 公庫/私庫機制 + 帶 KBDB 成功記錄(市場信任)—— 本次要做
> §7.1-7.4 原只想「投稿」單向(本次最小已做:成功記錄 5.1 + submit-p 雛形 5.2)。
> §7.5 是 richblack 2026-06-07 指示補的 **CHANGE**:把公庫/私庫的所有互動情境想全(pull/搜尋/投稿/市場同步),
> 待 review。下方 7.1-7.4 是已成立的基礎,7.5 是擴充全貌。
> richblack 2026-06-07:本次只做「可投稿 + 帶成功記錄免驗證」,這兩件嵌在一起。
> 背景:官方初期那批 recipe 是一次性建立、未驗證(只為「Arcrun 推出不能空空的」)。
> 壓測者打通的修正版(如 google_sheets_append method/body)回不了官方 → 官方仍錯,下個用戶再撞。
> 不該由 AI 手改種子 recipe(治標);正解是投稿機制 + 用真實成功記錄當免驗證依據(治本)。
### 7.1 兩件嵌在一起
1. 成功記錄(KBDB):判定單位是「**工作流執行**」(對標 n8n execution,非逐節點)。一條 workflow 跑完,
整體成功(到 Output、無 error)→ 把這次**用到的每個 recipe 節點**各記成功 +1;整體失敗 → 已跑到的 recipe 節點各記失敗 +1。
歸屬對象是 recipe(因為投稿的是 recipe),**canonical_id 取 auth recipe 的 `service` 欄位**(與投稿身份一致)。
單節點工作流(cron→gdrive,一個節點一個 credential)是最簡情形。存 D1entry/record),可累積、可查。
> 註(術語別混):**cypher-executor = 工作流引擎**,用 cypher **語法**(節點+邊的圖遍歷語意)把零件串成工作流,
> 本就在開源核心內,**不是** graph DB。「可選/不在核心」的是兩個插件:① **triplet**(知識三元組的圖**儲存**,
> 對標 Apache AGE)② **vectorize**CF 內建但可能多花錢)。recipe 成功記錄是純 D1 KBDB 核心能力,不依賴這兩個插件。
2. 投稿(registry/官方):投稿 recipe 時帶上它在投稿者環境的成功記錄。官方端看記錄即信,免人工驗證——真實打通比投稿者自寫測試可信(呼應 gherkin-not-safety-sandbox-is)。
### 7.2 本次範圍(最小)
- recipe 成功/失敗計數落地 D1(KBDB 成功記錄)。
- 一個投稿端點:收 recipe + 其成功記錄 → 進官方 recipe 池,**新增一個作者版本**app-store 模型 §7.5.5**非**覆蓋同 canonical_id;同 canonical 多作者並存)。
- 投稿者目前就是 richblack,不需誘因;重點是有入口可投 + 帶記錄免驗證。
### 7.3 信任機制:市場優先(靠量,非人力/強防偽)—— richblack 2026-06-07 拍板
> 這個決策之前討論過、那時選「先不做」,導致今天又重講一次 → 故此次**寫進 SDD**,不再丟。
**核心:recipe 信任靠「量」決定,像 GitHub 星數,不靠人力檢核、也不先做強防偽。**
不可能全世界這麼多 recipe 都靠人力檢核。改用市場機制:
- recipe 的成功/失敗由**全體使用者的真實執行**累積(投稿者 A 的 recipe 通過 2000 次;投稿者 B 的失敗 500 次 = 像被按爛)。
- 上傳爛 recipe → 別人用會失敗 → 累積失敗數 → 投稿者信用下降 → 有動機更新自己發佈的 recipe,避免持續被按爛。
- **數據來源 = §7.1 的成功記錄**(每次工作流執行把用到的 recipe 成功/失敗記到 KBDB)。市場機制 = 把這些**跨使用者**的記錄**同步回公共市場**匯總。
**為什麼先市場、暫不做強防偽(誠實,mindset §7)**
self-hosted 投稿者 = 自己環境 root,能寫任意數字進自己 KBDB → 自報 stat 可造假。
但**先信市場**:實驗一段時間,若市場反應爛(造假猖獗)再考慮強防偽(官方重跑 / 多方獨立回報——證據由投稿者控制不了的一方產生)。
故**本次不拿投稿者自報的數字當自動放行門檻**(那會造債:未來門檻是空的)。stat 先當存證 + 法律歸責軌跡。
**分期**
- **本次(5.2**:投稿端點 `POST /recipes/submit`。submit(私庫)vs submit-p(公共庫,需 exposure_consent = 暴露同意,mindset §6)。
公共庫**新增作者版本**app-store 模型 §7.5.5,非覆蓋)。投稿者帶的 stat 寫進官方 KBDB 當存證(不當門檻)。
- **後續(市場同步,§7.3 主體)**:跨環境「靠量同步回市場」——self-hosted 各用戶的真實成功/失敗匯總回公共市場,
形成 recipe 的星數/信用。是 §7.1 數據的跨環境聚合層。本次先留接口(5.1 已在各環境記錄,市場聚合另排 task)。
- **更後續(強防偽,只在市場失靈才做)**:官方 in-process 重跑投稿者工作流拿官方自己的 2xx / 第二投稿者獨立回報。
(註:app-store 模型下「版本並存」是常態、不刪舊版 §7.5.5;舊版靠市場淘汰,不做下架引導。)
### 7.4 與零件投稿的差異
零件投稿走 GitHub PR(人 merge + CI runtime 驗 wasm,記憶 component-submission-via-pr)。
recipe 是純文字資料(非 wasm),不需 CI 跑沙箱;它的驗證改用真實成功記錄。故 recipe 投稿機制與零件不同,不混用 component-registry-canon SDD。
### 7.5 公庫 / 私庫雙向機制(CHANGErichblack 2026-06-07 指示「把兩庫所有情境想好」)—— 待 review
> 背景修正:原 §7.1-7.4 只想了「投稿」單向,太窄。richblack 指出:
> **SaaS 版(standard mode)本來就只有公庫;self-hosted 版本來就只有私庫**。
> 兩庫的互動是 overall 架構(不只為投稿):私庫是公庫的**按需子集**——self-hosted 也許只用 10 個,
> 公庫也許有 10000 個,**不需每次全部拉下來,用到的再複製**。投稿(submit-p)只是其中一條反向流。
#### 7.5.1 兩個庫的物理本質(已確認現況)
| 庫 | 是誰 | 物理 | 規模 | 角色 |
|----|------|------|------|------|
| **公庫** | 官方 SaaS`cypher.arcrun.dev`)的 `RECIPES` KV | 官方 CF 帳號 | 大(~10000,含社群投稿) | 唯一公共真相、可瀏覽/搜尋/pull |
| **私庫** | 每個 self-hosted(用戶自己帳號)的 `RECIPES` KV | 用戶 CF 帳號 | 小(~10,按需子集) | 自己實際用到的 + 自己改的 |
- recipe key 都是 `recipe:{canonical_id}`(無 owner 前綴)。同一套部署的 KV = 那套的庫。**物理隔離**(不同帳號不同 KV)。
- `mode`cli config):`standard` = SaaS 用戶(只連公庫,無私庫);`self-hosted` = 自己一套(有私庫,可選連公庫 pull/submit);`local` = 純本地。
#### 7.5.2 三種使用者 × 互動矩陣
| 使用者 | 有哪些庫 | 取 recipe | 改 / 投稿 | 成功記錄 |
|--------|----------|-----------|-----------|----------|
| **SaaS 用戶**standard) | 只公庫 | 直接用公庫(公庫即他的庫) | 改了直接寫公庫(=投稿,需 consent) | 記在官方 KBDB(即市場數據本身) |
| **self-hosted 用戶** | 私庫(主)+ 公庫(唯讀來源) | **公→私 pull**:搜公庫→複製到私庫 | 私庫改 →(可選)**submit-p** 推回公庫 | 記在自己 KBDB;市場同步另排(§7.3 5.5 |
| **local** | 無遠端庫 | 本地 recipe 檔 | 無 | 本地 |
#### 7.5.3 四條互動流(窮舉)
1. **公→私 pull(核心,之前漏)**self-hosted `acr recipe pull <canonical_id>` → 從公庫只讀端點抓該 recipe → 寫進自己私庫 KV。
按需,**不全量同步**(10000 不必拉)。pull 也可帶公庫的成功記錄(市場星數)供用戶判斷要不要用。
2. **公庫瀏覽/搜尋**`acr recipe search <q>` / `list` → 打公庫只讀端點(`GET /public-recipes?q=`)。
self-hosted 沒搜尋能力就不知道公庫有什麼可 pull。SaaS 用戶同一端點即用。
3. **私→公 submit-p(已做雛形)**self-hosted 把私庫某 recipe 推回公庫(`POST /recipes/submit`,需 exposure_consent)。**新增作者版本**app-store 模型 §7.5.5,非覆蓋;同 canonical 多作者並存)。
4. **成功記錄 → 市場同步(§7.3 5.5,另排)**self-hosted 各環境真實成功/失敗匯總回公庫的市場星數。pull(流1) 帶的星數就來自這裡。
#### 7.5.4 公庫只讀端點(公→私 + 瀏覽的基礎,待實作)
官方 cypher 開**公開只讀**端點(無需 api_key,因公庫本就公共):
- `GET /public-recipes?q=&limit=&offset=` → 搜尋/列出公庫 recipe。**同 canonical_id 回多筆(多作者)**,各附作者 + 市場星數 success/failure,供 CC/AI 依數據選(§7.5.5)。
- `GET /public-recipes/:canonical_id?author=` → 取單一 recipe 全文(pull 用)。不指定 author → 回市場最佳版本(成功率最高)。
- **搜尋/取用落空 → 回 `{ found: false, canonical_id, hint }` 創作引導**(§7.5.6),不回空陣列乾等。讓 CC 知道下一步是「自己做一個成為作者」。
> 與現有 `GET /recipes`(已存在,列當前部署 KV)的差別:`/public-recipes` 語意是「**這是公庫**,給外部 self-hosted pull/瀏覽」,且**含作者維度 + 市場數據**。
> 在官方部署上讀同一 KV;命名分開是為了語意清楚 + 公庫的多作者/市場排序不污染內部 `/recipes`。
> **市場星數 per-uuid(§7.5.h 已實作)**5.1 改收集 API recipe 的 **uuid**(非 auth service),KBDB 市場星數記 per-uuid。
> 故 `GET /public-recipes/:canonical_id` 的「選市場最佳作者版本」**真正能區分 Leo 版/John 版**(各自 uuid 各自累積)。
> 舊資料無 uuid → fallback canonical_idmigration 後自然帶 uuid)。
#### 7.5.5 recipe = 具名作者作品(app-store 模型)+ UUID 身份 —— richblack 2026-06-07 拍板
> 這條推翻了我原本「覆蓋同 canonical_id」的錯誤前提。正確模型如下。
**每個投稿的 recipe 是一個「具名作者作品」,像 app store 上的一個 app,只有作者(+admin)能改。**
- Leo 投稿 `gsheets_upsert` = 在 app store 放了一個 app。**只有 Leo + admin 能動它**。
- John 覺得 Leo 的很爛 → John **另推一個** `gsheets_upsert`John 的作品)。**只有 John + admin 能動**。
- 公庫裡因此**同 canonical_id 並存多份**Leo 版、John 版),各帶作者、各帶獨立市場數據。
**所以:**
1. **submit-p ≠ 覆蓋**。投稿是**新增一個作者版本**,不是覆蓋同 canonical_id。(修正原 §7.2/§7.3 的「覆蓋」字眼。)
2. **沒有「公庫改了 Leo 要不要跟」的問題**:Leo 的作品只有 Leo 改;別人不滿意是自己推新作品,不是改 Leo 的。版本關係問題消失。
3. **向後相容、不刪舊 recipe**:Leo 的 5/10 不被刪,只是市場上沒人選 → 自然淘汰(長尾留著,無破壞性刪除)。
4. **選擇由市場數據驅動(CC/AI 操盤手選)**CC 搜 `gsheets_upsert` → 找到 2 個 → 看數據(Leo 5成功/10失敗、John 100成功/0失敗)→ **選 John 的**。Leo 的逐漸沒人用。
**身份模型(落地)= UUIDrichblack 2026-06-07 拍板,比 canonical_id+author 複合鍵更乾淨)**
**每個 recipe 一誕生就領一個 UUID = 唯一身份。`canonical_id` / `author` / `公私` 全是屬性,不是身份。**
- 身份(UUID) 與 歸屬(author 屬性) 分離 → 沒有「同 canonical_id 撞 key」問題(key 是 UUID,本就唯一)。
- **KV key 設計**`recipe:{uuid}` 存 recipe 本體;`idx:canonical:{canonical_id}` → UUID 清單(同 canonical 多 UUID);
私庫執行:`idx:installed:{canonical_id}` → 該 canonical 在本庫安裝的**唯一** UUIDpull/author 時定一個)→ 執行查找仍由 canonical_id 解析到唯一 recipe**不破執行**。
- **Leo 改 John 的**Leo 拿 John 版(UUID-J)改 → 發出去領**新 UUID-L** = Leo 作品。UUID-J/UUID-L 是兩個東西,不冒名、天然不衝突。
- **author 屬性 = 該 UUID 誕生時的投稿者**(誰投誰負責那版市場數據);可選帶 `derived_from: {uuid}` 溯源(致謝/fork upstream)。
**「Leo 跟 John 一模一樣」怎麼辦**(你問的):兩個不同 UUID 都通過、各跑各的市場數據。
**重複 = 市場冗餘,靠數據淘汰不靠技術阻止**(先有數據的勝,複製品沒人選自然沉底,§7.3 靠量不靠人力)。
**檢舉功能**:本次**不做**。檢舉 = 人力檢核,與「市場優先、暫不強防偽」(§7.3)矛盾,提前做還用不到 = 造債。
市場失靈才考慮(§7.3 更後續)。**CC 不檢舉**mindset §7:CC 是操盤手不是審查者、不代人裁決;CC 對爛 recipe 的「投票」= 不選它 = 市場數據)。
> 影響現有 code5.2 的 `POST /recipes/submit` 改「覆蓋」→「領新 UUID 新增」;recipe key 從 `recipe:{canonical_id}` 轉
> `recipe:{uuid}` + canonical 索引;執行查找改經 `idx:installed:{canonical_id}` 拿唯一 UUID(保證不破 component-loader/auth-dispatcher/credential-injector)。列入 7.5.f task。
#### 7.5.6 搜尋落空 = 創作入口(richblack 2026-06-07 拍板)
> app-store 模型的閉環關鍵:公庫沒有的 recipe,由 CC 現做成為作者,再投回公庫補上。
**CC 搜「gsheets_delete_sheet」公庫沒有 → 端點不能只回空陣列乾等,要回「可創作」訊號 → CC 說「那我做一個」→ CC 成為作者。**
- 公庫只讀端點(§7.5.4)搜尋**落空時的回應 = 創作引導**,不是空結果:
明確回 `{ found: false, canonical_id, hint: "公庫無此 recipe,可自行建立並 submit-p 投稿成為作者" }`
讓 CC(AI 操盤手)知道下一步是「自己做一個」而非卡住。
- CC 現做 → 跑成功累積市場數據(§7.1)→ submit-p 推回公庫 → 成為公庫**第一個** `gsheets_delete_sheet`CC 是作者)。
- **閉環**
- 公庫有 → pull 用(市場數據選最佳作者版本,§7.5.5)。
- 公庫沒有 → CC 現做成為作者 → 用 → 投稿 → 公庫從此有了。
- 呼應 mindset**CC 是大腦**arcrun 是 AI 呼叫的工具),缺能力時 CC 自己補(做 recipe / 工作流),
不停在「找不到」。工作流是 default、零件是例外(缺能力先想工作流/recipe,不是停手)。
---
## 8. KV list 上限威脅免費承諾 → 高頻 list 遷 D1CHANGE2026-06-08 richblack CF 收到 list 超標信)
> 事件:richblack 的 CF 帳號收到「Workers KV 免費等級每日 1000 次 list 上限已超」信,
> list API 回 429 直到隔日重置。**這直接威脅 arcrun「免費可用」核心承諾**[[deploy-via-local-deploy-not-ci]] 免費優先精神)。
> 方向拍板(richblack 2026-06-08):**高頻 list 改走 D1**(對齊 §6 Q2 的 KV vs D1 分工——要列舉/搜尋的進 D1)。
### 8.1 根因:哪些 KV list 是高頻(grep cypher-executor/src
| 位置 | list 操作 | 頻率 | 嚴重度 |
|------|----------|------|--------|
| `scheduled.ts:34` | `WEBHOOKS.list({prefix:'cron-idx:'})` | **每分鐘一次 = 1440/日** | 🔴 **常駐單獨就爆**>1000 上限) |
| `recipes.ts` public-recipes(§7.5 新加) | `RECIPES.list({prefix:'recipe:'})` | 每次 recipe 搜尋 | 🟠 搜尋頻繁就疊加爆 |
| `recipes.ts` GET /recipes / listAllRecipes | `RECIPES.list` | 每次列 recipe | 🟠 |
| `webhooks-list.ts` / `webhooks-named.ts` | `WEBHOOKS.list` | 每次列 workflow | 🟠 |
> **D1 額度遠寬**:免費 5M 讀/日、100K 寫/日(§6 Q4),對比 KV list 僅 1000/日。要「列舉/搜尋/排序」的天生該 D1。
### 8.2 緊急止血(cron,今天在爆)
`scheduled.ts` 每分鐘 list `cron-idx:` → 改成**不靠 list**
- 方案:維護**單一固定 key**(如 `cron-idx:_all` 存所有 cron workflow 的 {apiKey,name,cron_expr} JSON 陣列),
scheduled 只 `get` 一次(取代 list)。acr push 時 upsert 進這個 key。
- 1440 次/日 list → **0 次 list**(改 1 次 get/分鐘 = 1440 get/日,KV get 免費額度 100K/日,遠夠)。
### 8.3 治本(recipe / workflow 查詢遷 D1,分期)
對齊 §6 Q2recipe + workflow 進 D1):
- **recipe**recipe 本體從 RECIPES KV 遷進 KBDB D1entry_type='recipe' 或專表)。
public-recipes 搜尋、list 改 D1 queryLIKE/WHERE,含 §7.5 多作者/市場排序天生適合 SQL)。
執行時 resolveRecipe 也改 D1(但執行是 get-by-key 級,KV get 不爆——可漸進)。
- **workflow**workflow record 從 WEBHOOKS KV 遷 D1entry_type='workflow' 掛 project parent,§6 Q1)。
list workflow 改 D1 query。
- **保留 KV 的**session / credential / exec context(§6 Q2:短期高頻純 get,不 list)。
### 8.4 分期建議(待 richblack 拍板細節)
1. **P0 止血**(§8.2):cron 改單 key get。最小改動、今天可止血。
2. **P1**public-recipes / recipe list 改 D1(我 §7.5 新加的 list 是疊加元兇)。
3. **P2**workflow list 改 D1。
4. **P3**recipe/workflow 本體完整遷 D1resolveRecipe / KV→D1 讀寫)。
> **誠實 trade-off**:遷 D1 要寫 migration + 雙寫過渡(KV 舊資料相容),工程量不小。
> 但不遷 = 免費承諾破功(用戶用一用就 429)。**這是核心承諾必修項,不是優化。**
> 待 review §8 + 拍板分期顆粒度後動 code。**勿在 review 前動。**
---
## 6. 開放問題(待 review
1. **專案維度怎麼進 entries**`entry_type='project'` + workflow 用 `parent_id` 掛專案?還是 templates 定義「專案」虛擬表?
1. Leo:由你決定,我記得先前 KBDB 已經有實作,參考原有做法。
2. [DECIDED, CC 2026-06-07] use entry_type=project + parent_id tree (NOT templates virtual table). Matches existing KBDB: entry_type is an extensible string, entry-crud.ts already supports WHERE parent_id; no schema change (honors table-never-changes). "list workflows under project" = WHERE parent_id=PROJECT_ID AND entry_type=workflow. templates/slot reserved for structured records like recipe success stats.
2. KV vs D1 分工[拍板 2026-06-07]KV 不全廢,按特性分。workflow YAML + recipe + 成功記錄進 D1(要層級/列舉/排序);session、verdict、credential 留 KV(短期高頻純取用)。類比 PostgreSQL + Redis。workflow 變 entryentry_type=workflow 掛 project parent)。
1. Leo:同意
3. **triplet 獨立 repo 的接點**:基礎 KBDB 怎麼讓 triplet 模組「掛上」——HTTP hook?還是 triplet worker 自己讀同一個 D1
1. Leotriplet 其實也就是 template 加上一些函式,最簡單就是你給他一段文字抽取出三元組,但如果要自動化,就會提供 API,背後是工作流,例如 block 存入時就叫起 triplet 功能,這個功能如果自動化就是在安裝時同意,但應該也可以有 mcp 讓 AI 易於操作,如同前述,交貨時有 MCP,因為是 AI Friendly 的系統。
4. **init 建 D1 的冪等 + self-hosted 免綁卡確認**:D1 是否如 KV 一樣免費不綁卡(需查證 CF D1 免費額度條件)。
1. Leo:一樣不綁卡,除非他用量很大,屆時不是我來提醒而是 CF 會提醒他