arcrun — AI workflow execution engine (clean history)

Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。

此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在
richblack/arcrun 與本地 backup 分支)。含:
- acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe)
- recipe push 把關(資料外流提醒 + 打通檢查)
- 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1)
- CLI / cypher-executor / registry / 完整 SDD

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-06-03 15:52:38 +08:00
commit 922a57fe34
485 changed files with 89356 additions and 0 deletions
+611
View File
@@ -0,0 +1,611 @@
# Design Document: arcrun MVP
## Overview
arcrun MVP 的核心設計原則是**最小異動、最快可用**。所有目標都能透過以下三個操作達成:
1. **Cherry-pick**:從 `matrix` 搬移指定目錄,不重寫
2. **Carve-out**:移除 cypher-executor 中與 InkStone 耦合的程式碼路徑
3. **Supplement**:補充 contract.yaml 缺少的欄位、新增 CLI
不建立新的抽象層,不改變現有零件邏輯,只做讓開源可用所需的最小改動。
---
## Architecture
### 目標 Repo 結構
```
arcrun/(新獨立開源 repo
├── README.md
├── CONTRIBUTING.md
├── cypher-executor/
│ ├── src/
│ │ ├── index.ts
│ │ ├── types.ts
│ │ ├── graph-executor.ts
│ │ ├── lib/
│ │ │ ├── component-loader.ts ← 改:只從 WASM_BUCKET 讀,移除 KBDB/REGISTRY 邏輯
│ │ │ ├── component-dispatcher.ts
│ │ │ ├── wasm-executor.ts
│ │ │ ├── wasi-shim.ts
│ │ │ └── constants.ts ← 改:移除 MINI_ME / KBDB 特殊零件 hardcode
│ │ └── actions/
│ │ ├── triplet-parser.ts
│ │ ├── graph-builder.ts
│ │ ├── execution-evaluator.ts
│ │ ├── execution-logger.ts
│ │ ├── webhook-handlers.ts
│ │ ├── webhook-graph-resolver.ts ← 改:加入 credential 注入邏輯
│ │ └── (移除 autoPublishMissing.ts)
│ └── wrangler.toml ← 改:移除 9 個 InkStone bindings,新增 CREDENTIALS_KV
├── credentials/ ← 直接搬移,無需修改
│ └── src/...
├── builtins/ ← 直接搬移,無需修改
│ └── src/...
└── registry/
└── components/ ← 搬移後補充 contract.yaml
├── gmail/
├── google_sheets/
├── telegram/
├── line_notify/
├── ... (其餘 17 個零件)
└── cli/ ← 新增:arcrun CLI
├── package.json
├── tsconfig.json
└── src/
├── index.ts
├── commands/
│ ├── init.ts
│ ├── creds.ts
│ ├── push.ts
│ ├── run.ts
│ ├── validate.ts
│ ├── parts.ts
│ ├── list.ts
│ └── logs.ts
└── lib/
├── config.ts # 讀寫 ~/.arcrun/config.yaml
├── cf-api.ts # Cloudflare KV / R2 HTTP API wrapper
└── yaml-parser.ts # workflow.yaml 解析與三元組轉換
```
---
## Component Loader 改造(關鍵變更)
### 現況(matrix 版)
```typescript
// component-loader.ts 現有四層優先序:
// 1. 特殊零件 hardcode → MINI_ME / KBDB Service Binding
// 2. 內建零件 Map → 本地純轉換
// 3. 新版:查詢 KBDB record 含 component_type → WASM 或 Service Binding
// 4. 舊版 fallback:查詢無 component_type 的 KBDB record
```
### 開源版(arcrun
```typescript
// component-loader.ts 簡化為三層:
// 1. 內建零件 Map → 本地純轉換(passthrough / counter 等,保留)
// 2. WASM_BUCKET R2 直讀 → component-name.wasm
// 3. 找不到 → 回傳結構化錯誤
async function loadComponent(componentId: string, env: Env) {
// 層 1:內建零件(無需 R2
if (BUILTIN_COMPONENTS.has(componentId)) {
return BUILTIN_COMPONENTS.get(componentId)
}
// 層 2:從 WASM_BUCKET R2 讀取
const wasmKey = `${componentId}/${componentId}.wasm`
const wasmObj = await env.WASM_BUCKET.get(wasmKey)
if (wasmObj) {
return { type: 'wasm', buffer: await wasmObj.arrayBuffer() }
}
// 層 3:找不到
throw new Error(`Component not found: ${componentId}. 請確認 ${wasmKey} 存在於 WASM_BUCKET。`)
}
```
移除:`MINI_ME``KBDB` 特殊零件的 hardcode 路徑(`comp_claude_chat``comp_kbdb_search``comp_kbdb_history`)。
---
## Credential 注入流程設計
### 執行時序
```
acr run newsletter_subscribe
cypher-executor POST /webhook/:id
webhook-graph-resolver 讀 WEBHOOKS KV → workflow 定義
graph-executor 執行節點 send_thanks
執行前:credential-injector(新增)
查 send_thanks 對應零件 canonical_id = "gmail"
讀 registry/components/gmail/component.contract.yaml
發現 credentials_required: [{key: "gmail_token", inject_as: "access_token"}]
GET CREDENTIALS_KV["gmail_token"] → AES-GCM 解密
input.access_token = decryptedToken
wasm-executor 執行 gmail.wasmstdin = 含 access_token 的完整 input
回傳結果
```
### credential-injector 實作位置
放在 `cypher-executor/src/actions/credential-injector.ts`(新增),由 `graph-executor.ts` 在每個節點執行前呼叫。
```typescript
async function injectCredentials(
componentId: string,
input: Record<string, unknown>,
env: Env
): Promise<Record<string, unknown>> {
const contract = await loadContract(componentId) // 從 WASM_BUCKET 或本地讀取
if (!contract.credentials_required) return input
const enriched = { ...input }
for (const cred of contract.credentials_required) {
const record = await env.CREDENTIALS_KV.get(cred.key)
if (!record) {
throw new Error(
`缺少 credential: ${cred.key}\n修復:在 credentials.yaml 加入 ${cred.key} 後執行 acr creds push`
)
}
const { encrypted, iv } = JSON.parse(record)
enriched[cred.inject_as] = await decrypt(encrypted, iv, env.ENCRYPTION_KEY)
}
return enriched
}
```
---
## workflow.yaml 解析設計
### CLI push 流程
```
acr push newsletter_subscribe.yaml
yaml-parser.ts 讀取 workflow.yaml
解析 flow[] 三元組 → triplets: [{subject, relation, object}]
驗證關係詞(拒絕 PIPE
POST cypher-executor /cypher/search → ExecutionGraph(節點 + 邊)
合併 config: + ExecutionGraph → WorkflowDefinition
PUT WEBHOOKS KV[workflow_name] = JSON.stringify(WorkflowDefinition)
輸出 webhook URL
```
### workflow.yaml 格式(確認版)
```yaml
name: newsletter_subscribe
description: 訂閱電子報,發感謝信並記錄到 GSheets
flow:
- "input >> 完成後 >> send_thanks"
- "input >> 完成後 >> save_to_sheet"
- "send_thanks >> 完成後 >> output"
- "send_thanks >> 失敗時 >> notify_error"
- "save_to_sheet >> 完成後 >> output"
config:
send_thanks:
to: "{{input.email}}"
subject: "感謝訂閱!"
body: "歡迎加入!"
# access_token 由 credentials.yaml 的 gmail_token 自動注入
save_to_sheet:
action: write
spreadsheet_id: "{{creds.sheet_id}}"
range: "訂閱者!A:B"
values: [["{{input.email}}", "{{input.timestamp}}"]]
notify_error:
chat_id: "{{creds.telegram_chat_id}}"
text: "發信失敗:{{input.email}}"
```
---
## contract.yaml 補充欄位格式
### credentials_requiredgmail 範例)
```yaml
credentials_required:
- key: gmail_token
type: google_oauth
description: "Google OAuth access tokengmail.send scope"
inject_as: access_token
```
### config_examplegmail 範例)
```yaml
config_example: |
send_email: # 節點名稱(可自訂)
to: "" # 收件人 Email(必填)
subject: "" # 主旨(必填)
body: "" # 內文(必填)
# access_token 由 credentials.yaml 的 gmail_token 自動注入
```
### 各零件 credentials_required 對照表
| 零件 | key | type | inject_as |
|------|-----|------|-----------|
| gmail | gmail_token | google_oauth | access_token |
| google_sheets | google_oauth | google_oauth | access_token |
| telegram | telegram_bot_token | telegram_bot_token | bot_token |
| line_notify | line_token | line_token | token |
---
## CLI 技術設計
### 依賴
```json
{
"dependencies": {
"commander": "^12.0.0",
"js-yaml": "^4.1.0",
"chalk": "^5.3.0",
"ora": "^8.0.1"
}
}
```
### config.yaml 格式(~/.arcrun/config.yaml
```yaml
cloudflare_account_id: abc123
webhooks_kv_id: xyz789
credentials_kv_id: abc456
wasm_bucket: arcrun-wasm
cypher_executor_url: https://cypher-executor.xxx.workers.dev
credentials_worker_url: https://arcrun-credentials.xxx.workers.dev
api_token: ***(加密存本機)
```
### Cloudflare API 操作
CLI 使用 Cloudflare REST API(不依賴 Wrangler CLI):
- KV 寫入:`PUT /client/v4/accounts/{id}/storage/kv/namespaces/{ns_id}/values/{key}`
- KV 讀取:`GET /client/v4/accounts/{id}/storage/kv/namespaces/{ns_id}/values/{key}`
- KV 列出:`GET /client/v4/accounts/{id}/storage/kv/namespaces/{ns_id}/keys`
---
## wrangler.toml 變更對照
### 移除(InkStone 專屬)
```toml
# 全部移除:
[[services]]
binding = "KBDB"
service = "inkstone-kbdb-api"
[[services]]
binding = "REGISTRY"
service = "inkstone-component-registry"
[[services]]
binding = "CLINIC_GDRIVE"
service = "clinic-gdrive"
# ... CLINIC_EXCEL, CLINIC_ANALYSIS, CLINIC_RENDER, CLINIC_GSHEETS
[[services]]
binding = "AICEO"
service = "inkstone-aiceo-bot"
[[services]]
binding = "MINI_ME"
service = "inkstone-mini-me"
```
### 保留
```toml
[[kv_namespaces]]
binding = "EXEC_CONTEXT"
[[kv_namespaces]]
binding = "WEBHOOKS"
[[r2_buckets]]
binding = "WASM_BUCKET"
[ai]
binding = "AI"
```
### 新增
```toml
[[kv_namespaces]]
binding = "CREDENTIALS_KV"
id = "" # 用戶自行填入
```
---
## Standard 模式架構(用戶自己的 KVarcrun.dev 的引擎)
### 儲存責任分界
```
arcrun.dev 負責:
WASM_BUCKET 公眾零件庫(.wasm 二進位)
ANALYTICS_KV 零件執行統計
ACCOUNTS_KV API Key → tenant_id + CF API Token 對應
SUBMISSIONS_KV 零件提交審核狀態
用戶自己負責(一個 CF KV,arcrun.dev 不存取明文):
USER_KV
workflow:{name} → workflow 執行圖(JSON
cred:{key} → AES-GCM 加密 credential
```
### 完整系統圖
```
┌──────────────────────────────────────────────────────────────┐
│ arcrun.dev(你的 Cloudflare 帳號) │
│ │
│ auth-workerapi.arcrun.dev
│ POST /register → { api_key, tenant_id } │
│ ACCOUNTS_KV: { tenant_id, cf_api_token, api_key_hash } │
│ ※ 不儲存用戶 credential 或 workflow 內容 │
│ │
│ cypher-executorcypher.arcrun.dev,共享) │
│ X-Arcrun-API-Key → tenant_id → cf_api_token │
│ 用 cf_api_token 呼叫 CF KV API → 讀用戶自己的 USER_KV │
│ WASM_BUCKET: gmail.wasm / telegram.wasm / ...(共享) │
│ │
│ public registryregistry.arcrun.dev
│ GET /components → 零件清單 + 統計 + author + visibility │
│ POST /submit → 接收零件,沙盒驗收後設 author_only │
│ POST /analytics/record → 執行統計(非同步) │
└──────────────────────────────────────────────────────────────┘
↕ CF KV API(用戶的 cf_api_tokenKV Edit 權限)
┌──────────────────────────────────────────────────────────────┐
│ 用戶自己的 CF 帳號 │
│ USER_KV │
│ workflow:newsletter → { triplets, config } │
│ cred:gmail_token → { encrypted, iv } │
│ cred:telegram_bot → { encrypted, iv } │
└──────────────────────────────────────────────────────────────┘
```
### acr init 互動流程
```
$ acr init
? 你的 Cloudflare Account ID: abc123
? USER_KV Namespace ID(在 CF Dashboard 建立一個 KV 後貼上): kv_xyz
? CF API Token(只需 KV Edit 權限,arcrun 用此存取你的 KV: ***
? Email(取得 arcrun.dev API Key: you@example.com
→ 呼叫 POST https://api.arcrun.dev/register { email, cf_api_token_hash }
→ 取得 api_key: ak_xxxxxxxx
✓ 設定完成 → ~/.arcrun/config.yaml
✓ 建立 credentials.yaml(已加入 .gitignore
你的 credential 與 workflow 存在你自己的 CF KVarcrun 不會儲存它們。
```
### API Key 驗證與 KV 存取 Middleware
```typescript
// cypher-executor/src/lib/tenant.ts(新增)
export async function resolveUserKv(request: Request, env: Env) {
if (env.MULTI_TENANT === 'false') {
// Self-hosted:直接用本地 KV binding
return { kv: env.LOCAL_KV, prefix: '' }
}
const apiKey = request.headers.get('X-Arcrun-API-Key')
if (!apiKey) throw new Response('Missing API Key', { status: 401 })
const hash = await sha256(apiKey)
const account = await env.ACCOUNTS_KV.get(`hash:${hash}`)
if (!account) throw new Response('Invalid API Key', { status: 401 })
const { cf_api_token, account_id, kv_namespace_id } = JSON.parse(account)
// 回傳 CF KV API wrapper,用用戶自己的 token 存取
return {
kv: new CfKvClient({ cf_api_token, account_id, kv_namespace_id }),
prefix: ''
}
}
```
### USER_KV Key Schema
```
Standard 模式(用戶自己的 KV):
workflow:{name} → WorkflowDefinition JSON
cred:{key} → { encrypted (base64), iv (base64) }
Self-hosted(本地 KV binding):
維持現有 key 格式,無 prefix
```
---
## 執行統計設計
### Analytics Record(非同步,不阻擋執行)
```typescript
// cypher-executor/src/actions/analytics.ts(新增)
export function recordExecution(
componentId: string,
version: string,
success: boolean,
durationMs: number
): void {
// fire-and-forget,不 await
fetch('https://registry.arcrun.dev/analytics/record', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ canonical_id: componentId, version, success, duration_ms: durationMs })
}).catch(() => {}) // 統計失敗不影響執行
}
```
### 統計聚合(registry Worker
```
ANALYTICS_KV 結構:
"stats:gmail:v1" → { total_runs: 140382, success_runs: 139444, total_ms: 16845840 }
每次 POST /analytics/record
原子更新(KV 樂觀鎖)→ total_runs++, success_runs += success, total_ms += duration_ms
GET /components 回傳:
success_rate = success_runs / total_runs * 100
avg_duration_ms = total_ms / total_runs
排序:total_runs × success_rateDESC
```
---
## 零件貢獻流程設計
### `acr parts publish` 流程
```
$ acr parts publish gmail-v2
1. CLI 讀取 registry/components/gmail-v2/ 目錄
- component.contract.yaml(必須有 author 欄位)
- main.go
- gmail-v2.wasm
2. POST https://registry.arcrun.dev/submit
multipart form:
contract: <yaml content>
source: <main.go content>
wasm: <binary>
Header: X-Arcrun-API-Key: ak_xxxxx
3. registry 回應:
{ submission_id: "sub_abc123", status: "pending_review" }
4. CLI 輸出:
✓ 提交成功(submission_id: sub_abc123
查詢進度:acr parts publish --status sub_abc123
```
### Registry 沙盒驗收與 visibility 狀態機
```
POST /submit 觸發(非同步執行):
[整合類零件:gmail、telegram、google_sheets、line_notify、http_request]
Step 1: 體積檢查(< 2048KB
Step 2: syscall 掃描(無 filesystem syscall;網路 syscall 允許,因需呼叫外部 API)
通過 → visibility: author_only(作者立即可用,等人工審核)
[功能類零件:所有其他零件]
Step 1: 體積檢查(< 2048KB
Step 2: 冷啟動時間(< 50ms
Step 3: syscall 掃描(無網路 / 無 filesystem
Step 4: Gherkin 測試(contract 中所有 scenario 100% 通過)
通過 → visibility: author_only(作者立即可用,等人工審核)
任一步驟失敗 → status: rejected(回傳 failed_step + reason
人工審核通過 → visibility: public
- 零件出現在所有人的 GET /components
- 開始累積公開執行統計
人工審核拒絕 → visibility 維持 author_only
- 作者仍可使用,但收到拒絕原因
- 作者修改後可重新提交
acr parts 顯示規則:
visibility: author_only → [待審核] 只有你可用(不顯示統計)
visibility: public → ★ 成功率 | N 次執行 | by @author
任一失敗 → status: rejected
- 回傳 { failed_step, reason },格式與 Requirement 2 相同
```
---
## 開發順序(Phase 對齊 requirements
```
Phase 1:搬移與清理(Requirement 1
1.1 建立 arcrun repo,搬移四個目錄
1.2 清理 cypher-executor/wrangler.toml
1.3 改寫 component-loader(移除 KBDB/REGISTRY/MINI_ME 路徑)
1.4 移除 autoPublishMissing.ts(依賴 REGISTRY binding
1.5 本機 wrangler dev 測試 /health
Phase 2:零件完整度(Requirement 2
2.1 審查 21 個零件 contract.yaml(表格回報)
2.2 補充 credentials_required4 個零件)
2.3 補充 config_example(全部 21 個)
2.4 驗證 main.go required 與 contract 一致
Phase 3credential 注入(Requirement 3
3.1 新增 credential-injector.ts
3.2 整合進 graph-executor 節點執行前
3.3 測試 gmail 零件端對端(credentials.yaml → push → run
Phase 4CLIRequirement 4
4.1 acr init--hosted / --self-hosted 分支)
4.2 acr creds pushHosted 走 APISelf-hosted 走 KV
4.3 acr push
4.4 arcrun run
4.5 acr validate
4.6 acr parts / acr parts scaffold / acr parts publish
4.7 acr list / acr logs
Phase 5:開源發布(Requirement 5
5.1 撰寫 README.md(含 --hosted 快速開始)
5.2 撰寫 CONTRIBUTING.md
5.3 確認無 InkStone 內部資訊殘留
5.4 GitHub 發布 + npm publish
Phase 6Hosted SaaSRequirement 6
6.1 建立 auth-workerapi.arcrun.dev
6.2 cypher-executor 加入 tenant middleware
6.3 CREDENTIALS_KV key schema 加 tenant prefix
6.4 部署至 arcrun.dev
Phase 7:統計與貢獻(Requirement 7 + 8
7.1 analytics.ts(執行後 fire-and-forget
7.2 registry /analytics/record 端點
7.3 ANALYTICS_KV 聚合邏輯
7.4 GET /components 加入統計排序
7.5 POST /submit 沙盒驗收 + author 寫入
7.6 acr parts publish 指令
```
@@ -0,0 +1,180 @@
# Requirements Document
## Introduction
arcrun MVP 是從 `matrix` monorepo 中 cherry-pick 出最小可獨立運作的 AI 工作流執行引擎,目標是作為**獨立開源 repo**(`arcrun`)發布。
**背景**`matrix` 因為同時承載 InkStone 內部服務(KBDB、CLINIC_*、AICEO、MINI_ME 等)與核心執行引擎,複雜度過高,難以讓外部開發者使用或貢獻。MVP 的任務是將執行引擎從內部服務中解耦,讓任何人都能自行在 Cloudflare 上部署一套完整的 AI 工作流系統。
**護城河邏輯**
- 開源:cypher-executor(執行引擎)、WASM 零件庫(21 個)、credentials Worker、CLI
- Hosted SaaS:一行指令註冊取得 API Key,直接使用公眾零件庫,無需部署任何 Worker
- 閉源(InkStone 付費):KBDB 向量搜尋、graph 查詢、Persona SDK、MatchGPT
**不在此次範圍**:KBDB 整合、前端管理介面、向量搜尋、新增 WASM 零件。
---
## Glossary
- **cypher-executor**:原 `matrix/cypher-executor`,執行 workflow 的 Cloudflare Worker。開源版移除所有 InkStone 內部 Service Binding,只保留 KV / R2 / Workers AI。
- **component(零件)**:以 TinyGo 編譯的 `.wasm` 檔案,以 WASI preview1 / stdin-stdout JSON 為 I/O 模型。
- **component.contract.yaml**:每個零件的規格宣告,含 `canonical_id``input_schema``output_schema``gherkin_tests`,開源版補充 `credentials_required``config_example`
- **credentials Worker**`arcrun/credentials`,以 AES-GCM 加密存取 API token,部署在用戶自己的 CF 帳號。
- **WASM_BUCKET**arcrun.dev 的 R2 bucket,儲存所有公眾 `.wasm` 零件二進位,由 Arcrun 負責。
- **USER_KV**:用戶自己 CF 帳號下的 KV Namespace,同時存放 workflow YAML 與加密 credential,由用戶負責,Arcrun 不經手。
- **workflow.yaml**:用戶撰寫的工作流定義,`flow:``>>` 三元組描述,`config:` 對應各節點參數,存在用戶自己的 USER_KV。
- **CLI(套件名 arcrun,指令 acr**Node.js/TypeScript CLI 工具,管理 credentials、workflow 的上傳與執行。安裝:`npm i -g arcrun`,使用:`acr <指令>`
- **credentials_required**contract.yaml 新欄位,宣告零件需要哪個 credential 以及注入到哪個 input 欄位。
- **config_example**contract.yaml 新欄位,提供 `acr parts scaffold` 指令使用的 config 範本。
- **Standard 模式(預設)**:用戶只需在自己 CF 帳號開一個 KV(存 credential + workflow),使用 arcrun.dev 的執行引擎與公眾零件庫,無需部署任何 Worker。
- **Self-hosted 模式**:用戶自行部署全套 Worker 至自己的 Cloudflare 帳號,有完全控制權,可貢獻零件至公眾庫。
- **auth-worker**arcrun.dev 上的帳號服務 Worker,處理 `POST /register` 自動發放 API Key,不儲存用戶 credential。
- **tenant_id**:每個 API Key 對應的租戶識別碼,用於讓 cypher-executor 知道要用哪個 Cloudflare API Token 去存取用戶的 USER_KV。
- **public registry**arcrun.dev 上的公眾零件庫,所有人共用,有執行統計與 author 資訊。
- **`acr parts publish`**:CLI 指令,自架用戶將自製零件提交至公眾 registry 審核。
- **execution analytics**:每次零件執行後非同步記錄的統計資料(使用次數、成功率),公開顯示於 `acr parts`
- **visibility**contract.yaml 欄位,值為 `author_only`(沙盒通過後作者立即可用)或 `public`(人工審核後所有人可用)。
---
## Requirements
### Requirement 1:搬移 cypher-executor 至獨立 repo 並移除 InkStone bindings
**User Story:** As a 開源用戶, I want 自行部署 cypher-executor 至我的 Cloudflare 帳號, so that 我不需要依賴 InkStone 的任何服務就能執行 AI 工作流。
#### Acceptance Criteria
1. THE cypher-executor `wrangler.toml` SHALL 移除以下 Service Bindings`KBDB``REGISTRY``CLINIC_GDRIVE``CLINIC_EXCEL``CLINIC_ANALYSIS``CLINIC_RENDER``CLINIC_GSHEETS``AICEO``MINI_ME`
2. THE cypher-executor `wrangler.toml` SHALL 保留以下 bindings`EXEC_CONTEXT`KV)、`WEBHOOKS`KV)、`WASM_BUCKET`R2)、`AI`Workers AI)。
3. THE cypher-executor `wrangler.toml` SHALL 新增 `CREDENTIALS_KV`KV Namespace binding),用於 credential 解密注入。
4. THE component-loader SHALL 從 `WASM_BUCKET` R2 直接讀取 `.wasm` 檔案,不透過任何 Service Binding 或外部 HTTP 查詢。
5. WHEN cypher-executor 收到執行請求,THE cypher-executor SHALL 不依賴 `KBDB``REGISTRY` 或任何 InkStone 內部 Service Binding,只使用 KV / R2 / Workers AI 完成執行。
6. THE arcrun repo SHALL 包含以下目錄:`cypher-executor/``credentials/``builtins/``registry/components/`21 個零件)。
---
### Requirement 2component.contract.yaml 完整度補充
**User Story:** As a 零件使用者, I want 每個零件的 contract.yaml 都有 `credentials_required``config_example`, so that CLI 能自動注入 credential,用戶也能快速知道如何設定節點。
#### Acceptance Criteria
1. THE `credentials_required` 欄位 SHALL 出現在以下 4 個零件的 contract.yaml 中:`gmail``google_sheets``telegram``line_notify`
2. WHEN `credentials_required` 存在,THE 欄位 SHALL 包含以下子欄位:`key`(對應 credentials.yaml 的 key 名稱)、`type`token 類型,如 `google_oauth``telegram_bot_token`)、`description`(說明)、`inject_as`(執行時注入到 input 的哪個欄位名稱)。
3. THE `config_example` 欄位 SHALL 出現在所有 21 個零件的 contract.yaml 中。
4. WHEN `config_example` 存在,THE 欄位 SHALL 為 YAML 字串,內容為可直接貼入 workflow.yaml `config:` 區塊的範本,需有人類可讀的說明註解。
5. FOR 需要 credential 的零件,THE `config_example` SHALL 包含一行註解,說明哪個 credential key 會被自動注入到哪個欄位(如 `# access_token 由 credentials.yaml 的 gmail_token 自動注入`)。
6. THE main.go 的 `required` 欄位 SHALL 與 contract 的 `input_schema.required[]` 保持一致,不得有欄位名稱不符。
---
### Requirement 3workflow YAML 格式與執行時 credential 注入
**User Story:** As a 工作流設計者, I want 用有語意的關係詞撰寫 workflow.yaml,且 credential 自動注入, so that workflow 定義中完全不出現明文 token。
#### Acceptance Criteria
1. THE workflow.yaml `flow:` 欄位 SHALL 以 `"A >> 關係詞 >> B"` 三元組陣列描述資料流。
2. THE cypher-executor SHALL 支援以下關係詞:`完成後``失敗時``對每個``條件滿足時``ON_SUCCESS``ON_FAIL``FOREACH``IF``ON_CLICK``CALLS_SUBFLOW`
3. THE cypher-executor SHALL 拒絕使用 `PIPE` 關係詞,並回傳明確錯誤訊息。
4. WHEN cypher-executor 執行一個節點,THE cypher-executor SHALL 查詢該節點對應零件的 `credentials_required`,若存在則從 `CREDENTIALS_KV` 解密對應 credential,並注入到 input 的 `inject_as` 欄位。
5. THE credential 注入 SHALL 發生在 WASM 執行前,用戶的 workflow `config:` 中不需也不應包含 token 值。
6. IF `credentials_required` 宣告的 credential key 在 `CREDENTIALS_KV` 中不存在,THE cypher-executor SHALL 回傳結構化錯誤,包含缺少的 key 名稱與修復步驟說明。
---
### Requirement 4CLIarcrun,指令 acr)核心指令
**User Story:** As a 開發者, I want 透過 `acr` CLI 管理 workflow 與 credentials, so that 不需要直接操作 Cloudflare KV / R2 API 就能完成部署與執行。
#### Acceptance Criteria
1. THE CLI SHALL 以 Node.js/TypeScript 實作,套件名 `arcrun`bin 名 `acr`,可透過 `npm i -g arcrun` 安裝,依賴只使用 `commander``js-yaml``chalk``ora`
2. THE `acr init` 指令 SHALL 以互動式問答產生 `~/.arcrun/config.yaml`,問答內容為:CF Account ID、USER_KV namespace ID、CF API Token(用於 cypher-executor 代存取用戶 KV)、email(取得 arcrun.dev API Key);並建立空白本機 `credentials.yaml`
3. THE `acr creds push [credentials.yaml]` 指令 SHALL 讀取 credentials.yaml,逐一加密上傳至用戶自己的 USER_KV,並顯示每個 key 的上傳結果。
4. THE `acr push <workflow.yaml>` 指令 SHALL 解析 `flow:` 三元組,轉換成執行圖,連同 `config:` 存入 `WEBHOOKS KV`,並輸出 webhook URL。
5. THE `acr run <workflow_name> [--input key=value...]` 指令 SHALL 觸發 cypher-executor 執行指定 workflow,顯示各節點執行結果;失敗時顯示具體節點、原因與修復步驟。
6. THE `acr validate <workflow.yaml>` 指令 SHALL 在執行前驗證:YAML 格式、關係詞合法性(無 PIPE)、所有節點在 config 中有對應、所有零件存在於 WASM_BUCKET、所有 credentials 已上傳至 CREDENTIALS_KV。
7. THE `acr parts` 指令 SHALL 列出所有可用零件(按類型分組),顯示每個零件的必填欄位與所需 credential。
8. THE `acr parts scaffold <component>` 指令 SHALL 從 contract 的 `config_example` 輸出可直接貼入 workflow.yaml 的 config 範本,以及對應的 credentials.yaml 欄位範本。
9. THE `acr list` 指令 SHALL 列出 WEBHOOKS KV 中所有已上傳的 workflow,顯示名稱與更新時間。
10. THE `acr logs <workflow_name>` 指令 SHALL 顯示最近執行記錄,包含時間、成功/失敗狀態、執行時間,失敗時顯示失敗節點與原因。
---
### Requirement 5README 與開源發布準備
**User Story:** As a 外部開發者, I want 看到清楚的 README5 分鐘內能完成部署, so that 降低試用門檻,吸引社群貢獻。
#### Acceptance Criteria
1. THE README.md SHALL 包含以下章節:專案定位(開源核心 vs 閉源付費服務說明)、快速開始(`acr init``acr creds push``acr push``acr run` 四步驟)、零件列表(21 個零件分類說明)、workflow YAML 語法說明(三元組 + 關係詞表格)、自行部署說明(Cloudflare Workers 部署步驟)。
2. THE README.md 快速開始 SHALL 以 `newsletter_subscribe` 為範例 workflow,展示 gmail + google_sheets + telegram 的完整串接。
3. THE repo SHALL 包含 `CONTRIBUTING.md`,說明如何新增零件(TinyGo 開發環境、contract.yaml 格式、本機測試指令)。
4. THE repo SHALL 確保所有 InkStone 內部資訊(Worker URL、KV namespace ID、帳號資訊)不出現在任何已提交的檔案中。
5. WHEN cypher-executor 部署後第一次被呼叫,THE cypher-executor SHALL 能正常回應 health check`GET /health` 回傳 `{ ok: true }`),不需要任何 InkStone 服務可用。
---
### Requirement 6Standard 模式 — API Key 註冊與用戶 KV 存取
**User Story:** As a 新用戶, I want 只需開一個 CF KV 就能開始使用 Arcrun,不需要部署任何 Worker, so that 最低門檻試用整個平台,且我的 credential 永遠在我自己的環境。
#### Acceptance Criteria
1. THE auth-worker SHALL 提供 `POST /register` 端點,接受 `{ email }` 後自動生成 API Key(格式:`ak_` 前綴 + 32 字元隨機字串),無需人工審核,立即回傳 `{ api_key, tenant_id }`
2. THE auth-worker SHALL 將 `{ tenant_id, email, created_at, api_key_hash }` 存入 `ACCOUNTS_KV`,只存 hash 不存明文 API Key。arcrun.dev 不儲存任何用戶 credential 或 workflow 內容。
3. WHEN `acr init` 執行,THE CLI SHALL 互動式詢問以下資料並寫入 `~/.arcrun/config.yaml`
- CF Account ID(用戶自己的)
- USER_KV namespace ID(用戶自己開的,存 credential + workflow
- CF API Token(供 cypher-executor 用 CF API 存取用戶 KV,只需 KV Edit 權限)
- email(呼叫 `POST https://api.arcrun.dev/register` 取得 API Key
4. THE cypher-executor SHALL 在每個 request 的 header 讀取 `X-Arcrun-API-Key`,驗證後取得該 tenant 的 CF API Token,用 Cloudflare API 存取用戶自己的 USER_KV;缺少或無效的 API Key 回傳 `401 Unauthorized`
5. THE `acr creds push` 指令 SHALL 使用用戶的 CF API Token,直接呼叫 Cloudflare KV API 將加密 credential 寫入用戶自己的 USER_KV,不經過 arcrun.dev。
6. THE `acr push <workflow.yaml>` 指令 SHALL 同樣直接寫入用戶自己的 USER_KV,不經過 arcrun.dev。
7. WHEN Self-hosted 模式,THE cypher-executor SHALL 可透過環境變數 `MULTI_TENANT=false` 停用 API Key 驗證,直接使用本地 KV binding,與現有行為相容。
---
### Requirement 7:公眾零件庫執行統計與貢獻榮譽
**User Story:** As a 零件使用者, I want 在 `acr parts` 看到每個零件的真實執行統計與作者資訊, so that 我能選擇最可靠的零件;As a 零件貢獻者, I want 我的名字和統計數字公開顯示, so that 我有動機將好零件推入公眾庫而非留在私庫。
#### Acceptance Criteria
1. THE contract.yaml SHALL 新增可選欄位 `author`GitHub username,如 `@alice`),在 `acr parts` 顯示時一起展示。
2. WHEN cypher-executor 執行完一個零件節點,THE cypher-executor SHALL 非同步 POST 以下資料至 `https://registry.arcrun.dev/analytics/record`,不阻擋主流程:
```json
{ "canonical_id": "gmail", "version": "v1", "success": true, "duration_ms": 120 }
```
不含任何用戶資料或 tenant_id。
3. THE public registry SHALL 聚合每個零件的執行統計:`total_runs`(總執行次數)、`success_rate`(成功率,百分比)、`avg_duration_ms`(平均執行時間)。
4. THE `acr parts` 指令 SHALL 顯示每個零件的統計資料,格式為:
```
• gmail Gmail 發信 by @alice
★ 99.2% 成功 | 140,382 次執行 | 平均 120ms
```
5. IF 零件存在於用戶自架的私有 WASM_BUCKET 而非公眾庫,THE `acr parts` SHALL 顯示該零件但標註 `[私有]`,不顯示統計數字與 author。
6. THE public registry SHALL 在 `GET /components` 回傳的零件清單中,依 `total_runs × success_rate` 排序,讓高品質高使用量的零件排在前面。
---
### Requirement 8:零件貢獻流程與 visibility 狀態
**User Story:** As a 零件開發者, I want 提交零件後立即能自己使用,等審核通過後公開給所有人, so that 不用等待審核就能驗證自己的零件是否有用。
#### Acceptance Criteria
1. THE contract.yaml SHALL 包含 `visibility` 欄位,值為 `author_only`(沙盒通過後作者立即可用)或 `public`(人工審核通過後所有人可用)。
2. THE `acr parts publish <component>` 指令 SHALL 打包指定零件的原始碼、`component.contract.yaml`、`.wasm`POST 至 `https://registry.arcrun.dev/submit`(帶 `X-Arcrun-API-Key` header)。原始碼語言不限,但編譯產出必須為 WASM + WASI preview1。
3. WHEN 零件提交後,THE registry SHALL 依零件類型執行不同層級的沙盒驗收:
- **整合類**(需呼叫外部 API,如 gmail、telegram):體積 / syscall 掃描通過 → `author_only`
- **功能類**(純邏輯,如 string_ops、if_control):體積 / syscall 掃描 / Gherkin 測試全通過 → `author_only`
- 任一必要步驟失敗 → `rejected`(回傳具體失敗步驟與原因)
4. WHEN 零件 visibility 為 `author_only`THE registry SHALL 讓該零件只對提交者的 API Key 可見,`acr parts` 顯示時標註 `[待審核]`,其他用戶看不到。
5. WHEN 人工審核通過,THE registry SHALL 將 visibility 改為 `public`,零件立即出現在所有人的 `acr parts` 清單,並開始累積公開執行統計。
6. WHEN 審核拒絕,THE registry SHALL 回傳具體失敗原因,零件保留 `author_only` 狀態讓作者繼續修改後重新提交。
7. THE `acr parts publish` 指令 SHALL 在提交後顯示 `submission_id`、目前 visibility 狀態,以及查詢審核進度的指令提示。
8. THE `acr parts` 指令 SHALL 對 `author_only` 零件顯示「[待審核] 只有你可用」,對 `public` 零件顯示執行統計與 author,讓貢獻者清楚知道零件的可用範圍。
+206
View File
@@ -0,0 +1,206 @@
# Implementation Plan: arcrun MVP
## Overview
依照 Design 的七個 Phase 實作。原則:最小異動,不重寫現有邏輯,只 cherry-pick + carve-out + supplement。
所有 Phase 13 工作在 `matrix` repo 對應目錄驗證後再搬到新 repo。
**PR #2claude/review-mvp-specs-8Bvdu)狀態:** 初始實作已提交,已修復以下問題後準備 merge:
- CF API Token 傳至 arcrun.dev 安全問題(已修復)
- 加密 fallback 格式不相容(已修復)
- submitComponent KBDB 依賴(已修復,改用 SUBMISSIONS_KV
- Webhook 路由缺 analytics(已修復)
- `require()` 在 ES module 中(已修復)
- api 類零件 `no_network_syscall: true` 錯誤(已修復)
---
## Phase 1:搬移與清理
- [x] 1. 建立 `arcrun` 獨立 repo 並初始化
- [x] 1.1 在 GitHub 建立新的 public repo(使用 matrix monorepo 的 `arcrun/` 子目錄代替,PR #2
- [x] 1.2 設定 `.gitignore`(排除 `node_modules/``.wrangler/``credentials.yaml``~/.arcrun/`
- [x] 1.3 從 `matrix` cherry-pick 四個目錄:
- `matrix/cypher-executor/``arcrun/cypher-executor/`
- `matrix/u6u-core/credentials/``arcrun/credentials/`
- `matrix/u6u-core/registry/components/``arcrun/registry/components/`
- _Requirements: 1.6_
- [x] 2. 清理 `cypher-executor/wrangler.toml`
- [x] 2.1 移除 9 個 InkStone Service BindingsKBDB、REGISTRY、CLINIC_*、AICEO、MINI_ME
- [x] 2.2 確認保留:`EXEC_CONTEXT``WEBHOOKS``WASM_BUCKET``AI`
- [x] 2.3 新增 `CREDENTIALS_KV``ANALYTICS_KV` KV namespace binding
- [x] 2.4 更新 `name``arcrun-cypher-executor`
- _Requirements: 1.1, 1.2, 1.3_
- [x] 3. 改寫 `cypher-executor/src/lib/component-loader.ts`
- [x] 3.1 移除對 MINI_ME、KBDB、InkStone bindings 的 hardcode
- [x] 3.2 實作三層邏輯:builtin Map → WASM_BUCKET R2 直讀 → 結構化錯誤
- _Requirements: 1.4, 1.5_
- [x] 4. 移除對 InkStone bindings 的依賴程式碼
- [x] 4.1 刪除 `autoPublishMissing.ts`(依賴 REGISTRY binding
- [x] 4.2 移除所有 `env.KBDB``env.REGISTRY``env.MINI_ME``env.AICEO``env.CLINIC_*` 引用
- _Requirements: 1.1, 1.5_
- [ ] 5. 本機驗證
- [ ] 5.1 `cd arcrun/cypher-executor && wrangler dev` 能啟動(無 binding 錯誤)
- [ ] 5.2 `GET /health` 回傳 `{ ok: true }`
- [ ] 5.3 上傳 `validate_json.wasm` 到 WASM_BUCKET,執行 `POST /execute` 能正常回傳結果
- _Requirements: 1.5, 5.5_
---
## Phase 2:零件完整度補充
- [x] 6. api 類零件 `no_network_syscall` 修正
- [x] 6.1 gmail、telegram、google_sheets、line_notify、http_request 改為 `no_network_syscall: false`
- _Requirements: 2.1_
- [ ] 7. 審查 21 個零件 contract.yaml 並補充 `credentials_required`
- [ ] 7.1 確認 gmail、google_sheets、telegram、line_notify 有 `credentials_required`PR #2 已加入,需驗證格式正確)
- [ ] 7.2 確認所有 21 個零件有 `config_example` 欄位
- [ ] 7.3 驗證 `main.go` required 欄位與 `contract.yaml` input_schema.required[] 一致
- _Requirements: 2.1, 2.2, 2.3_
---
## Phase 3Credential 注入整合
- [x] 10. `credential-injector.ts` 已實作(`arcrun/cypher-executor/src/actions/credential-injector.ts`
- [x] 10.1 讀取 contract.yaml from R2,解析 `credentials_required`
- [x] 10.2 從 `CREDENTIALS_KV` 讀取 AES-GCM 加密 token,注入到 input 對應欄位(inject_as
- [x] 10.3 credential 不存在時拋出結構化錯誤(含 key 名稱與修復步驟)
- _Requirements: 3.4, 3.5, 3.6_
- [ ] 11. 驗證 credential 注入整合進 graph-executor
- [ ] 11.1 確認 `graph-executor.ts` 在節點執行前正確呼叫 `injectCredentials`
- [ ] 11.2 確認注入只影響 WASM input,不修改 WEBHOOKS KV 中儲存的 workflow 定義
- _Requirements: 3.4, 3.5_
- [ ] 12. 端對端測試(手動)
- [ ] 12.1 建立 `credentials.yaml`,加入測試 token
- [ ] 12.2 執行 `acr creds push`,確認寫入 CREDENTIALS_KV 格式為 `{ encrypted, iv }`(無 `mode: 'base64'`
- [ ] 12.3 執行含 credential 的 workflow,確認 inject_as 欄位正確注入
- _Requirements: 3.4, 3.5_
---
## Phase 4CLI 開發
- [x] 13. CLI 專案骨架已建立(`arcrun/cli/`
- [x] 13.1 `package.json`name: `arcrun`bin: `acr`
- [x] 13.2 `tsconfig.json`module: NodeNext
- [x] 13.3 所有 10 個指令已實作骨架
- _Requirements: 4.1_
- [x] 14. `acr init` 已實作,修正項:
- [x] 14.1 Standard 模式不再傳送 `cf_api_token` 至 arcrun.dev(只傳 `email`
- [x] 14.2 `require()` 改用 `await import()` 修正 ES module 相容
- [ ] 14.3 **待補**`acr init` 需詢問 `ARCRUN_ENCRYPTION_KEY` 並寫入 config(目前加密 key 需手動設定)
- _Requirements: 4.2, 6.3_
- [x] 15. `acr creds push` 已實作
- [x] 15.1 讀取 `credentials.yaml`AES-GCM 加密後寫入用戶 CF KV`cred:{name}`
- [x] 15.2 加密 fallbackbase64)已移除,key 不足時直接拋錯提示生成指令
- _Requirements: 4.3, 6.5_
- [x] 16. `acr push` 已實作
- _Requirements: 4.4_
- [x] 17. `acr run` 已實作
- _Requirements: 4.5_
- [ ] 18. `acr validate` credential 檢測邏輯有誤,需修復
- [ ] 18.1 `extractCredentialRefs()` 目前掃描 `{{creds.xxx}}` 語法,但 injection 使用 `inject_as` key
- [ ] 18.2 改為讀取 contract.yaml 的 `credentials_required[].key`,與 `cred:{key}` KV 存在性比對
- _Requirements: 4.6_
- [x] 19. `acr parts``acr parts scaffold``acr parts publish` 已實作
- [ ] 19.1 `acr parts` 中 YAML 解析改用 `js-yaml`(目前用 regex,可能解析失敗)
- _Requirements: 4.7, 4.8_
- [x] 20. `acr list``acr logs` 已實作
- _Requirements: 4.9, 4.10_
---
## Phase 5:開源發布準備
- [x] 21. README.md 已撰寫(`arcrun/README.md`
- [x] 22. CONTRIBUTING.md 已撰寫(`arcrun/CONTRIBUTING.md`
- [ ] 23. 安全審查(PR merge 前執行)
- [ ] 23.1 搜尋 `.workers.dev` InkStone 網域
- [ ] 23.2 確認 wrangler.toml 所有 KV id 欄位留空
- [ ] 23.3 確認 `credentials.yaml``.gitignore`
- _Requirements: 5.4_
- [ ] 24. 發布(安全審查後)
- [ ] 24.1 `npm publish`CLI package `arcrun`
- _Requirements: 5.1_
---
## Phase 6Standard 模式 — auth-worker 與用戶 KV 代存取
- [ ] 25. 建立 `auth-worker`(新 Worker,部署至 `api.arcrun.dev`
- [ ] 25.1 建立 `auth-worker/` 目錄,初始化 Hono + wrangler.toml
- [ ] 25.2 實作 `POST /register`:接收 `{ email, account_id, kv_namespace_id }` + CF API Token 透過 header 傳入
- **不在 request body 中接收 CF API Token**Token 透過 header `CF-Api-Token` 傳入,減少 TLS 以外的洩漏面)
- 生成 `tenant_id``api_key`,存入 `ACCOUNTS_KV`
- [ ] 25.3 Bindings`ACCOUNTS_KV`
- _Requirements: 6.1, 6.2_
- [ ] 26. 改造 `cypher-executor` 支援 multi-tenant 用戶 KV 代存取
- [ ] 26.1 讀取 `MULTI_TENANT` env var(目前已宣告但未讀取),實作 tenant middleware
- [ ] 26.2 `X-Arcrun-API-Key` → 查 `ACCOUNTS_KV` → 取得用戶 cf_api_token + kv_namespace_id → 建立 `CfKvClient`
- [ ] 26.3 `CfKvClient` 已實作(`arcrun/cli/src/lib/cf-api.ts`),需移植到 `cypher-executor/src/lib/`
- [ ] 26.4 `credential-injector.ts` 改用 userKv 取得加密 credential
- [ ] 26.5 webhook 路由注入 userKv
- _Requirements: 6.4, 6.5, 6.6_
- [ ] 27. 端對端測試(用戶 KV 隔離)
- _Requirements: 6.4, 6.5_
---
## Phase 7:公眾零件統計與貢獻審核
- [x] 28. Analytics 基礎設施已建立
- [x] 28.1 `execution-logger.ts` 建立,`writeExecutionVerdict` 寫入 `ANALYTICS_KV`fire-and-forget
- [x] 28.2 `/execute` 路由已整合 `waitUntil(writeExecutionVerdict(...))`
- [x] 28.3 `/webhooks/:token/trigger` 路由已補上 `waitUntil(writeExecutionVerdict(...))`
- _Requirements: 7.2_
- [ ] 29. registry Worker analytics 端點
- [ ] 29.1 新增 `POST /analytics/record` 路由,原子更新 `ANALYTICS_KV`
- [ ] 29.2 `GET /components` 回傳加入 `total_runs``success_rate``avg_duration_ms`
- _Requirements: 7.3, 7.6_
- [x] 30. `author` 欄位已加入 contract.yaml 規格
- _Requirements: 7.1_
- [x] 31. 零件提交審核流程已實作(`arcrun/registry/src/actions/submitComponent.ts`
- [x] 31.1 沙盒驗收流程(sandboxAcceptance.ts):size_check + syscall_scan 已實作;cold_start + gherkin_tests 為 Phase 0 mock
- [x] 31.2 `SUBMISSIONS_KV` 儲存元數據,預設 `visibility: author_only`
- [ ] 31.3 `PATCH /submit/:id/approve` → 將 visibility 改為 `public`(待實作)
- [ ] 31.4 Gherkin 測試執行(取代 mock
- _Requirements: 8.2, 8.3, 8.4, 8.5_
---
## 待辦(無相依順序,可平行處理)
- [ ] A. `builtins/` 清理:`initComponents.ts` 仍用舊的 HTTP endpoint 模式上架零件(`buildComponentDefs` 含 URL),應改為呼叫 `POST /submit` 送 WASM binary + contract,或直接移除 builtins(功能已整合到 registry
- [ ] B. `validate` 指令 credential 檢測邏輯修復(見 Phase 4 Task 18
- [ ] C. `acr init` 加入 `ARCRUN_ENCRYPTION_KEY` 設定步驟
- [ ] D. `acr parts` YAML 解析改用 `js-yaml`
---
## Notes
- 標記 `*` 的子任務為選填,可跳過以加速 MVP 交付
- Gherkin 測試執行(sandbox 步驟 d)為 Phase 0 mockPhase 7 補充
- cold-start 測量(sandbox 步驟 b)為 Phase 0 mockPhase 2 補充
- CF API Token 永遠不離開用戶本機,arcrun.dev 只收 email + account_id + kv_namespace_id