Files
Arcrun/system-dev/wiki/decisions-summary.md
T
uncle6me-web f21906ca6a chore(wiki): 本 session capture(薄殼防複發/歷史債卡 + mistakes #19-21 + status)
兩次 /wiki-capture 累積的知識落盤:
- cards/decisions/ 新卡:薄殼防複發-能力對照表加smoke、薄殼規則晚於實作-MCP漂移是歷史債
  (+ 00-INDEX 編入,決策桶現 15 張)
- mistakes #19 死端點假綠(grep route/smoke 驗端點存在)
- mistakes #20 gitignored 檔無 git 史(時間靠檔內註記)
- mistakes #21 wrangler.toml services=[...] inline 在 [vars] 後被吸成 vars.services(issue #12)
- decisions-summary:薄殼防複發機制、workflow description 由操盤 CC 據實生成
- status:本 session #8/#11/#12 進度 + merge 結果

純記憶/文檔,無 code。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 01:43:26 +08:00

404 lines
17 KiB
Markdown
Raw 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.
---
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 Bindingrule 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}/` WorkerHono + 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
---
## R2WASM_BUCKET)的用途(rule 03 §2
**Q:WASM 零件怎麼存儲和部署?**
**決策**
- **平台內建零件**bundle 進各自 Worker 的 binary`.wasm` commit 進 repo)→ 部署時用 codeload tarball
- **用戶自製零件**(Phase 5 以後):動態上傳到 R2runtime 執行
**現在狀態**
- 平台零件不從 R2 讀
- R2 只在 Phase 5 啟用
**避坑**
- 不要問「怎麼從 R2 取 WASM」(錯誤假設)
- 平台零件的 WASM 已 deploy 進 Worker,沒有 R2
- 用 HTTP URL 呼叫,不是動態讀 R2
**詳見**rule 03 §2、rule 05 WASM 來源
---
## Cypher 怎麼調用零件避開 same-zone 10422026-06-06
**Qcypher 打 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
---
## 多 Worker ENCRYPTION_KEY 同步(2026-05-29
**Qauth_static_key / auth_service_account / cypher-executor 都需 ENCRYPTION_KEY,怎麼保持一致?**
**決策**
- secret 存在各 Worker 的 secret store(非環境變數,避免洩漏)
- `wrangler secret put ENCRYPTION_KEY` 手動設進各 Worker
- 初始化:`acr init` 生成,展示一次,user 自己 secret put
**為什麼不用 KV**
- secret 是敏感內容,不應在 KV 存(會被 list 洩漏)
- secret store 是 Cloudflare 的原生機制
**冪等性**
- `acr init` 多跑幾次,生成不同 key(目前不冪等)
- 若要冪等,init 應檢查現有 config → reuse 舊 key
**避坑**
- init 完成後驗證所有三個 Worker 都有同一份 key
- 若某個 Worker 的 key 遺漏或不同 → credential 解密失敗(會表現為 401/403)
- 重跑 init 不要覆蓋舊 secret(目前沒有 checkneed improvement
**詳見**2026-05-29-encryption-key-drift.md、rule 01 加解密
---
## 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
**QCLI / MCP / SDK 應該怎麼設計?**
**決策**
- **所有能力只實作一次,放在 APIcypher-executor**
- **介面層全是薄殼**:參數解析 + HTTP 呼叫 + 格式轉換,零邏輯
**違反例**
- CLI 迴圈 POST 多個 recipeseed 邏輯不該在介面)
- MCP 先 update 失敗再 insertupsert 邏輯應在 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/recordKBDB 鐵律:不暴露裸表操作)。硬把 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 跑 wasmCI 容器支援,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 全是 honotier2 另需 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.tsdownloadAndDeploy §2.5 共享、deploy 迴圈 manifest)、mistakes §13
---
## 自力救濟階梯——缺能力怎麼補(2026-06-26issue #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 只擋介面層 TSworkflow/code-node 是資料產物、不被擋。
code-node 規則已定、零件實作屬 wishlist C1 另案。**詳見**DECISIONS §9、07-thin-shell §3.5。
---
## embedding 是 base optional 模組(2026-06-26issue #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` 的 entrywiki 段落+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-26issue #5
**Qmira 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`
---
## 薄殼防複發機制=對照清單 + 本機 smoke2026-06-27issue #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-27issue #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 5R2 動態讀)
多人協作怎麼同步?
├─ 資料(KV/D1)?
│ └─ 各自 Worker 綁定,不共用 secret
├─ 程式碼(執行邏輯)?
│ └─ 只在 APIcypher-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/ | 遇到類似問題時 |