chore: D22 落地——docs/SDD/wiki/CLAUDE.md 進 repo(Gitea private 預設全 push)
頂層 D22 決策(leo 2026-07-03 拍板):推什麼由開發環境歸屬決定, Gitea private=除機敏值/build 產物/.github 外全 push。 解 T1.5 卡點:雲端工人 clone 拿得到 credential-store-migration.md,可就地改寫 SDD。 機敏掃描兩輪通過(新增 189 檔約 2.1MB,node_modules/dist/wasm 照舊排除)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# SDD 協議(每次啟動必讀)
|
||||
|
||||
## 第零原則:沒讀 SDD 不准動 code
|
||||
|
||||
任何 `.go` / `.ts` / `.tsx` / `.wasm` 相關變動,**必須**按以下順序執行。**不得簡化,不得跳過**。
|
||||
|
||||
### 步驟 1:讀總進度
|
||||
|
||||
先讀 `docs/3-specs/arcrun/arcrun.md`,了解當前 Phase。
|
||||
|
||||
### 步驟 2:定位對應 SDD
|
||||
|
||||
根據任務性質找對應 SDD:
|
||||
|
||||
| 任務類型 | 對應 SDD |
|
||||
|---------|---------|
|
||||
| Auth primitive WASM 零件(static_key/oauth2/service_account/mtls) | `docs/3-specs/arcrun/credential-primitives-wasm/` |
|
||||
| 清除 cypher-executor 裡的 TS 業務邏輯 | `docs/3-specs/arcrun/credential-primitives-wasm/` |
|
||||
| WASI shim host functions(kv_get / crypto_decrypt / crypto_sign_rs256) | `docs/3-specs/arcrun/credential-primitives-wasm/` |
|
||||
| Auth Recipe 系統(recipe schema、KV 格式) | `docs/3-specs/arcrun/auth-recipe.md` |
|
||||
| Landing Page | `docs/3-specs/arcrun/landing-page.md` |
|
||||
| CLI / SDK(Python/JS) | `docs/3-specs/arcrun/sdk-and-website/` |
|
||||
| arcrun-core-mvp 整體架構 | `docs/3-specs/arcrun-core-mvp/` |
|
||||
| Platform Evolution | `docs/3-specs/arcrun-platform-evolution/` |
|
||||
| Credential 長期規格(需求源) | `docs/user_requirements/credential_parts.md` |
|
||||
|
||||
讀 `design.md` 和 `tasks.md` 兩份。
|
||||
|
||||
### 步驟 3:宣告(強制格式)
|
||||
|
||||
開始動手前,在回覆開頭**逐字**貼出以下宣告:
|
||||
|
||||
```
|
||||
📋 已讀 SDD:
|
||||
- docs/3-specs/arcrun/arcrun.md(當前 Phase:<phase 名稱>)
|
||||
- <對應 SDD 的 design.md 路徑>
|
||||
- <對應 SDD 的 tasks.md 路徑>
|
||||
|
||||
🎯 本次對應 task:<task 編號,例如 "Phase 1.3 實作 auth_static_key main.go">
|
||||
|
||||
📐 本次 task 的 SDD 規範摘要:
|
||||
- <重點 1>
|
||||
- <重點 2>
|
||||
- <重點 3>
|
||||
|
||||
🚧 執行範圍:
|
||||
- 會修改:<檔案清單>
|
||||
- 會建立:<檔案清單>
|
||||
- 會刪除:<檔案清單>
|
||||
```
|
||||
|
||||
**不做這個宣告 = 違反 SDD 協議 = 停手等 richblack**。
|
||||
|
||||
### 步驟 4:check tasks.md 狀態
|
||||
|
||||
動手前:在 tasks.md 把對應 task 的 `- [ ]` 改成 `- [🔄]`(進行中標記)。
|
||||
完成後:改成 `- [x]`,不批次更新,每完成一個就立刻改。
|
||||
|
||||
## 什麼算「任務超出 SDD 範圍」?
|
||||
|
||||
以下情況屬於 **change**,不是 **modify**,**必須停手並與 richblack 確認**:
|
||||
|
||||
- SDD 沒寫到的新功能
|
||||
- 新增頂層目錄
|
||||
- 新增新的 Worker(不管是 cypher-executor / registry / 零件 worker)
|
||||
- 修改架構決策(例如「改用 xxx 取代 yyy」)
|
||||
- 跨多個子系統的連鎖修改
|
||||
|
||||
**停手不是怯懦,是專業**。猜錯方向比慢一小時更糟。
|
||||
|
||||
## 新增 SDD 的完整程序(richblack 確認後怎麼往下走)
|
||||
|
||||
> 2026-06-03 補:之前協議只寫「停手等確認」,沒寫「確認後怎麼做」,導致 CC 被
|
||||
> `pre-write-guard.sh` 規則 4.3 擋下後不知正路、卡死。這節補完整程序。
|
||||
|
||||
當「新增一個 SDD 子系統」(在 `docs/3-specs/` 下開新頂層目錄):
|
||||
|
||||
1. **停手,向 richblack 說明要新建哪個 SDD、為什麼**(change,見上節)。
|
||||
2. **取得 richblack 明確確認**後,依序:
|
||||
1. **更新白名單**:在 `.claude/hooks/pre-write-guard.sh` 的 `KNOWN_SDDS` 陣列加一行
|
||||
`"docs/3-specs/<新目錄名>" # YYYY-MM-DD richblack 確認新建(<一句用途>)`。
|
||||
(這步是「執行 richblack 已授權任務的必要步驟」,不是 AI 擅自放寬 guardrail——
|
||||
授權脈絡要明確,分類器才放行;沒有明確授權就改白名單 = 越界。)
|
||||
2. **建目錄 + 寫 design.md / tasks.md**(白名單放行後才寫得進去)。
|
||||
3. **若沒先更新白名單就 Write** → 規則 4.3 會擋你(這是對的,表示你跳了步驟 2.i)。
|
||||
|
||||
**為什麼白名單而非全放行**:開新 SDD 子系統 = 宣告新架構範圍,是稀有的人類決策點。
|
||||
白名單讓「AI 自己無中生有開子系統」必停下(AI 改不了白名單,除非 richblack 明確授權該任務)。
|
||||
已許可的目錄內寫檔零摩擦。
|
||||
|
||||
## 發現 SDD 本身有問題怎麼辦?
|
||||
|
||||
- SDD 和實作不一致 → 停手,列出矛盾點,與 richblack 確認哪一邊是對的
|
||||
- SDD 規範之間互相矛盾(例如禁令 A 和設計 B 衝突)→ 停手,引用矛盾原文,與 richblack 確認
|
||||
- **不可以自行猜哪個是對的**。CC 之前兩天就是這樣走錯的。
|
||||
|
||||
## 為什麼這個協議存在
|
||||
|
||||
arcrun 規範已經足夠細緻,CC 之前出錯不是因為不懂,而是因為**沒讀**或**讀了覺得「大概是這個意思」就動手**。SDD 協議強制把「先讀 → 定位 → 宣告 → 執行 → 更新」做成一條死規矩,沒有繞過去的路徑。
|
||||
@@ -0,0 +1,76 @@
|
||||
# 技術棧硬限制
|
||||
|
||||
## 三層語言對應(絕對不可混用)
|
||||
|
||||
| 層級 | 語言 | 位置 | 職責 |
|
||||
|-----|------|------|------|
|
||||
| 零件(Component) | **TinyGo 或 AssemblyScript → WASM** | `registry/components/{name}/` | 所有業務邏輯 |
|
||||
| 零件 Worker 包裝 | TypeScript(固定模板,不寫業務邏輯) | `.component-builds/{name}/` | WASI shim,stdin/stdout JSON |
|
||||
| Orchestration Worker | TypeScript + Hono | `cypher-executor/` | HTTP routing、workflow 執行排程、host functions |
|
||||
| CLI | TypeScript + Node.js | `cli/` | `acr` 指令 |
|
||||
| Python SDK | Python | `python-sdk/` | HTTP thin wrapper + client 端加密 |
|
||||
| JS SDK | TypeScript + Web Crypto | `js-sdk/` | HTTP thin wrapper + client 端加密 |
|
||||
| Frontend | React 19 + Vite + Tailwind v4 | `landing/` | Cloudflare Pages |
|
||||
|
||||
## 零件實作規範
|
||||
|
||||
### 只有兩種合法語言
|
||||
- **TinyGo**:`tinygo build -target=wasi -o {name}.wasm main.go`
|
||||
- **AssemblyScript**:`asc main.ts --target release -o {name}.wasm`
|
||||
|
||||
### I/O 模型
|
||||
- **stdin**:JSON input
|
||||
- **stdout**:JSON output
|
||||
- 不用 HTTP server,不監聽 socket(WASI preview1 沒 socket)
|
||||
|
||||
### Host Functions(零件呼叫外部能力的唯一管道)
|
||||
在 `u6u` namespace 下:
|
||||
|
||||
| Host Function | 用途 |
|
||||
|---|---|
|
||||
| `u6u.http_request` | 發 HTTP 請求 |
|
||||
| `u6u.kv_get` | 讀 Cloudflare KV(Worker 側依 key 前綴路由到正確 KV) |
|
||||
| `u6u.crypto_decrypt` | AES-GCM 解密(encryption key 永不暴露給 WASM) |
|
||||
| `u6u.crypto_sign_rs256` | RSA-SHA256 簽章(PKCS8 bytes 傳入) |
|
||||
|
||||
**所有 host function 在 `cypher-executor/src/lib/wasi-shim.ts` 實作**。零件透過 WASI import 使用。
|
||||
|
||||
## 資料儲存
|
||||
|
||||
| 儲存 | 用途 | Key 格式 |
|
||||
|-----|------|---------|
|
||||
| Cloudflare KV `WEBHOOKS` | workflow 定義(cypher binding YAML) | `webhook:{api_key}:{name}` |
|
||||
| Cloudflare KV `CREDENTIALS_KV` | 加密 credential | `{api_key}:cred:{name}` |
|
||||
| Cloudflare KV `RECIPES` | auth recipe / 動態 API recipe | `auth_recipe:{service}`, `rec_{hash}` |
|
||||
| Cloudflare KV `USERS_KV` | 用戶資料 | `user:{api_key}` |
|
||||
| Cloudflare KV `SESSIONS_KV` | session | `session:{token}` |
|
||||
| Cloudflare KV `ANALYTICS_KV` | 執行分析 | `execution:{timestamp}:{id}` |
|
||||
| Cloudflare KV `EXEC_CONTEXT` | workflow 執行中的 context | `ctx:{execution_id}:{node_id}` |
|
||||
| Cloudflare R2 `WASM_BUCKET` | **只用於用戶自製零件**(Phase 5 以後啟用) | `{api_key}:cmp:{hash}` |
|
||||
|
||||
**警告:R2 不存平台內建零件的 WASM**。平台零件已 bundle 進各自的 Worker binary(`[[wasm_modules]]` 或 `import ... assert { type: 'webassembly' }`)。
|
||||
|
||||
## 加解密規範
|
||||
|
||||
- **演算法**:AES-GCM 256-bit
|
||||
- **加密位置**:Client 端(CLI / Python SDK / JS SDK)
|
||||
- Python:`cryptography` 套件
|
||||
- JS:Web Crypto API(`crypto.subtle`)
|
||||
- **解密位置**:Server 端 **WASM primitive**(透過 host function `crypto_decrypt`)
|
||||
- cypher-executor TS **不解密**,只提供 host function
|
||||
- `ENCRYPTION_KEY` 只在 Worker host function 內部讀取,**永不經 stdin / 回傳值傳給 WASM**
|
||||
- **傳輸格式**:`{ name, encrypted, iv }`(iv base64、encrypted base64)
|
||||
|
||||
## 網路部署
|
||||
|
||||
- **平台 API(對外)**:`cypher.arcrun.dev`(cypher-executor)
|
||||
- **Landing**:`arcrun.dev`
|
||||
- **每個零件 Worker**:
|
||||
- **對內(cypher-executor 用來 fetch component,避開同 zone 死鎖)**:`arcrun-{kebab}.{WORKER_SUBDOMAIN}.workers.dev`
|
||||
- 例:`arcrun-kbdb-get.uncle6-me.workers.dev`
|
||||
- cypher-executor 從 `wrangler.toml [vars] WORKER_SUBDOMAIN` 組出此 URL
|
||||
- **對外(可選,零件對全網開放被 curl 用)**:`{kebab}.arcrun.dev`
|
||||
- 例:`gmail.arcrun.dev`、`kbdb-get.arcrun.dev`
|
||||
- 仍允許保留,但**禁止 cypher-executor 透過此 URL fetch**(會撞同 zone 自循環,見 [docs/incidents/2026-05-13-cypher-outbound-522.md](../../docs/incidents/2026-05-13-cypher-outbound-522.md))
|
||||
- **新增 component worker 部署清單**:`name = "arcrun-{kebab}"` + `[[routes]]` 對外(可選)+ dashboard 啟用 workers.dev(必須)
|
||||
- **部署工具**:Wrangler
|
||||
@@ -0,0 +1,153 @@
|
||||
# 禁止行為清單(零容忍)
|
||||
|
||||
**這份清單由 `.claude/hooks/*.sh` 強制執行。違反會 block 工具呼叫(exit 2)**。
|
||||
|
||||
---
|
||||
|
||||
## 第一類:零件實作層級的禁令
|
||||
|
||||
### 1.1 禁止在 `registry/components/` 下建立 TypeScript 檔案
|
||||
零件**只能**用 TinyGo(`.go`)或 AssemblyScript(`.ts` 但需 `asconfig.json`)實作,並編譯成 `.wasm`。
|
||||
cypher-executor/registry Worker 或 `.component-builds/` 內的 TS 不算零件邏輯,那是 WASI shim。
|
||||
|
||||
**Hook 會擋**:新增 `registry/components/*/{檔案}.ts`(除非目錄內有 `asconfig.json` 明確標記為 AssemblyScript)。
|
||||
|
||||
### 1.2 禁止建立新的 `auth_*` 目錄以外的 auth 實作
|
||||
所有 auth 邏輯只能在:
|
||||
- `registry/components/auth_static_key/`
|
||||
- `registry/components/auth_oauth2/`
|
||||
- `registry/components/auth_service_account/`
|
||||
- `registry/components/auth_mtls/`
|
||||
|
||||
**不可以**出現 `cypher-executor/src/auth-primitive/`、`cypher-executor/src/lib/auth-*.ts`、`auth-worker/`、`credential-worker/` 等目錄。
|
||||
|
||||
**Hook 會擋**:`mkdir` 或 `Write` 到上述違規路徑。
|
||||
|
||||
### 1.3 禁止用 `wrangler init/generate` 建立 auth/credential/jwt 相關的 TS Worker
|
||||
Auth primitive 必須透過 `component-worker-template/` 搭配 WASM binary 部署。
|
||||
|
||||
**Hook 會擋**:bash 指令含 `wrangler (init|generate) ... auth_`、`... credential_`、`... jwt_` 的 pattern。
|
||||
|
||||
---
|
||||
|
||||
## 第二類:cypher-executor TS 的禁令
|
||||
|
||||
### 2.1 禁止新增任何 credential / auth / jwt 相關的 TS 檔案
|
||||
**已存在但要刪**(Phase 1-3 範圍):
|
||||
- `cypher-executor/src/actions/credential-injector.ts` → 刪除(走 WASM auth primitive)
|
||||
- `cypher-executor/src/lib/jwt-signer.ts` → 刪除(RS256 移入 auth_service_account WASM)
|
||||
- `cypher-executor/src/lib/component-loader.ts` 的 `BUILTIN_API_RECIPES` 和 `BUILTIN_CREDENTIALS_MAP` → 整段刪除
|
||||
|
||||
**Hook 會擋**:新增任何路徑含以下關鍵字的 `.ts` 檔案:
|
||||
- `credential-injector`、`credential_injector`
|
||||
- `jwt-signer`、`jwt_signer`
|
||||
- `auth-dispatcher` 的 TS 若嘗試在裡面實作 credential 解密 / template 展開 / JWT signing,block
|
||||
|
||||
### 2.2 禁止在 cypher-executor 任何 TS 裡實作以下邏輯
|
||||
這些邏輯全部屬於 WASM 零件職責:
|
||||
|
||||
- AES-GCM 解密(`crypto.subtle.decrypt`)— 只准出現在 `wasi-shim.ts` 的 `crypto_decrypt` host function
|
||||
- RSA-SHA256 簽章(`crypto.subtle.sign` with RSASSA-PKCS1-v1_5)— 只准出現在 `wasi-shim.ts` 的 `crypto_sign_rs256` host function
|
||||
- Template 展開(`{{secret.X}}` / `{{runtime.X}}` 替換)— 只能在 WASM 零件內
|
||||
- PEM → PKCS8 解析
|
||||
- JWT header/payload/signature 組裝
|
||||
- Token exchange(拿 service account JWT 換 access_token)
|
||||
- 具體 API call 實作(例如 gmail send / telegram sendMessage / google sheets append)
|
||||
|
||||
**Hook 會擋**:
|
||||
- Write/Edit 到 `cypher-executor/src/` 下的 `.ts` 時,內容含:
|
||||
- `crypto\.subtle\.decrypt` 且檔名不是 `wasi-shim.ts`
|
||||
- `crypto\.subtle\.sign.*RSASSA` 且檔名不是 `wasi-shim.ts`
|
||||
- `interpolateTemplate`、`\{\{secret\.` 的模板邏輯
|
||||
- `BUILTIN_API_RECIPES`、`BUILTIN_CREDENTIALS_MAP`(新增用)
|
||||
- `gmail.googleapis.com/gmail/v1/users/me/messages/send` 類 hard-code API URL
|
||||
- `api.telegram.org/bot.*sendMessage`
|
||||
- `sheets.googleapis.com/v4/spreadsheets`
|
||||
- `notify-api.line.me/api/notify`
|
||||
|
||||
### 2.3 cypher-executor TS 的合法職責(允許)
|
||||
- HTTP routing(Hono routes)
|
||||
- workflow 執行排程(`graph-executor.ts`)
|
||||
- 呼叫 WASM 零件(透過 HTTP fetch 到對應 Worker URL,或 Service Binding fallback)
|
||||
- 提供 host function(`wasi-shim.ts` 的 `kv_get` / `crypto_decrypt` / `crypto_sign_rs256`)
|
||||
- KV/R2/Service Binding 存取封裝
|
||||
|
||||
---
|
||||
|
||||
## 第三類:架構層級的禁令
|
||||
|
||||
### 3.1 禁止新增 Service Binding
|
||||
**Cypher binding 不是 Cloudflare service binding**。它是 YAML/KV 裡的 URL 清單。
|
||||
|
||||
零件串接(workflow 層)一律走 HTTP URL,不走 `[[services]]`。
|
||||
|
||||
13 個現有的 `SVC_*` 綁定(`cypher-executor/wrangler.toml`,邏輯零件)是歷史遺產(效能優化),**保留但不新增**。
|
||||
|
||||
> **2026-06-06 註(credential-primitives-wasm Phase 7)**:self-hosted 的 cypher 與 auth worker 同在 `{sub}.workers.dev` zone,cypher `fetch()` 打 auth 觸發 CF **same-zone 1042**(壓測階段 11)。**未用 service binding 解**(評估後廢:service binding 靜態、加/改要重 deploy cypher)。改用 **`global_fetch_strictly_public` compatibility flag**(cypher wrangler.toml)讓 same-zone fetch 走公網前門 → 同 zone 也通,**auth 維持 HTTP fetch、不加 binding**。故本禁令不變。
|
||||
|
||||
**Hook 會擋**:bash 指令含 `wrangler tail` 以外、涉及 `[[services]]` 新增的 pattern;Edit wrangler.toml 新增 `[[services]]` 區塊時警告確認。
|
||||
|
||||
### 3.2 禁止以「從 R2 取 WASM」為設計
|
||||
平台內建零件已 bundle 進各自 Worker,不從 R2 取。
|
||||
R2 只在 Phase 5(用戶自製零件)啟用。
|
||||
|
||||
**Hook 會警告**:TS 中出現 `env.WASM_BUCKET.get(` 的新增 code(除非在明確標註的 Phase 5 user-submit 路徑中)。
|
||||
|
||||
### 3.3 禁止複製貼上 Worker 程式碼到新目錄
|
||||
要改 `gmail` 零件 → 改 `registry/components/gmail/main.go`,重新編譯、部署。
|
||||
**不准**新建 `gmail-v2/`、`new-gmail/`、`gmail-worker/` 等目錄。
|
||||
|
||||
**Hook 會擋**:`mkdir` 或 `Write` 到 `{component-name}-v2/`、`new-{component-name}/`、`{component-name}-worker/` 類路徑。
|
||||
|
||||
### 3.4 禁止在 SDK 內做 server 職責
|
||||
- **禁止**:SDK 裡做 server 端解密、credential-injector 重實作、workflow executor、auth recipe 解析
|
||||
- **允許**:SDK 做 HTTP thin wrapper + client 端加密(AES-GCM)
|
||||
|
||||
---
|
||||
|
||||
## 第四類:流程層級的禁令
|
||||
|
||||
### 4.1 禁止沒讀 SDD 就動 code
|
||||
見 `00-sdd-protocol.md`。
|
||||
|
||||
### 4.2 禁止批次更新 tasks.md
|
||||
每完成一個 task 就立刻 mark `- [x]`。不准「先全部做完再一次更新」。
|
||||
|
||||
### 4.3 禁止新建 SDD 而不事先與 richblack 確認
|
||||
SDD 屬於架構決策,必須人確認。CC 不可以自行在 `docs/3-specs/` 底下建新目錄。
|
||||
例外:在現有 SDD 目錄內新增 `requirements.md` / `design.md` / `tasks.md` 的單檔補充(需在 CLAUDE.md 已註記的 SDD 範圍內)。
|
||||
|
||||
---
|
||||
|
||||
## 第五類:薄殼原則的禁令(詳見 07-thin-shell.md)
|
||||
|
||||
### 5.1 禁止在薄殼介面(cli/src/、arcrun-mcp/src/)實作業務邏輯
|
||||
能力只實作一次,放在 API(cypher-executor 端點)。薄殼只做介面轉換 + 暴露。
|
||||
|
||||
**Hook 會擋**(規則 7.x,範圍 `cli/src/` 與 `arcrun-mcp/src/` 的 .ts/.js):
|
||||
- 7.1 新增 `seedApiRecipes` / `seedAuthRecipes` / `seedRecipes` 編排函式(seed 是 API 行為,§4.1 反例)
|
||||
- 7.2 介面層拼裝 upsert(同函式內 PATCH + POST + upsert/找則改否則建 字樣)
|
||||
- 7.3 client 端「全部成功才做下一步」gate(`deployFullyOk` 類,§4.1 反例)
|
||||
|
||||
### 5.2 禁止用 recipe / 客製零件補 API 缺的能力
|
||||
缺能力 → 去補 API endpoint,不是在 recipe 層拼湊或用零件繞過(hook 偵測不到語意,靠 code review)。
|
||||
|
||||
### 5.3 禁止同一 API 能力在不同介面用不同參數簽名 / 連不同帳號
|
||||
`validate` 在 CLI 吃 YAML、MCP 卻要 `api_key`+`graph` = 底層分歧。薄殼差異只能來自介面慣例。
|
||||
所有薄殼讀同一份身份來源(見 07 §4)。
|
||||
|
||||
---
|
||||
|
||||
## Hook Block 訊息格式
|
||||
|
||||
當 hook 擋住一個操作時,訊息格式統一為:
|
||||
|
||||
```
|
||||
❌ BLOCKED by arcrun CLAUDE rules
|
||||
違反項:<禁令編號,例如 2.2>
|
||||
原因:<簡短說明>
|
||||
正確做法:<該改去哪裡、該用什麼方式>
|
||||
參考:.claude/rules/<對應檔案>
|
||||
```
|
||||
|
||||
這樣 CC 拿到錯誤訊息後有機會自行導正,不是被擋死就愣住。
|
||||
@@ -0,0 +1,165 @@
|
||||
# 零件架構與部署模式(必讀,CC 最常搞錯的地方)
|
||||
|
||||
## 第一核心概念:每個 WASM 零件 = 一個獨立 Worker = **兩個** URL
|
||||
|
||||
**不是**從 R2 即時載入 WASM 執行。
|
||||
**不是**用 service binding 串零件。
|
||||
**不是**一個 Worker 裡跑多個零件。
|
||||
|
||||
**是**:每個零件都是獨立部署的 Worker,每個都有**兩個 URL**:
|
||||
|
||||
| URL 類型 | Pattern | 用途 |
|
||||
|---|---|---|
|
||||
| 對內(cypher-executor 用)| `arcrun-{kebab}.{WORKER_SUBDOMAIN}.workers.dev` | cypher-executor fetch component 走這個,避開同 zone 自循環死鎖(P0 #9)|
|
||||
| 對外(直接 curl 用,可選)| `{kebab}.arcrun.dev` | 用戶單獨打 component 測試或 self-hosted 用法 |
|
||||
|
||||
例:`kbdb_get` 零件:
|
||||
- 對內:`arcrun-kbdb-get.uncle6-me.workers.dev`(cypher-executor 走這個)
|
||||
- 對外:`kbdb-get.arcrun.dev`(用戶 / 直 curl)
|
||||
|
||||
**為什麼這樣設計**:CF Workers 「同 zone 自循環防護」會讓綁 `cypher.arcrun.dev/*` 的 cypher-executor fetch 同 zone `*.arcrun.dev` 撞 522。完整事件報告:[docs/incidents/2026-05-13-cypher-outbound-522.md](../../docs/incidents/2026-05-13-cypher-outbound-522.md)。改走 workers.dev 子域繞過。
|
||||
|
||||
### 零件 Worker 的結構
|
||||
|
||||
```
|
||||
registry/components/{name}/
|
||||
├── main.go ← TinyGo 原始碼(實際零件邏輯)
|
||||
├── component.contract.yaml ← 輸入/輸出規格
|
||||
└── {name}.wasm ← TinyGo 編譯產物
|
||||
```
|
||||
|
||||
部署時,透過 `component-worker-template/` 把 WASM 包進一個 Hono Worker:
|
||||
```
|
||||
.component-builds/{name}/
|
||||
├── package.json
|
||||
├── wrangler.toml ← name = "arcrun-{name}",route = "{name}.arcrun.dev"
|
||||
├── component.wasm ← 從 registry/components/{name}/ 複製過來
|
||||
└── src/index.ts ← 固定的 WASI shim(POST / → stdin → WASM → stdout → JSON)
|
||||
```
|
||||
|
||||
**src/index.ts 是通用模板**,所有零件都用同一份。這個 TS 只做 WASI runtime,不是業務邏輯。
|
||||
|
||||
---
|
||||
|
||||
## R2(WASM_BUCKET)的真正用途
|
||||
|
||||
R2 存 WASM 只是**用戶自製零件上傳**用的。
|
||||
|
||||
**平台內建零件不從 R2 讀取**——它們在部署時就已 bundle 進 Worker 的 binary(透過 `[[wasm_modules]]` 或 `import` with `assert { type: 'webassembly' }`)。
|
||||
|
||||
Phase 5(封測後)才會啟用「用戶 push 自製零件 → 存 R2 → 動態執行」這條路徑。
|
||||
|
||||
**結論:當 CC 問「怎麼從 R2 取出 WASM」時,幾乎都是走錯路徑**。平台零件是獨立 Worker,走 HTTP 呼叫,不是 R2 動態載入。
|
||||
|
||||
---
|
||||
|
||||
## Cypher binding 的正確定義
|
||||
|
||||
**Cypher binding 不是 Cloudflare 的任何 binding 機制。**
|
||||
|
||||
Cypher binding 是一張 YAML 清單,內容是「一個 workflow 要呼叫哪些零件 URL」。存放在:
|
||||
- 本地:`workflow.yaml`(用戶寫的 workflow)
|
||||
- KV:`WEBHOOKS` KV(用戶 `acr push` 後存入)
|
||||
|
||||
Cypher executor 執行 workflow 時:
|
||||
1. 從 KV 讀出 workflow YAML
|
||||
2. 按 graph 順序解析每個節點的 `component`
|
||||
3. 用 HTTP fetch 打對應的零件 URL
|
||||
4. 把 output 當作下個節點的 input
|
||||
|
||||
**這就是 Cypher binding——用 HTTP URL 把零件串起來,存在 YAML/KV 裡**。
|
||||
|
||||
### 為什麼不能用 Service Binding?
|
||||
|
||||
Service binding 需要 `wrangler.toml` 裡寫死 `[[services]]`,且要 redeploy 才生效。arcrun 是類 n8n 服務,用戶建立新 workflow 時**絕對不可能**要他 redeploy。所以 workflow 層一定要 HTTP。
|
||||
|
||||
### Service Binding 的僅存合法用途
|
||||
|
||||
只在 `cypher-executor` 和**平台內建邏輯零件之間**保留(效能優化,避免公網往返)。看 `cypher-executor/wrangler.toml` 裡的 13 個 `[[services]]` 綁定就是這個用途。
|
||||
|
||||
**禁止新增任何 Service Binding**。所有新零件(含 auth primitive)都走 HTTP URL 路徑。
|
||||
|
||||
**same-zone 1042 的解(credential-primitives-wasm Phase 7,2026-06-06)**:self-hosted 的 cypher 與 auth worker 同在 `{sub}.workers.dev` zone,cypher `fetch()` 打 auth 觸發 CF **1042**(官方 docs:「fetch from another Worker on the **same zone**」;官方 cypher 在 `cypher.arcrun.dev`、打 `*.workers.dev` 屬跨 zone 故不踩——非官方有 flag)。**解法不是 service binding**(評估後廢:靜態、加/改要重 deploy),而是 cypher wrangler.toml 加 **`global_fetch_strictly_public` flag**——讓 same-zone fetch 走公網前門 → 同 zone 也通。auth 維持 HTTP fetch、不加 binding。官方加此 flag 行為不變(本就跨 zone),self-host 被修好 → **官方與 self-host 共用同一份 toml**。
|
||||
|
||||
**仍禁止**:為**用戶自製 / 服務專屬零件**(`gmail-worker`、`notion-worker` 之類)新增 binding——那些是 recipe 的事,不該有 binding。**workflow 層(用戶串零件)一律 HTTP URL 不變。**
|
||||
|
||||
---
|
||||
|
||||
## 零件之間怎麼串:實際流程
|
||||
|
||||
假設 workflow 是:webhook → gmail(要 auth)→ google_sheets(要 auth)
|
||||
|
||||
```
|
||||
用戶 POST https://cypher.arcrun.dev/webhooks/named/xxx/trigger
|
||||
│
|
||||
▼
|
||||
cypher-executor(Worker)讀 workflow YAML
|
||||
│
|
||||
├─ 節點 1: component = gmail
|
||||
│ a. 查 auth_recipe:gmail → primitive = static_key
|
||||
│ b. HTTP POST https://auth-static-key.arcrun.dev
|
||||
│ { action: "authenticate", api_key, service: "gmail" }
|
||||
│ → 回傳 { auth_headers: { Authorization: "Bearer ..." } }
|
||||
│ c. HTTP POST https://gmail.arcrun.dev
|
||||
│ { to, subject, body, _auth_headers }
|
||||
│ → gmail 零件 Worker 執行 WASM → 回傳 { success, data }
|
||||
│
|
||||
└─ 節點 2: component = google_sheets
|
||||
... 相同模式
|
||||
```
|
||||
|
||||
**cypher-executor 本身不做 credential 解密、不做 JWT signing、不做 auth header 組裝**。這些全在 auth primitive WASM 零件內,cypher-executor 只負責 HTTP routing 和工作流排程。
|
||||
|
||||
---
|
||||
|
||||
## 實際禁令(CC 看這裡)
|
||||
|
||||
### 禁止在 `registry/components/` 下建立 TypeScript 檔案
|
||||
零件邏輯一律 TinyGo 或 AssemblyScript,編譯成 `.wasm`。
|
||||
|
||||
### 禁止把 auth 邏輯寫在 `cypher-executor/src/` 裡
|
||||
credential 解密、JWT signing、template 展開(`{{secret.X}}`)全部屬於 auth primitive WASM 零件的職責。cypher-executor 只呼叫它們。
|
||||
|
||||
### 禁止問「怎麼從 R2 取 WASM」
|
||||
平台內建零件**不從 R2 取**。每個零件已部署成獨立 Worker,走 HTTP URL。用戶自製零件才用 R2(Phase 5,未啟用)。
|
||||
|
||||
### 禁止新增 Service Binding
|
||||
13 個現有的 SVC_*(邏輯零件)是歷史遺產,不新增。新零件(含 auth primitive)一律走 HTTP URL。self-hosted 的 same-zone 1042 用 `global_fetch_strictly_public` flag 解,不靠新增 binding(見上「合法用途」段,Phase 7)。
|
||||
|
||||
### 禁止重建已存在的零件 Worker
|
||||
要改 `gmail` 零件邏輯 → 改 `registry/components/gmail/main.go`,重新編譯 `.wasm`,重新部署對應 Worker。**不要**在 `cypher-executor/src/lib/` 或其他地方建「新的 gmail 實作」。
|
||||
|
||||
---
|
||||
|
||||
## 部署一個新零件的完整步驟(auth_static_key 為例)
|
||||
|
||||
1. 建立 `registry/components/auth_static_key/`:
|
||||
- `main.go`(TinyGo 實作)
|
||||
- `component.contract.yaml`(IO 規格)
|
||||
2. 編譯:`cd registry/components/auth_static_key && tinygo build -target=wasi -o auth_static_key.wasm main.go`
|
||||
3. 建立 `.component-builds/auth_static_key/`:
|
||||
- 複製 `component-worker-template/src/index.ts`
|
||||
- 複製 `component-worker-template/package.json`
|
||||
- 新建 `wrangler.toml`:
|
||||
```toml
|
||||
name = "arcrun-auth-static-key"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2025-02-19"
|
||||
[vars]
|
||||
COMPONENT_ID = "auth_static_key"
|
||||
[[routes]]
|
||||
pattern = "auth-static-key.arcrun.dev/*"
|
||||
zone_name = "arcrun.dev"
|
||||
```
|
||||
- 複製 `auth_static_key.wasm` 到此目錄為 `component.wasm`
|
||||
4. `cd .component-builds/auth_static_key && pnpm install && pnpm deploy`
|
||||
5. **Dashboard 啟用 workers.dev URL**(必須,否則 cypher-executor fetch 不到):
|
||||
- Workers & Pages → `arcrun-auth-static-key` → Settings → Domains & Routes → workers.dev → Enable
|
||||
- 啟用後 URL:`arcrun-auth-static-key.{WORKER_SUBDOMAIN}.workers.dev`
|
||||
6. 驗證對外:`curl https://auth-static-key.arcrun.dev` → 應回 `{ok: true, component: "auth_static_key"}`
|
||||
7. 驗證對內:`curl https://arcrun-auth-static-key.{WORKER_SUBDOMAIN}.workers.dev` → 應同樣回 200
|
||||
8. cypher-executor 透過 `wasmWorkerUrl()` 自動組對內 URL 呼叫(不用手動註冊)
|
||||
|
||||
**這是唯一正確的部署流程**。任何偏離這個流程的「替代方案」都要先和 richblack 確認。
|
||||
|
||||
**Step 5 為什麼必須**:見 arcrun.md P0 #9(2026-05-13)。cypher-executor 走對內 URL 避開同 zone 自循環死鎖;若 workers.dev 未啟用,cypher-executor fetch 該 component 會 404。
|
||||
@@ -0,0 +1,77 @@
|
||||
# 當前進度(SessionStart 會注入此檔重點)
|
||||
|
||||
> 更新時間:2026-04-19
|
||||
> 權威來源:`docs/3-specs/arcrun/credential-primitives-wasm/tasks.md`
|
||||
> 此檔僅摘要,詳細狀態以 tasks.md 為準。
|
||||
|
||||
---
|
||||
|
||||
## 封測狀態
|
||||
|
||||
**原定明天封測,richblack 決定推遲**,原因:cypher-executor 有三套 TS 業務邏輯違反「零件一律 WASM」架構原則(Phase 1-3 要清除的程式碼),在清除前不封測。
|
||||
|
||||
---
|
||||
|
||||
## 目前 Phase:Credential Primitives TS → WASM
|
||||
|
||||
**SDD 位置**:`docs/3-specs/arcrun/credential-primitives-wasm/design.md` + `tasks.md`
|
||||
|
||||
### 已完成
|
||||
|
||||
- **Phase 0.1–0.5**:核心合併(u6u-core 併入 arcrun、21 個零件 contract 完整、刪除重複 `credentials/` 目錄、CREDENTIALS_KV binding 確認、刪除 `matrix/u6u-core/`)
|
||||
- `registry/components/` 下 21 個零件(邏輯 + API)都有 `main.go` + `.wasm`
|
||||
|
||||
### 進行中 / 未完成
|
||||
|
||||
| Task | 狀態 | 阻擋關係 |
|
||||
|-----|------|---------|
|
||||
| 0.6 wasi-shim 新增 `kv_get` / `crypto_decrypt` / `crypto_sign_rs256` host functions | ⬜ 未開始 | **Phase 1-3 的硬前置** |
|
||||
| 0.7 component-loader 新增 WASM runner 路徑 | ⬜ 未開始 | **Phase 1-3 的硬前置** |
|
||||
| 1.1-1.8 `auth_static_key` WASM 零件(TinyGo) | ⬜ 未開始 | 涵蓋 80% 服務 |
|
||||
| 2.1-2.6 `auth_service_account` WASM 零件(JWT signing) | ⬜ 未開始 | Google Service Account 等 |
|
||||
| 3.1-3.5 清除 `component-loader.ts` 的 `BUILTIN_API_RECIPES` | ⬜ 未開始 | 要先有 Phase 1-2 的 WASM 零件 |
|
||||
| 4.1-4.4 `auth_oauth2` + `auth_mtls`(封測後) | ⬜ 未開始 | 非阻擋項 |
|
||||
| 5.1-5.7 核心穩定驗證(全域搜尋確認無殘餘 TS) | ⬜ 未開始 | 封測啟動門檻 |
|
||||
|
||||
### Phase 1-3 要**徹底刪除**的 TS 檔案(不是搬、不是改,是刪)
|
||||
|
||||
| 檔案 | 違反什麼 |
|
||||
|-----|---------|
|
||||
| `cypher-executor/src/actions/credential-injector.ts` | AES 解密、template 展開、JWT 邏輯 —— 應在 WASM |
|
||||
| `cypher-executor/src/lib/jwt-signer.ts` | RS256 JWT 簽章邏輯 —— 應在 `auth_service_account.wasm` |
|
||||
| `cypher-executor/src/lib/component-loader.ts` 的 `BUILTIN_API_RECIPES`(~100 行) | gmail/telegram/line/gsheets/http_request/cron 的 TS 實作 —— 應全部走對應 WASM 零件 |
|
||||
|
||||
---
|
||||
|
||||
## 下一個 session 第一件要做的事
|
||||
|
||||
**讀 `docs/3-specs/arcrun/credential-primitives-wasm/tasks.md`**,然後決定從 Phase 0.6 還是 0.7 開始。
|
||||
|
||||
0.6(host functions)和 0.7(WASM runner)是並列的前置工作,哪個先都可以,但都要在 Phase 1 開始之前完成。
|
||||
|
||||
---
|
||||
|
||||
## SDD 索引
|
||||
|
||||
| 子系統 | SDD |
|
||||
|--------|-----|
|
||||
| **主要(正在動)** Credential Primitives WASM 改寫 | `docs/3-specs/arcrun/credential-primitives-wasm/` |
|
||||
| **LI (LLM Interface)** — AI 操盤手使用體驗(2026-05-16 新建,mira dogfood 痛點轉化) | `docs/3-specs/llm-interface/` |
|
||||
| arcrun 總進度 | `docs/3-specs/arcrun/arcrun.md` |
|
||||
| Auth Recipe 系統(schema、預建 20 個服務) | `docs/3-specs/arcrun/auth-recipe.md` |
|
||||
| Landing Page | `docs/3-specs/arcrun/landing-page.md` |
|
||||
| SDK + Website | `docs/3-specs/arcrun/sdk-and-website/design.md` |
|
||||
| arcrun MVP 整體 | `docs/3-specs/arcrun-core-mvp/design.md` |
|
||||
| Credential 長期規格(需求源) | `docs/user_requirements/credential_parts.md` |
|
||||
| Platform Evolution | `docs/3-specs/arcrun-platform-evolution/design.md` |
|
||||
| Tech Stack 詳細 | `docs/3-specs/tech.md` |
|
||||
|
||||
---
|
||||
|
||||
## 技術備註(CC 常搞錯的點)
|
||||
|
||||
1. **每個 WASM 零件 = 獨立 Worker = 公開 URL**(例:`gmail.arcrun.dev`)。不是從 R2 動態讀。
|
||||
2. **Cypher binding = YAML 裡寫 URL 清單**。不是 Cloudflare service binding。
|
||||
3. **cypher-executor 只做 routing + host functions**。業務邏輯全在 WASM 零件。
|
||||
4. **TinyGo 有限制**:`crypto/rsa` 支援不全 → 用 host function `crypto_sign_rs256` 讓 Worker 代簽。
|
||||
5. 詳見 `.claude/rules/03-component-architecture.md`。
|
||||
@@ -0,0 +1,60 @@
|
||||
# arcrun mindset(給 CC 的世界觀 — 走歪前先讀這個)
|
||||
|
||||
> 這是「為什麼」層,不是禁令層(禁令在 02-forbidden)。
|
||||
> 違反禁令會被 hook 擋;違反 mindset 不會被擋,但會讓你做出「技術上對、方向上錯」的東西。
|
||||
> 來源:2026-05 richblack 與 CC 的一連串判準釐清(權威記錄在 DECISIONS.md)。
|
||||
>
|
||||
> 註:這是步驟 7「arcrun-mindset Skill」的前身。Skill 正式建立前,本檔是 CC 的 mindset 來源。
|
||||
|
||||
---
|
||||
|
||||
## 1. 工作流是 default,零件是稀有例外
|
||||
|
||||
AI 開發時的預設順序:
|
||||
1. **預設寫工作流**(串服務 / 自用 / 給少數人用 → 全部先工作流,用 http_request + 流程控制 primitive 串)。
|
||||
2. **要打的服務不提供串接但有 API,且有必要讓全 arcrun 生態重用** → 才建零件(零件 = API 薄殼,只打一個 endpoint)。
|
||||
3. **建零件前必問「你有必要嗎?」**:看到「有 API 可包成零件」≠「該包」。自用 → 工作流。
|
||||
|
||||
**CC 的典型走歪**:把「需要一個能力」直接翻成「做一個零件」,把「能包」當「該包」。
|
||||
mira 的 claude_api / km_writer 就是這樣被錯做成零件的(其實是自用服務膠水,該是工作流)。
|
||||
|
||||
## 2. Arcrun 是 AI 呼叫的工具(AI → 工具),不是工具回頭呼叫 AI
|
||||
|
||||
需要 AI 判斷 / 轉換時,是**操盤的 CC 自己做**,再呼叫工作流做確定性的下一步。
|
||||
**不要在工作流裡放零件回頭呼叫一個 LLM**。n8n 需要 AI 節點是因為它沒大腦;arcrun 的大腦就是 CC。
|
||||
(ai_transform_compile/run 因此被刪除。)
|
||||
|
||||
## 3. arcrun 不做授權判斷
|
||||
|
||||
「能不能打通」由發 API key 的服務裁決,不是 arcrun。401/403 是對方服務在行使授權,不是 arcrun 的 bug。
|
||||
auth_recipe 只定義「怎麼認證」,不含「誰准用」清單。不要加「arcrun 替用戶擋掉某些 endpoint」的功能。
|
||||
|
||||
## 4. 零件投稿走 GitHub PR(人 merge = 人類閘門)
|
||||
|
||||
零件投稿不是 registry self-service,是 GitHub PR。人 merge = 天然人類閘門(AI 偽造不了 GitHub approve),
|
||||
把關(假零件偵測 / 純WASI / Gherkin)由 CI PR check 跑(CI 能 runtime 跑 wasm,CF Worker 不能)。
|
||||
§8「不依賴 CI」指執行鏈路(高頻);零件投稿稀有,走 PR/CI 是例外、不違反。
|
||||
|
||||
## 5. 發佈安全的底氣是純 WASI 沙箱,不是 Gherkin
|
||||
|
||||
Gherkin 全綠 ≠ 零件安全(投稿者可寫避重就輕的 Gherkin)。真正框死破壞力的是**純 WASI 沙箱**
|
||||
(零件只能 stdin→stdout、無網路 syscall、無檔案系統)。Gherkin 驗契約 + 沙箱框死 + 市場補長尾 = 風險可控,非零風險。
|
||||
|
||||
## 6. 暴露 / 送資料的動作 → 人類明示同意(資料外流警示)
|
||||
|
||||
把資料 / workflow 變成「可被外部呼叫」(部署 webhook、recipe push)= 暴露面 → 需人類明示同意,不分公私庫。
|
||||
**不禁止**用戶公開(他的自由),但要**確定他自己明示同意**(不是 AI 替他決定)。
|
||||
警示同時是「保護措施入口」(提示可加 API Key / 權限 / 限流)。
|
||||
|
||||
## 7. 誠實限制(最重要的 mindset:不假裝、不假綠)
|
||||
|
||||
- **AI 技術上能偽造人類確認**(confirmed_by_human、exposure_consent、gherkin_evidence 都能塞)。
|
||||
這些機制的價值是**法律歸責 + 軌跡可審**,不是技術防偽。**絕不在文件 / 程式裡聲稱「不可能繞過」。**
|
||||
- **絕不代替人類做有風險的確認**(建零件、暴露資料)。非 TTY(你直跑)就拒絕,不要自己塞 flag 假裝人類同意了 —— 那是明確越界。
|
||||
- **禁假綠**(DECISIONS §3c/§7):stub / 未實作就回 success:false 或明確標 unimplemented,不要回傳假資料假裝成功。
|
||||
缺 credential 打不到 2xx 就誠實標「未驗收:缺 X」,不 mock 充綠燈。
|
||||
- **完成 = 客觀證據**(編譯 exit code / HTTP status + trace),不是口頭宣布「我做好了」。
|
||||
|
||||
---
|
||||
|
||||
詳細判準與來龍去脈見 `DECISIONS.md`。每條都有對應的慘痛教訓,不是憑空規定。
|
||||
@@ -0,0 +1,133 @@
|
||||
# 薄殼原則(鐵律)— 能力長在 API,介面只暴露
|
||||
|
||||
> 來源:`docs/壓測報告.md` §5.4/§5.5(設計者本人於壓測中釐清)+ DECISIONS §1。
|
||||
> 違反此原則的典型後果:每改一個能力要同步多份介面、介面間漂移(壓測 §5.1:CLI 改了讀
|
||||
> 全域/專案/.env,MCP 沒跟上,兩者打不同帳號)、能力被某個介面綁架後別的介面用不到。
|
||||
> 這條由 `.claude/hooks/pre-write-guard.sh` 規則 7.x 部分強制(見下「hook 強制範圍」)。
|
||||
|
||||
---
|
||||
|
||||
## 0. 一句話
|
||||
|
||||
**所有能力(business logic)只實作一次,放在 API(cypher-executor HTTP 端點)。
|
||||
CLI / MCP / Python lib / JS lib 全是薄殼:只做「介面轉換 + 暴露」,不含任何商業邏輯。**
|
||||
|
||||
```
|
||||
CLI ─┐
|
||||
MCP ─┤ ← 全是薄殼:參數解析 / 格式轉換 / 暴露,不含商業邏輯
|
||||
Python lib ─┤
|
||||
JS lib ─┘
|
||||
↓ 全部呼叫同一個
|
||||
┌──────────────────────────────┐
|
||||
│ API(唯一真相,能力都在這) │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 什麼是「能力下沉到 API」(正例 vs 反例)
|
||||
|
||||
### 正例:upsert
|
||||
- ✅ **API 提供 `upsert` 端點**(內部 GET 找→有則 update 無則 insert)。CLI/MCP/lib 只呼叫它。
|
||||
- ❌ 在 MCP 裡自製「先 call update API、失敗再 call insert API」的拼裝邏輯。
|
||||
- ❌ 在 recipe 層拼湊 upsert(recipe/零件補 API 缺的能力 = 走歪;正解是補在 API)。
|
||||
|
||||
### 正例:seed recipe(壓測 §4.1 的反例修正)
|
||||
- ✅ **API 在「部署/註冊完成」時保證 recipe 就緒**(seed 是 API 行為,由一個端點完成 `POST /init/seed`)。
|
||||
- ✅ **種子資料(清單)放 server**:`cypher-executor/src/lib/*-seeds.ts`。「裝好後預設有哪些 recipe」
|
||||
是 API 的能力,種子資料是這能力的一部分。薄殼只呼叫 `/init/seed` 一次。
|
||||
- ❌ 在 CLI `init.ts` 裡用迴圈 POST 11 個 recipe + 客戶端「全部成功才 seed」的 if 判斷
|
||||
(這正是 §4.1 seed 永遠不被 seed 的根因:邏輯被寫進了某個介面)。
|
||||
|
||||
> **種子資料檔(`*-seeds.ts`)是一個普遍類別,不是某個零件的特例。**
|
||||
> 它含 endpoint / `{{template}}` 字串(recipe 的資料欄位),rule 02 §2.2 hook 對**整類** `*-seeds.ts`
|
||||
> 豁免 endpoint/template 檢查——因為那是「資料宣告」不是「呼叫實作」。新增任何 `xxx-seeds.ts`
|
||||
> 自動適用,**不需為個別零件/recipe 改 hook**(richblack 原則:不為單一零件改全域規則)。
|
||||
|
||||
### 判準口訣
|
||||
> **「這段邏輯換一個介面(CLI→MCP)要不要重寫?」**
|
||||
> 要重寫 → 它是能力,該在 API。
|
||||
> 不用重寫(只是把 API 回傳值換個格式印出來)→ 它是薄殼該做的事。
|
||||
|
||||
---
|
||||
|
||||
## 2. 薄殼「允許」做的事(窮舉)
|
||||
|
||||
1. 解析介面慣例的輸入(CLI 吃檔案路徑 / MCP 吃 JSON 參數)→ 轉成 API 期望的 payload。
|
||||
2. 呼叫 API(HTTP fetch / service binding)。
|
||||
3. 把 API 回傳值轉成該介面的輸出格式(CLI 印彩色文字 / MCP 回 structured JSON)。
|
||||
4. **client 端加密**(AES-GCM)——唯一例外,因 API 期望收到已加密 payload(見 rule 01 加解密)。
|
||||
5. 讀取「身份設定」(哪個帳號 / 哪個 cypher URL)——但所有薄殼必須讀**同一份**身份來源
|
||||
(見 §4 統一帳號來源)。
|
||||
|
||||
## 3. 薄殼「禁止」做的事
|
||||
|
||||
1. ❌ **在薄殼介面(CLI/MCP/lib)裡用多個 API 呼叫拼裝**出一個 API 沒有的能力(upsert / seed / 任何 N-step 編排)。
|
||||
> **界線(2026-06-26 收窄,issue #4)**:禁的是「**把編排邏輯寫進介面層 TS**」,不是禁「用資料方式(workflow/code-node)自救」。
|
||||
> 自家 API 缺能力 → 補進 API(你能改);**第三方 API 缺能力 → 走 workflow/code-node 補丁是合法的**(你改不了第三方 API,不能被規則卡死)。詳見 §3.5 自力救濟階梯。
|
||||
2. ❌ **用零件**補 API 缺的能力 → 污染零件庫(缺能力 → 先看 §3.5 階梯,真需新穩定能力才走零件 PR)。
|
||||
> 這條的原始精神(要保留):當初是 AI 把多步驟工作寫成**零件**污染零件庫,才訂此禁令。**禁的是「亂建零件」,不是「禁止任何補丁」。**
|
||||
3. ❌ 寫死判斷來補 API 缺口(例:`deployFullyOk` 那種 client 端 gate)。
|
||||
4. ❌ 同一 API 能力在不同介面用不同參數簽名(`validate` 在 CLI 吃 YAML、在 MCP 卻要 `api_key`+`graph` = 底層分歧,違反「同一 API」)。差異只能來自介面慣例(檔案路徑 vs 字串),不能來自底層實作。
|
||||
5. ❌ 任一薄殼連的帳號 / 後端與別的薄殼不同(CLI 連自架、MCP 連平台 = 違反「同一 API」前提)。
|
||||
|
||||
---
|
||||
|
||||
## 3.5 缺能力時怎麼補:自力救濟階梯(普世規則,issue #4)
|
||||
|
||||
> **問題**:§3 舊版預設「缺能力 → 去補 API」,這**預設 API 是你能改的**。對**第三方 API**(如 Google Sheets:一次只能倒全部、輸出前無法 filter)不成立——若 Google 不開該 API、規則又禁用 workflow「倒出來自己篩」,用戶被自己的規則卡死。
|
||||
> **解法**:把「補丁」分層,維持零件庫最小,但開放「用資料方式自救」的合法路徑。
|
||||
|
||||
**主界線(一句話)**:**「那個 API 你能不能改?」** 自家 API 缺能力 → 補 API;第三方 API 缺能力 → workflow/code-node 補丁。
|
||||
|
||||
| 情況 | 正解 | 為何 |
|
||||
|---|---|---|
|
||||
| 能打既有 API | **recipe**(沒有就建 recipe) | 單一 API 呼叫的封裝 |
|
||||
| **自家** API(KBDB / cypher)缺能力 | **補進 API** + 可同時發 issue | 你能改,能力該長在 API |
|
||||
| **第三方** API 缺能力(gsheets filter / 無 upsert API) | **可投稿的 workflow 補丁** + 發 issue 建議原廠加 API | 你改不了第三方 API,但不能被卡死 |
|
||||
| 非 call-api 的純計算(如整篇文章轉大寫) | **code-node**(空白 code 零件內寫 JS) | recipe/workflow 都做不到,又不該為此建一堆專用零件 |
|
||||
| 真需新穩定能力(極少數) | 自建零件 → PR | 維持零件庫最小,只有非用零件不可才建 |
|
||||
|
||||
**三個配套原則**:
|
||||
1. **補丁 workflow 可像 recipe 一樣被呼叫,但明示它是 workflow、且可投稿**(呼應 wishlist C6「工作流即零件」)。讓 AI 一遇阻就「用資料方式」自救,而非建零件。
|
||||
2. **code-node**:原廠不提供某純計算時,AI 至少能用一個空白 code 零件寫 JS 自救(呼應 wishlist C1;架構決策:JS 在 CF Workers isolate 跑,不嵌 QuickJS/Rust)。
|
||||
3. **補丁是過渡**:原廠出 API 後,補丁 workflow 因效能較差自然被減少使用、淘汰。
|
||||
|
||||
**upsert 範例**:有些服務原廠提供 upsert API(→ recipe 直接打),有些沒有(→ 做一個 upsert workflow 達成,**而非建專用零件**)。§1 把 upsert 當「該補進 API」的正例——那**只對自家 API 成立**;對改不了的第三方 API,arcrun 端永遠補不進去,正解是 workflow 補丁。
|
||||
|
||||
> **與 hook 的關係**:§3.1 禁的「介面層拼裝」由 `pre-write-guard.sh` 7.x 擋(範圍 `cli/src/`、`arcrun-mcp/src/` 的 TS)。**workflow/code-node 補丁是資料產物(YAML / 空白零件內的 JS),不是介面層 TS → 本就在 hook 範圍外,合法不被擋。** hook 防線不變,本次只釐清「資料方式自救」是合法路徑。
|
||||
|
||||
---
|
||||
|
||||
## 4. 統一帳號來源(薄殼共用同一身份)
|
||||
|
||||
所有薄殼讀**同一份**身份設定:
|
||||
- self-hosted:`~/.arcrun/config.yaml` / 專案層 `.arcrun.yaml` / `ARCRUN_*`、`CLOUDFLARE_*` env(見 `config-layering.md`)。
|
||||
- standard:平台 api_key。
|
||||
|
||||
**MCP 目前的已知違反**(壓測 §5.2):MCP 用 Cloudflare service binding 焊死平台 `arcrun-cypher-executor`,
|
||||
self-hosted 用戶用 MCP 連不到自己的 cypher。修法見
|
||||
`docs/3-specs/arcrun/sdk-and-website/mcp-account-source.md`(SDD proposal)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出貨順序(最低出貨標)
|
||||
|
||||
- **CLI + MCP 兩個薄殼先到位**(AI 偏好 MCP,故 MCP 不可長期落後),且兩者覆蓋**同一組 API 能力**。
|
||||
- Python / JS lib 隨後補。
|
||||
- 出貨順序由「介面被誰用」決定,不是由「哪個好做」決定。
|
||||
- **介面進度本來就會不一致**(薄殼模型的預期狀態)——這本身不是 bug。
|
||||
bug 是「**底層 API 能力不齊 / 介面含了不該含的邏輯 / 帳號來源不統一**」這三者。
|
||||
|
||||
---
|
||||
|
||||
## 6. hook 強制範圍(與「靠人判斷」的邊界)
|
||||
|
||||
`pre-write-guard.sh` 規則 7.x 能擋的是**語法層可偵測**的反例:
|
||||
- CLI/MCP 檔案內出現「迴圈 POST 多個 recipe」「先 update 失敗再 insert」這類拼裝 pattern 的明顯特徵 → 警告/擋。
|
||||
- 新增 `seedApiRecipes` / `seedAuthRecipes` 這類「seed 邏輯寫在介面層」的函式 → 擋(改去 API)。
|
||||
|
||||
**hook 擋不了的**(需 CC 自律 + code review):
|
||||
- 把商業邏輯藏在看似無害的 helper 裡。
|
||||
- recipe 層拼裝(recipe 是資料,hook 不解析語意)。
|
||||
→ 故本檔是 mindset,hook 是底線;兩者都不可省。誠實限制見 mindset §7(不假裝 hook「不可能繞過」)。
|
||||
@@ -0,0 +1,43 @@
|
||||
# 2. Architecture — 系統設計 + 技術棧 + 決策
|
||||
|
||||
> 「是什麼」層:arcrun 的設計、技術棧、硬限制、關鍵決策。
|
||||
|
||||
## 快速導航
|
||||
|
||||
### 硬限制 & 規範
|
||||
|
||||
| 檔案 | 用途 |
|
||||
|------|------|
|
||||
| **01-tech-stack.md** | 三層語言、資料儲存、加解密、網路部署(硬規範) |
|
||||
| **02-forbidden.md** | 禁止行為清單(hook 強制執行) |
|
||||
| **03-component-architecture.md** | 零件架構:R2/binding/URL 正確定義 |
|
||||
| **00-sdd-protocol.md** | SDD 讀取協議(流程強制) |
|
||||
| **04-current-progress.md** | 當前 Phase + SDD 索引 |
|
||||
|
||||
### 設計原理
|
||||
|
||||
| 檔案 | 用途 |
|
||||
|------|------|
|
||||
| **06-mindset.md** | 設計哲學 7 條:為什麼做這些選擇 |
|
||||
| **07-thin-shell.md** | 薄殼原則鐵律(§1-6 原則 + hook 強制範圍) |
|
||||
| **05-deploy-convention.md** | 部署慣例(掃描式 workflow、lockfile、WASM 來源) |
|
||||
|
||||
### 決策記錄
|
||||
|
||||
| 檔案 | 用途 |
|
||||
|------|------|
|
||||
| **decisions/** | 架構決策歷史(搬來中) |
|
||||
|
||||
---
|
||||
|
||||
## 常見查詢
|
||||
|
||||
- **「零件怎麼部署?」** → 03-component-architecture.md 部署步驟
|
||||
- **「為什麼不用 service binding?」** → 03-component-architecture.md + 決策/same-zone-1042
|
||||
- **「怎樣設計新功能?」** → 06-mindset.md §1 + 07-thin-shell.md
|
||||
- **「禁止清單是什麼?」** → 02-forbidden.md
|
||||
- **「技術棧限制有哪些?」** → 01-tech-stack.md
|
||||
|
||||
---
|
||||
|
||||
更新時間:2026-06-08
|
||||
@@ -0,0 +1,398 @@
|
||||
# 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 查表,零件再多也是同一段查表邏輯)
|
||||
- **需要更多段邏輯、更多特例** → 會累積。(例:零件,每個是獨立程式、各自可能出錯)
|
||||
|
||||
不累積的東西不需要焦慮,它頂多是「未來一次性處理的設計點」。
|
||||
會累積的東西必須在「進入點」就把關(例:零件在投稿時就驗純淨)。
|
||||
@@ -0,0 +1,30 @@
|
||||
# [主題] — Architecture Decision Record
|
||||
|
||||
> 日期:[YYYY-MM-DD]
|
||||
> 狀態:[提議中 / 已採納 / 已廢棄]
|
||||
> 影響範圍:[哪些子系統 / 模組]
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
[遇到了什麼問題,需要做這個決定?]
|
||||
|
||||
## 決定
|
||||
|
||||
**[結論,一句話。]**
|
||||
|
||||
## 原因
|
||||
|
||||
[詳細說明為什麼這樣決定。]
|
||||
|
||||
## 放棄的選項
|
||||
|
||||
| 選項 | 放棄原因 |
|
||||
|------|---------|
|
||||
| [選項 A] | [原因] |
|
||||
| [選項 B] | [原因] |
|
||||
|
||||
## 影響與後續
|
||||
|
||||
[這個決定影響哪些地方?有什麼技術債或需要注意的事?]
|
||||
Reference in New Issue
Block a user