# Arcrun 決策記錄（DECISIONS.md）

> 這份檔案記錄 Arcrun 的**穩定決策**：架構定義、核心原則、為什麼這樣設計。
> 它很少改。任何 AI 或人接手 Arcrun，先讀這份。
> 流動的待辦在 `BACKLOG.md`。
>
> `DECISIONS.md` 不是寫完就死的。想法變了要回來改它，讓它跟上——
> 怕的不是改，是「想法變了但檔案沒跟著改」，那它就變成說謊的文件。
>
> 最後更新：2026-05（第一期規劃期間整理）

---

## 0. 設計哲學

**第一條：每多依賴一個不可掌控的第三方，就多一個單點故障。**
Arcrun 賣的本質是「減少你對不可控第三方的依賴」。核心開源（MIT，公司倒了還能 fork）、
支援 self-hosted（跑在你自己的 CF，甚至自己的 wazero）、workflow 是純文字你自己擁有。
但「減少依賴」不等於「零依賴」——每個依賴要問：它掛了，我有沒有退路？有退路的依賴可接受。

**第二條：解耦 / 原子化。**
什麼都要能單獨換掉、不被綁死。primitive 與 recipe 解耦、執行與發現解耦、
引擎與零件解耦。AI 時代變化快，「可改」本身就是核心價值。

**第三條：Arcrun 是 AI 用品，但設計目標是「讓 AI 不可能做歪」。**
管住 AI 的開發痛點，和要賣給用戶的產品，是同一件事。

---

## 1. 架構：primitive 與 recipe

**Arcrun 只有四種 WASM primitive：**
1. 流程控制（if / switch / filter / foreach / try_catch / wait …）
2. 文字 / 資料處理（string_ops / number_ops / array_ops / date_ops / set / merge …）
3. http_request（打任意 HTTP）
4. credential（四個 auth WASM：auth_static_key / auth_service_account / auth_oauth2 / auth_mtls）

**其他一切都是 recipe。** recipe = http_request + 一組固定設定（YAML 文字）。

| | primitive | recipe |
|---|---|---|
| 是什麼 | WASM Worker | YAML 文字 |
| 要不要 deploy | 要 | 不用 |
| 多久變一次 | 幾乎不變 | 一直長新的 |

**判準（真零件 vs 假零件）：** 一個零件若滿足任一條，它是假零件，該降級成 recipe：
- contract 或原始碼出現具體外部服務的 URL / domain
- 它宣告的能力是 http_request 的子集（打某固定 endpoint）

→ D1 / KV / Vectorize / Supabase / KBDB 的存取，**一律是 http_request + recipe**，
   絕不做 `d1_crud`、`kbdb_get` 這種「假零件」。

### 「recipe」有三種，不要混用這個詞

Arcrun 裡有三個都叫 "recipe" 的東西，職責不同：

| 名稱 | 是什麼 | KV key |
|---|---|---|
| **API recipe** | http_request + endpoint/method/headers/body 模板（`RecipeDefinition`） | `recipe:{canonical_id}` |
| **auth recipe** | 認證設定：primitive + base_url + 注入規則（`AuthRecipeDefinition`） | `auth_recipe:{service}` |
| **prompt recipe** | LLM prompt 的封裝，與 KBDB block 有關 | `prompt_recipe:{name}` |

一般講「recipe」= API recipe。假零件降級的終點是 **API recipe**（`recipe:{id}`）。
降級作業不碰 auth recipe、不碰 prompt recipe（prompt recipe 犍涉 KBDB block 展開，
是 BACKLOG 待決策項，不在此範圍）。

### recipe 與 primitive 的驗收標準不同（早期已定，2026-05 重新確認）

- **primitive：Gherkin 通過 = 驗收通過。** primitive 是封閉的邏輯，正確性不依賴外部世界，
  可以用「given / when / then」確定地驗證。
- **recipe：打得通（2xx）= 驗收通過。** recipe 是「指向外部 API 的指針」，正確性一半在定義
  （打不通就代表定義錯，「打通」已驗）、一半在外部服務當下的行為。

**關鍵認識：recipe 不用 Gherkin，不是偷懶，是 Gherkin 對 recipe 沒用。**
「2xx 但外部服務沒真的做事」「外部服務改了 API」——這些是 recipe 唯一的真實風險，
而 Gherkin 一樣擋不住（Gherkin 測的當下沒改就過，之後改了它早跑完了）。
能補這個風險的只有「執行 → 回報 → 修正」的市場機制，不是任何靜態驗收。
給 recipe 加 Gherkin = 花成本做一件不會多驗到任何東西的事。

recipe 的「語義正確性」（真的刪了那列嗎）交給市場：A 的 recipe 在 B 那裡失效，
B 的 AI 會因為「目的沒達成」去查、去修、提修改版。Arcrun 不監控全世界的 API 變動。

**例外——可以是 primitive 的「引擎內建能力」：** 若某能力不依賴任何外部 endpoint、
是 Arcrun 執行環境本身自帶的（如「workflow 中途暫存」），它可以是 primitive。
判準：「這段邏輯依不依賴外部服務的 endpoint？」依賴→recipe；引擎自帶→可 primitive。

### 工作流是 default，建零件要過人類閘門（2026-05 補，CC 把自用服務錯做成零件後定）

AI 開發時的預設順序：

1. **預設寫工作流**（串服務 / 自用 / 給少數人，都先工作流）。default、阻力最小。
2. **零件的正當時機**：服務不提供串接但有 API，且**有必要讓全 Arcrun 生態重用** → 才建零件
   （零件 = API 薄殼，只打一個 endpoint）。
3. **建零件 = 過人類閘門**。看到「有 API 可包成零件」≠「該包」，先問「你有必要嗎？」。
   AI 不可自行建零件，必須 (a) 經人類互動確認；(b) 明示舉證「為何工作流做不到」
   （舉證責任在 AI，預設假設工作流能做）。把關點在「建立零件的 API」本身——
   CLI / MCP / Python lib / JS lib 四路全收斂到這關。

**為什麼要人類閘門**：零件進公共庫 = 全生態都能打它。自用服務（通訊錄 / 帳本）沒設驗證就變零件
= 公開後門。安全 / 意圖機器判不了，必須人看。規範會忘、hook 不會（§7：判準寫成機械紅燈）。

**不限制自由**：別人要建零件是他的自由（開無驗證服務給人串也是），唯一硬約束「零件 = endpoint 薄殼」。
閘門不是禁止，是「要建得先說服人 + 舉證」的摩擦。

**ABC 三管齊下讓 AI 不選難路**：A 審核當場擋（§7 層二）+ B 工作流範本好寫（§7 層一）
+ C mindset 明示預設（§7 層三）。人類閘門是第四道，專擋意圖 + 安全。
原理：難路走的當下要痛、易路選的當下要爽、事先有聲音說易路是 default。
CC 把自用服務錯做成零件，正因這三者當時全缺。

---

## 2. TS 邊界規則（哪些程式碼能用 TS，哪些不能）

**判準：這段程式碼是「執行引擎 / 工具本身」，還是「一個零件該做的事」？**
- 引擎 / 工具（cypher-executor 解析 graph、CLI 讀 YAML、registry 驗收）→ **TS，第一期合法**
- 零件該做的事（打 API、處理資料、做認證）→ **必須 WASM（TinyGo），用 TS = 犯規**

**靠位置判斷，不靠肉眼判斷內容：**
- `registry/components/` 底下出現 `.ts` = 犯規（該目錄只准 .wasm + contract.yaml）
- `cypher-executor/src/` 底下的 `.ts` = 第一期合法（引擎程式碼，Tier 1/2 部署於 Cloudflare，
  本來就用 TS）。注意：這是「引擎邏輯可用 TS」，**不是**「引擎永遠是 Cloudflare 專屬」。長期見 §4。

**為什麼零件必須 TinyGo 不能 TS：** TS/JS 編不出獨立輕量的 WASI .wasm——
JS 需要一個 JS 引擎來跑，塞進 wasm 體積爆炸。TinyGo/AS/Rust 直接編譯成
自包含、只依賴 WASI 標準介面的 wasm（10–80KB）。這是三層 runtime 的物理前提（見 §4）。
TinyGo 為官方首選（語法與 TS 差異夠大，AI 不易把純 TS 邏輯誤搬）。

---

## 3. 開源 / 商業邊界

| 開源核心（MIT） | 服務側（付費 / 需 API Key） |
|---|---|
| cypher-executor、四種 primitive WASM、CLI（acr）、registry | KBDB 語義搜尋、KBDB graph 查詢、Persona / Mira 等 |

**KBDB 採 Supabase 模式：** KBDB 的 recipe 顯示在公共零件庫（能力可見＝引子），
要用就申請 API Key（註冊＝轉化），用爽了付費。arcrun.dev 已有「取得 API Key」入口。

**「執行」與「發現」解耦：**
- 執行：永遠本地、免費、可離線。不依賴公共庫。
- 發現（語義搜尋）：連線加值。self-hosted 用 local 關鍵字搜本地下載過的；
  連公共庫才能用 KBDB 語義搜尋（在大集合裡查意圖）。

**cypher-executor 裡的 KBDB = 污染（清）；公共零件庫的 KBDB = 服務（留，但不在第一期）。

---

## 3b. credential 是引擎能力（不是用戶零件）

（基於 2026-05 查核 graph-executor / credential-injector / auth-dispatcher 原始碼）

credential 的注入不是一個 workflow 節點做的事。它是**執行引擎在「呼叫零件之前」
自動做的一個步驟**：graph-executor.executeNode 的流程是：
組 ctx → 注入 credential → 才 runner(ctx)。零件拿到 ctx 時 credential 已被放進去，
零件自己不知道 credential 怎麼來的。

**credential 的本質 = KV 裡的加密值 + 一段「在零件執行前注入 ctx」的引擎逓輯。**
那四個 `auth_*` WASM（static_key / service_account / oauth2 / mtls）是「注入步驟的後端」，
不是用戶會在 workflow 擺的零件。用戶永遠不會直接呼叫 `auth_static_key`——
是 auth-dispatcher 在背後呼叫它。
→ 「零件白名單」「假零件判準」不適用於 `auth_*`，它們不是用戶零件。

**credential 系統現状是「新舊兩路並存」的半成品：**
- 新路（對的方向）：`auth-dispatcher` → HTTP 打 `auth_*` WASM，解密/JWT 全在 WASM 內。
  已支援 static_key / service_account / oauth2；mtls 尚未（Phase 4）。
- 舊路（要砸）：`injectCredentials` TS 裡解密，含 `BUILTIN_CREDENTIALS_MAP`。
  註解自認 Phase 1.9 將刪除。砦舊路是獨立清理，**不擋降級**（見 BACKLOG）。

**注入靠 `auth_recipe:{componentId}` 觸發。** 一個服務要能被注入 credential，
必須有對應的 auth recipe。KBDB 用 static_key，而 static_key 新路已支援
→ 降級 KBDB 的 credential 前置是「小」的（只需建一個 `auth_recipe:kbdb`）。

**修正一個舊裁決：** API recipe 的 `credentials_required` 欄位 **要留**。
`makeRecipeRunner`（零件執行）不讀它，但 `injectCredentials`（零件執行前的注入步驟）
會讀它。credential 不是在 runner 裡處理，是在 runner 之前那一步處理。**

---

## 3c. execute vs test：意圖決定路徑（服務側，不在第一期）

一個 AI 開發 recipe，有兩種意圖，對應兩個指令：

- **只打算自己用** → 用 `execute`。直連目標 API，**不經過 arcrun**，self-hosted 純粋。
- **打算公佈到公共零件庫換 credits** → 從開發的第一次打就用 `test`，`test` 明示走 arcrun relay。

**關鍵：用 `test` 這個動作本身，就是「我打算公佈」的意思表示。**
AI 用 test 開發時，每一次打都經過 relay，arcrun 當下就看到真實打通記錄——
不需 AI 事後交一份自己寫的 log（執行者不能驗證自己，見 §7），也不需 arcrun 事後重打
（重打 delete 要自備測試環境，跟開發者工作重複、荒謬）。

### test relay 經手 credential — 誠實處理

`test` 走 relay，請求裡帶著 credential。**relay 為了轉發給目標 API，必須在內部
持有明文 credential 一瞬間——這是 proxy 的物理本質，加密絕對絍不過。**
（加密只能保護「客戶→relay」傳輸途中防監聽；relay 內部必然看得到明文。）

唯一誠實的處理方式（四道合起來才站得住）：
1. **明示告知**：`acr` 第一次用 test 就告訴用戶「test 會讓請求（含 credential）經過 arcrun relay，
   arcrun 只記錄 HTTP 回應、不記錄 credential。要完全不經過請用 execute」。
2. **「不記錄」從「承諸」升級成「可驗證」**：relay 是開源的（arcrun 核心 MIT），
   用戶不需「相信」，可以讀 relay 原碼確認它沒記錄 credential。
   relay 程式上：credential 欄位全程不寫日誌、不寫存儲，只在轉發那一瞬間的記憶體。
3. **傳輸層 TLS**：客戶 → relay → 目標全程加密，防線路監聽。
4. **縮小經手 credential 的價值**：用 test 開發時建議用測試帳號的 credential，不是生產環境的。

**絕不做「假加密」**——不要讓用戶以為連 relay 都看不到 credential。
兩段分開誠實講：「傳輸途中加密（防監聽）+ relay 內部短暫持有明文（開源可驗證、不記錄）」。

**範圍：** `test` / relay / credits 這整套是服務側工程，依賴公共零件庫與 credits 系統存在。
**不在第一期**（第一期是 self-hosted 能跡、`execute` 能用）。

---

## 4. cypher-binding、三層 runtime、執行核心（長期，現在不做）

> 註：credential 是引擎能力、不是第四種 primitive。詳見下方 §3b（基於 2026-05 原始碼查核）。

### cypher-binding 是什麼
workflow 不是「部署出來的東西」，是「一張可隨時改的紙」——紙上寫一排零件 + 順序 + 條件。
執行核心讀紙：跑第一個，跑完回來問紙，紙說下一個是誰，就跑下一個。
對比 service binding（CF 機制，a/b 都要 deploy）：cypher-binding **不 deploy**，
改 workflow 零部署成本。可能慢一點，但「寫好、按下去就跑」（像 n8n）。

### 三層 runtime 目標
零件與執行核心目標是能跑在三種環境：
- Tier 1：Cloudflare Workers（現況）
- Tier 2：企業自架 workerd（不信任 CF 雲）
- Tier 3：極輕量 WASI runtime（wazero，無人機 / 邊緣設備）

**現在不寫任何 wazero / workerd 程式碼。** 第一期沒有 Tier 2/3 用戶。
避債的方式不是「現在做三層」，是「現在不要做任何擋死三層的決定」。

### cypher-executor 概念上分兩層
（現在糊在一起。這**不是會累積的債**——它是一支程式、就一支，未來一次性重構即可；
不像零件會複利累積。它是「待設計的未來解法」，不需要為它焦慮。）

- **(1) 執行核心** — 讀紙、依序/依條件呼叫零件。應能編成純 WASI，跑三層任何地方。
- **(2) Cloudflare 整合層** — webhook / KV / cron / Service Binding / HTTP 路由，只服務 Tier 1/2。

Tier 3（無人機）只需要 (1)。現在不拆，但**從現在起新程式碼要有意識地把
「讀紙、呼叫零件的核心邏輯」和「Cloudflare 特有存取」分開寫，不要糊得更死**。

### KV 依賴的根源，與「紙要自包含」
現在 cypher-executor 依賴 KV，根源**不是**「紙存在 KV」，是「紙上寫的是 hash
（cmp_xxx / rec_xxx），要查 KV 才能翻譯成真正的零件」。

**hash 查表不是會累積的債**——它是 component-loader 裡固定的一段邏輯，零件再多它也不變
（同一段邏輯處理更多資料 ≠ 需要更多段邏輯）。但它讓「紙」無法自包含。

Tier 3 的正解**不是**「帶一個 SQLite 上無人機來翻譯 hash」——那只是把依賴從 KV 換成
SQLite，沒有消除依賴。正解是**紙本身自包含**：紙上直接寫 URL / 內嵌 recipe YAML，
不寫 hash。無人機上只有「自包含的紙 + WASM 零件」，不需要任何查表設施。

- hash = Tier 1 的**儲存格式**（KV 去重 / 版本管理，Tier 1 保留無妨）
- 自包含 = **執行格式**
- 中間隔一個「展開」步驟：打包給無人機時做一次，不是執行時做（類似 `acr push` 的轉換）

**附帶好處：** 自包含的紙人類 / AI 可直讀——這正是 Arcrun 核心賣點（紙人人可讀）。
hash 其實偷偷腐蝕了這個賣點。

---

## 5. 第一期範圍鎖定

**第一期用戶 = 會開 CF 帳號的開發者，在 VSCode 用 Claude Code（CC），self-hosted。**
鎖定 Claude，因為所有「防做歪」機制都是針對 Claude 行為校準的；
Gemini / Codex 服從度不同，第一期不支援。

**第一期做：** 清污染 → 降級假零件成 recipe → 補零件庫真把關 →
白名單 hook → （搬家）→ mindset Skill → README 重寫成單一路徑 → acr init --self-hosted。

**第一期明確不做：** SaaS、API Key 多租戶、小白 onboarding、視覺化的圖、
arcrun-gui 拖拉畫布、arcrun-mcp 命名大改、新 primitive、Gemini/Codex 支援、
三層 runtime、KBDB 訂閱層。

**SaaS 解凍條件（不靠心情）：** (a) self-hosted 有 ≥3 個你以外的人部署成功並貢獻
≥1 個 recipe，且 (b) 視覺化 Skill 已驗證「AI 畫的圖能讓非技術者看懂」。

---

## 6. CF 帳號與專案模型

- **CF 帳號：一個人一個，永遠就一個。** 不隨專案增加。「實驗環境」是一個 prefix，不是一個帳號。
- **Arcrun 部署：一套就夠**（像 n8n 就一套）。不是每個專案各一套。
- **「專案」是 Arcrun 的一等公民**：專案 = 一組「引用 workflow」的三元組關係（存在 KBDB / 三元組儲存，
  不需要新資料表）。同一個 workflow 可被多個專案引用，改一次全部生效。
- 預設共用 workflow（引用）；只有結構性差異才 fork，且 fork 要有摩擦感。
- credential 綁專案：KV key 帶專案前綴（`mira:notion_token`），邏輯隔離，非物理隔離。

---

## 7. 「讓 AI 不做歪」的三層機制 + 閉環

**閉環原則：下指令的、執行的、驗證的，必須是三個不同的角色。**
執行者驗證自己 = 沒有驗證。驗證的標準必須來自執行者碰不到、改不了的地方
（一份判準 / 一個程式 / 一個拿著判準的獨立角色）。

閉環：指令（說要什麼，不說怎麼做）→ 查閱判準 → 執行 → 獨立驗證 → 不合格退回並告知正路。

**事前防禦（擋已知的錯）：**
- 層一：範本——AI 不從白紙生成，永遠在改一個正確的範本（acr new / scaffold）
- 層二：會回嘴的 CLI——走歪當場 exit 2 + 指回正路（這是真護城河）
- 層三：mindset Skill——給 AI「Arcrun 很簡單、一切皆 recipe」的世界觀

**事後機制（抓漏網的錯）：** 事前防禦永遠堵不滿，剩下交給事後：
- 第一層：可審計軌跡——每個動作留不可竄改記錄，事後能追
- 第二層：不變式測試——核心原則寫成自動測試，每次 commit 跑（最重要）
- 第三層：定期獨立審查——拿判準重看，審查者手上必須有判準

**關鍵心態：不要訓練自己的「辨識能力」**——不可靠、會累、無法轉移給 low-code 用戶。
要把判準寫成機械測試，讓「紅燈」代替「辨識」。同一個錯誤，以「問句」形式出現你抓不到，
以「紅燈」形式出現你一定抓到。

---

## 8. 執行鏈路不依賴 GitHub Actions（零件投稿例外，2026-05-30 釐清）

Arcrun 第一期的**執行鏈路**（init / push / run / recipe）全在「用戶機器 + Cloudflare」之間，
不經過 GitHub Actions。這是常態高頻動作，用戶不該被 CI 卡住。理由見 §0 第一條。

**零件投稿是例外，走 GitHub PR + CI（2026-05-30）：**
- 零件投稿是**稀有低頻**事件（primitive 極少、未來絕大部分是 recipe；建零件要過人類閘門）。
- 稀有事件用 PR 治理最自然：**PR 必須有人 merge = 人類閘門**（AI 偽造不了 GitHub approve）；
  把關（假零件偵測 / 純WASI / Gherkin / 覆蓋檢查）由 **CI（PR check）跑**。
- 為何非 CI 不可：**CF Workers 禁止 request-time 編譯 WASM** → registry Worker 跑不了 Gherkin / 向量；
  CI 有 tinygo + 能 runtime 跑 wasm，是唯一既能跑 wasm 又「執行者碰不到」的 venue。
- **這不違反 §8 精神**：§8 防的是「高頻執行鏈路被 CI 卡住」；零件投稿稀有、且該由 PR 把關，
  用 PR/CI 反而更對。兩者是不同性質的事。
- 詳見 `docs/3-specs/component-gatekeeping/`。

---

## 9. 自力救濟階梯——缺能力時怎麼補（2026-06-26，issue #4）

§3.1 舊版「缺能力 → 去補 API」預設 **API 是你能改的**。對**第三方 API**（gsheets 一次倒全部、輸出前無法 filter；
無 upsert API 的服務）不成立——若規則又禁用 workflow「倒出來自己篩」，**用戶被自己的規則卡死**。

**結論**：不廢 §3.1，**收窄**成「禁在薄殼介面層 TS 拼裝 + 禁亂建零件污染零件庫」，並開放「用資料方式自救」的合法路徑。

**主界線（一句話）**：**「那個 API 你能不能改？」**
| 情況 | 正解 |
|---|---|
| 能打既有 API | recipe |
| **自家** API（KBDB/cypher）缺能力 | 補進 API + 可發 issue |
| **第三方** API 缺能力 | **可投稿的 workflow 補丁** + 發 issue 建議原廠加 API |
| 純計算（轉大寫等） | **code-node**（空白 code 零件寫 JS） |
| 真需新穩定能力（極少） | 自建零件 → PR |

**配套**：補丁 workflow 可呼叫且明示可投稿（wishlist C6）／code-node 在 CF Workers isolate 跑 JS（wishlist C1）／
補丁是過渡（原廠出 API 後自然淘汰）。**hook 不變**：7.x 擋的是「介面層 TS 拼裝」，workflow/code-node 是資料產物、
本就在 hook 範圍外、合法不被擋。**code-node 規則已定，空白零件實作屬 wishlist C1 另案。**
原始精神保留：當初訂此禁令是防 AI 把多步驟工作寫成**零件**污染零件庫——禁的是亂建零件，不是禁任何補丁。
落地：`docs/2-architecture/07-thin-shell.md §3.5` + `.claude/rules/07-thin-shell.md`（兩份同步）+ `02-forbidden.md §5.2`。

---

## 10. embedding 是 base optional 模組（2026-06-26，issue #7 / mira-dissolve T2.4）

KBDB 查詢三模式：關鍵字（base LIKE，always）／語義（embed 模組）／圖（graph 插件，另 repo）。**語義歸 base 的 optional 模組**。

**設計鐵則**：
- **binding 開/關，不拆 repo**：有 `VECTORIZE`+`AI` binding 才啟用（`embedEnabled()`）；沒有 → 降級 LIKE，**API 不變**。
- **不裝保持輕**（free-tier 友善）：預設關；`acr init` 問、預設 N。
- **精耕非地毯式**：只 embed 被標 `metadata_json.embed:true` 的 entry（wiki 段落 + node gloss），不對每個 block 灌。
- **base 對內容語意無知**：用通用 `embed:true` flag 觸發，**不寫死 entry_type 白名單**（否則 base 知道 wiki/graph 概念 = 破壞解耦）。
- **不動三表**：只標既有 `is_embedded` bookkeeping 欄（base 從不讀）；向量存 Vectorize，不進 D1 表。
- **誠實降級 + 發現閉環**：`?mode=semantic` 沒開 vectorize → 回 keyword + `capability_hint`「叫 CC 幫你開」。不假裝有語義。
- **CC 幫開路徑**：CC 寫 config `kbdb_embed:true` + `acr update`（deploy `ensureVectorizeIndex` REST 建 index 冪等 + 注入 binding）。

模型 `@cf/baai/bge-base-en-v1.5`（768 維，cosine）。落地：`kbdb/src/embed.ts` + entries route + config.ts/deploy.ts/init.ts + MCP `kbdb_search mode`。
詳見 kbdb-base SDD Phase 12。**與 #5.1 source 過濾協調**：語義 search 的 metadata filter 共用 source，語義/關鍵字都能按來源篩。

---

## 11. #5 KBDB 缺口分流 + 頂層覆寫（2026-06-26，issue #5，普世框架視角）

mira dogfood 開的缺口，**Mira 已蒸發 → 當「未來任何 self-hosted 用戶都會撞的框架缺口」處理**，不為 Mira 特例。四點分流：
- **source 過濾 ✅做**：普世成立（按來源篩 + 語義/關鍵字都會用）。json_extract 零建表（見 mistakes #18）。
- **documents 聚合（GROUP BY page_name）❌不做**：舊河道頁特例；新架構「跨 vault 的圖」走 **graph MCP** traverse/neighbors，
  不靠 KBDB 出 SQL 聚合端點。**頂層 mira-dissolve R6 已否決**。
- **cypher proxy DELETE ⏸擱置**：依賴頂層「死資料自動刪除原則」（mira-dissolve T8 未定）。
  且裸 `DELETE /entries/:id` **無 owner 檢查**，直接 proxy 暴露＝跨租戶刪除風險 → 補時要先讀 entry 驗 owner_id 才放行
  （與 #6 對 records DELETE 同理擱置）。
- **embed-on-write → 併 #7**（embed 模組職責，前端不該手動戳 process-page）。
- **能力對照文件**：`docs/4-guides/kbdb-capabilities.md`，**刻意不寫「documents/process-page 待移植」**（舊河道視角，新架構不移植）。

**通用教訓**：碰「舊 Mira/SaaS KBDB 開的需求」先查頂層 mira-dissolve 是否已重審覆寫，別照舊 issue 悶頭做（可能做白工）。

---

## 附：如何判斷「某個東西會不會累積成債」

通用判準——當「東西」變多時：
- **同一段邏輯處理更多資料** → 不累積。（例：hash 查表，零件再多也是同一段查表邏輯）
- **需要更多段邏輯、更多特例** → 會累積。（例：零件，每個是獨立程式、各自可能出錯）

不累積的東西不需要焦慮，它頂多是「未來一次性處理的設計點」。
會累積的東西必須在「進入點」就把關（例：零件在投稿時就驗純淨）。
