f21906ca6a
兩次 /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>
404 lines
17 KiB
Markdown
404 lines
17 KiB
Markdown
---
|
||
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
|
||
|
||
---
|
||
|
||
## 多 Worker ENCRYPTION_KEY 同步(2026-05-29)
|
||
|
||
**Q:auth_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(目前沒有 check,need 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)
|
||
|
||
**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/ | 遇到類似問題時 |
|