# 零件架構與部署模式（必讀，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；該卷已封存於 `system-dev/docs/3-specs/archive/`，**但本段規則仍現行有效**）：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。
