# 交辦文件：完成 arcrun self-hosted harness（給接手的 CC）

> 建立：2026-06-01（由前一個 CC 調查後撰寫）
> 對象：接手的外部 CC
> 目的：把 arcrun 補到「任何 CC 在自己的 CF 帳號上 self-host 後就能順暢開發、且不可能重蹈 mira 的錯」的程度。
>
> **先讀**：`DECISIONS.md`（穩定決策）、`.claude/rules/06-mindset.md`（mindset）、`BACKLOG.md`（流動待辦）。
> 本文件不取代它們，只是把「今天要做的三件事」連同已查證的實況整理好，讓你不用重跑調查。

---

## 0. 戰法已轉變（最重要的背景）

richblack 2026-06-01 決定：**從 SaaS 改成 self-hosted 開源策略。**

這直接改變 harness 的成功定義：

- **舊定義**：在 richblack 的 prod 帳號（`cypher.arcrun.dev`）上能跑。
- **新定義**：**任何 CC 在自己的 CF 帳號上 `acr init --self-hosted` 後就能跑通一個含 recipe 的 workflow，而且寫錯時會被程式擋住。**

richblack 會用另一個 CF 帳號實測 self-host。所以「self-hosted 一鍵起得來」從「第一期重要但非阻擋項」**升為今日第一優先**。

### arcrun 現在的核心心智（richblack 2026-06-01 校準，比 DECISIONS §1 更硬）

- 核心**零件數量少、由 richblack 維護、不接受 CC 自製**（可投稿 PR，人 merge = 閘門）。
- 其他人做的一律是 **recipe**（= http_request + 一組 YAML 設定，不用 deploy）。
- arcrun 是**一套給 CC 的 harness**：事前提醒 CC 能用什麼 / 不能做什麼，事後用程式擋住讓它**無法犯錯**。
- **你不用管 mira。** mira 是錯誤做法的源頭（見 §1），它自己會修。你的目標是讓**任何** CC 都能用，且絕不會發生 mira 的錯。

---

## 1. mira 故障 = 症狀樣本（已定位，不用你修）

mira（`/Users/youlinhsieh/Documents/tech_projects/InkStoneCo/polaris/mira/arcrun/*.yaml`）的 workflow 寫 `component: kbdb_get` / `claude_api` / `telegram` 等。這些是 **mira 當初自己錯做的「假零件」**（DECISIONS §1 判準：打固定 endpoint 的東西是假零件，該是 recipe）。

本次整修（BACKLOG 步驟3）已把這些假零件**降級成 recipe + 刪掉零件目錄**（registry/components 從 33 → 22）。所以 mira 斷了。

**這證明的事**：mira 的錯，正是當時 harness 沒擋住的漏洞。零件刪了，但 harness 還缺「**事前告訴 CC 別這樣做 + 事後擋住 CC 這樣做**」的完整機制 → 下一個 CC 還會犯同樣的錯。**這就是你要補的（§3 task 2）。**

---

## 2. 已查證的實況（你不用重查，2026-06-01 實打 prod）

### 2.1 降級後的 recipe 鏈路是「活的」✅

實打 `https://cypher.arcrun.dev/recipes`（richblack prod）確認以下 recipe 都在 KV：

| canonical_id | hash | endpoint | auth_service |
|---|---|---|---|
| `kbdb_get` | rec_4c7dcf9b | `https://kbdb.finally.click{{_path}}` | kbdb |
| `gmail_send` | rec_cd426129 | gmail.googleapis.com/.../send | google_gmail_sa |
| `google_sheets_append` | rec_9fd1b662 | sheets.googleapis.com{{_path}} | google_sheets_sa |

→ **「對的用法」（`component: kbdb_get` 走解析鏈 step 6 查 `recipe:kbdb_get`）本身是通的。** 不需要重建 recipe。

> 注意：這是 richblack 的 prod KV。**self-host 的新帳號 KV 是空的**，需要 seed 這些 recipe（見 §3 task 1 的 seed 步驟）。

### 2.2 component-loader 解析鏈（`cypher-executor/src/lib/component-loader.ts`）

`resolveComponent` 依序嘗試 8 層（行號近似，以實際檔案為準）：

```
0. 平台 orchestration 零件（trigger_workflow）          line ~88
1. 內建零件（純 JS）                                    line ~96
2. 外部 URL（http(s)://...）                            line ~100
3. cmp_hash → WEBHOOKS KV idx → 邏輯 Worker             line ~105
4. rec_hash → RECIPES KV idx → recipe 執行              line ~115
5. 邏輯零件 canonical_id → Service Binding (SVC_*)      line ~122
5.5 auth recipe (auth_recipe:{service})                line ~127
6. KV recipe canonical_id → RECIPES KV → fetch 外部 API line ~130  ← kbdb_get 等降級 recipe 命中這層
7. WASM HTTP runner（白名單 WASM_HTTP_RUNNER_IDS）       line ~134
8. 找不到 → 報錯                                        line ~142
```

`WASM_HTTP_RUNNER_IDS` 白名單（line ~36）現只剩：`http_request` / `cron` / 4 個 `auth_*` primitive。
→ `claude_api`、`kbdb_upsert_block`（BACKLOG 標 deferred、源碼暫留）**不在白名單也沒 recipe** → 用到它們的 workflow 會落到 step 8 報錯。這是 mira 自己的問題，不在你範圍。

### 2.3 `acr init --self-hosted` 現況：純手動問答，差很遠

`cli/src/commands/init.ts` 的 `initSelfHosted()`（line 105-131）**只是問 6 個問題後寫進 config**：
要求 CC 自己**事先**部署好 Worker、建好 KV、再手填 Account ID / cypher URL / 兩個 KV namespace ID / WASM bucket / CF token。

BACKLOG 步驟7 要的是「**貼 CF token → 自動建 KV、部署 Worker、自動 workers.dev、寫回 config**」。**這是最大缺口，task 1 的主體。**

config 讀取端已支援 self-hosted（`cli/src/lib/config.ts:52` 已能用 `cypher_executor_url`），所以你只要把「自動部署」這段補上，config/執行端不用動。

### 2.4 CI/CD 已是通用掃描式（可重用於 self-host 部署）

`.github/workflows/deploy.yml` 掃所有含 `wrangler.toml` 的目錄自動部署（見 `.claude/rules/05-deploy-convention.md`）。
self-host 自動部署可以參考同一套掃描邏輯（`find . -name wrangler.toml`），對每個目錄跑 `wrangler deploy`。

---

## 3. 今天要做的三件事（按序，全在 harness 主線）

> richblack 指示：「全部要做」（含 `acr init --self-hosted`）。
> 三件都做完 = 今天可交付：外部 CC 能 self-host 起來、用對的方式開發、犯錯被擋。

### 🔴 Task 1：完成 `acr init --self-hosted` 一鍵自動化（第一優先）

> ✅ **實作狀態（2026-06-02，已大致完成）**：定稿形態為 **installer 模式**（richblack 拍板）——
> 用戶只做：申請 CF 帳號 → 裝 wrangler → 裝 acr → `acr init --self-hosted`（貼 token），其餘自動。
> 已實作（typecheck 過）：`cli/src/lib/api-recipe-seeds.ts`（10 recipe 種子）、`cf-api.ts` 的
> `CfAccountClient`（建 KV/R2/查 subdomain/驗 token）、`deploy.ts`（常數 + downloadAndDeploy）、
> `initSelfHosted()` 改寫、`acr update`、`cypher-executor/scripts/seed-api-recipes.ts`。
> **唯一剩餘前置（13.6）**：repo 沒有含預編譯 wasm 的 GitHub release（.wasm 不 commit，rule 05）→
> `downloadAndDeploy()` 目前**誠實回 implemented:false 不假裝部署**（mindset §7）。建 KV/R2/seed/config
> 已可跑；release 產製管道補上後部署即自動化。定稿設計見 SDD `self-hosted-init.md`（含 §6 前置依賴）。
> **以下原始子步驟保留供對照**；KBDB recipe 採 Supabase 模式進 seed（richblack 2026-06-02）。

**目標**：CC 只需提供「CF Account ID + CF API Token」，CLI 自動完成其餘一切。

**SDD**：定稿 `docs/3-specs/arcrun/sdk-and-website/self-hosted-init.md`（installer 模式，已與 richblack 對齊）。

**子步驟**：
1. 改 `cli/src/commands/init.ts` 的 `initSelfHosted()`：
   - 收 CF Account ID + CF API Token（要 KV Edit + Workers Scripts Edit + R2 權限）。
   - 用 CF API（或 shell out `wrangler`）**自動建 7 個 KV namespace**：WEBHOOKS / CREDENTIALS_KV / RECIPES / USERS_KV / SESSIONS_KV / ANALYTICS_KV / EXEC_CONTEXT（清單見 `.claude/rules/01-tech-stack.md` 資料儲存表）+ R2 WASM_BUCKET。
   - **自動部署所有 Worker**：cypher-executor + registry + 22 個 `.component-builds/*`。可重用 §2.4 的 `wrangler.toml` 掃描。每個 worker 的 `wrangler.toml` 已含 `workers_dev = true`（BACKLOG 步驟 P1#2 已做），部署後 workers.dev URL 自動啟用。
   - **把 cypher-executor 的 `[vars] WORKER_SUBDOMAIN` 改成 CC 自己的帳號 subdomain**（self-host 關鍵，見 P0 #9：cypher-executor 走 `arcrun-{name}.{subdomain}.workers.dev` 對內 URL）。
   - **seed 降級 recipe + auth recipe 進 RECIPES KV**：新帳號 KV 是空的。把 §2.1 那些 recipe（kbdb_get/gmail_send/...）+ auth recipe seed 寫進去。auth recipe seed 已有 `cypher-executor/scripts/seed-auth-recipes.ts`，API recipe 需確認有對應 seed 機制（routes/recipes.ts 是動態 push，可能要寫一份 seed 腳本或用 `acr recipe push`）。
   - 寫回 config（現有欄位已足夠）。
2. **runtime secret 不進 CLI 自動化**：`CF_SECRETS_API_TOKEN` 等由 CC 自己 `wrangler secret put`（rule 05 禁止 secret 進自動化流程）。CLI 應在最後**印出提示**告訴 CC 要手動 put 哪些 secret 到哪些 worker。

**驗收（客觀證據，不是口頭宣布 — mindset §7）**：
- richblack 用全新 CF 帳號跑 `acr init --self-hosted` → 全程無手動建 KV / 部署。
- 跑完後 `acr push` 一個含 `component: kbdb_get`（或 http_request + 自建 recipe）的 workflow → trigger → HTTP 2xx + execution trace 證明跑通。

### 🔴 Task 2（已重定義 2026-06-01）：封鎖自製零件 + recipe 入庫把關

> ✅ **實作狀態（2026-06-02，第一期部分完成）**：(1) 封鎖自製零件 = 靠 GitHub PR 人 merge，無需新做
> （矛盾已釐清）。(2a) 資料外流提醒 = **既有實作已涵蓋**（recipe.ts `obtainExposureConsent` + exposure-warning.ts，
> 非 TTY 拒絕）。(2b) 打通檢查 = **新增** `probeRecipeEndpoint`（recipe.ts，typecheck 過）：push 後實打
> endpoint，提醒級不硬擋，含 {{模板}} 誠實說明待 run 才知，401/403 標「多半缺 credential 非 bug」。
> 公共庫 relay 檢核（--public）= 第一期後。SDD `recipe-push-gatekeeping.md` + tasks.md W2。

> ⚠️ **方向修正（richblack 2026-06-01）**：原 Task 2「acr validate 擋假零件名」**作廢**。
> 理由：自製/修改零件的路已封鎖（CC 造不出零件）→「擋假零件」這件事不存在；workflow 引用
> recipe（`component: kbdb_get`）是合法且未來唯一的擴充方式，不該被當假零件擋。
> 把關點**從 workflow validate 移到 recipe 入庫（push）那一刻**。
> 已動的 yaml-parser.ts（LEGAL_PRIMITIVES / findSuspectComponents）**已回退**。

**新目標**：
1. **封鎖自製零件**：靠「零件投稿走 GitHub PR + 人 merge」天然閘門（DECISIONS §8）。零件數量少、
   絕大多數是 recipe → 不為零件 PR 蓋自動化把關（量少，人工檢查即可；爆量才回頭想自動化）。
2. **recipe 入庫把關**（CC 唯一能擴充的是 recipe，一律用 push，自有庫/公共庫同一套指令）：
   - **自有庫（self-hosted）= 提醒級**：(a) 資料外流提醒——會讓資料/服務對外可見的動作需人類明示同意；
     (b) 打通檢查——push 時實打 endpoint 回報 2xx 與否（誠實標原因，不假綠，不硬擋）。
   - **公共庫 = 維護者 relay 檢核**（實際打通、真收到成功回傳）— 第一期後。

**SDD**：已寫 design 給 richblack review →
`docs/3-specs/component-gatekeeping/recipe-push-gatekeeping.md`（+ tasks.md W2 節）。**review 通過才動 code。**

**動到的檔案（待 review）**：`cli/src/commands/recipe.ts`（push 加提醒 + 打通檢查）、確認 data-exfil hook 涵蓋 recipe push 路徑。

**驗收**：
- `acr recipe push` 會產對外 webhook 的東西 → 印資料外流警示 + 要人類同意；非 TTY → 拒絕。
- `acr recipe push` endpoint 可達的 recipe → 回報「✓ HTTP 2xx」。
- `acr recipe push` 缺 credential → 回報「⚠️ 未打通：缺 credential」（誠實），仍允許 push。
- workflow 引用 recipe（`component: kbdb_get`）**不被任何 validate 步驟當假零件擋**。

### 🔴 Task 3：README 重寫成單一路徑 — harness「事前提醒」

**目標**：self-hosted 開源後，README 是外界 CC 唯一入口。砍掉「玩法一/二/三」三選一，講清楚單一正確路徑。

**子步驟**（改根 `README.md`）：
1. 砍三選一玩法，留**一條路**：`acr init --self-hosted` → 寫 workflow（primitive 串 + recipe）→ `acr push` → trigger。
2. 明示心智（呼應 mindset §1）：「零件就這固定幾個由我們維護、不接受自製；要打外部 API 就寫 recipe；要編排就寫工作流。」
3. 連到 `.claude/rules/06-mindset.md` / arcrun-mindset Skill，讓 CC 一開始就有正確世界觀。

**驗收**：README 讀完，一個沒看過 arcrun 的 CC 知道：能用什麼、不能自製零件、打外部 API 要寫 recipe、怎麼 self-host 起來。

---

## 4. 今天「不要做」的（避免你走偏）

| 項目 | 為何不做 |
|---|---|
| 修 mira | richblack 明示不用管，mira 自己修 |
| 步驟2 `acr recipe test` / relay / credits | DECISIONS §3c 明確劃為服務側、非第一期 |
| 步驟6 搬家拆 matrix | 純 repo 整理，不影響 CC 能否用 |
| 砍 `injectCredentials` 舊路 / `BUILTIN_CREDENTIALS_MAP` | 獨立清理，不擋交付（DECISIONS §3b / BACKLOG「第一期之後」）|
| 新 primitive / Gherkin 真跑 / 入站認證 | richblack 已標「不要現在做」 |

---

## 5. 鐵律提醒（違反會被 hook block）

- 任何 code 變動前先讀對應 SDD + 在回覆開頭宣告（`.claude/rules/00-sdd-protocol.md`）。
- `registry/components/` 下禁止 TS；cypher-executor TS 禁止 credential/auth/JWT 業務邏輯（`.claude/rules/02-forbidden.md`，hook 強制）。
- 每完成一個 task 立刻更新對應 tasks.md / BACKLOG.md 的 `[x]`，不批次。
- 誠實限制（mindset §7）：stub / 未完成就標 unimplemented，**不假綠**；完成 = 客觀證據（exit code / HTTP status + trace），不是口頭宣布。

---

## 6. 交棒：CLAUDE.block.md / README 表達優化（InkStoneCo 總管 2026-06-15）

**背景**：總管與 leo 釐清 Mira 自製零件衝突時，實測一個現象——**連 Opus 讀著 `cli/harness/CLAUDE.block.md` + rules/，都把 recipe 誤解了四輪**（以為 recipe 是私有腳本、以為缺能力可用工作流拼裝補）。leo 的標準是「**Haiku 都能輕易理解**」。block 是被 `acr install-harness` 裝進各子 repo（含 mira）的**唯一入口**，細節雖在 `rules/` 但子 repo CC 預設只看 block → block 不夠清楚 = 下一個 CC 還會誤解。**這是表達缺口，不是讀者問題。**

**請 arcrun CC 做（改 `cli/harness/CLAUDE.block.md`，順帶對齊 §3 README）**：把現有「不要自製零件 / 打外部 API 寫 recipe」兩句，補成「該做什麼 + 做完去哪 + 已違規的怎麼退場」。三個已核實的盲點要講清楚（論證在下，成品文案你按自己 rules 寫）：

1. **recipe 是公共資產，不是私人腳本**。`acr recipe push` = 投稿公共庫（`DEFAULT_PUBLIC_LIBRARY_URL`）。公私是**屬性**（UUID 身份模型，`cypher-executor/src/routes/recipes.ts` §7.5.5：身份=uuid、公私/author 是屬性、多作者版本並存），不是兩條路。**「發現缺某 stock recipe → 自己做一個 push 上去」是被鼓勵的，不必問人。** 現 block 只寫「寫 recipe」「不用部署」→ 讀者誤以為私有自用、不上傳（正好反了）。

2. **缺能力 → 補 API，不准用 recipe/工作流拼裝**。這條 rules 其實寫得很對（07-thin-shell §1 upsert 正反例、02-forbidden §5.2、01 §8），但 **block 完全沒提** → 子 repo CC 看不到。請把「缺單一 API 能力 = 去補 API endpoint，不拼裝」這條判準帶進 block（口訣：「換個介面要不要重寫？要→是能力該在 API」）。

3. **零件的退場路徑空白**。現 block 只說「不要自製零件」，沒說「**已經有自製零件的（如 mira 的 6 個）該怎麼辦**」。請補：已自製零件 → 映射到 stock（`claude_api`→自己做 AI 判斷別放工作流，mindset §2；`kbdb_*`→走已備的 `acr kbdb` 薄殼/MCP）或改寫成 recipe；無 stock 對應的單 API 缺口 → 寫 recipe 投稿。

**驗收（leo 標準）**：改完後，一個 **Haiku** 級 CC 讀 block 就能答對：recipe 要不要投稿（要）、缺 upsert 怎麼辦（補 API 不拼裝）、已有自製零件怎麼退場（映射 stock / 改 recipe）。

**連帶（非 arcrun 範圍，總管已記）**：「真正可複用的多步驟業務流程能否投稿共享」是 wishlist C6（InkStoneCo 頂層），與「拼裝補 API 缺口」是兩回事（C6 紅線已劃清），arcrun 不必現在處理。

> 注：本交棒由總管寫**判準與論證**，**成品文案 + hook（若要）由 arcrun CC 按自己 rules 產出**。總管不直接改 arcrun 實作檔（職責邊界 D9）。
