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,154 @@
|
||||
# 禁止行為清單(零容忍)
|
||||
|
||||
**這份清單由 `.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 禁止用**零件**補 API 缺的能力(污染零件庫)
|
||||
缺能力 → 先看 07 §3.5 自力救濟階梯分流:**自家** API 缺 → 補 API endpoint;**第三方** API 缺(gsheets filter / 無 upsert)→ 走 **workflow/code-node 補丁**(合法,是資料產物非介面層 TS,hook 範圍外);純計算 → code-node;真需新穩定能力才建零件 PR。
|
||||
禁的是「在介面層拼裝」(5.1,hook 擋)與「亂建零件污染零件庫」,**不是禁任何補丁**(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,138 @@
|
||||
# 部署慣例(CI/CD)
|
||||
|
||||
> **核心原則:新增 Worker = 新目錄 + `wrangler.toml`,不用改 workflow。**
|
||||
|
||||
`.github/workflows/deploy.yml` 是**通用掃描式** workflow,不該為每個 Worker 手寫 job。
|
||||
|
||||
---
|
||||
|
||||
## Workflow 如何找到要部署的 Worker?
|
||||
|
||||
```
|
||||
find . -name 'wrangler.toml' -not -path '*/node_modules/*' -not -name 'wrangler.test.toml'
|
||||
```
|
||||
|
||||
每一個命中的目錄 = 一個部署單位。無論是:
|
||||
|
||||
- `cypher-executor/` (orchestration Worker)
|
||||
- `registry/` (合約管理 Worker)
|
||||
- `.component-builds/{name}/` (零件 Worker,25+ 個)
|
||||
- 未來新增的任何 Worker
|
||||
|
||||
**無需改 workflow,只要符合掃描規則就會自動部署**。
|
||||
|
||||
---
|
||||
|
||||
## 觸發邏輯
|
||||
|
||||
| 觸發 | 部署範圍 |
|
||||
|------|---------|
|
||||
| `push` 到 main | diff 涉及的 Worker 目錄才部署 |
|
||||
| `push` 到 main + 改 `registry/components/{name}/` | 連動 rebuild `.component-builds/{name}/component.wasm` 再 deploy |
|
||||
| `workflow_dispatch` + `force_all=true` | 全部 Worker |
|
||||
| `workflow_dispatch` + `only=a,b,c` | 只部署指定清單 |
|
||||
| `push` 但 base sha 不可及(首次) | 全部 Worker |
|
||||
|
||||
---
|
||||
|
||||
## 新增 Worker 的步驟
|
||||
|
||||
### 如果是新 WASM 零件 Worker
|
||||
|
||||
1. 在 `registry/components/{new_name}/` 建 `main.go` + `component.contract.yaml`
|
||||
2. 在 `.component-builds/{new_name}/` 建 Worker 模板:
|
||||
- `wrangler.toml`(name/routes/bindings)
|
||||
- `package.json`(hono + workers-types + wrangler 即可,參考 `auth_static_key/package.json`)
|
||||
- `tsconfig.json`(可直接複製)
|
||||
- `src/index.ts`(WASI shim,方案 A:import `../../cypher-executor/src/lib/wasi-shim`)
|
||||
3. 本地跑 `pnpm install` 產 `pnpm-lock.yaml`
|
||||
4. 本地跑 `tinygo build -target=wasi -o {new_name}.wasm main.go` 先驗證 build 通過
|
||||
5. Commit push → CI 自動 rebuild WASM + deploy
|
||||
|
||||
### 如果是新 orchestration/service Worker
|
||||
|
||||
1. 在 repo 根建新目錄(類似 `cypher-executor/`)
|
||||
2. `wrangler.toml` + `package.json` + `pnpm-lock.yaml` + `src/index.ts` + `tsconfig.json`
|
||||
3. Push → CI 自動部署
|
||||
|
||||
---
|
||||
|
||||
## Runtime Secret 管理
|
||||
|
||||
**CI 只提供 Cloudflare 驗證,不碰 runtime secret**。
|
||||
|
||||
- GH Actions secrets:`CLOUDFLARE_API_TOKEN`、`CLOUDFLARE_ACCOUNT_ID`(一次性設好)
|
||||
- Runtime secret(例:`ENCRYPTION_KEY`、`OPENAI_KEY`、`GOOGLE_API_KEY`):
|
||||
- **由 richblack 一次性手動** `wrangler secret put <KEY>` 設進各 Worker
|
||||
- 不進 CI,不進 `wrangler.toml` `[vars]`
|
||||
- 需要的 Worker:`auth_static_key`、`auth_service_account`(兩個都要 `ENCRYPTION_KEY`)
|
||||
|
||||
---
|
||||
|
||||
## Lockfile 規範
|
||||
|
||||
- **統一使用 pnpm**。新增 Worker 只放 `pnpm-lock.yaml`,不要 `package-lock.json`
|
||||
- 若新建 Worker 時用 `npm install` 產出 `package-lock.json`,**刪掉它**,改跑 `pnpm install`
|
||||
- `cypher-executor/` 和 `registry/` 的 `package-lock.json` 已於 2026-04-20 刪除
|
||||
|
||||
**現存例外**(歷史遺產,混合期不強制遷移):
|
||||
- `.component-builds/{if_control, switch, ... 16 個舊邏輯零件}/` 仍是 `package-lock.json`,workflow 有 fallback 分支(`pnpm install --no-frozen-lockfile`)可跑
|
||||
- `builtins/`、`landing/` 同上
|
||||
|
||||
**新增 Worker 一律 pnpm,不要製造新的混合情況**。
|
||||
|
||||
---
|
||||
|
||||
## WASM 來源
|
||||
|
||||
> **⚠️ 慣例變更(richblack 2026-06-02,self-hosted 開源策略)**:
|
||||
> 原慣例「`.component-builds/{name}/component.wasm` 不 commit 進 repo」**已推翻**。
|
||||
> 現在 **commit `.component-builds/*/component.wasm` 進 repo**,因為 self-host 用戶 / `acr init --self-hosted`
|
||||
> 從 GitHub(codeload tarball)直接拿這份 wasm 部署到自己的 CF——repo 必須自帶可部署的 wasm。
|
||||
> 決策依據:`docs/3-specs/arcrun/sdk-and-website/self-hosted-init.md §6`。
|
||||
|
||||
### 現行規則(2026-06-02 起)
|
||||
|
||||
- **`.component-builds/*/component.wasm` → commit 進 repo**(部署來源)。`.gitignore` 用否定規則放行:
|
||||
```
|
||||
*.wasm # 預設排除
|
||||
!.component-builds/**/component.wasm # 例外放行部署物
|
||||
```
|
||||
- **`registry/components/*.wasm` → 仍不 commit**(build 中間產物,部署不直接用,`.gitignore` 仍排除)。
|
||||
- 本地開發 build:`cd registry/components/{name} && tinygo build -target=wasi -o {name}.wasm main.go && cp {name}.wasm ../../../.component-builds/{name}/component.wasm`,**然後 commit `.component-builds/{name}/component.wasm`**。
|
||||
- CI(deploy.yml):仍在 deploy 前自動 rebuild + copy(部署 prod 用最新 source;與 repo 內 commit 的 wasm 不衝突——前者給 CI deploy prod,後者給 self-host 用戶當部署來源)。
|
||||
|
||||
### 誠實 trade-off(mindset §7)
|
||||
|
||||
commit wasm 進 repo → 每次 rebuild 在 git 歷史累積二進位,**repo 長期會膨脹**。
|
||||
可接受(self-host 體驗優先),未來若膨脹過劇再考慮 git-lfs / 按需安裝(self-hosted-init.md §6.6)。
|
||||
|
||||
---
|
||||
|
||||
## 並行度
|
||||
|
||||
`max-parallel: 5` — 避免觸發 Cloudflare Workers API rate limit。
|
||||
|
||||
Worker 數量 > 5 時,deploy 會分批跑。25 個 Worker 大約 5 輪 × ~30 秒 = 2-3 分鐘可完成全部。
|
||||
|
||||
---
|
||||
|
||||
## 禁止事項
|
||||
|
||||
1. **禁止**為新 Worker 手動加 deploy job 到 `deploy.yml`。通用掃描會自動處理,手加就是重複工作。
|
||||
2. **禁止**把 runtime secret(API key / encryption key / credential)放進 GH Actions secrets 或 `wrangler.toml` `[vars]`,只能用 `wrangler secret put`。
|
||||
3. **禁止**在 CI 裡跑不必要的測試阻擋 deploy。測試在 PR / 本地跑,`main` 推上去就 deploy(trunk-based)。若要測試關,開新 workflow 檔,不要污染 deploy workflow。
|
||||
4. **禁止**跳過 TinyGo rebuild 直接 deploy 舊 `.wasm`。CI 的 rebuild 步驟是確保部署的是最新 source。
|
||||
|
||||
---
|
||||
|
||||
## 驗證指令
|
||||
|
||||
本地模擬 CI 的掃描結果:
|
||||
|
||||
```bash
|
||||
find . -name 'wrangler.toml' -not -path '*/node_modules/*' -not -name 'wrangler.test.toml' \
|
||||
| xargs -n1 dirname | sort -u
|
||||
```
|
||||
|
||||
應列出 ~25 個目錄。任何「我新增了 Worker 但沒被 deploy」的問題,先跑這條確認目錄被掃到。
|
||||
@@ -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「不可能繞過」)。
|
||||
Reference in New Issue
Block a user