---
name: decisions-summary
description: 架構決策快速查 — 做選擇時的關鍵 trade-off（不是全文，是導引 + 連結）
metadata:
  type: reference
  last_updated: 2026-06-26
---

# 架構決策快速查

> **用途**：遇到「X 該怎麼設計？」時，快速找到已有決策的 trade-off。
> **來源**：DECISIONS.md（權威記錄）+ rule 02-07（執行細節）。

---

## 零件 vs 工作流（mindset §1）

**Q：新需求該包零件還是寫工作流？**

**決策**：零件是稀有例外，工作流是 default。

| | 工作流 | 零件 |
|---|--------|------|
| **何時用** | 自用 / 少數人 / 試驗 | 全 arcrun 生態必須重用 |
| **構建成本** | 低（YAML） | 高（TinyGo/AS + WASI） |
| **生命周期** | 短（項目級） | 長（平台級） |
| **例子** | RSS→Sheet、郵件分類、GitHub issue bot | gmail、telegram、http_request |

**避坑**：「有 API 可包」≠「該包」。mira 錯做成零件的 claude_api、km_writer、kbdb_upsert_block 本該是工作流。

**詳見**：DECISIONS.md §1、mindset §1

---

## Service Binding vs Cypher Binding（rule 03 §1）

**Q：零件之間怎麼串接？**

**決策**：
- **Cypher binding = YAML 裡的 URL 清單**（用戶 workflow 定義）→ HTTP 呼叫
- **Service Binding = wrangler.toml 的 `[[services]]`**（平台內建邏輯零件之間效能優化）→ 禁止新增

**Why**：
- workflow 是用戶定義的動態圖，不能要求重新部署
- Service binding 靜態，修改 toml 要 redeploy → 違反 workflow 靈活性
- 13 個現有的 SVC_* binding（邏輯零件）是歷史遺產，保留但不擴展

**新零件怎麼部署**：
1. 建 `registry/components/{name}/main.go`
2. 編譯 `.wasm`
3. 包進 `.component-builds/{name}/` Worker（Hono + WASI shim）
4. 部署成獨立 Worker：`arcrun-{name}.{sub}.workers.dev`（對內） + `{name}.arcrun.dev`（對外）
5. cypher 透過 HTTP fetch 呼叫

**避坑**：
- 不是「用 service binding 快」就用，那違反架構
- self-hosted 同 zone 1042 不是加 binding 解，是加 flag（§ same-zone-1042）

**詳見**：rule 03 §1-3、2026-05-13-cypher-outbound-522.md

---

## R2（WASM_BUCKET）的用途（rule 03 §2）

**Q：WASM 零件怎麼存儲和部署？**

**決策**：
- **平台內建零件**：bundle 進各自 Worker 的 binary（`.wasm` commit 進 repo）→ 部署時用 codeload tarball
- **用戶自製零件**（Phase 5 以後）：動態上傳到 R2，runtime 執行

**現在狀態**：
- 平台零件不從 R2 讀
- R2 只在 Phase 5 啟用

**避坑**：
- 不要問「怎麼從 R2 取 WASM」（錯誤假設）
- 平台零件的 WASM 已 deploy 進 Worker，沒有 R2
- 用 HTTP URL 呼叫，不是動態讀 R2

**詳見**：rule 03 §2、rule 05 WASM 來源

---

## Cypher 怎麼調用零件避開 same-zone 1042（2026-06-06）

**Q：cypher 打 component worker 返回 522？**

**決策**：
- **官方**（cypher 在 cypher.arcrun.dev、component 在 {name}.arcrun.dev）→ 跨 zone，不踩
- **Self-hosted**（都在 {sub}.workers.dev）→ 踩 same-zone 防護

**解法**（不是 service binding）：
```toml
# cypher-executor/wrangler.toml
compatibility_flags = [ "nodejs_compat", "global_fetch_strictly_public" ]
```

這讓 same-zone fetch 走公網前門 → 同 zone 也通。

**為什麼不用 binding**：
- binding 靜態（toml 改要 redeploy）
- component 清單動態（用戶 workflow 決定）
- 改變 architecture 必要性低（flag 無副作用）

**避坑**：
- self-hosted cypher 務必有 `global_fetch_strictly_public`
- 若仍 522 → 檢查 flag 是否生效（某些 Wrangler 版本可能有 bug）

**詳見**：2026-05-13-cypher-outbound-522.md、2026-06-06 commit、rule 03 §3

---

## Recipe UUID 模型（kbdb-base §7.5）

**Q：多作者同 canonical recipe 怎麼並存？**

**決策**：
- **舊模型**（已廢）：canonical_id 是唯一鑰匙，新版覆蓋舊版
- **新模型**（UUID）：recipe:{uuid} 是真正鑰匙，同 canonical 多 uuid 並存

**app-store 設計**：
- `canonical_id`：服務名（gmail_send）
- `uuid`：版本身份（Leo 的 gmail_send ≠ John 的 gmail_send）
- `author`：誰做的
- `market_stat`：per-uuid 的成功/失敗率（區分作者）

**pull 邏輯**：
- 公庫搜找 canonical → 回多 uuid
- `acr recipe pull gmail_send` → 自動選市場最佳版本（成功率高）
- `acr recipe pull gmail_send --author john` → 明確指定

**向後相容**：
- 舊 workflow 用 canonical_id → resolveRecipe 要能找到
- migrate-uuid 端點把舊 key 轉新 uuid（冪等）

**避坑**：
- 不是簡單的 key 重構，是多維度身份（canonical + uuid + author）
- D1 per-uuid 記錄，不是 per-canonical（區分市場數據）
- 新 submit-p 要幌露警示（mindset §6）

**詳見**：kbdb-base design §7.5、credential-primitives-wasm

---

## 薄殼原則（rule 07）

**Q：CLI / MCP / SDK 應該怎麼設計？**

**決策**：
- **所有能力只實作一次，放在 API（cypher-executor）**
- **介面層全是薄殼**：參數解析 + HTTP 呼叫 + 格式轉換，零邏輯

**違反例**：
- CLI 迴圈 POST 多個 recipe（seed 邏輯不該在介面）
- MCP 先 update 失敗再 insert（upsert 邏輯應在 API）
- SDK 自製 credential-injector（應在 WASM primitive）

**同一 API 在不同介面的簽名**：
- ✅ 差異來自介面慣例（CLI 讀檔案、MCP 讀 JSON）
- ❌ 底層實作分歧（CLI 連自架、MCP 連官方 = 差不同帳號）

**帳號統一**：
- CLI / MCP / SDK 必須讀同一份身份設定（config.yaml / env）
- self-hosted 的已知問題：MCP 可能指官方（mcp-account-source §5.2）

**避坑**：
- 介面間差異一大就改，不是「某個介面功能不全」就在那加邏輯
- seed / upsert / 複雜編排 → 都去補 API，不是繞過

### MCP「等於」CLI 嗎？——齊的單位是「能力」不是「端點」（2026-06-15 釐清）

**Q：MCP 和 CLI 該不該完全一致？API 有的端點是不是都要兩邊各開一個？**

**結論**：
- rule 07 §5 的「CLI + MCP 覆蓋同一組 API 能力」**指該暴露給人/AI 的能力**——不是「API 的每個端點都要上兩邊」。
- 「介面進度暫時不一致」**不是 bug**（§5 明寫）；bug 是「底層能力不齊 / 介面含不該含的邏輯 / 帳號來源不統一」這三者。所以 MCP=CLI 是**出貨目標**，不是**任一時刻的不變量**。
- **底層 API/proxy 可以有端點刻意不上 CLI/MCP**——那是分層,不是疏漏。判準看「這端點的消費者是誰」。

**實例（KBDB 資料層）**：
- 該齊的齊了：MCP 6 個 `kbdb_*` 工具 ↔ CLI `acr kbdb` 6 個子命令,一一對應（template/record/query/search）。差異只來自介面慣例（MCP 吃 JSON、CLI 吃 `--slots a,b,c`），底層同一條 cypher `/kbdb/*` proxy。
- **`/kbdb/entries` 裸 CRUD 刻意兩邊都不接**：它的消費者是 **mira 的 Python client**（直連 HTTP 遷移用），不是人/AI。CLI/MCP 給的抽象是更高階的 template/record（KBDB 鐵律:不暴露裸表操作）。硬把 entries CRUD 塞進 CLI/MCP **反而違反鐵律**。

**附帶釐清（驗收邊界）**：MCP 工具經 `env.KBDB` service binding 那一跳是 worker 間內部專線,**curl 從外面驗不到**（無公開 URL）。要驗它得在真 MCP client 裡 call;或靠「同 `kbdbFetch` 路徑的既有工具已驗過」佐證 binding 活。

**詳見**：rule 07（完整）、壓測報告 §5.1 seed 反例、kbdb-base/tasks.md 9.1/9.2/9.6

---

## Haiku 就能搞定是設計目標（mindset §1 + 壓測）

**Q：為什麼要用 Haiku 壓測，不直接用 Sonnet？**

**決策**：
- arcrun 價值主張：比直接開發容易 → 省 token + 省時間
- 若只有 Sonnet 能驅動，「省」就不成立（Sonnet 貴）
- **Haiku 就能搞定才是設計達成度的證明**

**測試邏輯**：
- Haiku 過 ✅ → 設計目標達成
- Haiku 不過 → 判別：是介面缺陷還是模型本質限制
  - 升 Sonnet，Sonnet 過 → 介面太難，要改介面
  - Sonnet 也不過 → 功能 bug，要改邏輯

**避坑**：
- 不要「Haiku 過不了就換 Sonnet」，那是放棄設計目標
- Haiku 撞牆 = 發現「使用者自己搞不定」的點 → 要磨介面白痴化

**詳見**：test_case.md 第一段、mindset §7 誠實限制

---

## 不依賴 CI（執行鏈路 vs 零件投稿）

**Q：為什麼部署走 local script，不是 GitHub Actions？**

**決策**：
- **執行鏈路**（高頻：workflow、recipe、API）→ 不依賴 CI（用戶實時操作）
- **零件投稿**（稀有：新零件）→ 走 PR/CI（人 merge = 閘門，CI 驗收）

**執行鏈路不能走 CI**：
- `acr run` 用戶實時呼叫，不能等 CI 跑（分鐘級延遲不可接受）
- workflow 是 YAML，不是程式碼編譯

**零件投稿走 CI**：
- 零件投稿稀有（幾周一次）
- PR check 能 runtime 跑 wasm（CI 容器支援，CF Worker 不支援）
- 把關（WASI 沙箱驗證、Gherkin 全綠、假零件檢測）由 CI 跑

**避坑**：
- 執行鏈路不要加 CI gate（會卡用戶）
- 部署走 local script，不是 workflow dispatch
- 零件投稿不要 self-service（要人 review）

**詳見**：rule 05 部署慣例、mindset §4

---

## self-hosted 部署：共享 install + 內容指紋跳過（2026-06-12）

**結論**：`acr update` 不每個 worker 各裝依賴、不每次全部重部。改成「root 共享一次 install」+
「內容指紋 manifest 跳過未變動」。

**原因**：23 個 component worker 的 runtime dep 全是 hono（tier2 另需 zod/mcp-sdk/yaml），各裝
~324MB node_modules 是 23× 重複；且冪等重跑被誤解成「每次重做全部」，22 個沒變的白跑。

**機制**：
- **共享 install**：tarball root 裝一次（hono+wrangler+tier2 deps），各 worker 靠 node 往上 resolve。
  207MB×1 取代 324MB×23（`wrangler deploy --dry-run` 驗證 tier1+tier2 都 bundle 成功）。
- **manifest**（`~/.arcrun/deploy-manifest.json`）：注入後算 content hash，與上次成功比 → 相同跳過。
  只記成功者（失敗下次必重試）；hash 含 accountId（換帳號/KV 自動重部，不誤跳）；`--force` 清空全重部。
- 共享失敗自動退回各 worker 自裝（不破壞既有路徑）。

**詳見**：cli/src/lib/deploy.ts（downloadAndDeploy §2.5 共享、deploy 迴圈 manifest）、mistakes §13

---

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

**Q：缺一個能力，該補 API 還是別的？**

**主界線一句話**：**「那個 API 你能不能改？」**
- 能打既有 API → recipe
- **自家** API 缺 → 補進 API
- **第三方** API 缺（gsheets filter / 無 upsert）→ **可投稿的 workflow 補丁**（不是建零件！）
- 純計算 → **code-node**（空白 code 零件寫 JS）
- 真需新穩定能力（極少）→ 零件 PR

**避坑**：§3.1 禁的是「介面層 TS 拼裝 + 亂建零件污染零件庫」，**不是禁任何補丁**。第三方 API 改不了，
不能被「只能 recipe」卡死。hook 7.x 只擋介面層 TS，workflow/code-node 是資料產物、不被擋。
code-node 規則已定、零件實作屬 wishlist C1 另案。**詳見**：DECISIONS §9、07-thin-shell §3.5。

---

## embedding 是 base optional 模組（2026-06-26，issue #7）

**Q：語義查詢/embedding 放哪、怎麼開？**

**決策**：embedding 歸 **base 的 optional 模組**（非 graph/ingest）。binding 開/關不拆 repo。
- 有 `VECTORIZE`+`AI` binding 才啟用；沒有 → 降級 LIKE keyword，**API 不變**。
- 預設關（free-tier 友善）；`acr init` 問、`kbdb_embed:true` + `acr update` 開（deploy 建 Vectorize index）。
- **精耕非地毯式**：只 embed 標 `metadata_json.embed:true` 的 entry（wiki 段落+gloss），不對每個 block 灌。
- **base 用通用 flag 不寫死 entry_type 白名單**（守解耦，base 對內容語意無知）。
- **不動三表**（向量存 Vectorize）；`?mode=semantic` 沒開 → 降級 keyword + `capability_hint`（發現閉環，不假綠）。

模型 `@cf/baai/bge-base-en-v1.5`（768/cosine）。**避坑**：deploy 注入 `[ai]`/`[[vectorize]]` binding 必須在
`stripOfficialOnlyBindings` **之後**（否則 [ai] 被清，見 mistakes #17）。**詳見**：DECISIONS §10、kbdb-base SDD Phase 12。

---

## 碰舊 Mira/SaaS KBDB 需求先查頂層覆寫（2026-06-26，issue #5）

**Q：mira dogfood 開的 KBDB 缺口還要做嗎？**

**決策**：Mira 已蒸發 → 當**普世框架缺口**處理，且**先查頂層 mira-dissolve 是否已重審覆寫**（別照舊 issue 悶頭做）。
- source 過濾 ✅做（json_extract 零建表，mistakes #18）
- documents 聚合 ❌不做（走 graph MCP，頂層 R6 否決）
- DELETE proxy ⏸擱置（依賴頂層 T8；裸 delete 無 owner 檢查＝跨租戶刪除風險）
- embed-on-write → 併 #7

**詳見**：DECISIONS §11、`docs/4-guides/kbdb-capabilities.md`。

---

## 薄殼防複發機制＝對照清單 + 本機 smoke（2026-06-27，issue #11）

**Q：CLI/MCP 薄殼漂移（死端點假綠）怎麼防複發？**

**決策**：雙層，非 CI。
- **層 1 能力對照清單** `docs/4-guides/cli-mcp-capability-matrix.md`：一張表「能力 × CLI 端點 × MCP 端點 × route 存在? × 同源?」，新增薄殼能力**必填一行**，PR review 對照。
- **層 2 本機 smoke test** `scripts/thin-shell-smoke.sh`：對每能力打真端點、**斷言非 404**（死端點當場現形）。**本機手動跑非 CI/cron/輪詢**（守 flag 紅線，對齊「執行鏈路不依賴 CI」）。
- **機制自驗**：注入故意死端點 → smoke 當場攔（已驗證能攔，exit 1）。

**為什麼**：根因是**假綠**——宣稱「一致性落地」但端點不存在，光靠宣稱會再犯。smoke 區分「code 有 route」vs「prod 真部署」（首跑就揭 #8 search/backfill 在 prod 仍 404=未部署）。

**避坑**：不用 CI 高頻打真端點（違 flag 紅線）；smoke 是「宣布完成前手動跑一次」的閘。

**詳見**：thin-shell-alignment SDD §5、[[薄殼規則晚於實作-MCP漂移是歷史債]] 卡、mistakes #19。

---

## workflow description 由操盤 CC 據實生成、用戶可改（2026-06-27，issue #8）

**Q：強制 workflow 填 description，但 low-code 用戶不知道要填，怎麼辦？**

**決策**：強制非空落 API（不變）；**空時由操盤的 CC 據實寫一句「能做什麼」，用戶可改**——非逼用戶手填、非介面層機械塞佔位。

**調和關鍵（分清兩種「生成」）**：
- ✅ **操盤 CC 據實生成**：CC 剛建這 workflow、最懂它做什麼 → 寫真描述（leo 例「呼叫可 Upsert Google Sheets」）。**真描述非假裝**，不違 mindset §7。
- ❌ **介面層機械塞佔位**（從 name 複製、`workflow_xxx`）＝假描述，仍禁。

**為什麼**：low-code 用戶不知道要填 description（逼填違北極星「不增負擔」）；但自動機械填＝假裝有（違誠實）。北極星「執行力外包給 AI」＝AI 據實幫他寫好、他可改。

**避坑**：description 是一句「能做什麼」供語意搜尋，**非寫文章**。

**詳見**：workflow-discovery SDD §3.2。

---

## 快速決策樹

```
新功能怎麼做？
├─ 自用 / 少數人？
│  └─ 工作流（YAML）
├─ 全生態必須重用？
│  ├─ 已有服務提供串接？
│  │  └─ HTTP 調用 + recipe
│  └─ 要深度集成？
│     └─ 零件（TinyGo）
│
新零件怎麼部署？
├─ 平台內建？
│  └─ .component-builds/{name}/ Worker + HTTP URL
├─ 用戶自製？
│  └─ Phase 5（R2 動態讀）
│
多人協作怎麼同步？
├─ 資料（KV/D1）？
│  └─ 各自 Worker 綁定，不共用 secret
├─ 程式碼（執行邏輯）？
│  └─ 只在 API（cypher-executor），介面全薄殼
├─ 身份設定？
│  └─ 同一個 config（所有介面讀同一份）
```

---

## 進階參考

| 決策域 | 文件 | 優先閱讀 |
|--------|------|---------|
| 零件 | rule 03、rule 05 | 要建新零件時 |
| Recipe UUID | kbdb-base §7.5 design.md | 改 recipe 鑰匙時 |
| Credential | rule 01 、credential-primitives-wasm | 加新 auth primitive 時 |
| 部署 | rule 05、deploy.ts | 新增 Worker 時 |
| 介面設計 | rule 07、sd-and-website/design.md | 改 CLI/MCP/SDK 時 |
| 事件 | docs/incidents/ | 遇到類似問題時 |
