Files
Arcrun/DECISIONS.md
Leo 7d8cbe4299 docs: Phase 3 收尾 — §8 釐清零件投稿走 PR、BACKLOG 記新需求
- DECISIONS §8 釐清:執行鏈路(高頻)不依賴 CI;零件投稿(稀有)走 PR/CI 是例外、不違反精神
  (CF Workers 不能 runtime 編譯 wasm,CI 是唯一能跑 wasm 又執行者碰不到的 venue)
- BACKLOG 步驟 4 收尾(投稿改 PR,標已做/不做/未來搬 CI)
- BACKLOG 步驟 5 被 PR 方向取代;新增步驟 5b 資料外流警示(先做,需新 SDD)
- BACKLOG 待決策加:用戶 API 保護機制(入站認證,資安優勢)、recipe/part/function 架構釐清
- component-gatekeeping tasks.md 收尾狀態

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 13:42:46 +08:00

341 lines
20 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.
# 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 WASMauth_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 recipeprompt recipe 犍涉 KBDB block 展開,
是 BACKLOG 待決策項,不在此範圍)。
### recipe 與 primitive 的驗收標準不同(早期已定,2026-05 重新確認)
- **primitiveGherkin 通過 = 驗收通過。** 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、處理資料、做認證)→ **必須 WASMTinyGo),用 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 標準介面的 wasm1080KB)。這是三層 runtime 的物理前提(見 §4)。
TinyGo 為官方首選(語法與 TS 差異夠大,AI 不易把純 TS 邏輯誤搬)。
---
## 3. 開源 / 商業邊界
| 開源核心(MIT) | 服務側(付費 / 需 API Key |
|---|---|
| cypher-executor、四種 primitive WASM、CLIacr)、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_*` WASMstatic_key / service_account / oauth2 / mtls)是「注入步驟的後端」,
不是用戶會在 workflow 擺的零件。用戶永遠不會直接呼叫 `auth_static_key`——
是 auth-dispatcher 在背後呼叫它。
→ 「零件白名單」「假零件判準」不適用於 `auth_*`,它們不是用戶零件。
**credential 系統現状是「新舊兩路並存」的半成品:**
- 新路(對的方向):`auth-dispatcher` → HTTP 打 `auth_*` WASM,解密/JWT 全在 WASM 內。
已支援 static_key / service_account / oauth2mtls 尚未(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 bindingCF 機制,a/b 都要 deploy):cypher-binding **不 deploy**
改 workflow 零部署成本。可能慢一點,但「寫好、按下去就跑」(像 n8n)。
### 三層 runtime 目標
零件與執行核心目標是能跑在三種環境:
- Tier 1Cloudflare Workers(現況)
- Tier 2:企業自架 workerd(不信任 CF 雲)
- Tier 3:極輕量 WASI runtimewazero,無人機 / 邊緣設備)
**現在不寫任何 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 CodeCC),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 + CI2026-05-30):**
- 零件投稿是**稀有低頻**事件(primitive 極少、未來絕大部分是 recipe;建零件要過人類閘門)。
- 稀有事件用 PR 治理最自然:**PR 必須有人 merge = 人類閘門**AI 偽造不了 GitHub approve);
把關(假零件偵測 / 純WASI / Gherkin / 覆蓋檢查)由 **CIPR check)跑**
- 為何非 CI 不可:**CF Workers 禁止 request-time 編譯 WASM** → registry Worker 跑不了 Gherkin / 向量;
CI 有 tinygo + 能 runtime 跑 wasm,是唯一既能跑 wasm 又「執行者碰不到」的 venue。
- **這不違反 §8 精神**:§8 防的是「高頻執行鏈路被 CI 卡住」;零件投稿稀有、且該由 PR 把關,
用 PR/CI 反而更對。兩者是不同性質的事。
- 詳見 `.agents/specs/component-gatekeeping/`
---
## 附:如何判斷「某個東西會不會累積成債」
通用判準——當「東西」變多時:
- **同一段邏輯處理更多資料** → 不累積。(例:hash 查表,零件再多也是同一段查表邏輯)
- **需要更多段邏輯、更多特例** → 會累積。(例:零件,每個是獨立程式、各自可能出錯)
不累積的東西不需要焦慮,它頂多是「未來一次性處理的設計點」。
會累積的東西必須在「進入點」就把關(例:零件在投稿時就驗純淨)。