docs: add SDD specs + user requirements + tests

- .agents/specs/: spec-driven-dev docs for arcrun MVP, auth-recipe,
  credential-primitives-wasm (active refactor), landing-page,
  sdk-and-website, u6u-core-mvp, u6u-platform-evolution.
- .agents/steerings/tech.md: detailed tech stack rationale.
- docs/user_requirements/: long-form requirements incl. credential
  primitives, pages spec, py strategy analysis.
- tests/: end-to-end harness scaffolding.

These are the durable context backing CLAUDE.md's SDD protocol.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-20 17:48:24 +08:00
parent cadcaef3b0
commit 13b01328c1
36 changed files with 7499 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
# arcrun — 進度與待辦
> 設計細節見 `arcrun/README.md`(產品說明)和 `arcrun/BETA_TEST.md`(封測指南)。
> 這份文件只記錄:目前狀態、還差什麼、封測能不能啟動。
---
## 一、封測目標場景
封測者是工程師朋友,有自己的網頁,需要後端自動化。目標是他能在 AI 協助下,一次或很少次完成以下完整流程:
1. `acr init` 取得 api_key
2. `acr parts scaffold` 查零件格式,AI 幫寫 workflow YAML
3. 若內建零件不足,`acr recipe push` 增加打外部 API 的 recipe
4. `acr creds push` 上傳 OAuth tokengmail / google_sheets 等)
5. `acr push` 部署 workflow,取得 Webhook URL
6. 網頁 POST /webhooks/named/{name}/trigger,結果存 Google Sheets
---
## 二、場景各步驟驗證狀態
### Step 1acr init → api_key
- [x] `acr init` Standard 模式完成,api_key 存入 `~/.arcrun/config.yaml`
- [x] 已驗證:`mode: standard, api_key: ak_...` 正確
### Step 2acr parts scaffold → AI 看到零件格式
- [x] `acr parts` 列出 21 個零件,完全內建,不依賴 registry.arcrun.dev
- [x] `acr parts scaffold google_sheets` 輸出 spreadsheet_id / range / operation / values 格式與 credentials.yaml 範本
- [x] 已驗證:輸出可直接貼入 YAML
### Step 3acr recipe push → 打外部 API
- [x] `acr recipe push` 上傳成功,回傳 rec_hash
- [x] workflow 使用 `component: rec_xxxxxxxx`acr push 後 trigger 能正確呼叫外部 API
- [x] 已驗證(2026-04-18):httpbin_post recipe → trigger → httpbin.org/post 回傳正確 ✅
### Step 4acr creds push → 自動注入 token
- [x] `POST /credentials` API 完成,以 `{api_key}:cred:{name}` 存入 KV
- [x] Webhook trigger 時 injectCredentials 從 KV 取得 token 自動注入
- [x] `/register` 現在回傳 `encryption_key``acr init` 自動存入 config
- [x] `acr creds push` 從 config 讀 encryption_key,不再需要手動設定環境變數
- [x] 已驗證(2026-04-18):beta@arcrun.dev 帳號完整流程:init → creds push → trigger → credential 注入成功 ✅
### Step 5acr push → Webhook URL
- [x] `acr push workflow.yaml` 部署成功,顯示 Webhook URL 和完整 curl 範例
- [x] config 中的 `component` / 參數在 push 時套入 graph 節點
- [x] 已驗證(2026-04-18):sheet-test workflow push 成功 ✅
### Step 6:網頁 POST → 執行 → 結果到 Google Sheets
- [x] `POST /webhooks/named/{name}/trigger -H 'X-Arcrun-API-Key: ...'` 觸發執行正常
- [x] google_sheets 零件有實作(append row 到 Sheets API
- [x] 已驗證(2026-04-18):trigger sheet-test → 報「缺少 credential」(符合預期,credential 未上傳)✅
- [ ] **未驗證**:真實 google_oauth token + acr creds push → trigger → Google Sheets 實際寫入
- 需要真實 OAuth token 才能完整驗證
---
## 三、封測啟動阻擋項
P0 全部清除才啟動封測。
| # | 項目 | 狀態 | 說明 |
|---|------|------|------|
| 1 | acr parts scaffold 正確輸出 | ✅ 完成 | 21 個零件內建清單 |
| 2 | acr recipe push 端對端 | ✅ 完成 | httpbin_post 驗證通過 |
| 3 | acr creds push 代碼 | ✅ 完成 | 需 ARCRUN_ENCRYPTION_KEY |
| 4 | credential 注入端對端 | ✅ 完成 | 無 token 時錯誤訊息正確 |
| 5 | acr push + webhook trigger | ✅ 完成 | 端對端驗證通過 |
| 6 | acr creds push 實測 | ✅ 完成 | /register 回傳 encryption_keyacr init 自動存入 configCLI 1.0.9|
| 7 | Google Sheets 真實寫入 | ⚠️ 部分驗證 | credential 注入已驗證;實際 Sheets 寫入需真實 OAuth token |
| 8 | 第三方服務認證 recipe | ✅ 完成 | 20 個服務(Notion/Slack/GitHub/OpenAI 等),CLI 1.1.0 |
**目前狀況**P0 全部完成。Google Sheets 實際寫入可由封測者用真實 token 驗證,不阻塞啟動。
---
## 四、封測前 P3(啟動當天)
- [ ] 用封測者 email 呼叫 `/register`,取得 api_key
- [ ] 將 ARCRUN_ENCRYPTION_KEY 以安全方式提供給封測者
- [ ] 確認聯絡管道
---
## 五、已知限制(封測期間不修)
1. `if_control` false branch 不路由(條件 false 時後續節點不執行)
2. 多節點 context 不自動解包(上游輸出 flat merge,下游需從 `data.result` 取值)
3. 用戶自製邏輯零件(Phase 5)封測後才實作
---
## 六、實作進度
| Phase | 內容 | 狀態 |
|-------|------|------|
| 0 | Workers 部署、CI/CD、DNS | ✅ |
| 1 | CLI 基礎(init / validate / run / parts | ✅ |
| 2 | /register、/cypher/execute、21 個零件 | ✅ |
| 3 | Service Binding 架構、{{variable}} 插值、ON_FAIL 修正 | ✅ |
| 4 | 動態 Recipe KVCRUD)、acr recipe 指令 | ✅ |
| 5 | 用戶自製邏輯零件(WASM push) | ⏸ 封測後 |
| 6 | Credential 多租戶({api_key}:cred:{name})、acr creds push | ✅ |
| 7 | acr parts 內建清單、acr parts scaffold | ✅ |
| 8 | /webhooks/named、acr push 改版、config 套入 graph | ✅ |
### CLI 版本
| 版本 | 變更 |
|------|------|
| 1.1.0 | auth recipe 系統:20 個服務預建(Notion/Slack/GitHub/OpenAI/Google SA 等);acr auth-recipe 指令 |
| 1.0.9 | /register 回傳 encryption_keyacr init 自動儲存;creds push 不需手動設環境變數 |
| 1.0.8 | acr push → webhooks/namedconfig 套入 graphacr parts 內建清單 |
| 1.0.7 | acr creds push → POST /credentials |
| 1.0.6 | acr recipe push / list / delete |
| 1.0.5 | hello.yaml 改 string_ops--version 修正 |
| 1.0.4 | config/context 分離 |
| 1.0.3 | 初始發布 |
+254
View File
@@ -0,0 +1,254 @@
# Auth Recipe System — SDD
> 文件類型:SDDSoftware Design Document
> 建立:2026-04-19
> 狀態:實作中
---
## 一、目標
封測前完成,讓封測者碰到「我要連 X 服務」都有辦法,而不是「還沒做」。
**精神**`http_request` 是容器零件,auth recipe 是「如何對這個服務認證」的設定層,兩者分離。新增一個服務 = 寫一份 YAML,不需要改程式碼、不需要重新部署 Worker。
---
## 二、三層模型
```
Layer 3: Auth Recipe (YAML/JSON in RECIPES KV)
公共,描述「如何對某服務認證」
key: auth_recipe:{service}
例: auth_recipe:notion, auth_recipe:slack
↓ 引用
Layer 2: Auth Primitive (TypeScript in Worker)
四個通用認證邏輯:static_key | oauth2 | service_account | mtls
封測只做 static_key 和 service_account (Google JWT)
↑ 使用
Layer 1: Tenant Secret (CREDENTIALS_KV)
每個 tenant 自己的加密 credential
key: {api_key}:cred:{name}
```
---
## 三、Auth Recipe Schema
```typescript
interface AuthRecipeDefinition {
kind: 'auth_recipe'; // 區別 RecipeDefinition 用
service: string; // canonical_id, e.g. "notion"
version: number;
primitive: 'static_key' | 'oauth2' | 'service_account' | 'mtls';
base_url: string;
display_name?: string;
description?: string;
// service_account 用
service_account_kind?: 'google_jwt';
token_exchange?: {
endpoint: string; // e.g. https://oauth2.googleapis.com/token
scopes: string[];
};
required_secrets: Array<{
key: string; // CREDENTIALS_KV 的名稱
label: string; // UI/CLI 顯示
type?: 'string' | 'json_blob'; // default: string
help?: string;
help_url?: string;
}>;
inject: {
header?: Record<string, string>; // "Authorization": "Bearer {{secret.token}}"
query?: Record<string, string>;
body?: Record<string, string>;
};
created_at: number;
updated_at: number;
}
```
**Template 語法**
- `{{secret.KEY}}` → 從 tenant 的 CREDENTIALS_KV 解密取值
- `{{runtime.access_token}}` → service_account JWT exchange 後取得的短期 token
---
## 四、KV 儲存
沿用現有 `RECIPES` KV namespace,不新增 binding。
```
auth_recipe:{service} → AuthRecipeDefinition JSON
```
與現有 `recipe:{id}` / `idx:{hash}` 的 key 不衝突。
---
## 五、執行流程
### 5.1 static_key(涵蓋 ~80% 服務)
```
trigger → graph-executor
→ injectCredentials(componentId, input, env, apiKey)
→ resolveAuthRecipe("notion", RECIPES KV)
→ 取得 required_secrets: [{key: "notion_token", ...}]
→ 從 CREDENTIALS_KV 讀 "{api_key}:cred:notion_token"
→ AES-GCM 解密
→ 展開 inject.header templates ({{secret.notion_token}} → 實際值)
→ 注入 _auth_headers, _auth_query, _auth_body 到 input
→ makeAuthRecipeRunner(recipe)
→ 合併 _auth_headers 到 fetch headers
→ 呼叫 recipe.base_url + input._path
→ 回傳結果
```
### 5.2 service_accountGoogle 家族)
```
injectCredentials
→ resolveAuthRecipe("google_sheets_sa", RECIPES KV)
→ 解密 service_account_json (JSON blob)
→ signGoogleJwt(serviceAccountJson, scopes) via crypto.subtle (RSASSA-PKCS1-v1_5 + SHA-256)
→ POST token_exchange.endpoint → 取得 access_token
→ 展開 inject.header: { Authorization: "Bearer {{runtime.access_token}}" }
→ 注入 _auth_headers
```
---
## 六、Context key 慣例
注入後的認證資訊以 `_auth_` 前綴攜帶,不污染業務欄位:
| Key | 說明 |
|---|---|
| `_auth_headers` | `Record<string, string>` — 要合併進 fetch headers |
| `_auth_query` | `Record<string, string>` — 要附加到 URL query string |
| `_auth_body` | `Record<string, string>` — 要合併進 request body |
`makeAuthRecipeRunner` 在發出 fetch 前讀取這三個欄位,之後從 body 中剔除(不迴傳給下游)。
---
## 七、向後相容
- 現有 `BUILTIN_API_RECIPES`gmail, google_sheets, telegram, line_notify**不動**
- 現有 `BUILTIN_CREDENTIALS_MAP` **不動**
- auth recipe 解析在 component-loader step 5.5(新增),在 step 6 KV recipe 和 step 7 builtin 之前
-`auth_recipe:{service}` 不存在 → 繼續往下走,行為與現在完全相同
---
## 八、新增/修改的檔案
| 檔案 | 類型 | 說明 |
|---|---|---|
| `cypher-executor/src/routes/recipes.ts` | 修改 | 加 `AuthRecipeDefinition` 型別、`resolveAuthRecipe``/auth-recipes` CRUD routes |
| `cypher-executor/src/actions/credential-injector.ts` | 修改 | 加 auth recipe 分支:static_key + service_account |
| `cypher-executor/src/lib/jwt-signer.ts` | 新增 | Google JWT signing via crypto.subtle |
| `cypher-executor/src/lib/component-loader.ts` | 修改 | step 5.5 auth recipe lookup + `makeAuthRecipeRunner` |
| `cypher-executor/src/lib/auth-recipe-seeds.ts` | 新增 | 20 個常用服務的 auth recipe 定義 |
| `cli/src/commands/auth-recipe.ts` | 新增 | `acr auth-recipe list/info/scaffold` |
| `cli/src/commands/parts.ts` | 修改 | `cmdPartsScaffold` fallback 到 auth recipe |
| `cli/src/index.ts` | 修改 | 註冊 auth-recipe 指令 |
---
## 九、封測前預計的 Auth Recipe 清單(20 個)
### static_key 類(~80% 服務)
| service | 認證方式 | credential key |
|---|---|---|
| `notion` | Bearer token (header) | `notion_token` |
| `slack` | Bot Token (Bearer) | `slack_bot_token` |
| `github` | PAT (Bearer) | `github_token` |
| `openai` | API key (Bearer) | `openai_api_key` |
| `anthropic` | API key (x-api-key) | `anthropic_api_key` |
| `airtable` | PAT (Bearer) | `airtable_token` |
| `discord` | Bot token ("Bot TOKEN") | `discord_bot_token` |
| `stripe` | Secret key (Bearer) | `stripe_secret_key` |
| `twilio` | AccountSid + AuthToken (Basic Auth) | `twilio_account_sid`, `twilio_auth_token` |
| `sendgrid` | API key (Bearer) | `sendgrid_api_key` |
| `hubspot` | Private App token (Bearer) | `hubspot_token` |
| `linear` | API key (Bearer) | `linear_api_key` |
| `shopify` | Admin API token (X-Shopify-Access-Token) | `shopify_access_token` |
| `resend` | API key (Bearer) | `resend_api_key` |
| `supabase` | Service role key (Bearer + apikey) | `supabase_service_key` |
| `typeform` | PAT (Bearer) | `typeform_token` |
| `jira` | API token + email (Basic Auth) | `jira_api_token`, `jira_email` |
### service_account 類(Google 家族,JWT signing
| service | scopes | credential key |
|---|---|---|
| `google_sheets_sa` | spreadsheets | `google_service_account` |
| `google_gmail_sa` | gmail.send | `google_service_account` |
| `google_drive_sa` | drive | `google_service_account` |
> 注意:三個 Google 服務可共用同一個 `google_service_account` credential,只是 scope 不同。
---
## 十、實作進度
### Server (cypher-executor)
- [x] `AuthRecipeDefinition` 型別 + `resolveAuthRecipe`
- [x] `/auth-recipes` CRUD routes
- [x] `injectFromAuthRecipe` — static_key primitive
- [x] `lib/jwt-signer.ts` — Google JWT via crypto.subtle
- [x] `injectFromAuthRecipe` — service_account primitive
- [x] `makeAuthRecipeRunner` in component-loader
- [x] step 5.5 in createComponentLoader
- [x] auth-recipe-seeds.ts (20 services)
- [x] seed script / deploy seeds to KV2026-04-19 全部 ✅)
### CLI (arcrun)
- [x] `commands/auth-recipe.ts` — list / info / scaffold
- [x] 更新 `commands/parts.ts` — scaffold fallback
- [x] 更新 `index.ts` — 註冊指令
- [x] 版本升 1.1.0
- [x] npm publisharcrun@1.1.0
### 驗證
- [ ] notion (static_key) 端對端
- [ ] google_sheets_sa (service_account) 端對端
- [ ] 舊有 google_sheets builtin 向後相容確認
---
## 十一、長期演進:TinyGo WASM Primitive(封測後)
> 參考:`docs/user_requirements/arcrun/credential_parts.md`
**目前封測版**Layer 2 primitive 邏輯在 `cypher-executor` TypeScript 中實作(`credential-injector.ts`)。
**長期目標**:四個 primitive 各自編譯為獨立 TinyGo WASM,取代現有 TS 實作:
```
arcrun/registry/components/auth_static_key/ ← TinyGo WASM
arcrun/registry/components/auth_oauth2/ ← TinyGo WASM
arcrun/registry/components/auth_service_account/ ← TinyGo WASM
arcrun/registry/components/auth_mtls/ ← TinyGo WASM
```
每個 primitive 實作統一 interface`Authenticate` / `NeedsRefresh` / `Refresh` / `Test`)。
切換時 `cypher-executor``injectFromAuthRecipe` 改為呼叫對應 WASM,邏輯不變。
**何時做**:封測驗證完成、TinyGo crypto 支援確認後(特別是 RS256/ES256 JWT signing)。
在此之前,**不建立任何 TypeScript SDK 或 Python SDK 來包裝 credential 邏輯**。
### 禁止的做法
- ❌ 建立 `js-sdk/``python-sdk/` 包裝 credential 加解密
- ❌ 在 client 端重實作 AES-GCM encrypt/decrypt
- ❌ 用 TypeScript 重寫已計劃用 TinyGo 實作的 primitive 邏輯
@@ -0,0 +1,178 @@
# Design Document: Credential Primitives TS → WASM 改寫
## Overview
`cypher-executor` 中以 TypeScript 實作的 credential 注入邏輯,改寫為 4 個獨立的 WASM 零件。這是 `credential_parts.md` 長期規格的實現,不再是「未來 Phase」。
**動機**:TS 實作無法在地端(workerd)和邊緣端(Wazero)執行。WASM 零件跨 runtime 可攜,符合 u6u 三層部署架構。
**嚴格規範(richblack 2026-04-19 確認)**cypher-executor TS **完全不實作**任何 credential / auth / template / JWT / 解密邏輯。所有業務邏輯必須在 TinyGo WASM 零件內。TS 僅負責 HTTP routing + 呼叫 WASM + host function 提供 runtime primitivecrypto.subtle / KV / fetch)。
---
## 現有 TS 實作(要刪除的)
| 檔案 | 功能 | 對應 WASM Primitive |
|------|------|---------------------|
| `credential-injector.ts``injectFromAuthRecipe()` | static_key template 展開 | `auth_static_key` |
| `credential-injector.ts` — service_account 分支 | JWT signing + token exchange | `auth_service_account` |
| `credential-injector.ts``decryptCredential()` | AES-GCM 解密 | host function(所有 primitive 共用) |
| `credential-injector.ts``interpolateTemplate()` | `{{secret.KEY}}` 替換 | 內建在各 primitive |
| `jwt-signer.ts``exchangeGoogleJwt()` | PEM→PKCS8→RS256→token | `auth_service_account` |
| `component-loader.ts` — BUILTIN_API_RECIPES | gmail/telegram/line/gsheets 寫死邏輯 | 刪除,改用 auth recipe + `http_request` 零件 |
| `credential-injector.ts` — BUILTIN_CREDENTIALS_MAP | 舊路徑 flat injection | 刪除,統一走 auth recipe |
| `arcrun/credentials/` | 重複的 credentials Worker | 刪除,路由已在 cypher-executor |
---
## 4 個 WASM Primitive 設計
### 統一 I/O 介面(stdin/stdout JSON
```
stdinWorker → WASM):
{
"action": "authenticate" | "needs_refresh" | "refresh" | "test",
"api_key": "ak_xxx", // 租戶識別,用來組 KV key
"service": "openai", // 對應 auth_recipe:{service}
"request": { "method": "GET", "url": "/path", "headers": {}, "body": null }
}
WASM 內部流程:
1. recipeJSON = kv_get("auth_recipe:" + service)
2. 依 recipe.required_secrets 逐一 kv_get("{api_key}:cred:{name}") → {encrypted, iv}
3. secrets[name] = crypto_decrypt(encrypted, iv)
4. service_accountcrypto_sign_rs256(jwt, pkcs8) + http_request 換 token
5. 展開 recipe.inject 的 {{secret.X}} / {{runtime.X}} 模板
stdoutWASM → Worker):
{
"success": true,
"auth_headers": { "Authorization": "Bearer xxx" },
"auth_query": {},
"auth_body": {},
"runtime": { ... updated runtime state,供下次 refresh 用 }
}
```
### auth_static_key
**位置**`arcrun/registry/components/auth_static_key/`
**語言**TinyGo 或 AssemblyScript
功能:
1. 讀取 `recipe.inject.header/query/body` 模板
2.`secrets` 展開 `{{secret.KEY}}` 模板
3. 回傳 `auth_headers` / `auth_query` / `auth_body`
涵蓋:~80% 服務(Bearer token, API Key, Basic Auth, custom header
### auth_service_account
**位置**`arcrun/registry/components/auth_service_account/`
**語言**TinyGo 或 AssemblyScript
功能:
1.`secrets.service_account_json` 解析 private key
2. JWT signingRS256PEM→PKCS8→sign
3. POST token exchange endpoint → 取得 access_token
4. 展開 `{{runtime.access_token}}` 模板
**crypto 考量**
- TinyGo 的 `crypto/rsa` + `crypto/x509` 支援有限
- 若 TinyGo 不支援 RS256:使用 host function 讓 Worker 的 `crypto.subtle` 代簽
- 或改用 AssemblyScript(有 as-crypto 套件)
### auth_oauth2(新建)
**位置**`arcrun/registry/components/auth_oauth2/`
功能:
1. `needs_refresh`:檢查 `runtime.expires_at` 是否過期
2. `refresh`:用 `runtime.refresh_token` + `secrets.client_secret` 換新 token
3. `authenticate`:展開 `{{runtime.access_token}}` 到 headers
### auth_mtls(新建)
**位置**`arcrun/registry/components/auth_mtls/`
功能:
1.`secrets` 讀取 client cert + key
2. 回傳 TLS 設定(由 Worker runtime 執行實際 mTLS handshake
---
## cypher-executor 改動
### 保留(TS routing 層)
- `routes/credentials.ts` — HTTP CRUD for credentials(接收加密的 payload
- `routes/recipes.ts` — HTTP CRUD for auth recipes
- `routes/auth.ts` — OAuth flow routing
- `graph-executor.ts` — workflow 執行排程
- `lib/wasi-shim.ts` — WASM runtime + host functions(加解密 / KV / 簽章 / HTTP 實際由 `crypto.subtle` / env binding / fetch 執行,但**呼叫時機由 WASM 決定**)
### 修改
- `actions/credential-injector.ts`**整檔刪除**,改為新檔 `actions/auth-dispatcher.ts`(約 30 行):
1.`resolveAuthRecipe(componentId)` 取得 `primitive` 名稱(static_key / service_account / oauth2 / mtls
2. 載入對應的 `auth_{primitive}.wasm`
3. 送 stdin`{ action, api_key, service, request }`**不送 secrets、不送 recipe plaintext**
4. WASM 透過 host function 自行 `kv_get` 讀 recipe + 加密 secret`crypto_decrypt` 解密
5. 讀 stdout → 合併 `_auth_headers` / `_auth_query` / `_auth_body` 進 ctx
- `lib/component-loader.ts`**刪除 `BUILTIN_API_RECIPES`**(含 http_request / gmail / telegram / line_notify / google_sheets 的 TS 實作),全部改走 WASM runner。每個 `.wasm` 零件都已編譯並以獨立 Worker 部署(`{canonical-id-kebab}.arcrun.dev`)。loader 新增的「WASM runner」路徑就是「canonical_id → HTTP URL 查表後 fetch」,**不做** WASM instantiate。
- **R2 動態注入 WASM 路徑作廢**richblack 2026-04-19 確認:CF workerd 無法以 R2 物件臨時 instantiate WASM)。用戶自製零件(Phase 5)同樣走「產生獨立 Worker」流程,不從 R2 讀。
### 刪除
- `lib/jwt-signer.ts` — 整檔刪除,RS256 簽章移入 `auth_service_account` WASM(透過 host function `crypto_sign_rs256`
- `credential-injector.ts` 整檔刪除(見上)
- `component-loader.ts``BUILTIN_API_RECIPES` 整段刪除
- `BUILTIN_CREDENTIALS_MAP` 已在 `credential-injector.ts` 內,隨檔一併刪
---
## Host FunctionsWASM ↔ Worker 的橋接)
auth primitive WASM 需要呼叫外部能力時,透過 host function。全部放 `u6u` namespace。**錯誤回傳非零 uint32;成功 = 0 且把結果寫入 `outPtr` 指向的 buffer**。
| Host Function | TinyGo 簽章 | 用途 |
|---|---|---|
| `http_request` | `(urlPtr/Len, methodPtr/Len, headersPtr/Len, bodyPtr/Len, outPtr, outLenPtr) uint32` | HTTP 請求(已實作) |
| `kv_get` | `(keyPtr, keyLen, outPtr, outLenPtr) uint32` | 讀 KV。Worker 依 key 前綴路由到 `CREDENTIALS_KV` / `RECIPES` |
| `crypto_decrypt` | `(encPtr, encLen, ivPtr, ivLen, outPtr, outLenPtr) uint32` | AES-GCM 解密。encryption key 由 Worker 從 `env.ENCRYPTION_KEY` 內部讀取,**永遠不暴露給 WASM** |
| `crypto_sign_rs256` | `(dataPtr, dataLen, pkcs8Ptr, pkcs8Len, outPtr, outLenPtr) uint32` | Worker 用 `crypto.subtle.sign('RSASSA-PKCS1-v1_5' + SHA-256)`private key 以 PKCS8 bytes 傳入 |
這些 host function 在 `lib/wasi-shim.ts` 中以 WASI import 提供。
### 安全邊界
- `ENCRYPTION_KEY` 只在 `crypto_decrypt` host function 內部使用,**絕不**經 stdin / 回傳值 / 任何路徑傳給 WASM
- `api_key` 經 stdin 傳入 WASM(讓 WASM 自己組 `{api_key}:cred:{name}` KV key
- `kv_get` 在 Worker 側檢查 key 前綴:
- `auth_recipe:*` → 讀 `RECIPES`
- `{api_key}:cred:*` → 讀 `CREDENTIALS_KV`,且 `{api_key}` 必須等於 stdin 傳入的 api_key(防越權)
- 其他前綴 → 回傳錯誤
---
## 關於解密位置
採用**方案 B(唯一方案)**WASM 透過 host function `crypto_decrypt()` 自行解密。
- cypher-executor TS 完全不解密、不知道 plaintext
- `ENCRYPTION_KEY` 永遠留在 Worker host function 內
- WASM 知道要解哪份 ciphertext(經 `kv_get` 讀到的 `{encrypted, iv}`),但拿不到 encryption key
- 這樣 TS 層完全沒有零件業務邏輯,符合 CLAUDE.md §禁止行為 1/6
(歷史註記:曾規劃方案 A「TS 先解密再送 stdin」,已廢棄 — 違反「TS 不得實作零件邏輯」。)
---
## 不做的事
- ❌ 不改 recipe YAML schema — 沿用現有格式
- ❌ 不改 KV 儲存結構 — `auth_recipe:{service}` / `{api_key}:cred:{name}` 不變
- ❌ 不改 SDK API — SDK 仍是 HTTP thin wrapper
- ❌ 不建新的 Worker — 在 cypher-executor 內完成
@@ -0,0 +1,165 @@
# Implementation Tasks: Credential Primitives TS → WASM
**嚴格規範(richblack 2026-04-19**cypher-executor TS 不得實作任何 credential / auth / template / JWT / 解密邏輯。全部走 TinyGo WASM + host functions(方案 B)。
**封測狀態**:推遲(richblack 2026-04-19 決定)。先完成 Phase 1-3 清除違規 TS,再啟動封測。
---
## Phase 0:核心合併(u6u-core → arcrun
- [x] 0.1 把 `u6u-core/builtins/` 搬到 `arcrun/builtins/`
- [x] 0.2 確認 `arcrun/registry/components/` 21 個零件的 contract.yaml 完整(21/21
- [x] 0.3 刪除 `arcrun/credentials/` 整個目錄(重複,credential route 已在 cypher-executor
- [x] 0.4 更新 `arcrun/cypher-executor/wrangler.toml`:確認 CREDENTIALS_KV binding 存在
- [x] 0.5 刪除 `matrix/u6u-core/` 整個目錄(2026-04-19 完成,只剩 credentials/ 已被 cypher-executor 取代)
- [x] 0.6 在 `cypher-executor/src/lib/wasi-shim.ts` 新增 host functions
- `u6u.kv_get(keyPtr, keyLen, outPtr, outLenPtr) uint32` — 依 key 前綴路由到 `CREDENTIALS_KV` / `RECIPES`,越權檢查 api_key
- `u6u.crypto_decrypt(encPtr, encLen, ivPtr, ivLen, outPtr, outLenPtr) uint32` — 用 `env.ENCRYPTION_KEY` + `crypto.subtle` AES-GCM 解密;key 不暴露給 WASM
- `u6u.crypto_sign_rs256(dataPtr, dataLen, pkcs8Ptr, pkcs8Len, outPtr, outLenPtr) uint32``crypto.subtle.sign('RSASSA-PKCS1-v1_5' + SHA-256)`
- 2026-04-19 完成:wasi-shim.ts 新增 `createArcrunHostFunctions(env, apiKey)` factory,集中 AES-GCM 解密 + RSA sign + KV 前綴路由越權檢查。WASI imports 的 u6u namespace wiring 本來就已接好(只是當時沒有實作 factory)。typecheck 通過。
- [x] 0.7 在 `cypher-executor/src/lib/component-loader.ts` 新增 WASM runner 路徑:
- 所有 WASM 零件(含 auth primitive、API 零件、未來用戶自製)一律走 HTTP URL(`{canonical-id-kebab}.arcrun.dev`)到獨立 Worker
- **R2 動態注入路徑作廢**richblack 2026-04-19 確認:CF workerd 不支援以 R2 物件臨時 instantiate WASM;用戶自製零件同樣走「產生獨立 Worker」流程,不走 R2)
- cypher-executor 本身**不做** WASM instantiate,也不直接呼叫 `createArcrunHostFunctions`;那個 factory 是**零件 Worker 側**`.component-builds/{name}/src/index.ts`)用的,在 Phase 1 建立 auth_static_key Worker 時接上
- 2026-04-19 完成:`component-loader.ts` 新增 `WASM_HTTP_RUNNER_IDS`10 個 canonical_id6 個 API 零件 + 4 個 auth primitive)+ `wasmWorkerUrl()` URL 慣例輔助函數;解析鏈新增為第 8 層(放在 `BUILTIN_API_RECIPES` fallback 之後,避免 Phase 3 尚未完成時 API 零件 Worker 未部署造成 404Phase 3 刪除 `BUILTIN_API_RECIPES` 後,API 零件會自然落到此層)。auth primitive 從此層進入。`tsc --noEmit` 通過。
---
## Phase 1auth_static_key WASM(優先,涵蓋 80% 服務)
方案 BWASM 自行讀 KV + 解密,TS 不碰 plaintext。
- [x] 1.1 建立 `arcrun/registry/components/auth_static_key/` 目錄
- [x] 1.2 寫 `component.contract.yaml`input: `{action, api_key, service, request}` → output: `{success, auth_headers, auth_query, auth_body, runtime}`
- [x] 1.3 實作 `main.go`TinyGo):
- 宣告 host imports`kv_get` / `crypto_decrypt`static_key 不需要 http_request
- 從 stdin 讀 `{action, api_key, service}`
- `kv_get("auth_recipe:" + service)` → recipe JSON → 驗證 `primitive == "static_key"`
- 對每個 non-optional `recipe.required_secrets``kv_get("{api_key}:cred:{name}")``{encrypted, iv}``crypto_decrypt` → plaintext
- 展開 `{{secret.X}}` / `{{runtime.X}}` 模板於 `inject.header/query/body`;未知 key 展空字串(與 TS parity);其他 namespace 的 `{{...}}` 原樣保留
- 輸出 stdout JSON `{success, auth_headers, auth_query, auth_body, runtime}`
- [x] 1.4 `tinygo build -o auth_static_key.wasm -target=wasi main.go` — 2026-04-19 編譯通過(1.1MB,在 contract 限制 2MB 內)
- [🔄] 1.5 建立 `.component-builds/auth_static_key/`(用 `component-worker-template`)並部署到 `auth-static-key.arcrun.dev`
- 2026-04-20 完成**建置**部分:`.component-builds/auth_static_key/{wrangler.toml, package.json, tsconfig.json, src/index.ts, component.wasm}` 全數到位
- 方案 A:`src/index.ts` 直接 import `../../../cypher-executor/src/lib/wasi-shim``createWasiShim` + `createArcrunHostFunctions`(以 `ArcrunHostEnv` 結構型別相容);AES 解密邏輯仍只存在於 wasi-shim.ts 一處(rule 02 §2.2)
- 綁同組 KV:CREDENTIALS_KV (e7f4320f88d343f187e35e3543dd74c9) / RECIPES (9cf9db905c6241f78503199e58b2ffe0);ENCRYPTION_KEY 走 `wrangler secret put`
- `wrangler deploy --dry-run` 通過(1192 KiB, 419 KiB gzip);實際 `wrangler deploy` + `secret put ENCRYPTION_KEY` 留給 richblack 執行
- [x] 1.6 建立 `auth-dispatcher.ts`(取代 `credential-injector.ts`):查 auth recipe → HTTP POST 到對應 auth primitive URL → 合併 `_auth_headers` 進 ctx
- 2026-04-20 完成:`cypher-executor/src/actions/auth-dispatcher.ts` 新建,export `tryAuthDispatch(componentId, input, env, apiKey)`
- 流程:查 `resolveAuthRecipe` → primitive 在 `SUPPORTED_PRIMITIVES`(目前只有 `static_key`)→ fetch `wasmWorkerUrl('auth_static_key')` → 合併 `_auth_headers/_auth_query/_auth_body`
- 自引用防護:`AUTH_PRIMITIVE_IDS` set 排除 4 個 `auth_*` componentId
- `wasmWorkerUrl``component-loader.ts` export 出來共用
- `graph-executor.ts` 改為:先試 `tryAuthDispatch`(新路徑),沒命中 fallback 到舊 `injectCredentials`(Phase 1.9 刪)
- 檢查過 auth-dispatcher.ts 無 `crypto.subtle` / `interpolate` / `{{secret.` / hard-code API URL,符合 rule 02 §2.2
- `tsc --noEmit` 通過
- [ ] 1.7 端對端測試:openai recipe → 成功注入 `Authorization: Bearer <openai_key>`
- [ ] 1.8 端對端測試:twilio recipeBasic Auth)→ 成功注入
- [ ] 1.9 **刪除 `credential-injector.ts` 整檔**`decryptCredential` / `decryptSecrets` / `interpolateTemplate` / `BUILTIN_CREDENTIALS_MAP` 全刪)
---
## Phase 2auth_service_account WASM
- [🔄] 2.1 建立 `arcrun/registry/components/auth_service_account/` 目錄
- [🔄] 2.2 寫 `component.contract.yaml`
- [🔄] 2.3 實作 `main.go`
- 從 stdin 讀 `{api_key, service}` + `kv_get` 拿 recipe + 解密 SA JSON
- 解析 SA JSON 取 `client_email` / `private_key`PEM
- PEM → PKCS8 bytes(純 Gobase64 decode + 去 header/footer
- 組 JWT header + payloadbase64url),呼叫 `crypto_sign_rs256(signingInput, pkcs8)` 拿 signature
- 組完整 JWT → `http_request` POST `token_uri` → 拿 `access_token`
- 展開 `{{runtime.access_token}}` 模板
- [x] 2.4 `tinygo build -o auth_service_account.wasm -target=wasi main.go` — 2026-04-20 編譯通過(1.1MB,在 contract 限制 2MB 內)
- [x] 2.5 建立 `.component-builds/auth_service_account/` 並部署到 `auth-service-account.arcrun.dev`
- 2026-04-20 完成**建置**部分:`.component-builds/auth_service_account/{wrangler.toml, package.json, tsconfig.json, src/index.ts, component.wasm}` 全數到位
- 方案 A:`src/index.ts` 重用 `createArcrunHostFunctions` 提供 kv_get/crypto_decrypt/crypto_sign_rs256,**額外加 `http_request` host function**(token exchange 用,非 crypto 不受 §2.2 約束)。http_request 直接回 response body 原文(WASM 端 json.Unmarshal 找 access_token)
- 綁同組 KV:CREDENTIALS_KV / RECIPES;ENCRYPTION_KEY 走 `wrangler secret put`
- `wrangler deploy --dry-run` 通過(1248 KiB, 440 KiB gzip);實際 `wrangler deploy` + `secret put ENCRYPTION_KEY` 留給 richblack 執行
- `auth-dispatcher.ts``SUPPORTED_PRIMITIVES` 加入 `'service_account'`,workflow 用 google SA recipe 會自動走新 WASM 路徑
- [ ] 2.6 端對端測試:google_sheets_sa recipe → 成功取得 access_token → 注入 header
- [x] 2.7 **刪除 `lib/jwt-signer.ts` 整檔** — 2026-04-20 完成
- `cypher-executor/src/lib/jwt-signer.ts` 已刪除(RS256 JWT 邏輯移入 `auth_service_account.wasm`)
- `credential-injector.ts` 原 line 23 `import { exchangeGoogleJwt }` 移除
- `credential-injector.ts` 原 line 140-150 service_account 分支改為 throw(任何 service_account recipe 已被 auth-dispatcher 攔截;這條 TS fallback 若被觸發即表架構錯亂,直接爆錯比沈默解密更安全)
- `cypher-executor` tsc --noEmit 通過
---
## Phase 3:清理 component-loader 的 TS 實作(全刪)
目標:`BUILTIN_API_RECIPES` 整段刪除,所有服務走 WASM runnerHTTP URL 路徑)。
- [x] 3.1 確認 `http_request.wasm` / `gmail.wasm` / `telegram.wasm` / `line_notify.wasm` / `google_sheets.wasm` 都在 `registry/components/` 且可執行 — 2026-04-20 驗證 6 個(含 cron)全數存在,main.go + .wasm 齊備
- [x] 3.2 確認上述零件 Worker 都已部署(`{name}.arcrun.dev` 可用) — 2026-04-20 完成**建置**部分
- 6 個 Worker 建置到位:`.component-builds/{http_request, gmail, telegram, line_notify, google_sheets, cron}/{wrangler.toml, package.json, tsconfig.json, src/index.ts, component.wasm}`
- 方案 A:5 個需 http_request 的零件(http_request/gmail/telegram/line_notify/google_sheets)`src/index.ts` 共用模板;cron 是純計算不註冊 host function
- 全部透過 `createWasiShim` 複用 cypher-executor/src/lib/wasi-shim.ts(rule 02 §2.2 邊界)
- 6 個 `wrangler deploy --dry-run` 全通過(~1.17 MB / ~413 KB gzip 每個);實際 `wrangler deploy` 留給 richblack 執行
- [x] 3.3 `component-loader.ts` 的內建路徑改為查對應 Worker URL → HTTP POST — 2026-04-20 完成
- 原本第 7 層是 `BUILTIN_API_RECIPES` fallback、第 8 層是 `WASM_HTTP_RUNNER_IDS` (HTTP URL);兩層合併為第 7 層 `WASM_HTTP_RUNNER_IDS` 直接走 `makeHttpRunner(wasmWorkerUrl(id))`
- 解析鏈新編號 1-8,順序不變(外部 URL → recipe hash → component hash → R2 → Service Binding → auth recipe runner → WASM HTTP runner → 找不到)
- [x] 3.4 **刪除 `BUILTIN_API_RECIPES` 整個 Record**`http_request` / `gmail` / `telegram` / `line_notify` / `google_sheets` / `cron` 的 TS 實作全刪) — 2026-04-20 完成
- `cypher-executor/src/lib/component-loader.ts` 原 line 253-326 `BUILTIN_API_RECIPES` 常數 + fallback lookup 全刪(約 80 行)
- 全域搜尋確認:`gmail.googleapis.com/...messages/send` / `api.telegram.org/bot.*sendMessage` / `sheets.googleapis.com/v4/spreadsheets` / `notify-api.line.me/api/notify` 在 cypher-executor TS 中已不存在(auth-recipe-seeds.ts 的 `base_url` 是 recipe 資料欄位,不是 hard-coded API call)
- `cypher-executor` tsc --noEmit 通過
- [ ] 3.5 端對端測試:workflow 用 gmail auth recipe + gmail.wasm Worker → 成功發信
- [ ] 3.6 端對端測試:workflow 用 http_request.wasm Worker + auth_static_key 注入 → 成功呼叫任意 API
---
## Phase 4auth_oauth2 + auth_mtls WASM(封測後)
- [ ] 4.1 建立 `arcrun/registry/components/auth_oauth2/`
- [ ] 4.2 實作:`needs_refresh` / `refresh` / `authenticate` 三個 action
- [ ] 4.3 建立 `arcrun/registry/components/auth_mtls/`
- [ ] 4.4 實作:輸出 TLS cert/key(實際 mTLS handshake 由 Worker runtime 執行,WASM 無法做 socket
---
## Phase 5:封測啟動門檻 — 核心穩定驗證
**全部通過才能啟動封測**
- [ ] 5.1 所有 20 個 auth recipe seed 可正常運作(static_key 17 個 + service_account 3 個)
- [ ] 5.2 `cypher-executor/src/actions/credential-injector.ts` **不存在**
- [ ] 5.3 `cypher-executor/src/lib/jwt-signer.ts` **不存在**
- [ ] 5.4 `cypher-executor/src/lib/component-loader.ts``BUILTIN_API_RECIPES` / `BUILTIN_CREDENTIALS_MAP`
- [ ] 5.5 `cypher-executor/src/` 全域搜尋 `crypto.subtle.decrypt` 只出現在 `wasi-shim.ts``crypto_decrypt` host function
- [ ] 5.6 `cypher-executor/src/` 全域搜尋 `crypto.subtle.sign` 只出現在 `wasi-shim.ts``crypto_sign_rs256` host function
- [ ] 5.7 `cypher-executor/src/` 全域搜尋 `interpolate` 回傳 0 筆(template 展開全在 WASM
- [ ] 5.8 全域搜尋 `{{secret\.` / `{{runtime\.` 在 TS 檔案中回傳 0 筆
---
## Phase 6:通用 CI/CD deploy workflow
**背景**(2026-04-20 richblack 決定):現 `.github/workflows/deploy.yml` 只部署 cypher-executor + registry + 已刪除的 credentials,漏掉 Phase 1-3 產出的 8 個 Worker,且硬編碼每個 job 導致未來新增 Worker 都要改 CI。改為**通用掃描式 workflow**:任何含 `wrangler.toml` 的目錄 = 部署單位,改到該目錄下任何檔案 = 觸發重新 deploy。
**關鍵決策**:
- 零件 `.wasm` 由 CI build(不 commit):`registry/components/{name}/main.go` 改動時才重 build,用 timestamp / content hash 判斷
- `.component-builds/{name}/component.wasm` 由 CI 從 `registry/components/{name}/{name}.wasm` 複製產生(deploy 前一步)
- 統一用 pnpm(`.component-builds/*` 本來就是;順勢把 cypher-executor 的 `package-lock.json` 砍了)
- runtime secret(`ENCRYPTION_KEY`)不進 CI,由 richblack 一次性 `wrangler secret put`
- registry Worker 的 `wrangler.toml` 現階段不改(職責是合約管理,與封測無關;`sandboxAcceptance.ts` 的 rule 02 §2.2 審查留到 Phase 5 用戶自製零件啟動時)
### Tasks
- [x] 6.1 改寫 `.github/workflows/deploy.yml`:動態掃描所有含 `wrangler.toml` 的目錄(排除 `node_modules/` + Pages 專案),用 matrix job fanout 部署;分兩層(tier1=`.component-builds/*`,tier2=其他),tier1 全綠後才 tier2(避免 service binding target 未存在)
- [x] 6.2 加上 TinyGo build 步驟:tier1 matrix 一律 setup-tinygo + 從 `registry/components/{name}/main.go` rebuild `.wasm` → copy 到 `.component-builds/{name}/component.wasm`
- [x] 6.3 diff-aware:push 到 main 時比對 `github.event.before..github.sha`,只 deploy 有 diff 的 Worker(含 `registry/components/{name}/` 連動 `.component-builds/{name}/`);`workflow_dispatch` 提供 `force_all` + `only` 選項
- [x] 6.4 統一 pnpm:刪除 `cypher-executor/package-lock.json` + `registry/package-lock.json`;workflow 優先 `pnpm install --frozen-lockfile`,若該目錄無 `pnpm-lock.yaml` 則 fallback 到 `--no-frozen-lockfile`(混合期容錯)
- [x] 6.5 加 `max-parallel: 5` 控制 Workers API rate limit(tier1 和 tier2 各自)
- [ ] 6.6 驗證:`workflow_dispatch` + `force_all=true` 手動跑一次,24 個 Worker(tier1 21 個 + tier2 3 個)全綠(待 richblack 手動觸發)
- [x] 6.7 文件:在 `.claude/rules/` 加一份 `05-deploy-convention.md`(「新增 Worker = 新目錄 + wrangler.toml,不用改 CI」)
---
## Notes
- 方案 B 是唯一方案(方案 A 已廢棄,違反 CLAUDE.md §禁止行為)
- Phase 0.6host functions+ 0.7WASM runner)是 Phase 1-3 的硬前置,必須先做
- 若 TinyGo `encoding/base64` 可用就直接用;若不可用則自行實作(見 gmail/main.go 的 `base64URLEncode`
- `auth_mtls` 的 TLS handshake 無法在 WASM 內做(WASI preview1 沒 socket),只能輸出 cert/key 讓 Worker 在 fetch 時用
- **每個 auth primitive WASM 都是獨立部署的 Worker**(透過 `component-worker-template/`),不是從 R2 動態載入
- Cypher binding = workflow YAML 裡的 URL 清單,不是 Cloudflare service binding
+325
View File
@@ -0,0 +1,325 @@
# arcrun.dev Landing Page — SDD
> **目標**:給工程師一個門面,可以取得 API Key、管理 Key、探索 APISwagger),同時藉此獲得會員 Email。
> **原則**:先快速可用,不追求功能完整。榮譽牆、Python Lib 是後期。
---
## 0. 範圍(這份 SDD 涵蓋)
| 功能 | 說明 |
|---|---|
| 首頁 Hero | 說明 arcrun 是什麼,CTA 取得 API Key |
| OAuth 登入 | Google / GitHub(用自己的 auth recipe — dogfooding |
| API Key 管理 | 查看、Rotate、Revoke |
| Swagger UI | 嵌入 `/api`,讓工程師直接試打 |
| 榮譽牆 `/integrations` | 靜態骨架,先列 20 個 recipe,無動態數字 |
| 中英切換 | `?lang=zh` |
**不在本次範圍**Python lib、Donate 整合、Social Proof 即時數字、貢獻者排行。
---
## 1. 技術選型
### 1.1 框架:Next.jsApp Router
**選 Next.js 而非 Astro 的原因**
- `finally-click` 已有完整 Next.js + OAuth 回調實作,可直接複用模式
- API Key 管理頁有登入態保護需求,Next.js 的 middleware 最直接
- Cloudflare Pages 支援 Next.js`@cloudflare/next-on-pages`
- Astro 在動態路由保護上摩擦較多
### 1.2 部署:Cloudflare Pages
```
arcrun.dev → Cloudflare PagesNext.js
API calls → cypher.arcrun.dev(現有 Worker
```
### 1.3 儲存:現有 cypher-executor CREDENTIALS_KV + 新增 USERS_KV
現有 cypher-executor Worker 已有:
- `CREDENTIALS_KV``{api_key}:cred:{name}` 存 tenant credentials
- `RECIPES`auth recipes
新增需求:
- **USERS_KV**:存 user 帳號,key = `user:{provider}:{provider_user_id}`
- value: `{ email, display_name, api_key, created_at, provider }`
- **SESSIONS_KV**:存 login sessionkey = `sess:{session_id}`
- value: `{ api_key, email, expires_at }`
- TTL = 7 天
兩個 KV 都加到 cypher-executor `wrangler.toml`
### 1.4 OAuth — Dogfooding 自己的 Auth Recipe
登入用 arcrun 自己的 auth recipe
- 不是用 arcrun auth recipe 的 `http_request` runner 去打第三方
- 而是 **直接複用 recipe YAML 裡定義的 OAuth App 設定(client_id/secret**
- Worker 端實作 standard OAuth2 authorization_code flow
**支援提供商(MVP**
- Googlegoogle_drive recipe 的 OAuth App,或另建 arcrun-login Google App
- GitHubgithub recipe 的 OAuth App
登入 OAuth App 與 auth recipe 的 OAuth App **可以是同一個**(只要 scopes 包含 `openid profile email`),但更乾淨的做法是登入用獨立的 Google/GitHub App(只要 email scope),auth recipe 用的是資源存取 App。
**決策:登入用獨立 OAuth App**
- `GOOGLE_CLIENT_ID``GOOGLE_CLIENT_SECRET` — 只申請 `openid profile email`
- `GITHUB_CLIENT_ID``GITHUB_CLIENT_SECRET` — 只申請 `read:user` + `user:email`
- 以 Worker Secret 方式存入 cypher-executor
---
## 2. 頁面結構
```
arcrun.dev/
├── / 首頁(Hero + Code snippet + CTA
├── /login 登入頁(Google / GitHub 按鈕)
├── /auth/callback OAuth callbackPages Function
├── /dashboard API Key 管理(需登入)
├── /api Swagger UI(嵌入 swagger.json
└── /integrations 服務目錄(靜態,20 個 recipe)
```
---
## 3. 登入 / OAuth 流程
### 3.1 流程圖
```
用戶點「Google 登入」
→ GET /auth/google/startWorker 端)
→ redirect 到 Google OAuthstate = random, 存 SESSIONS_KV sess:state:{state} = {provider, redirect_back}
→ 用戶同意
→ GET /auth/callback?code=...&state=...Worker 端)
→ 驗 state
→ 用 code 換 access_tokenPOST google token endpoint
→ 用 token 取 userinfoGET google userinfo
→ upsert USERS_KV user:{provider}:{provider_id} = {email, display_name, api_key, ...}
→ 若新用戶:呼叫現有 /register?email=... 取得 arcrun API Key
→ 建立 sessionSESSIONS_KV sess:{session_id} = {api_key, email, ...}TTL=7d
→ Set-Cookie: arcrun_session={session_id}; HttpOnly; Secure; SameSite=Lax
→ redirect 到 /dashboard
```
### 3.2 「若新用戶取得 API Key」的邏輯
現有 `/register` endpoint 接受 `email` 回傳 `api_key`HMAC 確定性)。
但 landing page 需要的是**真正綁定到用戶帳號的 key**,且用戶可以 rotate/revoke。
**方案:延伸現有 register endpoint**
`/register` 目前:`HMAC(secret, email)` → 確定性 api_key,存 CREDENTIALS_KV
新增邏輯:
1. 若 USERS_KV 已有此 user → 直接用記錄裡的 api_key
2. 若新 user → 呼叫現有 `/register`(保持 HMAC 確定性邏輯)→ 拿到 api_key → 存入 USERS_KV
**好處**:不破壞現有 register 邏輯;登入後的 dashboard 顯示的 key = 現有 key = 封測用的 key。
### 3.3 Rotate / Revoke
- **Rotate**:產生新 UUID v4 key → 更新 USERS_KV 記錄 → 舊 key 失效(透過把新 key 加到 CREDENTIALS_KV,舊 key 的資料都跟著 API Key 命名空間走,所以 credentials 會留在舊 namespace
- 簡化版:Rotate 後顯示提示「您的 workflow credentials 已和舊 Key 分離,請重新設定」
- **Revoke**USERS_KV 記錄 `revoked: true` → Worker middleware 拒絕此 key
---
## 4. API 端點(新增到 cypher-executor
```
GET /auth/google/start → redirect 到 Google OAuth
GET /auth/github/start → redirect 到 GitHub OAuth
GET /auth/callback?code=&state= → 換 token、建立 session
POST /auth/logout → 清 session cookie
GET /me → 回傳當前登入用戶資訊(需 session cookie 或 API Key
PUT /me/api-key/rotate → 產生新 key
DELETE /me/api-key → Revoke(標記撤銷)
```
---
## 5. 前端頁面設計
### 5.1 首頁(/
```
Hero:
Stop fighting OAuth.
One API key. Every service. Works anywhere.
[Get API Key — Free] [View on GitHub]
Before/After:
40 行 OAuth 程式碼 → auth.bind("google_drive")
Code Demo(三個 tab):
Python / JavaScript / HTTPn8n 用戶)
[Get Free API Key] 按鈕
```
### 5.2 登入頁(/login
```
arcrun
登入或建立帳號
[Continue with Google]
[Continue with GitHub]
不需要信用卡。API Key 立即可用。
```
### 5.3 Dashboard/dashboard
```
歡迎,{display_name}
您的 API Key
┌────────────────────────────────┐
│ ak_xxxxxxxxxxxxxxxxxxxx [複製] │
└────────────────────────────────┘
[Rotate Key] [Revoke Key]
使用說明:
Authorization: Bearer {key}
或 X-Arcrun-API-Key: {key}
[登出]
```
### 5.4 Swagger UI/api
- 嵌入 `<SwaggerUIBundle>` JSCDN
- `url: 'https://cypher.arcrun.dev/swagger.json'`(現有 Worker 已有 `/docs` openapi endpoint
- 頂部說明:「這是 arcrun 的原始 API。Python / JS lib 是它的包裝,任何能發 HTTP request 的工具都能直接用。」
### 5.5 服務目錄(/integrations
- 靜態列出 20 個 auth recipe(從 seed data 產生)
- 每個 recipe:名稱、認證方式(static_key / service_account)、所需 credentials
- 「找不到你要的服務?開 PR 貢獻 Recipe」CTA
---
## 6. 檔案結構
```
arcrun/landing/ ← 新 Next.js 專案
├── app/
│ ├── layout.tsx
│ ├── page.tsx 首頁
│ ├── login/
│ │ └── page.tsx
│ ├── dashboard/
│ │ ├── page.tsx
│ │ └── middleware.ts (或 root middleware
│ ├── api-docs/
│ │ └── page.tsx Swagger UI
│ └── integrations/
│ └── page.tsx
├── middleware.ts 保護 /dashboard(讀 cookie
├── lib/
│ └── auth.ts session helpers
├── public/
├── next.config.ts
├── package.json
└── wrangler.toml CF Pages 設定
```
cypher-executor 新增:
```
arcrun/cypher-executor/src/routes/
├── auth.ts ← 新增(OAuth start/callback/logout/me
```
cypher-executor wrangler.toml 新增:
```toml
[[kv_namespaces]]
binding = "USERS_KV"
id = "<to be created>"
[[kv_namespaces]]
binding = "SESSIONS_KV"
id = "455d0505c7534883a4d4985ab8295857"
```
---
## 7. 環境變數 / Secrets
### cypher-executorWorker Secrets
```
GOOGLE_CLIENT_ID Google OAuth App client_id(僅 openid profile email scope
GOOGLE_CLIENT_SECRET Google OAuth App client_secret
GITHUB_CLIENT_ID GitHub OAuth App client_idread:user + user:email scope
GITHUB_CLIENT_SECRET GitHub OAuth App client_secret
SESSION_SECRET 隨機 32 bytes,用於 HMAC session ID(或直接用 UUID
```
### landingPages Environment Variables
```
NEXT_PUBLIC_API_BASE https://cypher.arcrun.dev
```
---
## 8. 實作步驟(Checklist
### Phase 1cypher-executor 後端擴充
- [x] `wrangler kv:namespace create USERS_KV` → 填入 wrangler.toml (id: 25bef01d079148919578894434d58c4d)
- [x] `wrangler kv:namespace create SESSIONS_KV` → 填入 wrangler.toml (id: 455d0505c7534883a4d4985ab8295857)
- [x] 建立 `arcrun/cypher-executor/src/routes/auth.ts`
- [x] GET `/auth/google/start`
- [x] GET `/auth/github/start`
- [x] GET `/auth/callback`(換 token → userinfo → upsert USERS_KV → 建 session → Set-Cookie → redirect
- [x] POST `/auth/logout`
- [x] GET `/me`(讀 session cookie 或 API Key header
- [x] PUT `/me/api-key/rotate`
- [x] DELETE `/me/api-key`revoke
- [x]`src/index.ts` 掛載 `authRouter`
- [ ] `wrangler secret put GOOGLE_CLIENT_ID` 等 4 個 secrets ← **用戶需自建 Google/GitHub OAuth App**
- [x] `wrangler deploy` ← 已部署(Worker version 7877857b
### Phase 2Next.js Landing 專案
- [x] `npx create-next-app@latest arcrun/landing --typescript --tailwind --app`
- [x] 設定 `@cloudflare/next-on-pages`Next.js 15 + .npmrc legacy-peer-deps
- [ ] 建立 `middleware.ts`(保護 /dashboard,讀 `arcrun_session` cookie)← 待做
- [x] 首頁(`app/page.tsx`):Hero + Code Demo tab + CTA
- [x] 登入頁(`app/login/page.tsx`):Google / GitHub 按鈕(href 到 cypher.arcrun.dev/auth/google/start
- [x] Dashboard`app/dashboard/page.tsx`):顯示 API KeyRotate / Revoke 按鈕
- [x] Swagger UI`app/api-docs/page.tsx`):client component,動態 import swagger-ui CDN
- [x] 服務目錄(`app/integrations/page.tsx`):靜態,列 20 個 recipe
- [ ] 中英切換 ← 低優先,可延後
- [x] `wrangler pages deploy` → https://42a8d302.arcrun-landing.pages.dev
- [ ] Cloudflare dashboard 設定 arcrun.dev custom domain → arcrun-landing Pages project
### Phase 3:驗收(待 OAuth Secrets 填入後)
- [ ] Google / GitHub OAuth 完整流程(登入 → dashboard → 看到 key
- [ ] Rotate:新 key 出現
- [ ] Revoke:舊 key 的 API 呼叫回傳 401
- [ ] Swagger UI 正常載入,可試打 `/health`
- [x] `/integrations` 正確列出 20 個服務
---
## 9. 待決事項(開始實作前確認)
| 問題 | 預設決策 |
|---|---|
| Google OAuth App 是否要另建(只有 email scope? | 是,另建;auth recipe 的 App 不動 |
| Rotate 後舊 credentials 是否遷移? | 不遷移,顯示提示 |
| Domain arcrun.dev 是否已購入且在 Cloudflare | 假設是(wrangler.toml 有設 zone_name |
| 登入後 redirect 預設到 /dashboard | 是,可從 `?redirect=` 覆寫 |
@@ -0,0 +1,281 @@
# Design Document: arcrun SDK Libraries + Website
## Overview
本設計涵蓋 arcrun 的三個新增交付物:
1. Python SDK lib`pip install arcrun`
2. JS/TS SDK lib`npm install arcrun``@arcrun/sdk`
3. arcrun.dev 網站完善(零件列表、recipe 列表、登入管理)
**設計原則:修改不重建。** SDK 是 `cypher.arcrun.dev` HTTP API 的 thin wrapper。不在 client 端重新實作任何 server 端已有的邏輯(workflow 執行、credential 注入、auth recipe 解析)。唯一在 client 做的是 AES-GCM 加密(因為 server 的 POST /credentials 期望收到加密後的 payload)。
---
## Architecture
### 系統關係圖
```
使用者程式碼
├── CLIacr → cypher.arcrun.devHTTP API
├── Python SDKarcrun → cypher.arcrun.devHTTP API
└── JS SDKarcrun / @arcrun/sdk → cypher.arcrun.devHTTP API
arcrun.dev 網站(Next.js / Cloudflare Pages
├── /login → /auth/google/start, /auth/github/startcypher.arcrun.dev
├── /dashboard → /me, /me/api-key/rotatecypher.arcrun.dev
├── /integrations → /auth-recipescypher.arcrun.dev
└── /components → /recipes + 靜態零件清單(embedded
cypher.arcrun.devCloudflare Worker — cypher-executor,不改)
├── POST /credentials ← 接收 { name, encrypted, iv }
├── GET /credentials ← 列出 credential 名稱
├── DELETE /credentials/:name ← 刪除 credential
├── GET /auth-recipes ← 列出 20 個 auth recipe
├── GET /auth-recipes/:service ← 單一 recipe 詳情
├── POST /webhooks/named ← 部署 workflow
├── POST /webhooks/named/:name/trigger ← 觸發 workflow
├── GET /webhooks/named ← 列出 workflow
├── POST /register ← 註冊取得 API Key
├── GET /me ← 當前用戶資訊
└── /auth/* ← OAuth 流程
```
---
## Python SDK`arcrun/python-sdk/`
### 目錄結構
```
arcrun/python-sdk/
├── pyproject.toml ← hatchling build, name="arcrun", deps=[httpx>=0.27, cryptography>=42]
├── README.md
└── arcrun/
├── __init__.py ← from .client import Arcrun
├── client.py ← Arcrun class(主入口)
├── crypto.py ← AES-GCM 加密(client 端,用 cryptography 套件)
├── creds.py ← CredentialsClientpush/list/delete
├── auth.py ← AuthClientsetup/bind/get_token/list_services
└── workflows.py ← WorkflowClientrun/push/list/delete
```
### API 設計
```python
from arcrun import Arcrun
# 建構 — api_key 從參數 > 環境變數 > ~/.arcrun/config.yaml 自動取得
client = Arcrun()
# 或明確指定
client = Arcrun(api_key="ak_xxx", encryption_key="hexstring")
# Auth:設定並綁定服務
client.auth.setup("openai", api_key="sk-xxx") # 加密 + 上傳
openai_client = client.auth.bind("openai") # 取回 pre-auth client
response = openai_client.get("/models") # httpx.Client
token = client.auth.get_token("openai") # raw token string
services = client.auth.list_services() # [{ service, display_name, ... }]
# Credentials:低階操作
client.creds.push("my_token", "value123")
names = client.creds.list()
client.creds.delete("my_token")
# Workflows
result = client.workflows.run("my-flow", {"email": "user@example.com"})
url = client.workflows.push("my-flow", graph_dict)
workflows = client.workflows.list()
```
### Credential 加密流程
```
setup("openai", api_key="sk-xxx")
1. GET /auth-recipes/openai → recipe(含 required_secrets, inject
2. 對應 required_secrets[0].key = "openai_api_key"
3. crypto.py 用 encryption_key AES-GCM 加密 "sk-xxx"
4. POST /credentials → { name: "openai_api_key", encrypted, iv }
5. 本地 _cred_cache["openai_api_key"] = "sk-xxx"(供 bind() 用)
bind("openai")
1. GET /auth-recipes/openai → recipe.inject.header = { Authorization: "Bearer {{secret.openai_api_key}}" }
2. 用 _cred_cache["openai_api_key"] 替換 template → "Bearer sk-xxx"
3. 回傳 AuthenticatedClient(base_url="https://api.openai.com/v1", headers={"Authorization": "Bearer sk-xxx"})
```
**注意**`bind()` 依賴 `setup()` 在同一 session 建立的 `_cred_cache`。跨 session 使用時(credential 已上傳但 cache 不存在),`bind()` 無法解析 template — 此時 `get_token()` 也無法返回值。**這是已知限制,封測期間先接受。** 長期解法是 server 提供 `/credentials/:name/secret` 解密端點(u6u-core/credentials 已有)。
### 關鍵差異:crypto.py 的定位
`crypto.py` 只做 **加密**encrypt),不做解密。
功能等同 `u6u-core/credentials/src/actions/crypto.ts``encrypt()` 函數。
解密只在 server 端發生(cypher-executor 的 `credential-injector.ts``u6u-core/credentials/getCredentialSecret.ts`)。
---
## JS/TS SDK`arcrun/js-sdk/`
### 目錄結構
```
arcrun/js-sdk/
├── package.json ← name TBDarcrun vs @arcrun/sdk),tsup build
├── tsconfig.json ← ES2020, NodeNext
└── src/
├── index.ts ← export class Arcrun
├── crypto.ts ← Web Crypto API AES-GCM encryptclient 端)
├── creds.ts ← CredentialsClientpush/list/delete
├── auth.ts ← AuthClientsetup/bind/getToken/listServices
└── workflows.ts ← WorkflowClientrun/push/list/delete
```
### API 與 Python SDK 對等
```typescript
import { Arcrun } from 'arcrun' // or '@arcrun/sdk'
const client = new Arcrun() // reads ARCRUN_API_KEY from env
await client.auth.setup('openai', { api_key: 'sk-xxx' })
const oai = await client.auth.bind('openai')
const models = await (await oai.get('/models')).json()
const token = await client.auth.getToken('openai')
const services = await client.auth.listServices()
await client.creds.push('my_token', 'value')
const names = await client.creds.list()
const result = await client.workflows.run('my-flow', { email: 'user@example.com' })
```
### Build 產物
```
dist/
├── index.js ← ESM
├── index.cjs ← CJS
├── index.d.ts ← TypeScript 型別
└── index.d.cts
```
### Crypto 實作
使用 Web Crypto API`crypto.subtle`),相容 Node 18+ / browsers / CF Workers / Deno
```typescript
async function encrypt(plaintext: string, hexKey: string): Promise<{ encrypted: string; iv: string }> {
const key = await crypto.subtle.importKey('raw', hexToBytes(hexKey), { name: 'AES-GCM' }, false, ['encrypt']);
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, new TextEncoder().encode(plaintext));
return { encrypted: toBase64(ciphertext), iv: toBase64(iv.buffer) };
}
```
---
## arcrun.dev 網站
### 現有狀態(`arcrun/landing/`
已完成:
- [x] `/` — Hero + Code DemoPython/JS/HTTP tabs
- [x] `/login` — Google + GitHub OAuth 按鈕(前端 OK,需設 OAuth secrets
- [x] `/dashboard` — API Key 查看/Copy/Rotate/Revoke(依賴 `/me` API
- [x] `/integrations` — 20 個 recipe 靜態卡片
- [x] `/api-docs` — Swagger UI CDN 嵌入
- [x] `middleware.ts` — 保護 `/dashboard`(未登入 → `/login`
- [x] Cloudflare Pages 部署
待完成:
- [ ] OAuth secrets 設定(`GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` / `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`
- [ ] `/components` 頁面(零件列表 — 21 個 WASM 零件的 input/output/config_example
- [ ] 首頁 code demo 更新為三種使用方式(CLI / Python / JS
- [ ] 登入流程真實驗證
### 新增頁面:`/components`
```
/components
├── 零件卡片(21 個)
│ ├── canonical_id
│ ├── display_name
│ ├── description
│ ├── input_schemarequired / optional 欄位)
│ ├── output_schema
│ ├── credentials_requiredif any
│ └── config_exampleYAML code block
└── 分類篩選(邏輯 / API / 控制流)
```
資料來源:靜態嵌入(從 `registry/components/*/component.contract.yaml` 在 build 時讀取),不依賴 runtime API。
### OAuth 設定(待 richblack 操作)
需要在 Cloudflare Worker 設定以下 secrets
```bash
wrangler secret put GOOGLE_CLIENT_ID --name arcrun-cypher-executor
wrangler secret put GOOGLE_CLIENT_SECRET --name arcrun-cypher-executor
wrangler secret put GITHUB_CLIENT_ID --name arcrun-cypher-executor
wrangler secret put GITHUB_CLIENT_SECRET --name arcrun-cypher-executor
wrangler secret put SESSION_SIGNING_SECRET --name arcrun-cypher-executor
```
---
## server 端需要的修改
### cypher-executor 修改(最小化)
目前 `POST /credentials` 端點(`routes/credentials.ts`)接收 `{ name, encrypted, iv }` 後直接存 KV。
SDK 需要的改動:
1. **`GET /auth-recipes` 回應格式**:目前 list 端點回 `{ recipes: [...] }` 但 recipe 的 `service` 欄位是 key — SDK 已在 list_services() 正確處理 ✅
2. **`GET /auth-recipes/:service` 回應格式**:目前回 `{ success: true, recipe: {...} }` — SDK 需讀 `body.recipe` 而非 body 本身 ✅
3. **`POST /credentials` 不需改動** — SDK 自己做 AES-GCM 加密後送 `{ name, encrypted, iv }`
4. **未來**:新增 `GET /credentials/:name/secret` 端點(解密返回 plaintext),讓跨 session 的 `bind()` 能工作。但此端點在 `u6u-core/credentials/src/actions/getCredentialSecret.ts` 已有實作 — 需要在 cypher-executor 整合或 Service Binding 到 u6u-credentials Worker。**封測後再做。**
---
## 不做的事(明確排除)
- ❌ 不在 SDK 裡做 workflow 解析或 YAML 處理 — 那是 CLI 的職責
- ❌ 不在 SDK 裡做 server-side 解密 — 解密只在 server 端
- ❌ 不建新的 credentials Worker — 用現有的
- ❌ 不建新的 KV namespace — 用現有的 CREDENTIALS_KV
- ❌ 不改 cypher-executor 的 credential-injector.ts — 那已經完成且測試通過
---
## 實作順序
```
Phase 1Python SDK 重建 + 測試
1.1 重建 arcrun/python-sdk/(按本 SDD 的結構)
1.2 修正上次的 bugrecipe 回應 wrapper、inject key "header" vs "headers"、secret key mapping
1.3 對 cypher.arcrun.dev live 測試全部 API
1.4 本地安裝測試(pip install -e .
Phase 2JS SDK 重建 + 測試
2.1 重建 arcrun/js-sdk/(按本 SDD 的結構)
2.2 同步修正 Python SDK 發現的所有 recipe 格式問題
2.3 buildtsup+ 本地測試
Phase 3arcrun.dev 網站補完
3.1 新增 /components 頁面
3.2 更新首頁 code demo(三種使用方式)
3.3 OAuth secrets 設定(需 richblack 操作 GCP / GitHub
3.4 登入流程驗證
Phase 4GitHub README + 發布
4.1 更新 arcrun/README.md — 三種 Quick Start
4.2 pip publisharcrun
4.3 npm publishTBD 套件名)
4.4 最終驗證:從零開始 pip install / npm install / 打 API
```
@@ -0,0 +1,131 @@
# Requirements: arcrun SDK Libraries + Website
## Introduction
arcrun 目前有三個使用介面:
1. **CLI**`acr` 指令)— 已完成,用 YAML 定義 workflow 並推送執行
2. **Python / JS SDK lib**(本次新增)— `pip install arcrun` / `npm install arcrun`,讓開發者在寫程式時直接用 arcrun 功能
3. **arcrun.dev 網站**(本次完成)— 登入取得 API Key、管理 Key、瀏覽零件 / recipe 列表
**核心原則**SDK lib 是 `cypher.arcrun.dev` HTTP API 的 thin wrapper。所有業務邏輯(加解密、credential 注入、workflow 執行)都在 server 端完成。Client 端不重做 server 已有的邏輯。
**現有基礎設施**(不重建,直接使用):
- `cypher.arcrun.dev`cypher-executor Workerworkflow 執行、credential 管理、auth recipe、webhook
- `u6u-core/credentials`credential WorkerAES-GCM 加解密)— arcrun/credentials 是其 cherry-pick
- `arcrun/cli`CLI 工具(已發布 npm `arcrun@1.1.0`
- `arcrun/landing`Next.js 前端(已部署 Cloudflare Pages,有 hero/login/dashboard/integrations 骨架)
---
## Glossary
- **SDK lib**Python / JS 套件,wrapping `cypher.arcrun.dev` HTTP API,安裝後可在程式碼中直接使用
- **auth.setup()**:上傳一個服務的 credential(如 Notion token、OpenAI API Key)到 arcrun
- **auth.bind()**:取回已設定服務的 pre-authenticated HTTP client
- **auth.get_token()**:取回某服務的 raw tokenescape hatch,給官方 SDK 用)
- **workflows.run()**:觸發已部署的 workflow
- **workflows.push()**:上傳 workflow 定義
- **Recipe**:描述「如何對某服務認證」的 YAML 設定,存在 RECIPES KV
---
## Requirements
### Requirement 1Python SDK`pip install arcrun`
**User Story:** As a Python 開發者, I want `pip install arcrun` 後在程式碼中使用 arcrun, so that 不用離開寫程式環境就能串接 20+ 服務。
#### Acceptance Criteria
1. THE Python SDK SHALL 以 `arcrun` 套件名發布到 PyPI,支援 Python 3.10+。
2. THE SDK SHALL 提供以下 API
- `Arcrun(api_key=, base_url=)` — 建構 clientapi_key 支援從環境變數 `ARCRUN_API_KEY``~/.arcrun/config.yaml` 自動讀取
- `client.health()` — 健康檢查
- `client.auth.list_services()` — 列出可用 auth recipe 服務
- `client.auth.setup(service, **kwargs)` — 上傳 credential
- `client.auth.bind(service)` — 取得 pre-authenticated HTTP client
- `client.auth.get_token(service)` — 取得 raw token
- `client.creds.push(name, value)` — 上傳加密 credential
- `client.creds.list()` — 列出 credential 名稱
- `client.creds.delete(name)` — 刪除 credential
- `client.workflows.run(name, input)` — 觸發 workflow
- `client.workflows.push(name, graph)` — 上傳 workflow
- `client.workflows.list()` — 列出已部署 workflow
3. THE SDK 的 credential 加密 SHALL 在 client 端完成(使用 `cryptography` 套件 AES-GCM),然後以 `POST /credentials` 上傳加密後的 `{ name, encrypted, iv }` 到 server。
4. THE `auth.bind()` SHALL 從 server 取得 auth recipe 的 inject template,在 client 端用 cache 的 plaintext 值填入,回傳 pre-configured `httpx.Client`
5. THE SDK SHALL 使用 `httpx` 做 HTTP clientasync 版使用 `httpx.AsyncClient`)。
6. THE SDK 位置 SHALL 為 `arcrun/python-sdk/`build 系統用 `hatchling``pyproject.toml`)。
---
### Requirement 2JavaScript/TypeScript SDK`npm install arcrun`
**User Story:** As a JS/TS 開發者, I want `npm install arcrun` 後在程式碼中使用 arcrun, so that 可以嵌入現有 Node.js / Deno / Cloudflare Workers 專案。
#### Acceptance Criteria
1. THE JS SDK SHALL 以 `arcrun` 套件名發布到 npm,提供 ESM + CJS 雙格式 + TypeScript 型別定義。
2. THE SDK SHALL 提供與 Python SDK 對等的 APIcamelCase 版):
- `new Arcrun({ apiKey?, baseUrl? })` — 讀 `process.env.ARCRUN_API_KEY`
- `client.health()` — 回傳 `Promise<unknown>`
- `client.auth.listServices()` / `setup()` / `bind()` / `getToken()`
- `client.creds.push()` / `list()` / `delete()`
- `client.workflows.run()` / `push()` / `list()` / `delete()`
3. THE SDK 的 credential 加密 SHALL 使用 Web Crypto API`crypto.subtle` AES-GCM),相容 Node 18+、browsers、Cloudflare Workers、Deno。
4. THE `auth.bind()` SHALL 回傳一個有 `get/post/put/delete/patch` 方法的 `AuthenticatedClient`base URL + auth headers 已配置。
5. THE SDK SHALL 使用原生 `fetch()` API,不依賴外部 HTTP client 套件。
6. THE SDK 位置 SHALL 為 `arcrun/js-sdk/`build 用 `tsup`ESM + CJS + DTS),`tsconfig.json` target ES2020 + NodeNext module。
7. THE JS SDK 套件名與 CLI 套件名衝突(都叫 `arcrun`),SHALL 使用 `@arcrun/sdk` 或由 richblack 決定套件名。
---
### Requirement 3arcrun.dev 網站完成
**User Story:** As a 潛在用戶, I want 在 arcrun.dev 上登入取得 API Key、瀏覽零件和 recipe 列表, so that 我可以評估 arcrun 是否符合需求並立即開始使用。
#### Acceptance Criteria
1. THE 網站 SHALL 在 `arcrun.dev` 提供以下頁面:
- `/` — 首頁 Hero + 三種使用方式(CLI / Python / JS
- `/login` — Google + GitHub OAuth 登入
- `/dashboard` — 登入後顯示 API Key(查看/Copy/Rotate/Revoke
- `/integrations` — 列出 20 個 auth recipe 服務,可按分類篩選
- `/components` — 列出所有零件(21 個 WASM 零件),顯示 input/output schema、config_example
- `/api-docs` — Swagger UI,可直接試打 API
2. THE 登入 SHALL 使用 Google + GitHub OAuth,流程走 `cypher.arcrun.dev``/auth/*` 端點。
3. THE 登入後 SHALL 自動對該 email 呼叫 `/register` 取得 API Key(若已有則取回現有 key)。
4. THE `/dashboard` SHALL 允許 Rotate(生成新 key)、Revoke(標記失效)、Copy to clipboard。
5. THE 網站 SHALL 部署在 Cloudflare Pages(現有 `arcrun/landing`),使用 Next.js App Router。
6. THE 首頁 code demo 區 SHALL 包含三個 tabPython、JavaScript、HTTP/curl,展示三種使用方式。
---
### Requirement 4GitHub README 更新
**User Story:** As a GitHub 訪客, I want README 清楚說明三種使用方式, so that 我能選擇最適合的方式開始用 arcrun。
#### Acceptance Criteria
1. THE `arcrun/README.md` SHALL 包含三種 Quick Start
- **CLI**`npm i -g arcrun && acr init && acr push workflow.yaml && acr run`
- **Python**`pip install arcrun && from arcrun import Arcrun && ...`
- **JavaScript**`npm install arcrun && import { Arcrun } from 'arcrun' && ...`
2. THE README SHALL 包含完整零件列表(21 個)和 auth recipe 列表(20 個服務)。
3. THE README SHALL 連結到 `arcrun.dev`(取得 API Key)和 Swagger UIAPI 文件)。
---
### Requirement 5SDK 發布
**User Story:** As a SDK 使用者, I want 公開安裝並直接使用, so that 不需要從原始碼 build。
#### Acceptance Criteria
1. THE Python SDK SHALL 發布到 PyPI`pip install arcrun` 可安裝。
2. THE JS SDK SHALL 發布到 npm`npm install arcrun`(或 `@arcrun/sdk`)可安裝。
3. THE 發布前 SHALL 完成以下測試(對 `cypher.arcrun.dev` live API):
- `health()`
- `auth.list_services()`
- `auth.setup()` + `auth.bind()` ✅(至少一個 static_key 服務如 openai
- `creds.push()` + `creds.list()`
- `workflows.list()`
@@ -0,0 +1,120 @@
# Implementation Plan: arcrun SDK Libraries + Website
## Overview
按 Design 的四個 Phase 實作。原則:修改不重建,SDK 是 HTTP API thin wrapper,加密只在 client 做 encrypt(不做 decrypt)。
**前置依賴**:必須先完成 `credential-primitives-wasm/tasks.md` 的 Phase 0-3(核心合併 + WASM primitives),確認核心穩定後才開始建三個介面。
---
## Phase 0(前置):核心合併 + WASM 改寫
> 詳見 `.agents/specs/arcrun/credential-primitives-wasm/tasks.md`
>
> 摘要:
> - 合併 u6u-core → arcrun(搬 builtins、刪重複 credentials
> - credential-injector TS → auth_static_key / auth_service_account WASM
> - 刪除 component-loader 內建 API recipes TS
> - 驗證 20 個 auth recipe 正常運作
---
## Phase 1Python SDK
- [ ] 1. 建立 `arcrun/python-sdk/` 目錄
- [ ] 1.1 `pyproject.toml`name=arcrun, deps=[httpx>=0.27, cryptography>=42], build-system=hatchling
- [ ] 1.2 `arcrun/__init__.py``from .client import Arcrun`
- [ ] 1.3 `arcrun/crypto.py`AES-GCM encrypt only(使用 `cryptography` 套件)
- [ ] 1.4 `arcrun/creds.py`CredentialsClient — push(加密 + POST /credentials)、listGET /credentials)、delete
- [ ] 1.5 `arcrun/auth.py`AuthClient — setupfetch recipe → match secrets → encrypt → push)、bindfetch recipe → resolve headers from cache → return AuthenticatedClient)、get_token、list_services
- [ ] 1.6 `arcrun/workflows.py`WorkflowClient — runPOST /webhooks/named/{name}/trigger)、pushPOST /webhooks/named)、listGET /webhooks/named)、delete
- [ ] 1.7 `arcrun/client.py`Arcrun class — 讀 api_key / encryption_key 從 param > env > config.yaml
- [ ] 2. 修正上次已知的 bug
- [ ] 2.1 `_fetch_recipe()` 回應是 `{ success: true, recipe: {...} }`,需讀 `.recipe` 欄位
- [ ] 2.2 `inject` 下的 key 是 `header`singular),不是 `headers`
- [ ] 2.3 `required_secrets[].key` 是 prefixed(如 `openai_api_key`),setup() 的 kwargs alias 要能對應
- [ ] 2.4 `list_services()` 回應的 recipe 用 `service` 欄位(不是 `service_id`
- [ ] 3. 測試(對 cypher.arcrun.dev live API
- [ ] 3.1 `health()``{"ok": true}`
- [ ] 3.2 `auth.list_services()` → 20 個服務
- [ ] 3.3 `auth.setup("openai", api_key="sk-test-dummy")` → 成功
- [ ] 3.4 `auth.bind("openai")` → AuthenticatedClient with Authorization header
- [ ] 3.5 `auth.get_token("openai")` → "sk-test-dummy"
- [ ] 3.6 `creds.push("test_token", "value123")` → 成功
- [ ] 3.7 `creds.list()` → 含 "test_token"(注意 KV eventual consistency
- [ ] 3.8 `workflows.list()` → []
- [ ] 3.9 cleanup: `creds.delete("test_token")`
---
## Phase 2JS/TS SDK
- [ ] 4. 建立 `arcrun/js-sdk/` 目錄
- [ ] 4.1 `package.json`name TBDarcrun vs @arcrun/sdk),deps=devDeps onlytsup, typescript, @types/node
- [ ] 4.2 `tsconfig.json`ES2020, NodeNext
- [ ] 4.3 `src/crypto.ts`Web Crypto API AES-GCM encrypt only
- [ ] 4.4 `src/creds.ts`CredentialsClient — push/list/delete via fetch
- [ ] 4.5 `src/auth.ts`AuthClient — setup/bind/getToken/listServices
- [ ] 4.6 `src/workflows.ts`WorkflowClient — run/push/list/delete
- [ ] 4.7 `src/index.ts`export class Arcrun + re-exports
- [ ] 5. 同步修正(與 Python SDK 同樣的 recipe 格式問題)
- [ ] 5.1 `_fetchRecipe()``body.recipe`
- [ ] 5.2 inject key: `header` not `headers`
- [ ] 5.3 setup() secret key alias matching
- [ ] 5.4 listServices() 用 `service` 欄位
- [ ] 6. Build + 測試
- [ ] 6.1 `tsup` build → dist/index.js + dist/index.cjs + dist/index.d.ts
- [ ] 6.2 Node.js 腳本對 live API 測試(同 Python 測試項目)
---
## Phase 3arcrun.dev 網站
- [ ] 7. 新增 `/components` 頁面
- [ ] 7.1 從 `registry/components/*/component.contract.yaml` 讀取 21 個零件資料
- [ ] 7.2 卡片顯示:canonical_id, display_name, description, input required/optional, credentials_required, config_example
- [ ] 7.3 分類篩選:邏輯類 / API 類 / 控制流類
- [ ] 8. 更新首頁
- [ ] 8.1 Code demo tabs 改為 CLI / Python / JS 三個
- [ ] 8.2 CLI tab 展示 `acr init → acr push → acr run`
- [ ] 8.3 Python tab 展示 `pip install arcrun → Arcrun() → auth.setup → auth.bind`
- [ ] 8.4 JS tab 展示 `npm install arcrun → new Arcrun() → auth.setup → auth.bind`
- [ ] 9. OAuth 流程補完
- [ ] 9.1 確認 cypher-executor 的 `/auth/google/start``/auth/github/start``/auth/callback` 路由正確
- [ ] 9.2 提供 richblack OAuth secrets 設定指令清單
- [ ] 9.3 richblack 設定 secrets 後驗證登入流程
- [ ] 10. 部署
- [ ] 10.1 Cloudflare Pages build + deploy
- [ ] 10.2 驗證所有頁面可存取
---
## Phase 4README + 發布
- [ ] 11. 更新 `arcrun/README.md`
- [ ] 11.1 三種 Quick StartCLI / Python / JS
- [ ] 11.2 零件列表(21 個)
- [ ] 11.3 Auth Recipe 列表(20 個服務)
- [ ] 11.4 連結到 arcrun.dev 和 Swagger UI
- [ ] 12. 發布
- [ ] 12.1 Python SDK`pip install build && python -m build && twine upload dist/*`
- [ ] 12.2 JS SDK`npm run build && npm publish`
- [ ] 12.3 驗證:從零開始 `pip install arcrun` / `npm install arcrun` + hello world
---
## Notes
- JS SDK 套件名需 richblack 決定(`arcrun` 已被 CLI 佔用 → 可能用 `@arcrun/sdk`
- OAuth secrets 設定需 richblack 手動操作(GCP Console + GitHub Settings
- `bind()` 跨 session 限制是已知的,封測期間先接受
- credential 加密用的 `encryption_key` 目前由 `/register` 回傳,`acr init` 自動存入 config
+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 指令
```
+180
View File
@@ -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
@@ -0,0 +1,822 @@
# Design Document: u6u Platform Evolution
## Overview
u6u 平台演進的核心目標是將現有的「HTTP endpoint 零件 + 單一 Cloudflare 部署」架構,演進為「WASM 零件模型 + 三層物理部署 + 雙面翻轉畫布」的完整平台。
設計的最高原則是 **Dogfooding**:每一層都是下一層的第一個用戶。底層先建立最小可運行的能力,再用自己的方式往上蓋。這確保每個設計決策都被真實使用場景驗證,而非紙上談兵。
### Bootstrap 順序(不可跳過)
```
Phase 0:最小 WASM 執行核心
→ Component Dispatcher 能在 CF Workers 執行一個 .wasmstdin/stdout JSON
→ validate_json.wasm 作為第一個真實零件(TinyGo< 50KB,驗證整個 pipeline
→ Component Registry API/guide、/validate-contract、/components
Phase 1:遷移現有零件
→ 將 u6u-builtins 的 20 個 HTTP endpoint 逐一遷移為 .wasm
→ 遷移期間 Component Dispatcher 雙模式並存(HTTP fallback
→ 每個零件附帶 component.contract.yaml
Phase 2Cypher 語意擴展 + Multi-Tier Dispatcher
→ 支援 IS_A、ON_SUCCESS、ON_FAIL、CALLS_SUBFLOW、ON_CLICK
→ Component Dispatcher 路由層(Tier 1 CF / Tier 2 workerd / Tier 3 Wazero
Phase 3:前端畫布(用自己的 Web Components 開發)
→ 先建立 Web Components 零件庫(u6u-btn、u6u-card 等)
→ 畫布本身用這些 Web Components 組裝
→ 雙面翻轉介面
```
### 關鍵設計約束
- **KBDB 不變量**:永遠只有三張表(blocks / templates / slots),不新增表
- **API-First 鐵律**:所有跨服務通訊只透過 HTTP API,禁止相對路徑引用
- **零件 I/O 不變量**:唯一合法的 I/O 模型是 `stdin_stdout_json`
- **Tier 3 約束**:無 V8、無 Node.js、無網路,所有零件必須在 Wazero 上跑
---
## Architecture
### 系統全景圖
```mermaid
graph TB
subgraph "Tier 1 — Cloudflare Workers(雲端)"
CE[Cypher Executor<br/>GraphExecutor]
CD[Component Dispatcher<br/>路由層]
CR[Component Registry<br/>KBDB HTTP API]
KBDB[(KBDB<br/>blocks/templates/slots<br/>+ Vectorize)]
R2[(R2<br/>.wasm 二進位)]
CE --> CD
CD --> CR
CR --> KBDB
CR --> R2
end
subgraph "Tier 2 — workerd self-hosted(企業地端)"
T2D[Tier 2 Dispatcher<br/>同 wasi-shim,不同部署]
T2R[本地 Registry 快取]
T2D --> T2R
end
subgraph "Tier 3 — 邊緣載具"
T3E[Go 排程引擎]
Wazero[Wazero Runtime]
SQLite[(SQLite<br/>本地 KBDB)]
DTN[DTN 佇列]
T3E --> Wazero
T3E --> SQLite
T3E --> DTN
end
subgraph "前端"
Canvas[雙面翻轉畫布<br/>React 19 + Web Components]
WC[Web Components 零件庫<br/>u6u-btn / u6u-card / ...]
Canvas --> WC
end
CD -->|WASM 執行| Tier1WASM[.wasm 執行]
CD -->|Cypher binding| ExtSvc[外部服務<br/>MCP / n8n / 任意 URL]
CD -->|HTTP| T2D
T2D -->|Wazero IPC| Wazero
DTN -->|Burst 傳輸| T2D
Canvas -->|u6u:trigger event| CE
```
### Cypher Binding 的正確定義
**Cypher binding** 是 u6u 的核心執行機制,指「用 Cypher 三元組語法把零件串接成工作流,串接關係儲存在 KBDB,不寫死在程式碼裡」。
這個概念相對於 Cloudflare Workers 原生的 **Service Binding**(需要 deploy、串接關係寫死在 wrangler.toml)。
`cypher-executor` 就是執行 Cypher binding 的引擎。
**零件本身只有兩種 component_type**
| component_type | 說明 | 需要 deploy |
|---|---|---|
| `wasm` | 所有後端零件(內建或用戶自建),本地 WASM 執行 | 否 |
| `service_binding` | 多個零件預組合成單一高頻零件的效能最佳化(如 OAuth + GSheets 常用組合) | 是 |
> **重要:`cypher_binding` 不是 component_type。** 它是整個執行引擎的名字,描述「零件如何被串接」,而不是「零件如何被執行」。所有零件(不管是內建還是用戶自建、不管是打外部 API 還是純邏輯)都是 `.wasm`,透過 Cypher 三元組串接。
> **所有後端零件都是 `.wasm`。** 需要呼叫外部 HTTP API 的零件(如 google-sheets、http-request),透過 WASI shim 注入的 **host function** 發出網路請求,不在 .wasm 內部直接呼叫網路 syscall。
### Component Dispatcher 路由決策樹
```mermaid
flowchart TD
A[Cypher Executor 呼叫零件 id] --> B{查 Component Registry<br/>取得合約}
B --> C{component_type?}
C -->|wasm| E{當前 Tier?}
C -->|service_binding| SB{有 CF Service Binding?}
SB -->|是| SBExec[CF Service Binding 執行<br/>需 deploy,效能最佳]
SB -->|否| SBErr[回傳錯誤:binding 未宣告]
E -->|Tier 1 / Tier 2| I[workerd WASM<br/>WebAssembly.instantiate<br/>+ WASI shim(兩者相同)]
E -->|Tier 3| L[Wazero IPC<br/>stdin/stdout,完全離線]
I --> RC{runtime_compat<br/>包含 cf-workers?}
RC -->|否| J[回傳 RUNTIME_INCOMPATIBLE 錯誤]
RC -->|是| Exec[執行 .wasm]
```
### KBDB 資料模型(tpl-component
```mermaid
erDiagram
TEMPLATES {
string template_id "tpl-component"
string name
string description
}
BLOCKS {
string block_id "comp-{id}-{version}"
string template_id
string user_id
string page_name
}
SLOTS {
string slot_id
string block_id
string key
string value
}
TEMPLATES ||--o{ BLOCKS : "defines"
BLOCKS ||--o{ SLOTS : "has"
```
---
## Components and Interfaces
### 1. Component Registry`u6u-core/registry/`
Component Registry 是 KBDB 的薄包裝層,透過 HTTP API 管理零件合約。
#### 零件命名機制
零件有兩個名稱,職責完全不同:
| 欄位 | 由誰決定 | 用途 | 範例 |
|---|---|---|---|
| `display_name` | 建立者自由取 | 顯示用,不影響任何邏輯 | `宇宙無敵 GSheets 超級寫入器` |
| `canonical_id` | Registry AI 正規化後確認 | 搜尋、版本控制、Cypher 引用的唯一鍵 | `gsheets_create_table` |
**canonical_id 正規化流程:**
```
提交者輸入 display_name
Registry 用 Workers AI 建議 canonical_id
(格式:{service}_{verb}_{object},全小寫底線)
同時搜尋 Vectorize,若相似度 > 0.9 的 canonical_id 已存在
→ 提示「可能與 gsheets_create_table 重複,是否作為新版本提交?」
提交者確認或修改 canonical_id
上架,canonical_id 永久不變
```
#### 零件分類機制
採用「強制 category + 自由 tags」雙層分類:
- **`category`**:強制填,有限集合,定義前後端邊界
- `logic`:後端邏輯零件(.wasm,純計算/轉換)
- `api`:後端 API 零件(.wasm + cypher_binding,呼叫外部服務)
- `ui`:前端 UI 元件(Web Component,瀏覽器執行)
- `style`:前端樣式零件(CSS tokens
- `anim`:前端動畫零件
- **`tags`**:自由增加,跨零件共享語意
- 例:`gsheets_create_table``["google", "sheets", "spreadsheet", "storage", "write"]`
- 例:`excel_write_row``["microsoft", "excel", "spreadsheet", "storage", "write"]`
- 搜尋「外部存儲」時,兩個都能透過 Vectorize 語意搜尋找到
**HTTP 端點:**
```
GET /components/guide → 機器可讀開發指引(Markdown)
POST /components/validate-contract → 驗證 component.contract.yaml 格式
POST /components → 提交零件(.wasm + contract)觸發沙盒驗收
GET /components/:id → 取得零件合約(最優版本)
GET /components/:id/versions → 取得所有版本清單(含評分)
GET /components/search?q=... → 語意搜尋零件
```
**KBDB 整合:**
- 每個零件版本 = 一個 Block`block_id = comp-{id}-{version}`
- Template = `tpl-component`(預先建立,不新增表)
- `.wasm` 二進位存 R2KBDB slot 只存 `wasm_r2_key`
- `description` + `tags` 欄位寫入 Vectorize 索引,支援語意搜尋
**Slot 欄位對應(tpl-component):**
| Slot key | 說明 | 範例值 |
|---|---|---|
| `canonical_id` | 正規化功能名稱(永久不變,搜尋/版本控制用) | `gsheets_create_table` |
| `display_name` | 建立者自取的顯示名稱 | `宇宙無敵 GSheets 超級寫入器` |
| `category` | 零件分類(有限集合) | `logic` / `api` / `ui` / `style` / `anim` |
| `version` | 實作版本 | `v1` |
| `wasi_target` | WASM 目標 | `preview1` |
| `stability` | 穩定性標籤 | `floating` |
| `runtime_compat` | 相容 runtimeJSON 陣列) | `["cf-workers","wazero"]` |
| `component_type` | 零件類型 | `wasm` / `service_binding` |
| `max_size_kb` | 體積上限 | `2048` |
| `max_cold_start_ms` | 冷啟動上限 | `50` |
| `no_network_syscall` | 禁止網路 syscall | `true` |
| `input_schema` | JSON SchemaJSON 字串) | `{"type":"object",...}` |
| `output_schema` | JSON SchemaJSON 字串) | `{"type":"object",...}` |
| `gherkin_tests` | 測試案例(JSON 字串) | `[{"scenario":"..."}]` |
| `wasm_r2_key` | R2 物件鍵(wasm 模式) | `components/validate_json/v1.wasm` |
| `service_binding_key` | CF binding keyservice_binding 模式) | `CLINIC_GSHEETS` |
| `description` | 自然語言描述(寫入 Vectorize) | `在 Google Sheets 建立新工作表` |
| `tags` | 自由標籤(JSON 陣列,跨零件共享語意) | `["google","sheets","storage","write"]` |
| `success_rate` | 成功率(0-1 | `0.98` |
| `avg_duration_ms` | 平均執行時間 | `12` |
| `call_count` | 被調用次數 | `1024` |
| `status` | 狀態 | `active` / `deprecated` / `tombstone` |
| `deprecated_at` | 棄用時間戳記 | `1700000000000` |
### 2. Component Dispatcher`cypher-executor/src/lib/component-loader.ts` 擴展)
Component Dispatcher 是 `createComponentLoader` 的升級版,新增 WASM 執行路徑。
**介面定義:**
```typescript
// 零件類型(只有兩種)
type ComponentType =
| 'wasm' // 所有後端零件,透過 Cypher binding 串接,本地 WASM 執行
| 'service_binding'; // 效能最佳化:CF Service Binding,需 deploy,用於高頻預組合零件
// 新版 ComponentDescriptor
type ComponentDescriptor = {
component_type: ComponentType;
// WASM 模式
wasm_r2_key?: string;
runtime_compat?: string[];
max_cold_start_ms?: number;
// Service Binding 模式(CF Worker 間高效呼叫,需 deploy
binding?: string; // wrangler.toml 中宣告的 binding key
path?: string;
};
```
**Tier 1 WASM 執行(CF Workers 原生):**
Cloudflare Workers 原生支援 `WebAssembly.instantiate`,但 WASI preview1 需要手動實作 WASI imports。設計採用輕量 WASI shim 方案:
```typescript
// WASI preview1 shim(只實作 stdin/stdout/stderr,其餘 syscall 回傳 ENOSYS
function createWasiImports(stdin: Uint8Array): {
imports: WebAssembly.Imports;
getStdout: () => Uint8Array;
} {
const stdoutChunks: Uint8Array[] = [];
let stdinOffset = 0;
return {
imports: {
wasi_snapshot_preview1: {
fd_write: (fd: number, iovs: number, iovs_len: number, nwritten: number) => { /* ... */ },
fd_read: (fd: number, iovs: number, iovs_len: number, nread: number) => { /* ... */ },
proc_exit: (code: number) => { throw new Error(`wasm exit: ${code}`); },
// 其餘 syscall 回傳 ENOSYS76
fd_seek: () => 76,
fd_close: () => 0,
environ_get: () => 0,
environ_sizes_get: () => 0,
args_get: () => 0,
args_sizes_get: () => 0,
clock_time_get: () => 0,
random_get: (buf: number, buf_len: number) => { /* crypto.getRandomValues */ return 0; },
},
},
getStdout: () => { /* 合併 stdoutChunks */ },
};
}
```
> **設計決策**:不使用 `@cloudflare/workers-wasi`(已停止維護)。改用自製輕量 WASI shim,只實作 `fd_read`/`fd_write`/`proc_exit`/`random_get`,其餘 syscall 回傳 `ENOSYS`。這足以支援 TinyGo/Rust/AssemblyScript 的 stdin/stdout 零件,且不引入外部依賴。
**執行流程:**
```
1. 從 R2 取得 .wasm 二進位(ArrayBuffer
2. WebAssembly.compile(buffer) → WebAssembly.Module
3. 建立 WASI imports shim(注入 stdin = JSON.stringify(input)
4. WebAssembly.instantiate(module, imports)
5. 呼叫 _start() 或 main()
6. 從 stdout buffer 讀取輸出
7. JSON.parse(stdout) → output
```
**R2 快取策略:**
- 第一次呼叫:從 R2 fetch `.wasm``WebAssembly.compile` 後快取 `WebAssembly.Module`Worker 記憶體,跨請求共享)
- 後續呼叫:直接用快取的 Module,只重新 instantiate(避免重複編譯)
### 3. Cypher Triplet Parser 擴展(`cypher-executor/src/actions/triplet-parser.ts`
現有 parser 只支援 `PIPE / IF / FOREACH / CONTINUE`。需擴展支援新語意關係。
**新增 EdgeType**
```typescript
export type EdgeType =
| 'PIPE' | 'IF' | 'FOREACH' | 'CONTINUE' // 現有
| 'IS_A' | 'ON_SUCCESS' | 'ON_FAIL' // 新增:執行語意
| 'ON_CLICK' | 'CALLS_SUBFLOW' // 新增:觸發語意
| 'CONTAINS' | 'HAS_STYLE' | 'HAS_BEHAVIOR'; // 新增:結構語意
```
**URI 協議解析:**
```typescript
// 節點 componentId 解析
function resolveComponentId(uri: string): {
type: 'component' | 'workflow' | 'ui' | 'style';
canonicalId: string;
stability: 'floating' | 'stable' | 'pinned';
pinnedVersion?: string;
} {
// component://validate_json@stable → { type: 'component', canonicalId: 'validate_json', stability: 'stable' }
// component://validate_json@pinned:v1 → { type: 'component', canonicalId: 'validate_json', stability: 'pinned', pinnedVersion: 'v1' }
// workflow://wf_save_to_db → { type: 'workflow', canonicalId: 'wf_save_to_db', stability: 'floating' }
// ui://u6u-btn → { type: 'ui', canonicalId: 'u6u-btn', stability: 'floating' }
}
```
**ON_SUCCESS / ON_FAIL 執行語意:**
GraphExecutor 需要區分「節點執行成功」vs「節點執行失敗」,而非依賴 context 欄位:
```typescript
// 在 executeNode 中,捕捉 try/catch 後分別走 ON_SUCCESS / ON_FAIL 邊
case 'ON_SUCCESS':
// 只在上游節點成功時執行
if (!nodeError) {
result = await this.executeNode(nextNode, ...);
}
break;
case 'ON_FAIL':
// 只在上游節點失敗時執行(接收 error context
if (nodeError) {
result = await this.executeNode(nextNode, graph, { ...context, error: nodeError }, ...);
}
break;
```
**CALLS_SUBFLOW 執行語意:**
```typescript
case 'CALLS_SUBFLOW': {
// 從 KBDB 載入子 Workflow 定義
const subWorkflowId = nextNode.componentId!.replace('workflow://', '');
const subGraph = await loadWorkflowFromKBDB(subWorkflowId, env);
const subExecutor = new GraphExecutor(loader);
const subResult = await subExecutor.execute(subGraph, result as Record<string, unknown>, kvNamespace);
result = { ...(result as Record<string, unknown>), ...subResult.data as Record<string, unknown> };
break;
}
```
### 4. Web Components 零件庫(`u6u-core/web-components/`
Web Components 以原生 Custom Elements API 實作,不依賴任何框架。
**`<u6u-btn>` 介面:**
```typescript
// HTML attributes
interface U6uBtnAttributes {
label: string; // 顯示文字
color?: string; // 主題色(CSS custom property
tooltip?: string; // 滑鼠懸停提示(純靜態)
workflow?: string; // workflow://id
disabled?: boolean;
}
// 發出的自訂事件
interface U6uTriggerEvent extends CustomEvent {
detail: {
workflowId: string;
payload: Record<string, unknown>;
};
}
```
**`<u6u-card>` Smart Container 邏輯:**
```typescript
// u6u-card 攔截子元件的 u6u:trigger 事件
// 收集同容器內所有 u6u-text-input / u6u-text-field 的值
// 合併至 payload 後再向上冒泡
connectedCallback() {
this.addEventListener('u6u:trigger', (e: Event) => {
const trigger = e as CustomEvent;
e.stopPropagation();
const inputs = this.querySelectorAll('u6u-text-input, u6u-text-field');
const collected: Record<string, unknown> = {};
inputs.forEach(input => {
const name = input.getAttribute('name');
const value = (input as any).value;
if (name) collected[name] = value;
});
this.dispatchEvent(new CustomEvent('u6u:trigger', {
bubbles: true,
composed: true,
detail: {
...trigger.detail,
payload: { ...trigger.detail.payload, ...collected },
},
}));
});
}
```
### 5. 雙面翻轉畫布(`inkstone-admin/frontend/web/`
畫布本身用 React 19 + Web Components 組裝,體現 dogfooding 原則。
**翻轉狀態機:**
```mermaid
stateDiagram-v2
[*] --> UIView: 初始狀態
UIView --> LogicView: 點擊翻面按鈕
LogicView --> UIView: 點擊翻面按鈕
LogicView --> Editing: 修改三元組
Editing --> Saving: 確認儲存
Saving --> LogicView: KBDB 寫入成功
Saving --> LogicView: KBDB 寫入失敗(顯示錯誤)
```
---
## Data Models
### Component Contract YAML(完整規格)
```yaml
canonical_id: "validate_json" # 正規化功能名稱(永久不變,Registry AI 正規化後確認)
display_name: "JSON 格式驗證器" # 建立者自取,顯示用
category: "logic" # logic | api | ui | style | anim
version: "v1" # 實作版本
wasi_target: "preview1" # WASM 目標格式
stability: "floating" # floating | stable | pinned
runtime_compat:
- "cf-workers"
- "workerd"
- "wazero"
constraints:
max_size_kb: 2048
max_cold_start_ms: 50
no_network_syscall: true
no_filesystem_syscall: true
io_model: "stdin_stdout_json" # 唯一合法值
input_schema:
type: object
required: ["json_string"]
properties:
json_string:
type: string
description: "待驗證的 JSON 字串"
output_schema:
type: object
properties:
valid:
type: boolean
error:
type: string
description: "驗證失敗時的錯誤訊息"
gherkin_tests:
- scenario: "合法 JSON 通過驗證"
given: '{"json_string":"{\"key\":\"value\"}"}'
then_contains: '{"valid":true}'
- scenario: "非法 JSON 回傳錯誤"
given: '{"json_string":"not-json"}'
then_contains: '{"valid":false,"error":'
tags: ["validation", "json", "utility"]
description: "驗證輸入字串是否為合法 JSON 格式"
```
### 零件開發語言決策
**內建零件使用 TinyGo**(純邏輯零件)和 TinyGo + `json.RawMessage`(需要任意 HTTP body 的零件)。不引入 Rust 作為內建零件語言。
**用戶自建零件支援三種語言,按難度分層:**
| 語言 | 目標用戶 | JSON 能力 | 備註 |
|---|---|---|---|
| **AssemblyScript** | 一般用戶(TS 背景) | 社群套件 `assemblyscript-json`,支援動態 JSON | 語法最接近 TS,門檻最低;靜默錯誤風險,沙盒驗收必須通過 |
| **TinyGo** | 技術較強用戶(Go 背景) | 靜態 struct 完整支援;`json.RawMessage` 處理任意 body | 編譯期報錯,AI 生成安全性較高 |
| **Rust** | 進階用戶 | `serde_json::Value` 完整動態 JSON | 生態最成熟,體積最小;學習曲線陡 |
**`/components/guide` 端點提供三份語言範例**,用戶根據自身背景選擇。
**內建零件 JSON 策略(TinyGo):**
```go
// 固定 schema 零件(google-sheets、gmail 等)→ 靜態 struct
type Input struct {
SpreadsheetId string `json:"spreadsheet_id"`
Range string `json:"range"`
AccessToken string `json:"access_token"`
}
// 任意 body 零件(http-request)→ json.RawMessage 傳遞 raw bytes,不解析
type Input struct {
URL string `json:"url"`
Method string `json:"method"`
Body json.RawMessage `json:"body"` // 任意 JSON,不解析
}
```
### Workflow Cypher 三元組(完整語法)
```yaml
kind: Workflow
id: wf_submit_form
triplets:
# 節點類型宣告
- "btn_submit >> IS_A >> ui://u6u-btn"
- "step_validate >> IS_A >> component://validate_json"
- "step_save >> IS_A >> component://kbdb_write"
# 前端觸發後端
- "btn_submit >> ON_CLICK >> step_validate"
# 成功/失敗分支
- "step_validate >> ON_SUCCESS >> step_save"
- "step_validate >> ON_FAIL >> step_notify_error"
# 子流程呼叫
- "step_save >> ON_SUCCESS >> CALLS_SUBFLOW >> workflow://wf_notify_user"
# 容器結構
- "card_main >> CONTAINS >> btn_submit"
- "card_main >> CONTAINS >> input_name"
```
### Evaluation BlockKBDB tpl-evaluation
每次 Workflow 執行後,Evaluator Agent 寫入一個 Evaluation Block
| Slot key | 說明 |
|---|---|
| `run_id` | 執行唯一 ID |
| `workflow_id` | Workflow ID |
| `component_id` | 被評價的零件 ID |
| `verdict` | `success` / `failed` / `timeout` |
| `duration_ms` | 執行時間 |
| `error_message` | 失敗訊息(可選) |
| `evaluated_at` | 評價時間戳記 |
### Pitfall BlockKBDB tpl-pitfall
| Slot key | 說明 |
|---|---|
| `component_id` | 問題零件 ID |
| `failure_pattern` | 失敗模式描述 |
| `first_seen_at` | 首次發現時間戳記 |
| `occurrence_count` | 發生次數 |
---
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property Reflection(去重分析)
在寫出最終屬性前,先做冗餘分析:
- **1.1 + 1.2**:都是合約欄位完整性驗證,合併為 Property 1「合約格式完整性」
- **2.1 + 2.2**:驗收流程執行 + 失敗回報,合併為 Property 2「沙盒驗收流程正確性」
- **2.3 + 2.4**:提交後可讀取 + 冪等提交,合併為 Property 3「零件提交冪等性與持久性」
- **3.1 + 3.5**WASM 執行 + 雙模式路由,合併為 Property 4「Component Dispatcher 路由正確性」
- **3.4 + 6.4**:不相容 Tier 回傳錯誤 + 結構化錯誤,合併為 Property 5「Dispatcher 錯誤結構完整性」
- **4.1**URI 解析 round-trip,獨立為 Property 6
- **4.2 + 4.4**:版本選擇算法(floating 最高分 + pinned 固定版本),合併為 Property 7「版本選擇策略正確性」
- **4.5**:版本保留不變量,獨立為 Property 8
- **5.7**Confluence 屬性,獨立為 Property 9
- **8.3 + 8.4 + 8.5**Web Components 事件與渲染,合併為 Property 10「Web Components 事件與渲染冪等性」
- **10.6 + 12.5**:評價冪等性 + 查詢冪等性,合併為 Property 11「系統操作冪等性」
最終保留 11 個屬性,每個提供獨立驗證價值。
---
### Property 1: 合約格式完整性
*For any* component contract object,若缺少任何必填欄位(`id``version``wasi_target``stability``runtime_compat``constraints.max_size_kb``constraints.max_cold_start_ms``constraints.no_network_syscall``constraints.io_model``input_schema``output_schema``gherkin_tests`),合約驗證器 SHALL 拒絕該合約並回傳包含缺失欄位名稱的錯誤;反之,包含所有必填欄位的合約 SHALL 通過格式驗證。
**Validates: Requirements 1.1, 1.2, 1.4**
---
### Property 2: 沙盒驗收流程正確性
*For any* 提交的零件(.wasm + contract),若零件在驗收步驟 N 失敗,Component_Registry 的回應 SHALL 包含步驟 N 的名稱與具體失敗原因,且不執行步驟 N+1 之後的步驟。
**Validates: Requirements 2.1, 2.2**
---
### Property 3: 零件提交冪等性與持久性
*For any* 通過驗收的零件(id, version),提交後從 Component_Registry 讀取該零件的合約,所有欄位值 SHALL 與提交時的合約完全一致(序列化 round-trip);對相同 (id, version) 重複提交 N 次,KBDB 中 SHALL 只存在一個對應的 Block。
**Validates: Requirements 2.3, 2.4**
---
### Property 4: Component Dispatcher 路由正確性
*For any* 零件合約,若 `io_model = "stdin_stdout_json"`Component_Dispatcher SHALL 使用 WASM 執行路徑,將 input JSON 寫入 stdin,從 stdout 讀取 output JSON;若 `io_model = "http_endpoint"`SHALL 使用 HTTP 路徑。對任意合法 JSON inputWASM 執行路徑的輸出 SHALL 與 HTTP 執行路徑的輸出語意等效。
**Validates: Requirements 3.1, 3.5, 3.6**
---
### Property 5: Dispatcher 錯誤結構完整性
*For any* (component_id, tier) 組合,若該零件的 `runtime_compat` 不包含當前 tierComponent_Dispatcher 的錯誤回應 SHALL 同時包含:零件 id、當前 tier 名稱、已嘗試的呼叫路徑清單,三個欄位缺一不可。
**Validates: Requirements 3.4, 6.4**
---
### Property 6: 零件 URI 解析 Round-Trip
*For any* 合法的零件 URI 字串(格式為 `component://id``component://id@stable``component://id@pinned:vN`),解析後再重新序列化 SHALL 產生與原始 URI 語意等效的字串;解析出的 `id``stability``pinnedVersion` 欄位 SHALL 與原始 URI 中的對應部分完全一致。
**Validates: Requirements 4.1**
---
### Property 7: 版本選擇策略正確性
*For any* 零件 id 下的版本集合(每個版本有 success_rate、avg_duration_ms、call_count 評分),當 stability = `floating` 時,Component_Dispatcher SHALL 選取「成功率 × 速度評分 × 被調用次數」最高的版本;當 stability = `pinned:vN` 時,無論版本集合中其他版本的評分如何,SHALL 永遠選取版本 vN。
**Validates: Requirements 4.2, 4.4**
---
### Property 8: 歷史版本永久保留不變量
*For any* 已上架的零件版本(id, version),無論該版本後來被標記為 `deprecated``tombstone`,其 `.wasm` 二進位 SHALL 永遠可從 R2 讀取,且 `pinned` 引用 SHALL 永遠能透過 Component_Dispatcher 執行該版本。
**Validates: Requirements 4.5, 10.5**
---
### Property 9: Cypher 三元組解析 Confluence(順序無關性)
*For any* 合法的 Cypher 三元組集合,無論三元組在輸入陣列中的排列順序如何,`parseTriplets` 產生的執行圖(節點集合、邊集合、拓撲結構)SHALL 語意等效——即相同的節點 id 集合、相同的 (from, to, type) 邊集合。
**Validates: Requirements 5.7**
---
### Property 10: Web Components 事件與渲染冪等性
*For any* workflow URI 設定於 `<u6u-btn>``workflow` attribute,使用者點擊後發出的 `u6u:trigger` 事件 detail 中的 `workflowId` SHALL 與 URI 中的 id 完全一致;*For any* 一組具名 `<u6u-text-input>` 元件置於 `<u6u-card>` 內,觸發事件後收集到的 payload SHALL 包含所有輸入元件的 name-value 對;*For any* attribute 值,對同一 Web Component 設定相同 attribute 值 N 次,渲染結果 SHALL 與設定一次相同(冪等渲染)。
**Validates: Requirements 8.3, 8.4, 8.5**
---
### Property 11: 系統操作冪等性
*For any* Workflow 執行日誌(run_id),Evaluator_Agent 對相同 run_id 處理 N 次,KBDB 中 SHALL 只存在一個對應的 Evaluation Block,不產生重複記錄;*For any* Component_Registry 讀取操作的查詢參數,在 KBDB 資料不變的前提下,對相同參數呼叫 N 次 SHALL 回傳完全相同的結果。
**Validates: Requirements 10.6, 12.5**
---
## Error Handling
### Component Dispatcher 錯誤分類
| 錯誤類型 | 觸發條件 | 回應格式 |
|---|---|---|
| `COMPONENT_NOT_FOUND` | KBDB 中找不到零件 | `{ error: "COMPONENT_NOT_FOUND", component_id, tier }` |
| `RUNTIME_INCOMPATIBLE` | runtime_compat 不含當前 Tier | `{ error: "RUNTIME_INCOMPATIBLE", component_id, tier, attempted_paths: [] }` |
| `WASM_EXECUTION_TIMEOUT` | 超過 max_cold_start_ms | `{ error: "WASM_EXECUTION_TIMEOUT", component_id, timeout_ms }` |
| `WASM_INVALID_OUTPUT` | stdout 不是合法 JSON | `{ error: "WASM_INVALID_OUTPUT", component_id, raw_output }` |
| `WASM_SYSCALL_VIOLATION` | .wasm 嘗試網路/檔案 syscall | `{ error: "WASM_SYSCALL_VIOLATION", component_id, syscall_name }` |
| `CONTRACT_VALIDATION_FAILED` | 合約格式不合規 | `{ error: "CONTRACT_VALIDATION_FAILED", missing_fields: [] }` |
### 沙盒驗收失敗回應格式
```json
{
"success": false,
"failed_step": "syscall_scan",
"reason": "發現禁止的 syscallsock_connect",
"guide_anchor": "#syscall-constraints",
"component_id": "my_component",
"version": "v1"
}
```
### Tier 3 離線錯誤處理
Tier 3 在離線環境中,所有無法執行的操作都寫入 DTN 佇列,不拋出錯誤:
```go
// Go 排程引擎的錯誤處理策略
type DTNEntry struct {
Type string // "missing_component" | "sync_log" | "request_wasm"
Payload json.RawMessage
CreatedAt time.Time
RetryCount int
}
```
### Web Components 錯誤邊界
`<u6u-btn>``workflow` attribute 未設定時,點擊不發出事件,僅在 console 輸出警告:
```
[u6u-btn] workflow attribute is not set, click event ignored
```
---
## Testing Strategy
### 雙軌測試策略
本功能採用「單元測試 + 屬性測試」雙軌策略:
- **單元測試(Vitest)**:驗證具體範例、邊界條件、錯誤情境
- **屬性測試(fast-check**:驗證上述 11 個 Correctness Properties,每個屬性最少執行 100 次迭代
### 屬性測試配置
使用 `fast-check`(已在 tech stack 中),每個屬性測試標記格式:
```typescript
// Feature: u6u-platform-evolution, Property N: {property_text}
it.prop([fc.record({ id: fc.string(), version: fc.string(), ... })])(
'Property 1: 合約格式完整性',
(contract) => {
// ...
},
{ numRuns: 100 }
);
```
### 各 Phase 測試重點
**Phase 0WASM 執行核心):**
- Property 4WASM 執行路徑 round-trip`validate_json.wasm` 作為 ground truth
- Property 1:合約格式驗證
- 單元測試:WASI shim 的 `fd_read`/`fd_write` 正確性
**Phase 1(零件遷移):**
- Property 3:提交冪等性(20 個零件各提交兩次,驗證無重複)
- Property 2:沙盒驗收流程(各步驟失敗案例)
- Property 8:歷史版本保留(deprecate 後仍可讀取)
**Phase 2Cypher 擴展):**
- Property 9Confluence(三元組順序無關性,fast-check shuffle
- Property 6URI 解析 round-trip
- Property 7:版本選擇策略(floating 最高分、pinned 固定版本)
- Property 5:錯誤結構完整性
**Phase 3(前端畫布):**
- Property 10Web Components 事件與渲染冪等性(`@testing-library/react` + fast-check
- Property 11:評價冪等性(Evaluator Agent 重複處理)
### 整合測試
以下場景使用整合測試(1-3 個具體範例,不用 PBT):
- Tier 1 CF Workers 環境中實際執行 `validate_json.wasm`(驗證 WASM 在 Workers 環境可運行)
- KBDB tpl-component Template 建立與 Slot 讀寫(驗證 KBDB 整合)
- R2 `.wasm` 上傳與讀取(驗證 R2 整合)
- Vectorize 語意搜尋(驗證「查詢 Google Sheets 資料」能找到 `gsheets_get_entries`
### 單元測試重點(非 PBT
- WASI shim`fd_read` 正確讀取 stdin、`fd_write` 正確寫入 stdout
- `evaluateCondition`:現有條件評估函數的邊界案例
- `resolveComponentId`:URI 解析的邊界案例(空字串、特殊字元)
- `<u6u-card>` Smart Container:巢狀容器的事件冒泡行為
@@ -0,0 +1,219 @@
# Requirements Document
## Introduction
u6u 平台演進規格描述從現況(HTTP endpoint 零件、單一 Cloudflare 部署、無前端畫布)
到目標架構(WASM 零件模型、三層物理部署、雙面翻轉畫布)的完整演進路徑。
本規格涵蓋三個核心演進軸:
1. **零件模型遷移**:將 20 個內建零件從 Cloudflare Worker HTTP endpoint 遷移至 WASI preview1 `.wasm` 格式,附帶 `component.contract.yaml`,以 stdin/stdout JSON 作為唯一 I/O 模型。
2. **多 Tier 執行抽象**:讓 Cypher Executor 透過統一的 Component Dispatcher 介面,跨 Tier 1Cloudflare Workers)、Tier 2workerd 地端叢集)、Tier 3Go + Wazero 邊緣載具)呼叫零件。
3. **前端雙面畫布**:建立以 Web Components 為基礎的視覺化畫布,正面為 UI 視圖,反面為 Cypher 邏輯視圖,智慧容器自動打包表單值。
---
## Glossary
- **Component(零件)**:系統最小執行單元,一個零件只做一件事,以 `.wasm`WASI preview1)或 Web Component 形式存在。
- **Component_Contract**:每個零件附帶的 `component.contract.yaml`,定義 id、version、wasi_target、stability、runtime_compat、constraints、input_schema、output_schema、gherkin_tests。
- **Component_Registry**KBDB 中儲存所有零件合約與 `.wasm` 位置的索引,以 `tpl-component` Template Block 實作。
- **Component_Dispatcher**Cypher Executor 內部的路由層,根據零件的 `runtime_compat` 與目標 Tier 決定呼叫路徑(Service Binding / workerd HTTP / Wazero IPC)。
- **Cypher_Executor**Workflow 執行引擎,解析三元組語法,透過 GraphExecutor 執行節點,現部署於 Cloudflare Workers。
- **Cypher_Triplet**`"A >> 關係 >> B"` 格式的三元組,描述節點間的語意關係。
- **KBDB**:三位一體記憶庫(blocks / templates / slots),搭配 Cloudflare Vectorize,是平台唯一的持久化狀態來源。
- **Tier_1**:雲端層,Cloudflare Workers + D1 + Vectorize + R2,全球無伺服器部署。
- **Tier_2**:企業地端層,workerd 叢集 + Kùzu 或 PostgreSQL + AGE,高機密內網環境。
- **Tier_3**:邊緣載具層,Go 排程引擎 + 內嵌 Wazero + SQLite,無 V8、無網路的極限環境(無人機、AGV)。
- **WASI_Preview1**WebAssembly System Interface preview1 規格,零件唯一合法的 WASM 目標格式。
- **Canvas(畫布)**:前端雙面翻轉介面,正面為 UI 視圖,反面為 Cypher 邏輯視圖。
- **Smart_Container**:畫布上的排版容器(如 `<u6u-card>`),自動打包同容器內所有輸入元件的值並附加至觸發事件的 payload。
- **Forge_AI**:工匠 AI,負責在 Tier 2 地端接收零件規格、生成 TinyGo 程式碼、編譯並測試 `.wasm`
- **Evaluator_Agent**:強制評價代理,每次 Workflow 執行後自動評估成功率、效能、警告訊息。
- **Pitfall_Block**KBDB 中記錄已知問題的 Block,AI 搜尋時強制讀取以繞道。
- **Stability_Tag**:零件版本穩定性標籤,值為 `floating`AI 自動選最優)、`stable`(人工確認才換)、`pinned`(版本凍結)。
- **DTN**Delay-Tolerant NetworkingTier 3 邊緣載具在間歇性網路下的短點射傳輸協議。
---
## Requirements
### Requirement 1:零件合約規格標準化
**User Story:** As a 平台架構師, I want 每個零件都有標準化的 `component.contract.yaml` 合約, so that AI 能透過統一介面讀取零件能力,並在任何 Tier 上驗證相容性。
#### Acceptance Criteria
1. THE Component_Contract SHALL 包含以下必填欄位:`canonical_id`(功能合約名稱,永久不變)、`display_name`(人類可讀名稱,可自由撰寫)、`description`(語意搜尋用途,需精確描述零件能做什麼、適用情境,至少 20 字)、`version`(實作版本)、`wasi_target`(值為 `"preview1"`)、`stability`(值為 `floating``stable``pinned` 之一)、`runtime_compat`(陣列,值為 `cf-workers``workerd``wazero` 的子集)、`constraints``input_schema``output_schema``gherkin_tests`
1a. THE `canonical_id` SHALL 遵循以下命名規範,以確保全庫一致性:
- 格式:`{scope}_{action}``{scope}_{object}``{scope}`(單詞),全部小寫底線,不超過 4 個單詞
- **整合類**category: api):以服務名稱為 scope,可加動作;範例:`gmail``gmail_send``google_sheets``google_sheets_append``telegram``telegram_send`
- **資料處理類**category: data):以資料型別或操作為 scope;範例:`string_ops``array_ops``date_ops``json_transform`
- **控制流類**category: logic):以控制結構名稱命名;範例:`if_control``foreach_control``try_catch``switch``wait`
- **AI 類**category: ai):以 `ai_` 為前綴;範例:`ai_transform_compile``ai_summarize``ai_classify`
- 禁止:中文、空格、大寫、連字號(`-`)、版本號混入 id`gmail_v2``version: v2` 表達)
1b. THE `display_name` SHALL 為人類可讀的自由格式名稱(可中文、可含空格);此欄位供 UI 顯示用,不作為系統識別符。範例:`canonical_id: google_sheets_append``display_name: "Google Sheets — 新增一列"`
1c. THE `description` SHALL 用於 Vectorize 語意搜尋索引;撰寫時應以「能做什麼、適合什麼情境」為核心,避免只寫零件名稱的同義詞。範例:`"傳送 Gmail 電子郵件,適合 Workflow 完成時通知使用者、訂閱確認信、錯誤警報等場景。需要 Gmail OAuth token。"` 而非 `"Gmail 發信零件"`
2. THE Component_Contract SHALL 在 `constraints` 中包含以下欄位:`max_size_kb`(上限 2048)、`max_cold_start_ms`(上限 50)、`no_network_syscall`(布林值)、`io_model`(值為 `"stdin_stdout_json"`)。
3. WHEN 一個零件的 `id` 已存在於 Component_RegistryTHE Component_Registry SHALL 允許以新 `version` 值新增該零件的新實作,而不覆蓋舊版本。
4. THE Component_Contract SHALL 在 `gherkin_tests` 中至少包含一個正常情境(happy path)與一個錯誤情境(error path)的測試案例。
5. IF 一個零件的 `input_schema``output_schema` 涉及序列化或反序列化操作,THEN THE Component_Contract SHALL 包含一個 round-trip 測試案例,驗證 `parse(format(x)) == x`
---
### Requirement 2:零件沙盒驗收流程
**User Story:** As a 零件提交者(AI 或開發者), I want 提交的零件自動通過沙盒驗收, so that 只有符合品質標準的零件才能進入零件宇宙。
#### Acceptance Criteria
1. WHEN 一個零件被提交至 Component_RegistryTHE Component_Registry SHALL 依序執行以下驗收步驟:(a)體積檢查(`.wasm` 小於 `max_size_kb`)、(b)冷啟動時間測量(小於 `max_cold_start_ms`)、(c)syscall 掃描(不含網路或檔案系統 syscall)、(d)Gherkin 測試執行(所有 scenario 100% 通過)、(e)多 runtime 相容測試(`runtime_compat` 列出的所有 runtime)。
2. IF 任一驗收步驟失敗,THEN THE Component_Registry SHALL 拒絕該零件上架,並回傳包含失敗步驟名稱與具體原因的錯誤訊息。
3. WHEN 所有驗收步驟通過,THE Component_Registry SHALL 將零件合約存入 KBDB 的 `tpl-component` Template Block,並記錄上架時間戳記。
4. THE Component_Registry SHALL 以冪等方式執行驗收流程,對相同 `id``version` 的重複提交回傳相同結果而不重複執行測試。
---
### Requirement 3:現有 HTTP 零件遷移至 WASM
**User Story:** As a 平台開發者, I want 將現有 20 個 HTTP endpoint 零件遷移為 WASI preview1 `.wasm` 格式, so that 零件能在 Tier 3 邊緣載具(無 V8、無網路)上執行,消除技術債。
#### Acceptance Criteria
1. THE Component_Dispatcher SHALL 支援以 WASM 模式呼叫零件:讀取 `.wasm` 二進位、透過 WASI preview1 runtime 執行、將 input JSON 寫入 stdin、從 stdout 讀取 output JSON。
2. WHEN 一個 WASM 零件需要呼叫外部 HTTP API(如 Google Sheets),THE Component_Dispatcher SHALL 透過 host function 注入方式提供網路能力,而非允許 `.wasm` 內部直接發出網路 syscall。
3. THE Component_Dispatcher SHALL 在 Tier 1Cloudflare Workers)環境中,以 `workerd` 內建的 WASM 執行能力執行 WASI preview1 零件。
4. WHEN 一個零件的 `runtime_compat` 不包含當前執行環境的 TierTHE Component_Dispatcher SHALL 回傳錯誤,說明該零件不相容於當前 Tier,而非嘗試執行。
5. THE Component_Dispatcher SHALL 在遷移期間同時支援舊有 HTTP endpoint 模式(Service Binding 或外部 URL)與新 WASM 模式,以 Component_Contract 的 `io_model` 欄位區分呼叫路徑。
6. FOR ALL 現有 20 個內建零件,遷移後的 WASM 版本 SHALL 通過與原 HTTP 版本相同的 Gherkin 測試案例(round-trip 等效性)。
---
### Requirement 4:零件版本控制與穩定性標籤
**User Story:** As a Workflow 設計者, I want 在 Cypher 三元組中指定零件的穩定性需求, so that 關鍵業務流程不會因 AI 自動升級零件而中斷。
#### Acceptance Criteria
1. THE Cypher_Executor SHALL 支援以下三種零件引用語法:`component://id`(預設 floating)、`component://id@stable``component://id@pinned:vN`
2. WHEN 一個 Workflow 引用 `component://id`floating),THE Component_Dispatcher SHALL 從 Component_Registry 選取該 `id` 下「成功率 × 速度 × 被調用次數」評分最高的版本執行。
3. WHEN 一個 Workflow 引用 `component://id@stable`THE Component_Dispatcher SHALL 使用當前標記為 stable 的版本,並在有更優版本時記錄提示至 KBDB,但不自動切換。
4. WHEN 一個 Workflow 引用 `component://id@pinned:vN`THE Component_Dispatcher SHALL 永遠使用版本 `vN`,即使該版本已被標記為 Deprecated。
5. THE Component_Registry SHALL 保留所有歷史版本的 `.wasm` 二進位,不因版本淘汰而刪除檔案。
---
### Requirement 5Cypher 語意關係擴展
**User Story:** As a Workflow 設計者, I want Cypher 三元組支援完整的語意關係集合, so that 能描述條件分支、子流程呼叫、前端觸發等複雜業務邏輯。
#### Acceptance Criteria
1. THE Cypher_Executor SHALL 解析並執行以下語意關係:`IS_A`(節點類型宣告)、`ON_SUCCESS`(成功後繼)、`ON_FAIL`(失敗後繼)、`ON_CLICK`(前端點擊觸發)、`CALLS_SUBFLOW`(呼叫子 Workflow)、`CONTAINS`(容器包含關係)、`HAS_STYLE`(樣式關聯)、`HAS_BEHAVIOR`(行為關聯)。
2. WHEN 解析 `IS_A` 關係,THE Cypher_Executor SHALL 從 Component_Registry 載入對應的零件合約,並以合約的 `input_schema` 驗證節點的輸入 context。
3. WHEN 解析 `ON_SUCCESS``ON_FAIL` 關係,THE Cypher_Executor SHALL 根據上游節點的執行結果(成功或拋出錯誤)決定走向,而非依賴 context 中的特定欄位。
4. WHEN 解析 `CALLS_SUBFLOW` 關係,THE Cypher_Executor SHALL 以當前 context 作為子 Workflow 的 initialContext 執行,並將子 Workflow 的輸出合併回主流程 context。
5. WHEN 解析 `ON_CLICK` 關係,THE Cypher_Executor SHALL 接受來自前端 Smart_Container 打包的 payload,並以該 payload 作為 Workflow 的 initialContext。
6. THE Cypher_Executor SHALL 支援 URI 協議前綴:`component://`(零件引用)、`workflow://`Workflow 引用)、`ui://`(前端零件引用)、`style://`(樣式零件引用)。
7. FOR ALL 合法的 Cypher 三元組序列,THE Cypher_Executor SHALL 保證解析結果的冪等性:對相同輸入三元組集合,無論排列順序,產生語意等效的執行圖(Confluence 屬性)。
---
### Requirement 6Component Dispatcher 多 Tier 路由
**User Story:** As a 平台架構師, I want Cypher Executor 透過統一的 Component Dispatcher 介面呼叫跨 Tier 零件, so that Workflow 設計者不需要知道零件部署在哪個 Tier。
#### Acceptance Criteria
1. THE Component_Dispatcher SHALL 根據以下優先序決定呼叫路徑:(1Tier 1Cloudflare Service Binding(若零件部署為 Worker)或 WASM 直接執行;(2Tier 2workerd 叢集 HTTP endpoint;(3Tier 3Wazero IPCstdin/stdout)。
2. WHEN Component_Dispatcher 在 Tier 1 環境中呼叫一個 `runtime_compat` 包含 `cf-workers` 的零件,THE Component_Dispatcher SHALL 優先使用 Cloudflare Service Binding,若 binding 不存在則退回 WASM 執行模式。
3. WHEN Component_Dispatcher 在 Tier 3 環境中呼叫零件,THE Component_Dispatcher SHALL 只使用 Wazero 執行本地 `.wasm` 檔案,不發出任何網路請求。
4. IF Component_Dispatcher 無法在當前 Tier 找到可用的呼叫路徑,THEN THE Component_Dispatcher SHALL 回傳結構化錯誤,包含:零件 id、當前 Tier、嘗試的呼叫路徑清單。
5. THE Component_Dispatcher SHALL 對每次零件呼叫記錄執行時間(ms)、成功或失敗狀態,並非同步寫入 KBDB 的 Evaluation Block,不阻擋主流程。
6. WHILE Component_Dispatcher 執行零件呼叫,THE Component_Dispatcher SHALL 強制套用 Component_Contract 中的 `max_cold_start_ms` 作為逾時上限,超時後回傳逾時錯誤。
---
### Requirement 7Tier 3 邊緣離線生存能力
**User Story:** As a 邊緣載具操作者(無人機、AGV), I want 載具在完全離線環境中仍能執行預載的 Workflow, so that 業務不因網路中斷而停擺。
#### Acceptance Criteria
1. THE Tier_3 執行引擎 SHALL 在無網路連線的環境中,使用本地 SQLite 作為 KBDB 替代儲存,執行預先下載的 Cypher Workflow 與 `.wasm` 零件。
2. WHEN Tier_3 執行引擎在執行中發現缺少所需零件,THE Tier_3 執行引擎 SHALL 記錄缺失零件的 `id``input_schema` 至本地 DTN 佇列,待下次連網時以 Burst 傳輸方式送至 Tier_2 請求代工。
3. WHEN Tier_3 執行引擎收到來自 Tier_2 的新 `.wasm` 零件,THE Tier_3 執行引擎 SHALL 在執行前對該零件進行 syscall 掃描,確認不含網路或檔案系統 syscall,通過後才載入執行。
4. THE Tier_3 執行引擎 SHALL 在 Cypher 圖譜執行中途動態替換失敗零件(如感測器零件因環境變化失效),以 Component_Registry 中相同 `input_schema` 的備用零件繼續執行,不中斷整體 Workflow。
5. WHEN Tier_3 執行引擎重新連線至 Tier_2THE Tier_3 執行引擎 SHALL 將本地執行日誌(包含 trace、評價結果、Pitfall 記錄)同步至 Tier_2 的 KBDB,確保全局狀態一致。
---
### Requirement 8:前端 Web Components 零件庫
**User Story:** As a 前端開發者, I want 一套以 Web Components 標準實作的 u6u UI 零件庫, so that 畫布上的 UI 元件能在任何現代瀏覽器中獨立運作,不依賴特定前端框架。
#### Acceptance Criteria
1. THE Canvas SHALL 提供以下核心 Web Components`<u6u-btn>`(按鈕)、`<u6u-text-input>`(文字輸入)、`<u6u-text-field>`(多行文字)、`<u6u-chart>`(圖表)、`<u6u-card>`(智慧容器)。
2. THE `<u6u-btn>` SHALL 支援以下 HTML attributes`label`(顯示文字)、`color`(主題色)、`tooltip`(滑鼠懸停提示,純靜態,不觸發 Webhook)、`workflow`(綁定的 Workflow URI,格式為 `workflow://id`)。
3. WHEN `<u6u-btn>``workflow` attribute 被設定且使用者點擊按鈕,THE `<u6u-btn>` SHALL 發出 `u6u:trigger` 自訂事件,事件 detail 包含 `{ workflowId, payload }`
4. THE `<u6u-card>` SHALL 在接收到子元件的 `u6u:trigger` 事件時,自動收集同容器內所有 `<u6u-text-input>``<u6u-text-field>` 的當前值,合併至事件的 `payload` 後再向上冒泡。
5. FOR ALL Web ComponentsTHE Canvas SHALL 保證元件的 HTML attribute 變更能即時反映至視覺渲染,且渲染結果與 attribute 值之間的對應關係具有冪等性(相同 attribute 值永遠產生相同渲染結果)。
---
### Requirement 9:雙面翻轉畫布介面
**User Story:** As a 業務使用者(非工程師), I want 畫布上每個 UI 元件都能翻面查看並編輯其 Cypher 邏輯連線, so that 不需要寫程式就能理解並修改業務邏輯。
#### Acceptance Criteria
1. THE Canvas SHALL 為每個 UI 零件提供「翻面」操作,切換至邏輯視圖後,顯示該零件關聯的 Cypher 三元組(以視覺化節點連線方式呈現)。
2. WHEN 使用者在邏輯視圖中修改 Cypher 連線(新增、刪除或修改三元組),THE Canvas SHALL 即時更新對應 Workflow 的 KBDB Block,並在正面 UI 視圖中反映連線狀態變更(如按鈕顏色或 badge 提示)。
3. THE Canvas SHALL 在邏輯視圖中提供 Workflow URI 選擇器,列出 KBDB 中所有可用的 Workflow,讓使用者透過下拉選單完成 `ON_CLICK >> workflow://id` 的綁定,不需手動輸入 URI。
4. WHEN 使用者在畫布上將兩個 UI 零件拖入同一個 `<u6u-card>` 容器,THE Canvas SHALL 自動在邏輯視圖中顯示 Smart_Container 的自動打包關係,說明哪些輸入值會被自動收集。
5. THE Canvas SHALL 在使用者嘗試替換一個已綁定 Workflow 的 UI 零件時,只顯示 Component_Registry 中具備相同觸發能力(即 `u6u:trigger` 事件)的候選零件,過濾掉不相容的零件。
---
### Requirement 10:自動演化評價迴圈
**User Story:** As a 平台維運者, I want 每次 Workflow 執行後自動觸發 AI 評價, so that 系統能持續識別問題零件並累積避坑知識。
#### Acceptance Criteria
1. WHEN 一個 Workflow 執行完畢(無論成功或失敗),THE Evaluator_Agent SHALL 在執行結束後非同步評估以下維度:執行狀態(成功 / 失敗 / 逾時)、各節點執行時間、零件錯誤率趨勢。
2. WHEN Evaluator_Agent 發現某零件的錯誤率在連續 5 次執行中超過 50%THE Evaluator_Agent SHALL 在 KBDB 中為該零件建立 Pitfall_Block,記錄:零件 id、失敗模式描述、首次發現時間戳記。
3. WHEN Component_Dispatcher 在 Component_Registry 搜尋零件時,THE Component_Dispatcher SHALL 讀取目標零件的所有關聯 Pitfall_Block,並在選擇版本時降低有 Pitfall 記錄的版本的評分權重。
4. WHEN 一個零件連續 30 天無任何 Workflow 引用,THE Component_Registry SHALL 將該零件標記為 `Deprecated`,並從預設搜尋結果中移除,但保留 `.wasm` 二進位與合約。
5. WHEN 一個 `Deprecated` 零件再經過 90 天仍無引用,THE Component_Registry SHALL 將該零件移入墓地(tombstone 狀態),從所有搜尋結果中移除,但 `pinned` 版本的 `.wasm` 永遠保留且可被 Component_Dispatcher 存取。
6. THE Evaluator_Agent SHALL 以冪等方式處理重複的執行日誌,對相同 `run_id` 的重複評價請求回傳相同結果而不重複建立 Pitfall_Block。
---
### Requirement 11:零件開發指引(Component Authoring Guide
**User Story:** As a 零件開發者(使用自己的 AI 工具,如 Claude、GPT、本地模型), I want 平台提供完整的零件開發指引, so that 我的 AI 能根據指引生成符合合約規格的 `.wasm` 零件,並一次通過沙盒驗收。
#### Acceptance Criteria
1. THE Component_Registry SHALL 在 `GET /components/guide` 端點提供機器可讀的開發指引文件(Markdown 格式),內容包含:零件合約 YAML 完整範例、I/O 模型說明(stdin/stdout JSON)、各語言(TinyGo、Rust、AssemblyScript)的最小可運行範例程式碼、本地測試指令(`wasmtime` 執行方式)、常見錯誤與解法。
2. THE Component_Registry SHALL 在開發指引中明確列出所有禁止行為:網路 syscall、檔案系統 syscall、打包 runtimeQuickJS、Node.js 等)、超過 2MB、混合前後端邏輯於同一零件。
3. THE Component_Registry SHALL 在開發指引中提供 `component.contract.yaml` 的 JSON Schema 定義,讓開發者的 AI 能在提交前自行驗證合約格式正確性。
4. WHEN 一個零件提交驗收失敗,THE Component_Registry SHALL 在錯誤回應中附上指向開發指引對應章節的錨點連結(如 `#syscall-constraints`),讓開發者的 AI 能直接定位修復方向。
5. THE Component_Registry SHALL 提供 `POST /components/validate-contract` 端點,接受 `component.contract.yaml` 內容,回傳格式驗證結果(欄位完整性、schema 合法性、gherkin_tests 最低數量),讓開發者在提交 `.wasm` 前先驗證合約。
6. FOR ALL 開發指引中的程式碼範例,THE Component_Registry SHALL 保證範例能通過 Requirement 2 定義的沙盒驗收流程(指引本身是可執行的 ground truth)。
---
### Requirement 12KBDB Component Registry 整合
**User Story:** As a 系統開發者, I want Component Registry 完全以 KBDB 的 Template/Block/Slot 機制實作, so that 零件狀態與平台其他知識共享同一個持久化層,不引入新的資料庫。
#### Acceptance Criteria
1. THE Component_Registry SHALL 以 KBDB 的 `tpl-component` Template 儲存零件合約,每個零件版本對應一個 Block,Block 的 slots 對應合約的各欄位(id、version、wasi_target、stability、runtime_compat、constraints 等)。
2. THE Component_Registry SHALL 以 KBDB 的 Vectorize 索引零件的 `description``tags` 欄位,支援語意搜尋(如「查詢 Google Sheets 資料」能找到 `gsheets_get_entries`)。
3. WHEN Component_Dispatcher 搜尋零件時,THE Component_Registry SHALL 回傳按「成功率 × 速度評分 × 被調用次數」排序的版本清單,最多回傳 10 個候選版本。
4. THE Component_Registry SHALL 透過 KBDB 的 HTTP API 存取所有資料,不直接操作 D1 SQL,符合平台的 API-First 通訊鐵律。
5. FOR ALL Component_Registry 的讀取操作,THE Component_Registry SHALL 保證在 KBDB 資料不變的情況下,對相同查詢參數回傳相同結果(查詢冪等性)。
@@ -0,0 +1,411 @@
# Implementation Plan: u6u Platform Evolution
## Overview
依照 Bootstrap 順序分四個 Phase 實作,每個 Phase 都是下一個 Phase 的基礎。
技術棧:TypeScript、Hono、Zod、Vitest、fast-check,部署於 Cloudflare Workers。
---
## Phase 0:最小 WASM 執行核心
- [x] 1. 建立 Component Registry 基礎架構(`u6u-core/registry/`
- [x] 1.1 建立 `tpl-component` Template Block(透過 KBDB HTTP API
- 呼叫 KBDB `/templates` 建立 `tpl-component` template(若不存在)
- 定義所有 slot keyscanonical_id、display_name、category、version、wasi_target、stability、runtime_compat、constraints、input_schema、output_schema、gherkin_tests、wasm_r2_key、cypher_binding_url、service_binding_key、description、tags、success_rate、avg_duration_ms、call_count、status、deprecated_at
- _Requirements: 12.1_
- [x] 1.2 實作 `POST /components/validate-contract` 端點
- 以 Zod schema 驗證 component.contract.yaml 所有必填欄位
- 回傳缺失欄位清單(`missing_fields: string[]`
- _Requirements: 1.1, 1.2, 11.5_
- [ ]* 1.3 寫 property test for 合約格式完整性
- **Property 1: 合約格式完整性**
- **Validates: Requirements 1.1, 1.2, 1.4**
- 用 fast-check 生成隨機缺少任意必填欄位的合約物件,驗證 validator 必定拒絕並回傳該欄位名稱
- 用 fast-check 生成包含所有必填欄位的合約物件,驗證 validator 必定通過
- [x] 1.4 實作 `GET /components/guide` 端點
- 回傳 Markdown 格式開發指引(TinyGo 白名單、禁止行為、contract YAML 範例、wasmtime 測試指令)
- _Requirements: 11.1, 11.2, 11.3_
- [x] 1.5 實作 `POST /components` 零件提交端點(沙盒驗收流程)
- 依序執行五個驗收步驟:(a) 體積檢查、(b) 冷啟動時間測量、(c) syscall 掃描、(d) Gherkin 測試執行、(e) runtime 相容測試
- 任一步驟失敗立即停止,回傳 `{ success: false, failed_step, reason, guide_anchor, component_id, version }`
- 通過後以 KBDB HTTP API 寫入 Block`block_id = comp-{id}-{version}`
- 同時上傳 `.wasm` 至 R2slot `wasm_r2_key` 記錄 R2 key
- _Requirements: 2.1, 2.2, 2.3_
- [ ]* 1.6 寫 property test for 沙盒驗收流程正確性
- **Property 2: 沙盒驗收流程正確性**
- **Validates: Requirements 2.1, 2.2**
- 用 fast-check 生成在步驟 N 失敗的零件,驗證回應包含步驟 N 名稱與原因,且不執行步驟 N+1
- [ ]* 1.7 寫 property test for 零件提交冪等性與持久性
- **Property 3: 零件提交冪等性與持久性**
- **Validates: Requirements 2.3, 2.4**
- 用 fast-check 生成通過驗收的零件,提交後讀取合約驗證所有欄位 round-trip 一致
- 對相同 (id, version) 重複提交 N 次,驗證 KBDB 只存在一個 Block
- [x] 2. 實作 WASI preview1 shim 與 WASM 執行核心(`cypher-executor/src/lib/`
- [x] 2.1 實作輕量 WASI preview1 shim`wasi-shim.ts`
- 實作 `fd_read`(從 stdin buffer 讀取)、`fd_write`(寫入 stdout/stderr buffer)、`proc_exit`(拋出 Error)、`random_get``crypto.getRandomValues`
- 其餘 syscall 一律回傳 ENOSYS76
- 不引入任何外部依賴(不使用 `@cloudflare/workers-wasi`
- _Requirements: 3.1, 3.3_
- [x]* 2.2 寫單元測試 for WASI shim
- 測試 `fd_read` 正確讀取 stdin buffer(含多次讀取、邊界條件)
- 測試 `fd_write` 正確寫入 stdout bufferfd=1)與 stderr bufferfd=2
- 測試 `proc_exit` 拋出 Error
- [x] 2.3 實作 Tier 1 WASM 執行器(`wasm-executor.ts`
- 從 R2 fetch `.wasm` ArrayBuffer
- `WebAssembly.compile` 後快取 `WebAssembly.Module`Worker 記憶體,跨請求共享)
- 建立 WASI shim,注入 stdin = `JSON.stringify(input)`
- `WebAssembly.instantiate(module, imports)` → 呼叫 `_start()``main()`
- 從 stdout buffer 讀取輸出,`JSON.parse` 後回傳
- 套用 `max_cold_start_ms` 逾時(`Promise.race`
- _Requirements: 3.1, 3.3, 6.6_
- [ ]* 2.4 寫 property test for Component Dispatcher 路由正確性
- **Property 4: Component Dispatcher 路由正確性**
- **Validates: Requirements 3.1, 3.5, 3.6**
- 用 fast-check 生成合法 JSON input,驗證 WASM 執行路徑輸出與預期語意等效
- [x] 3. 建立 `validate_json.wasm` 第一個真實零件(TinyGo
- [x] 3.1 撰寫 `validate_json` TinyGo 原始碼(`u6u-core/registry/components/validate_json/main.go`
- 只使用白名單 import`os``io``encoding/json`
- 讀取 stdin JSON,解析 `json_string` 欄位,嘗試 `json.Unmarshal`
- 成功輸出 `{"valid":true}`,失敗輸出 `{"valid":false,"error":"..."}`
- _Requirements: 3.1, 11.6_
- [x] 3.2 撰寫 `validate_json` component.contract.yaml
- 包含所有必填欄位、gherkin_testshappy path + error path
- `runtime_compat: ["cf-workers","workerd","wazero"]`
- _Requirements: 1.1, 1.2, 1.4_
- [ ]* 3.3 寫單元測試 for validate_jsonGherkin 場景驗證)
- 測試合法 JSON 輸入回傳 `{"valid":true}`
- 測試非法 JSON 輸入回傳 `{"valid":false,"error":...}`
- [x] 4. Checkpoint — Phase 0 驗收
- 確認 `validate_json.wasm` 能在 CF Workers 環境中透過 WASM 執行器執行
- 確認 Component Registry `/guide``/validate-contract``/components` 端點可用
- 確認所有 Phase 0 測試通過,向使用者確認是否繼續 Phase 1
---
## Phase 1:遷移現有零件(20 個 HTTP → WASM
- [x] 5. 升級 Component Dispatcher 支援雙模式(`cypher-executor/src/lib/component-loader.ts`
- [x] 5.1 重構 `ComponentDescriptor` 型別(移除舊 `http_endpoint`,新增 `component_type`
- 定義 `ComponentType = 'wasm' | 'cypher_binding' | 'service_binding'`
- 新版 `ComponentDescriptor` 欄位:`component_type``wasm_r2_key``runtime_compat``max_cold_start_ms``url`cypher_binding)、`method``binding`service_binding)、`path`
- _Requirements: 3.5_
- [x] 5.2 實作路由決策邏輯(`component-dispatcher.ts`
- 查 Component Registry 取得合約
-`component_type` 分流:`wasm` → WASM 執行器;`cypher_binding` → HTTP POST 到外部 URL`service_binding` → CF Service Binding
- 檢查 `runtime_compat` 是否包含當前 Tier,不包含則回傳 `RUNTIME_INCOMPATIBLE` 錯誤
- _Requirements: 3.4, 6.1, 6.2_
- [ ]* 5.3 寫 property test for Dispatcher 錯誤結構完整性
- **Property 5: Dispatcher 錯誤結構完整性**
- **Validates: Requirements 3.4, 6.4**
- 用 fast-check 生成 (component_id, tier) 組合,當 runtime_compat 不含當前 tier,驗證錯誤回應同時包含 component_id、tier、attempted_paths 三個欄位
- [x] 6. 遷移 20 個內建零件(`u6u-core/builtins/``u6u-core/registry/components/`
- [x] 6.1 為每個零件撰寫 TinyGo 原始碼與 component.contract.yaml(批次作業)
- 每個零件:只用白名單 import、stdin/stdout JSON I/O、附帶 gherkin_tests
- 需要外部 API 的零件(如 gsheets):改用 `cypher_binding` 模式,contract 中記錄 `cypher_binding_url`
- _Requirements: 3.6, 11.6_
- [x] 6.2 透過 `POST /components` 批次提交 20 個零件至 Component Registry
- 每個零件通過沙盒驗收後自動寫入 KBDB
- 驗證 20 個零件的 Gherkin 測試全部通過
- _Requirements: 2.1, 3.6_
- [ ]* 6.3 寫 property test for 歷史版本永久保留不變量
- **Property 8: 歷史版本永久保留不變量**
- **Validates: Requirements 4.5, 10.5**
- 用 fast-check 生成已上架零件,標記為 deprecated 後,驗證 `.wasm` 仍可從 R2 讀取,pinned 引用仍可執行
- [x] 7. 實作 Component Registry 查詢端點
- [x] 7.1 實作 `GET /components/:id``GET /components/:id/versions`
- 透過 KBDB HTTP API 查詢 `tpl-component` blocks
- `/versions` 回傳按「成功率 × 速度評分 × 被調用次數」排序的版本清單(最多 10 個)
- _Requirements: 12.3_
- [x] 7.2 實作 `GET /components/search?q=...` 語意搜尋
- 呼叫 KBDB Vectorize API,以 `description` + `tags` 欄位做語意搜尋
- _Requirements: 12.2_
- [ ]* 7.3 寫單元測試 for 查詢冪等性
- 驗證相同查詢參數在 KBDB 資料不變時回傳相同結果
- [x] 8. Checkpoint — Phase 1 驗收
- 確認 20 個零件全部通過沙盒驗收並存入 KBDB
- 確認 Component Dispatcher 雙模式路由正確(WASM + cypher_binding
- 確認所有 Phase 1 測試通過,向使用者確認是否繼續 Phase 2
---
## Phase 2Cypher 語意擴展 + Multi-Tier Dispatcher
- [x] 9. 擴展 Cypher Triplet Parser`cypher-executor/src/actions/triplet-parser.ts`
- [x] 9.1 新增 EdgeType 定義
- 在現有 `PIPE | IF | FOREACH | CONTINUE` 基礎上新增:`IS_A | ON_SUCCESS | ON_FAIL | ON_CLICK | CALLS_SUBFLOW | CONTAINS | HAS_STYLE | HAS_BEHAVIOR`
- _Requirements: 5.1_
- [x] 9.2 實作 URI 協議解析函數(`resolveComponentId`
- 解析 `component://id``component://id@stable``component://id@pinned:vN``workflow://id``ui://id``style://id`
- 回傳 `{ type, canonicalId, stability, pinnedVersion? }`
- _Requirements: 4.1, 5.6_
- [ ]* 9.3 寫 property test for 零件 URI 解析 Round-Trip
- **Property 6: 零件 URI 解析 Round-Trip**
- **Validates: Requirements 4.1**
- 用 fast-check 生成合法 URI 字串,解析後再序列化,驗證語意等效;驗證解析出的 id、stability、pinnedVersion 與原始 URI 完全一致
- [ ]* 9.4 寫 property test for Cypher 三元組解析 Confluence
- **Property 9: Cypher 三元組解析 Confluence(順序無關性)**
- **Validates: Requirements 5.7**
- 用 fast-check 生成合法三元組集合,用 `fc.shuffledSubarray` 打亂順序,驗證 `parseTriplets` 產生相同節點集合與邊集合
- [x] 10. 擴展 GraphExecutor 執行語意(`cypher-executor/src/graph-executor.ts`
- [x] 10.1 實作 `IS_A` 關係處理
- 從 Component Registry 載入零件合約,以 `input_schema` 驗證節點輸入 context
- _Requirements: 5.2_
- [x] 10.2 實作 `ON_SUCCESS` / `ON_FAIL` 分支執行
-`executeNode` 的 try/catch 中,成功走 `ON_SUCCESS` 邊,失敗走 `ON_FAIL` 邊(傳遞 error context
- _Requirements: 5.3_
- [x] 10.3 實作 `CALLS_SUBFLOW` 子流程呼叫
- 從 KBDB 載入子 Workflow 定義,建立子 GraphExecutor 執行,將輸出合併回主流程 context
- _Requirements: 5.4_
- [x] 10.4 實作 `ON_CLICK` 前端觸發處理
- 接受來自前端 Smart Container 打包的 payload,作為 Workflow initialContext
- _Requirements: 5.5_
- [x] 10.5 實作 `CONTAINS` / `HAS_STYLE` / `HAS_BEHAVIOR` 結構語意解析(不執行,僅記錄圖結構)
- _Requirements: 5.1_
- [x] 11. 實作版本選擇策略(Component Dispatcher 升級)
- [x] 11.1 實作 floating 版本選擇算法
- 從 KBDB 查詢該 id 下所有版本,計算「成功率 × 速度評分 × 被調用次數」,選取最高分版本
- _Requirements: 4.2_
- [x] 11.2 實作 stable / pinned 版本選擇
- `stable`:使用當前標記為 stable 的版本,有更優版本時記錄提示至 KBDB 但不切換
- `pinned:vN`:永遠使用版本 vN,即使已 deprecated
- _Requirements: 4.3, 4.4_
- [ ]* 11.3 寫 property test for 版本選擇策略正確性
- **Property 7: 版本選擇策略正確性**
- **Validates: Requirements 4.2, 4.4**
- 用 fast-check 生成版本集合(各有不同 success_rate、avg_duration_ms、call_count),驗證 floating 選最高分;驗證 pinned:vN 無論其他版本評分如何永遠選 vN
- [x] 12. 實作 Evaluator Agent 與評價迴圈(`cypher-executor/src/actions/`
- [x] 12.1 實作 `execution-evaluator.ts`(擴展現有 `execution-logger.ts`
- Workflow 執行完畢後非同步寫入 KBDB Evaluation Block`tpl-evaluation`
- 記錄:run_id、workflow_id、component_id、verdict、duration_ms、error_message、evaluated_at
- 冪等處理:相同 run_id 不重複建立 Block
- _Requirements: 10.1, 10.6_
- [x] 12.2 實作 Pitfall Block 建立邏輯
- 偵測某零件連續 5 次執行錯誤率 > 50%,建立 `tpl-pitfall` Block
- 版本選擇時降低有 Pitfall 記錄的版本評分權重
- _Requirements: 10.2, 10.3_
- [x] 12.3 實作零件自動 Deprecated / Tombstone 狀態轉換
- 連續 30 天無引用 → 標記 `deprecated`,從預設搜尋移除
- 再 90 天無引用 → 標記 `tombstone`,從所有搜尋移除(pinned `.wasm` 永久保留)
- _Requirements: 10.4, 10.5_
- [ ]* 12.4 寫 property test for 系統操作冪等性
- **Property 11: 系統操作冪等性**
- **Validates: Requirements 10.6, 12.5**
- 用 fast-check 生成 run_id,對相同 run_id 呼叫 Evaluator N 次,驗證 KBDB 只存在一個 Evaluation Block
- 驗證相同查詢參數在資料不變時回傳相同結果
- [x] 13. Checkpoint — Phase 2 驗收
- 確認新 EdgeType 全部可解析執行(IS_A、ON_SUCCESS、ON_FAIL、CALLS_SUBFLOW、ON_CLICK
- 確認版本選擇策略(floating / stable / pinned)行為正確
- 確認 Evaluator Agent 冪等寫入 KBDB
- 確認所有 Phase 2 測試通過,向使用者確認是否繼續 Phase 3
---
## Phase 3:前端畫布(Web Components + 雙面翻轉)
- [x] 14. 建立 Web Components 零件庫(`u6u-core/web-components/`
- [x] 14.1 實作 `<u6u-btn>` Custom Element
- 支援 attributes`label``color``tooltip``workflow``disabled`
- `workflow` 設定且點擊時發出 `u6u:trigger` CustomEvent`{ workflowId, payload }`
- `workflow` 未設定時點擊不發出事件,console 輸出警告
- _Requirements: 8.2, 8.3_
- [x] 14.2 實作 `<u6u-text-input>``<u6u-text-field>` Custom Elements
- 支援 `name``placeholder``value` attributes
- `value` property 可被 `<u6u-card>` 讀取
- _Requirements: 8.1_
- [x] 14.3 實作 `<u6u-card>` Smart Container
- 攔截子元件的 `u6u:trigger` 事件(`stopPropagation`
- 收集同容器內所有 `<u6u-text-input>` / `<u6u-text-field>` 的 name-value 對
- 合併至 payload 後重新發出 `u6u:trigger``bubbles: true, composed: true`
- _Requirements: 8.4_
- [x] 14.4 實作 `<u6u-chart>` Custom Element(基礎版)
- 支援 `data` attributeJSON 字串)、基本折線圖渲染
- _Requirements: 8.1_
- [ ]* 14.5 寫 property test for Web Components 事件與渲染冪等性
- **Property 10: Web Components 事件與渲染冪等性**
- **Validates: Requirements 8.3, 8.4, 8.5**
- 用 fast-check 生成 workflow URI,驗證點擊後 `u6u:trigger` detail.workflowId 與 URI id 完全一致
- 用 fast-check 生成具名 input 集合置於 u6u-card,驗證收集到的 payload 包含所有 name-value 對
- 用 fast-check 生成 attribute 值,對同一元件設定相同值 N 次,驗證渲染結果冪等
- [x] 15. 建立雙面翻轉畫布(`inkstone-admin/frontend/web/`
- [x] 15.1 實作翻轉狀態機(`Canvas.tsx`
- 狀態:`UIView``LogicView`(點擊翻面按鈕切換)
- `LogicView``Editing`(修改三元組)→ `Saving`(確認儲存)→ `LogicView`
- _Requirements: 9.1_
- [x] 15.2 實作邏輯視圖(Cypher 三元組視覺化)
- 顯示零件關聯的 Cypher 三元組(節點連線方式)
- 修改三元組後即時更新 KBDB Workflow Block(透過 KBDB HTTP API
- _Requirements: 9.2_
- [x] 15.3 實作 Workflow URI 選擇器
- 列出 KBDB 中所有可用 Workflow,下拉選單完成 `ON_CLICK >> workflow://id` 綁定
- _Requirements: 9.3_
- [x] 15.4 實作 Smart Container 拖放與自動打包關係顯示
- 拖入同一 `<u6u-card>` 時,邏輯視圖自動顯示 CONTAINS 關係與自動打包說明
- _Requirements: 9.4_
- [x] 15.5 實作零件替換過濾器
- 替換已綁定 Workflow 的 UI 零件時,只顯示具備 `u6u:trigger` 能力的候選零件
- _Requirements: 9.5_
- [x] 15.6 在畫布中整合 u6u Web ComponentsDogfooding
- 畫布 UI 本身使用 `<u6u-btn>``<u6u-card>``<u6u-text-input>` 組裝
- 驗證 Web Components 在 React 19 環境中正確運作
- _Requirements: 8.1, 9.1_
- [ ]* 15.7 寫整合測試 for 畫布翻轉流程
- 測試 UIView → LogicView → Editing → Saving → LogicView 完整狀態轉換
- 測試 KBDB 寫入成功與失敗兩種情境
- [x] 16. Final Checkpoint — 全平台驗收
- 確認四個 Phase 的所有測試通過(`pnpm test` in each service
- 確認 Dogfooding:畫布本身用 u6u Web Components 組裝,每一層都是下一層的第一個用戶
- 確認 KBDB 不變量:仍只有三張表(blocks / templates / slots
- 確認 API-First 鐵律:所有跨服務通訊只透過 HTTP API
- 向使用者確認所有任務完成
---
## Phase 4:邊緣基礎設施(Tier 3 支援)
> 這個 Phase 不在零件遷移範圍內,是獨立的基礎設施工作。
- [ ] 17. Credentials 邊緣支援評估與改寫(`u6u-core/credentials/`
- [ ] 17.1 評估 Tier 3 是否需要 Credentials
- 場景:無人機在有網路時呼叫外部 API(如取得感測器資料),需要 access_token
- 結論:Tier 3 需要在連網時從 Tier 2 取得 Credential,離線時使用本地快取的加密 token
- _Requirements: 7.1, 7.2_
- [ ] 17.2 實作 Credential 本地快取機制(Tier 3 用)
- Tier 3 Go 排程引擎在連網時從 Tier 2 Credentials Worker 取得加密 token
- 存入本地 SQLiteAES-GCM 加密,key 存於設備安全儲存)
- 離線時從本地快取讀取,過期時加入 DTN 佇列等待更新
- _Requirements: 7.1, 7.3_
- [ ] 18. Go Cypher ExecutorTier 3 邊緣執行引擎)(`u6u-core/executor/`
- [ ] 18.1 用 Go 實作 Cypher 三元組解析器
- 解析 `"A >> 關係 >> B"` 格式,建立執行圖(nodes + edges
- 支援 IS_A、ON_SUCCESS、ON_FAIL、CALLS_SUBFLOW、ON_CLICK 語意關係
- 對應 `cypher-executor/src/actions/triplet-parser.ts` 的 Go 版本
- _Requirements: 5.1, 5.7_
- [ ] 18.2 用 Go + Wazero 實作 WASM 零件執行器
- 載入本地 `.wasm` 檔案,透過 Wazero 原生 WASI preview1 執行
- 注入 `u6u` host module`http_request` host function,透過 DTN 或直接 HTTP
- stdin/stdout JSON I/O,與 Tier 1/2 的 `wasm-executor.ts` 語意等效
- _Requirements: 7.1, 7.4_
- [ ] 18.3 實作 DTN 佇列(離線請求緩衝)
- 零件需要網路但當前離線時,寫入本地 SQLite DTN 佇列
- 連網時 Burst 傳輸:批次送出佇列中的請求,接收回應後繼續執行
- _Requirements: 7.2, 7.5_
- [ ]* 18.4 整合測試:validate_json.wasm 在 Wazero 執行
- 確認同一個 `.wasm` 在 Tier 1wasi-shim.ts)和 Tier 3Wazero)執行結果一致
---
## Notes
- 標記 `*` 的子任務為選填,可跳過以加速 MVP 交付
- 每個任務都引用具體的 Requirements 條款以確保可追溯性
- Checkpoint 任務確保每個 Phase 完成後有明確的驗收點
- Property tests 使用 fast-check,每個屬性最少執行 100 次迭代
- 所有跨服務呼叫只透過 KBDB HTTP API,不直接操作 D1 SQL
- TinyGo 零件只使用白名單 import`os``io``encoding/json`
## Phase 5u6u-mcp 對齊新 Registry + u6u-gui 前端
> 壓測前必須完成,讓 AIu6u-mcp)和人類(u6u-gui)都能操作新的 WASM 零件架構。
- [x] 19. 更新 u6u-mcp 對齊新 Component Registry`u6u-mcp/src/tools/`
- [x] 19.1 更新 `u6u_publish_component`
- 舊:呼叫 `/components/publish`payload 為 `{ component_id, gherkin, api_config }`
- 新:呼叫 `POST /components`payload 為 `{ contract: ComponentContract, wasm_base64: string }`
- 新增 `contract``wasm_base64` 參數,更新工具描述說明 TinyGo 零件提交流程
- [x] 19.2 更新 `u6u_search_components`
- 舊:呼叫 `/components/match`(不存在的端點)
- 新:呼叫 `GET /components/search?q={query}`(新 Registry 語意搜尋端點)
- 更新工具描述:AI 可用自然語言搜尋零件(如「查詢 Google Sheets 資料」)
- [x] 19.3 更新 `u6u_get_component`
- 舊:讀舊格式 slots`component_id``name``published_at`
- 新:對齊 `tpl-component` slot 欄位(`canonical_id``display_name``category``version``stability``wasm_r2_key` 等)
- 呼叫新 Registry `GET /components/:id` 端點
- [x] 19.4 新增 `u6u_get_component_guide` 工具
- 呼叫 `GET /components/guide`,回傳開發指引給 AI
- AI 在開發新零件前可先讀取指引,確保生成符合規範的 TinyGo 程式碼
- [x] 20. 建立 u6u-gui 前端(`u6u-gui/`
- [x] 20.1 建立 Cloudflare Pages 專案結構
- React 19 + Vite + Tailwind CSS v4
- 整合 `@u6u/web-components`alias 指向 `u6u-core/web-components/src`
- wrangler.toml 設定 Pages 部署
- [x] 20.2 建立主畫布頁面(`/canvas`
- 整合 `Canvas.tsx`(從 inkstone-admin 移植)
- 連接 Cypher Executor API`POST /cypher/execute`
- 連接 Component Registry API(搜尋、查詢零件)
- AI 操作後(透過 u6u-mcp 修改 KBDB)畫布即時反映變更
- [x] 20.3 建立零件庫頁面(`/components`
- 列出所有已上架零件(呼叫 `GET /components/search`
- 顯示零件合約、評分、版本歷史
- 提供「提交新零件」入口(連結到開發指引)
- [x] 20.4 建立 Workflow 管理頁面(`/workflows`
- 列出所有 Workflow(從 KBDB 查詢 `tpl-workflow`
- 點擊進入畫布編輯
- 顯示執行歷史(Evaluation Block
- [x] 20.5 部署至 Cloudflare Pages
- `pnpm build && npx wrangler pages deploy dist`
- 設定環境變數(KBDB_URL、CYPHER_URL、REGISTRY_URL
+62
View File
@@ -0,0 +1,62 @@
# Tech Stack — arcrun
## Runtime & Deployment
- **Cloudflare Workers** — all backend services deploy as Workers via Wrangler
- **Cloudflare KV** — workflow definitions, credentials (encrypted), recipes, sessions
- **Cloudflare R2** — WASM binary storage (`WASM_BUCKET`)
- **Cloudflare Pages** — frontend deployment (arcrun.dev landing page)
## Languages & Frameworks
- **TypeScript** — Workers (HTTP routing/orchestration only), CLI, SDKs
- **TinyGo / AssemblyScript** — all component logic, compiled to WASM (WASI preview1)
- **Hono** — HTTP framework for all Workers (routing, middleware, OpenAPI)
- **Zod** — schema validation and OpenAPI spec generation (`@hono/zod-openapi`)
- **React 19** + **Vite** — frontend (landing page)
- **Tailwind CSS v4** — styling
## Testing
- **Vitest** — test runner
- **@cloudflare/vitest-pool-workers** — Workers-specific test pool for cypher-executor
## Package Management
- **pnpm** — used in most packages (some use npm)
---
## Common Commands
### Per-service (run from the service directory)
```bash
# Start local dev server
pnpm dev # or: npm run dev
# Deploy to Cloudflare
pnpm deploy
# Run tests (single pass)
pnpm test # runs: vitest run
```
### WASM Components (TinyGo)
```bash
# Build a component
cd registry/components/{name}/
tinygo build -target=wasi -o main.wasm main.go
```
### CLI
```bash
cd cli/
npm run build
acr --help
```
## API Documentation
Each Worker exposes OpenAPI docs at runtime:
- `/doc` — OpenAPI JSON spec
- `/ui` — Swagger UI
Dev base URLs: Cypher Executor → `http://localhost:8788`
@@ -0,0 +1,340 @@
# arcrun.dev Pages 規格
> **讀者**CC(可直接照做)
> **部署**Cloudflare Pages + Workers
> **語言**:英文為主,中文切換
> **技術棧**Astro(靜態生成)+ Cloudflare Pages + D1(使用統計)
---
## 0. 這個 Pages 的三個角色
1. **門面**:第一次看到 arcrun 的人,30 秒內要懂「這是什麼、對我有什麼用」
2. **轉換漏斗**:工程師 → 試用 lib → 申請 API Key;小白 → 看榮譽牆 → 問 AI 能不能用
3. **社群磁鐵**:榮譽牆讓工程師有動機貢獻 recipe,貢獻越多服務越多,用戶越多
---
## 1. 網站結構(五個頁面)
```
arcrun.dev/
├── / 首頁(門面 + 轉換)
├── /docs 用法文件
├── /integrations 榮譽牆(服務目錄)
├── /api Swagger UI(原始 API
└── /changelog 版本記錄
```
---
## 2. 首頁(/
### 2.1 Hero Section
**英文**
```
Stop fighting OAuth.
One API key. Every service. Works anywhere.
arcrun handles Google, Notion, GitHub, Slack authentication
so your Python / JS code doesn't have to.
[Get API Key — Free] [View on GitHub]
```
**中文切換後**
```
不要再跟 OAuth 搏鬥了。
一個 API Key,接通所有服務,在哪跑都行。
[免費取得 API Key] [查看 GitHub]
```
語言切換按鈕放右上角,用 `?lang=zh` query paramCloudflare Worker 記住偏好存 cookie。
### 2.2 三行說清楚(Why arcrun
```
┌────────────────────┬────────────────────┬────────────────────┐
│ Before │ │ After │
│ │ │ │
│ 40 行 OAuth 程式 │ →→→ │ 1 行 │
│ GCP Console 設定 │ │ arcrun.auth.bind │
│ debug 兩天 │ │ ("google_drive") │
└────────────────────┴────────────────────┴────────────────────┘
```
### 2.3 Code Demo(互動式 tab
三個 tab 切換:Python / JavaScript / HTTP(給 n8n 小白)
**Python tab**
```python
pip install arcrun-auth
from arcrun import auth
# 就這樣,Google Drive 認證完成
drive = auth.bind("google_drive")
resp = drive.get("/files")
```
**JavaScript tab**
```javascript
npm install arcrun-auth
import { auth } from 'arcrun-auth'
const drive = await auth.bind('google_drive')
const resp = await drive.get('/files')
```
**HTTP tab(給 n8n 用戶)**
```
POST https://api.arcrun.dev/v1/auth/bind
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"service": "google_drive",
"secret": "{{ $env.GOOGLE_SA_JSON }}"
}
```
下方加一行小字:「n8n 用戶:用 HTTP Request 節點貼上這段,不需要安裝任何東西」
### 2.4 數字牆(social proof
```
127 個認證服務 1,247,832 次呼叫 89 位貢獻者
```
這三個數字從 D1 即時讀,每小時更新一次(Cloudflare KV cache)。
### 2.5 CTA
```
[免費取得 API Key]
註冊後立即可用,不需要信用卡
```
---
## 3. 榮譽牆(/integrations)★ 核心頁面
### 3.1 頁面頂部
```
127 個已驗證的認證服務
由社群工程師貢獻並測試,每個 recipe 都有真實使用數據
[搜尋服務...] [全部] [AI] [Google] [社群媒體] [生產力] [台灣]
```
### 3.2 服務卡片
每個 recipe 一張卡:
```
┌──────────────────────────────────────────┐
│ [圖示] Google Drive ★ 官方 │
│ │
│ 認證方式:Service Account │
│ 貢獻者:@richblack ──→ GitHub profile │
│ 驗證日期:2026-03-15 │
│ │
│ 使用次數:██████████ 12,847 次 │
│ │
│ [查看 Recipe] [複製 Python 範例] │
└──────────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ [圖示] OpenRouter │
│ │
│ 認證方式:API Key (Header) │
│ 貢獻者:@some_engineer ──→ GitHub │
│ 驗證日期:2026-04-01 │
│ │
│ 使用次數:██░░░░░░░░ 89 次 │
│ │
│ [查看 Recipe] [複製 Python 範例] │
└──────────────────────────────────────────┘
```
badge 規則:
- `★ 官方`arcrun 團隊維護
- `✓ 社群驗證`:100+ 次使用 + 30 天無錯誤回報
- `🆕 新加入`30 天內合併的 PR
### 3.3 貢獻者排行(頁面底部)
```
Top Contributors
🥇 @some_engineer 23 個 recipe 89,234 次呼叫
🥈 @another_dev 15 個 recipe 45,123 次呼叫
🥉 @third_person 8 個 recipe 12,456 次呼叫
...
[我也要貢獻 →] (連到 CONTRIBUTING.md
```
### 3.4 「我要貢獻」的 CTA
```
找不到你要的服務?
大部分 API Key 類的服務,填一份 YAML 就能加進來。
把 API 文件丟給 AI,五分鐘生成,開 PR 送出。
[查看 Recipe 格式] [開始貢獻]
```
---
## 4. 用法文件(/docs
### 結構
```
快速開始
├── 取得 API Key
├── Python 安裝與第一個範例
├── JavaScript 安裝與第一個範例
└── 直接用 HTTP(n8n / 任何工具)
認證方式
├── API Key 類服務
├── OAuth2 類服務
├── Google Service Account
└── mTLS
進階用法
├── 多帳號(multi-instance
├── 只取 tokenescape hatch
└── 錯誤處理
貢獻 Recipe
├── Recipe YAML 格式說明
├── 讓 AI 幫你寫 Recipe
└── 提交流程
```
### 「讓 AI 幫你寫 Recipe」這一節特別重要
```markdown
## 讓 AI 幫你寫 Recipe
把下面這段丟給 Claude / ChatGPT
再把目標服務的 API 文件一起貼進去:
---
請根據以下 API 文件,
生成一份符合 arcrun recipe schema 的 YAML。
Schema 文件:https://arcrun.dev/docs/recipe-schema
目標服務:[貼上 API 文件]
---
AI 生成後,你只需要:
1. 把 YAML 存成 recipes/community/服務名.yaml
2.`acr recipe test 服務名.yaml`
3. 開 PR
通常整個過程不超過十分鐘。
```
這一節讓「貢獻門檻」從「工程師才能做」變成「任何人叫 AI 做」。
---
## 5. API 文件(/api
直接嵌入 Swagger UI,連到 `https://api.arcrun.dev/swagger.json`
頁面頂部加一行說明:
```
這是 arcrun 的原始 API。
Python / JS lib 是它的包裝,
任何能發 HTTP request 的工具都能直接用。
```
這一句話讓 n8n 用戶、Make 用戶、甚至 Excel 用戶都知道「我也能用」。
---
## 6. 技術實作
### 6.1 技術選型
**Astro**(靜態生成)是首選,原因:
- 頁面大部分是靜態內容(docs / recipe 卡片),Astro 的 SSG 完美對應
- 動態數字(使用次數、貢獻者排行)用 Astro 的 `client:load` island 局部更新
- 部署到 Cloudflare Pages 零配置
**不用 Next.js**,因為你已在 Cloudflare 生態,Next.js 的 SSR 在 CF Pages 有摩擦。Astro + CF Pages 是更自然的組合。
### 6.2 資料來源
| 資料 | 來源 | 更新頻率 |
|---|---|---|
| Recipe 清單、metadata | GitHub repo `recipes/` 目錄 | CI merge 時觸發 rebuild |
| 使用次數 | Cloudflare D1API call log | 每小時從 D1 聚合 → KV cache |
| 貢獻者排行 | 同上 | 每小時 |
| 總呼叫次數 | 同上 | 每小時 |
### 6.3 多語言
用 Astro 的 i18n routing
- `/` → 英文
- `/zh/` → 中文
語言切換按鈕寫入 cookie `arcrun_lang`CF Worker 在 edge 讀 cookie 做 redirect。
不用 JS framework 的 i18n library,保持輕量。
### 6.4 部署流程
```
GitHub push to main
→ GitHub Actions 跑 astro build
→ 產出 dist/
→ 自動部署到 Cloudflare Pages
→ Pages 掛 arcrun.dev domain
```
recipe YAML 有變動時(PR merge)額外觸發一次 rebuild。
---
## 7. CC 的實作任務
### Phase 1:靜態骨架(3-5 天)
- [ ] Astro 專案初始化,設定 CF Pages 部署
- [ ] 首頁 Hero + Code Demo tab(靜態版,數字先寫死)
- [ ] `/integrations` 靜態版(先手動列 5-10 個服務)
- [ ] `/docs` 基本結構(快速開始 + Python 範例)
- [ ] `/api` 嵌入 Swagger UI
- [ ] 中英切換機制
### Phase 2:動態資料(3-5 天)
- [ ] D1 schema`recipe_calls(recipe_id, count, last_updated)`
- [ ] CF WorkerAPI call 時寫入 D1
- [ ] 每小時聚合 WorkerD1 → KV cache(總數 / per recipe / per contributor
- [ ] 首頁數字牆:從 KV 讀即時數字
- [ ] `/integrations` 卡片:使用次數從 KV 讀,進度條動態顯示
### Phase 3:社群功能(2-3 天)
- [ ] 貢獻者排行從 KV 讀
- [ ] Recipe 頁面:點「查看 Recipe」展開 YAML
- [ ] 點「複製 Python 範例」自動生成對應 code snippet
- [ ] GitHub PR merge webhook → 觸發 Pages rebuild
---
## 8. 一個不能省的細節
榮譽牆的貢獻者欄位**一定要連到他的 GitHub profile**,不是只顯示名字。
工程師貢獻的動機之一是「這個會出現在我的公開作品集」。連到 GitHub 就意味著他的 followers 可能看到他貢獻了 arcrun,這比任何 badge 都有效。
@@ -0,0 +1,486 @@
# arcrun-pyPython Lib 策略分析
> **核心問題**arcrun 的 auth 層要不要獨立成 Python lib?AI 會主動選它嗎?
> **決策前提**:本地為主雲端選配 + 免費引流 SaaS + 三維度分析
---
## 0. TL;DR
做。但要做對——它不是「arcrun SDK」,是 **獨立的 OAuth 地獄解藥**SaaS 帳號只是 optional power-up。
**產品名建議**`arcrun-auth`PyPI 套件名),import 時叫 `authkit``arc`
**定位**`pip install arcrun-auth` → 一行搞定 Google/GitHub/Slack/Notion/... 的認證。本地 keyring 存 secret,零註冊可用。註冊 arcrun 帳號才能多機同步 / 團隊共用 / 獲得平台代管的 OAuth App。
---
## 1. 你的直覺:為什麼這可能是大招
你觀察到的現象:
- **寫個「本地檔案 → Google Drive 備份」這種小腳本**,光認證花 2 天,日後還反覆壞。
- **這個痛點 99% 的 Python 使用者都遇過**。Stack Overflow 上 Google OAuth 的問題累計回答數十萬。
- **AI 寫程式時,這部分它也不會寫**——訓練資料裡的標準範本就是 `InstalledAppFlow.from_client_secrets_file()`,還是要人類先去 GCP Console 建 OAuth Client、下載 JSON、設 redirect URI、加測試用戶。
**如果 arcrun 能把這整套濃縮成 `auth.bind("google_drive")`,它就是開發者工具裡的「自來水」——沒人在乎水源廠長怎麼運作,大家只想打開水龍頭有水喝。**
而且你無意中踩到一個時代轉折點:**AI 在寫程式,AI 不想 debug OAuth**。這個 lib 是給 AI 用的,比給人類用的更重要。
---
## 2. 三維度分析:AI 到底會不會選 arcrun-auth
### 維度一:零註冊摩擦(最重要)
**現實**AI coding assistant 預設會產出訓練資料裡最常見的程式碼。Google Drive 的標準答案就是官方 quickstart——那段 40 行的 `InstalledAppFlow` 樣板碼,加上「去 GCP Console 建 OAuth Client」的一串步驟。
**AI 選擇替代 lib 的條件**(觀察 Copilot / Cursor / Claude Code 的實際行為):
| 條件 | 是否能壓制預設答案 |
|---|---|
| Lib 名稱出現在 prompt 裡 | ✅ 絕對會用 |
| 專案已有 `pyproject.toml` 宣告該 lib | ✅ 會用 |
| 該 lib 是 PyPI 下載量 Top N(訓練資料權重高) | ⚠️ 或許 |
| 使用者口頭說「用 XX 比較好」 | ✅ 會用(單次) |
| 該 lib 明顯更短更容易 | ⚠️ 有機會但不保證 |
**結論****零註冊摩擦是生死線**。
如果 `pip install arcrun-auth` 之後必須先去註冊 arcrun 帳號、拿 API token、設環境變數——AI 看到這一步就會放棄,回去寫 Google 官方那套,因為「官方的至少不用註冊第二家廠商」。
**對策**
- 本地模式必須是**零配置可用**的 default path。
- Secret 存在 `~/.arcrun/secrets.db`(加密,用系統 keyring 保護 master key)。
- 註冊 arcrun 帳號是 **opt-in** 的升級,不是必要。
- 第一次用 OAuth 時,**平台提供免費共用的 OAuth App**(下面 §3 詳細說)。
這個設計讓 AI 看到的心智路徑是:
```
目標:備份檔案到 Google Drive
選項 Agoogle-api-python-client(官方,但使用者要先去 GCP 設 OAuth)
選項 Barcrun-authpip install 完直接可用)
```
在這個對比下,AI 會選 B——**特別是它被告知「使用者不想 debug 認證」或類似的 prompt 時**。
### 維度二:跟 stdlib / 主流 lib 的對比
Python Google API 的主流選項:
| Lib | PyPI 週下載(估) | AI 熟悉度 | 認證難度 |
|---|---|---|---|
| `google-api-python-client` | ~30M | 極高 | 高(要自己設 OAuth) |
| `pydrive2` | ~200k | 中 | 高 |
| `gspread`(只 Sheets | ~2M | 高 | 高(OAuth 或 SA |
| **arcrun-auth** | 0 | 0 | **極低** |
**冷啟動難題**:新 lib 要進入 AI 的選擇集合,需要:
1. **量**PyPI 下載 + GitHub stars 進到「被訓練資料收錄」的級別(大概 GitHub 5k+ stars 是門檻)。
2. **品牌**:有代表性部落格文、教學影片、官方 API 文件連結到它。
3. **簡潔**:代碼範例比主流短 5 倍以上,讓使用者「一眼就想用」。
**arcrun 的優勢**
- 你本來就在教 n8n 課程,有現成學員管道可以鋪「這是 n8n 的 Python 版 auth」。
- 「AI 寫程式的 auth lib」是個還沒被佔領的定位詞。`langchain` 做了 LLM 層,但 auth 層還沒有明顯贏家。
- Claude Code 對 lib 選擇特別敏感——它會實際讀 `pyproject.toml` 並尊重已有宣告。
**對策**
- **第一批 adopter 是你的學員**(n8n 課 + AI 自動化課),他們會在實戰中用,累積 GitHub issues 和 blog post。
- **SEO 主打**:「Python Google Drive OAuth 簡化」「AI 自動化 Python 認證」這些長尾關鍵字現在沒有明顯答主。
- **Claude Code 優化**:寫一份 `AGENTS.md``.cursorrules` 範本,示範怎麼在 prompt 裡引導 AI 選 arcrun-auth。
### 維度三:痛點強度(OAuth 地獄避免)
**量化你那兩天 debug**
| 階段 | 時間成本 | 典型錯誤 |
|---|---|---|
| GCP 註冊 + 啟用 API | 15 min | 找不到哪個 API |
| 建 OAuth Client ID | 15 min | Desktop / Web / iOS 選錯 |
| 設 OAuth Consent Screen | 30 min | External / Internal 選錯;scope 加錯 |
| 加測試用戶 | 10 min | 漏加自己的 email |
| 寫 Python flow | 30 min | `run_local_server` vs `run_console` |
| 第一次跑遇到 `redirect_uri_mismatch` | 30-120 min | port 衝突、URI 沒加 |
| Token 過期處理 | 60 min | `creds.expired``refresh_token` 沒保存 |
| Service Account 模式(如果需要) | 120 min | domain-wide delegation 設定 |
| **合計** | **5-8 hrs(順的人)** | **2 天(不順的人,你當時的情況)** |
**arcrun-auth 對應版本**
```python
from arcrun import auth
# 首次執行:自動打開瀏覽器完成 OAuth,結果存本地 keyring
drive = auth.bind("google_drive")
# 直接呼叫 API
drive.post("/upload/drive/v3/files", params={"uploadType": "media"},
data=open("backup.zip", "rb"))
```
**時間成本:首次 2 min,之後 0 min**
這個壓倒性的體驗差距是產品的核心競爭力。**只要使用者試過一次,就不會再回去寫 `InstalledAppFlow`**——即使 AI 預設會產出官方版本。
---
## 3. 關鍵設計決策
### 3.1 OAuth App 誰擁有?(核心問題)
傳統做法:使用者自己去 GCP Console 註冊自己的 OAuth App,拿 client_id/client_secret。**這就是痛點來源**。
arcrun-auth 要消滅這步,只有兩條路:
**Option A:平台提供共用 OAuth App(推薦 default**
- arcrun 註冊一個 Google OAuth App,命名類似「arcrun Auth Broker」。
- 所有 arcrun-auth 使用者共用這個 App 的 client_id/secret。
- 使用者在 Google 授權頁面看到的是「arcrun Auth Broker 想存取您的 Google Drive」。
- **好處**:使用者零配置,arcrun 品牌曝光。
- **成本**Google 有 OAuth App 的限額(Verified App 才能超過 100 users),需要申請 Google OAuth Verification(要提供隱私政策、網域驗證、可能要付 $75 安全審查)。
**Option B:使用者 BYO OAuth App**
- 企業客戶或注重稽核的人需要這個。
-`~/.arcrun/config.toml` 放自己的 client_id/secret。
**Option Carcrun SaaS 代管**(付費)
- 使用者註冊 arcrun 帳號,平台幫你管 OAuth App、token、團隊共用、audit log。
- 這是付費 tier 的主要價值。
**建議**:A + B + C 三種都支援,默認 A;免費無限制 B;付費享受 C。
### 3.2 Secret 儲存層級(本地為主雲端選配)
```
優先級 1 (default):本地 keyring
- macOS Keychain / Windows Credential Manager / Linux libsecret
- zero config,安全性靠 OS
優先級 2 (opt-in):本地加密檔
- ~/.arcrun/secrets.enc
- master key 走 keyring 或 passphrase
- 給沒有 keyring 的環境(Docker、CI
優先級 3 (opt-in)arcrun 雲端
- 多機同步、團隊共用、audit log
- 需註冊 arcrun 帳號
- 本地 lib 只保存 arcrun API token,實際 service secret 存雲端
```
### 3.3 Secret 初始化流程
**靜態 key 模式(Notion、OpenAI、Stripe...**
```bash
# 選項 A:互動式
$ arcrun setup notion
? Notion Integration Token (hidden): ***
✓ Testing connection... OK
✓ Saved to keyring as notion/default
# 選項 B:環境變數
$ export ARCRUN_NOTION_TOKEN=secret_xxx
$ python script.py # arcrun-auth 自動讀
# 選項 C:程式碼內
notion = auth.bind("notion", secret={"token": os.environ["NOTION_TOKEN"]})
```
**OAuth 模式(Google、GitHub、Slack...**
```python
drive = auth.bind("google_drive")
# 如果是第一次:
# 1. 本地啟動一個臨時 HTTP server (http://localhost:random_port)
# 2. 開瀏覽器到 Google authorize URL
# 3. 使用者點同意
# 4. Google redirect 到 localhostlib 接到 code
# 5. 換 token,存 keyring
# 6. 回傳可用的 client
```
這個流程和 `InstalledAppFlow.run_local_server()` 本質上一樣——但差別是:
- **Client ID 不用使用者自己去 GCP Console 註冊**(由 arcrun 平台提供)。
- **Scope 由 recipe 宣告**(不用使用者自己查文件)。
- **Token 儲存自動化**(不是散落在 `token.json`)。
### 3.4 Recipe 來源
Python lib 和 Cloudflare Worker 版本**共用同一份 recipe YAML**。
```
arcrun-recipes/ # GitHub repo,公開
├── recipes/
│ ├── official/
│ │ ├── google_drive.yaml
│ │ ├── notion.yaml
│ │ └── ...
│ └── community/
│ └── ...
```
Python lib 啟動時檢查本地 `~/.arcrun/recipes/` 快取,過期就從 GitHub 或 arcrun 平台 API 拉最新。
**這是關鍵架構優勢**recipe 寫一次,Web 和 CLI 和 Python lib 全部受益。社群貢獻一份 Notion recipe,所有 runtime 自動支援。
---
## 4. API 設計(Python 版)
### 4.1 最簡路徑
```python
from arcrun import auth
# 取得認證好的 HTTP client(基於 httpx
client = auth.bind("google_drive")
# 相對 base_url 的路徑
resp = client.get("/files", params={"q": "name = 'backup.zip'"})
files = resp.json()["files"]
# 上傳
client.post("/upload/drive/v3/files",
params={"uploadType": "multipart"},
files={"file": ("backup.zip", open("backup.zip", "rb"))})
```
### 4.2 進階:非同步
```python
from arcrun import auth
async with auth.bind_async("google_drive") as client:
resp = await client.get("/files")
```
### 4.3 進階:多 instance
```python
# 同一個服務,多個帳號
personal = auth.bind("google_drive", instance="personal")
work = auth.bind("google_drive", instance="work")
```
### 4.4 進階:直接取 token(給不想透過 wrapper 的情況)
```python
# 取 raw access token,自己丟進任何 lib
token = auth.get_token("google_drive")
# 丟給 googleapiclient
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
creds = Credentials(token=token.access_token)
service = build("drive", "v3", credentials=creds)
```
這個 escape hatch 很重要——不強制使用者放棄他熟悉的官方 lib,只是把**認證這一層**剝離出來。這是你想要的「避免麻煩直接用 arcrun 的 auth 功能」的精確實作。
### 4.5 服務發現
```python
# CLI
$ arcrun list
google_drive Google Drive OAuth2
notion Notion API Key
github GitHub OAuth2
openai OpenAI API Key
...
# Python
from arcrun import auth
auth.list_services() # 回傳 dict
```
---
## 5. 商業模式:免費引流 → SaaS 付費
### 5.1 免費永久可用(本地模式)
| 功能 | 免費 | 付費 |
|---|---|---|
| `pip install arcrun-auth` | ✅ | ✅ |
| 本地 keyring 儲存 secret | ✅ | ✅ |
| 所有 recipe 可用 | ✅ | ✅ |
| 平台代管 OAuth App(免自己註冊) | ✅ | ✅ |
| 單機使用 | ✅ | ✅ |
### 5.2 付費 tier 提供的
| 功能 | 免費 | Pro ($9/mo) | Team ($29/user/mo) |
|---|---|---|---|
| 多機同步 secret | ❌ | ✅ | ✅ |
| 團隊共用 credential | ❌ | ❌ | ✅ |
| Audit log(誰在何時用了什麼 secret | ❌ | ✅ | ✅ |
| Secret rotation 提醒 | ❌ | ✅ | ✅ |
| 企業 OAuth App BYO | ❌ | ✅ | ✅ |
| SSO / SCIM | ❌ | ❌ | ✅ |
| arcrun SaaS 整合(workflow runtime | ❌ | ⚠️ 受限 | ✅ |
| Priority 社群 recipe 審核 | ❌ | ✅ | ✅ |
**商業邏輯**
- 免費 tier 足夠「個人開發者 + 側邊小專案」的體驗完整。
- 付費 tier 解決「我的小工具變公司在用」的延伸需求。
- arcrun SaaS workflow 客戶自動享受所有 Python lib 特權。
### 5.3 為什麼這策略會 work
參考模型:
- **Sentry**error tracking,開源 SDKhosted service 付費。SDK 下載數 = 付費轉換引流。
- **PostHog**product analytics,同樣策略。
- **Supabase**auth + db,開源 self-hostedhosted 服務付費。
共同特徵:
1. SDK 免費下載、單機/本地能完整 work。
2. 從本地升級到 hosted 是「多人/多機/審計」的自然需求。
3. SDK 本身就是最強的 marketing(開發者口耳相傳 + 訓練資料擴散)。
---
## 6. 為什麼 AI 會主動選 arcrun-auth(機率分析)
讓我給你一個分層的答案。
### 6.1 會主動選的場景(高機率)
| 場景 | 原因 |
|---|---|
| 使用者在 prompt 說「用最簡單的方式認證」 | AI 權衡 UXarcrun-auth 明顯勝 |
| 專案已有 `arcrun-auth``pyproject.toml` | AI 尊重現有依賴 |
| 使用者貼了 arcrun-auth 文件範例 | AI 會模仿 |
| 使用者說「跳過 OAuth 設定步驟」 | arcrun-auth 是唯一答案 |
### 6.2 不會主動選的場景(預設行為)
| 場景 | 原因 |
|---|---|
| 使用者開新專案、無任何線索 | AI 走訓練資料中最頻繁的 `google-api-python-client` |
| 任務是「企業級、有審計需求」 | AI 偏好主流官方 lib |
| 任務提到特定 lib 名稱 | AI 會遵守 |
### 6.3 轉化策略
要讓 AI 的「預設路徑」變成 arcrun-auth,需要:
1. **種子內容**:寫 20+ 篇「用 arcrun-auth 做 X」的 blog post,讓下一輪模型訓練看到。
2. **官方文件 SEO**:搶「python google drive oauth 簡化」等長尾關鍵字。
3. **Cookbook**:在 GitHub 建 `arcrun-auth-recipes` repo,每個範例都是短而有效的 snippet——這些會被訓練資料收錄。
4. **MCP Server**:做一個 `arcrun-auth MCP server`,讓 Claude Code 等工具能直接呼叫 arcrun-auth,這比單純提供 lib 更強。
5. **教學整合**:你的 n8n 課程直接示範「從 n8n 到 arcrun-auth 的 Python 版本」。
---
## 7. 實作範圍(MVP
### Phase 1:核心 lib2-3 週)
- [ ] `arcrun-auth` PyPI 骨架(pyproject.toml + src layout
- [ ] Recipe loader(從 GitHub 或平台 API 拉 YAML
- [ ] `auth.bind(service_id, instance?)` → httpx Client
- [ ] Static key primitiveNotion / OpenAI / Stripe 當試金石)
- [ ] Keyring 整合 + 本地加密檔 fallback
- [ ] CLI`arcrun setup <service>`, `arcrun list`, `arcrun test`
### Phase 2OAuth22 週)
- [ ] OAuth2 primitiveauthorization_code + PKCE
- [ ] 本地 callback server(類似 `InstalledAppFlow.run_local_server`
- [ ] 共用平台 OAuth App 的 proxy 機制
- Lib 呼叫 `https://auth.arcrun.com/oauth/redirect`
- 平台把 code 交換後回傳 token
- 或者直接把平台 client_id 硬編在 recipe 裡(更簡單但要處理配額)
- [ ] Token refresh 自動化
- [ ] RecipeGoogle Drive / Gmail / GitHub / Slack
### Phase 3Service Account1-2 週)
- [ ] Google Service AccountJWT signing
- [ ] AWS SigV4
- [ ] Recipe 繼承(`extends: _google_base`
### Phase 4:雲端同步(2 週)
- [ ] `arcrun login` → 綁定雲端帳號
- [ ] Secret sync 協議(本地加密後上傳,平台只存密文)
- [ ] 多機同步
- [ ] Audit log
### Phase 5AI 生態整合(1-2 週)
- [ ] MCP server(讓 Claude Code 能直接用)
- [ ] VS Code Extension(一鍵設定 credential
- [ ] `AGENTS.md` 範本(引導 AI 選 arcrun-auth
---
## 8. 風險與坑
### 8.1 Google OAuth Verification
**問題**:共用 OAuth App 要申請 Google Verification,否則會有「未驗證 App」警告 + 100 user 上限。
**對策**
- MVP 階段接受警告頁面(使用者自己點「進階 → 前往」)。
- 到 user 量接近 100 時申請 Verification。
- 企業客戶走 BYO OAuth App 路徑,不受影響。
- 若平台 OAuth App 卡關,有 fallbacklib 自動引導使用者建自己的 OAuth App(提供 CLI wizard)。
### 8.2 其他服務的 OAuth App 政策
- **GitHub**:免費建 OAuth App,無上限。✅
- **Slack**:免費建,但安裝到使用者 workspace 需管理員同意。⚠️
- **Microsoft / Azure**:相對嚴格,需 tenant admin consent。⚠️
- **Notion**Internal Integration 可以完全走 API key,免 OAuth。✅(最簡單)
### 8.3 keyring 在 Linux server / Docker 的問題
Linux server 沒 GUI keyring daemon。對策:
- Fallback 到加密檔案(用 env var 或 CLI 互動提供 master key)。
- Docker 場景有 `docker secret`、Kubernetes Secretlib 支援直接讀這些來源。
### 8.4 競品
目前沒有完全對標的產品,但相鄰玩家:
- **[keyring](https://pypi.org/project/keyring/)**:只做儲存,不做認證流程。我們用它當底層。
- **[httpx-auth](https://pypi.org/project/httpx-auth/)**:只做認證,不做 secret 管理,也沒有 recipe。
- **[authlib](https://pypi.org/project/authlib/)**OAuth 實作 lib,低階,還是要自己組。
- **各家 SDKgoogle-auth, slack-sdk**:綁特定家,不 unify。
**arcrun-auth 的差異化定位**
> **Unified credential broker for AI-era Python apps**
> 一個 lib 搞定所有服務、所有認證機制、所有 secret 儲存後端。
---
## 9. 最後的判斷
### 9.1 這個 lib 該不該做?
**該做**。原因:
1. 你描述的痛點是真的,而且規模巨大(Python + Google API 下載量是千萬級)。
2. 技術可行,也跟既有 arcrun 架構共用 recipe,邊際成本低。
3. 對 arcrun SaaS 是完美引流——免費 lib 的使用者是精準的付費轉換潛在客戶。
4. 時間窗口正確:AI 寫程式時代剛開始,這個定位還沒被佔領。
### 9.2 跟主 SaaS 的優先順序
**建議****主 SaaS 的 credential 系統先做(前一份規劃),arcrun-auth 當後續 Phase**。
原因:
- Cloudflare Worker 版的 primitives + recipes 是基礎建設,Python lib 是其 consumer。
- 先做 Python lib 會逼你在 recipe schema 上做二次修改,不划算。
- 主 SaaS 的 recipe 累積到 20-30 個服務後,開放 Python lib 體驗最好。
時程建議:
- **Month 1-2**:主 SaaS 的 4 個 primitive + 15 個 recipe(前一份規劃)。
- **Month 3-4**arcrun-auth Phase 1-2static key + OAuth2),私人 alpha。
- **Month 5**:公開 release,寫部落格、SEO、社群推廣。
- **Month 6+**:雲端同步、MCP、AI 生態整合。
### 9.3 一句話總結
> **arcrun-auth 不是「arcrun 的 Python 綁定」,是「OAuth 地獄的解藥」**。SaaS 是延伸。這個敘事才能在 AI 寫程式的時代站住腳。
@@ -0,0 +1,55 @@
# Arcrun 推廣策略修正
20260418 by Leo
## Arcrun 的最近幾次變化
- Arcrun 原是 Matrix 的原子化純雲端 CF 程式框架,有 MCP 讓 AI 使用
- 轉以 WASM + WASI + TinyGo 成為未來具有雲端、地端、邊緣端的執行能力,可以用來做到無人機等終端。
- 再解耦成獨立的 Open Source 專案,脫離 KBDB 用 YAML 即可,允許整個 Fork,但推廣 SaaS 模式
- 今天的變化是發現成為 Lib 和 n8n 社群節點的用法。說明如下。
## 推廣方式 1:寫成 Lib
參考文件:docs/user_requirements/arcrun/ADR-lib-and-landingPage/arcrun-py-strategy-analysis.md
Leo 教 n8n 時常舉例我叫 AI 幫我寫個簡單的程式把 server 的檔案備份到 Google Drive 後刪除,光是 OAuth 用 Service Account 就花了 2 天測試,後來還發生好幾次出錯重修。
網路設定對 vibe coder 太複雜了,看到 AI 一次次修改一次次出錯,知道一週後才穩定,而 n8n 只需要申請 credential 填入,10 分鐘搞定,差距太大了。
問題是,n8n 實在太慢了,全圖像界面也難以跟程式碼整合,vibe coder 就算知道了也無法整合,他會繼續用 python 就是無法忍受 n8n 的慢速、沉重,及對 AI 的不友善。
Arcrun 用幾個基礎 Auth 零件取代 n8n 幾百個不同的 credentials 設定,arcrun 完全可以跟程式碼整合,只是目前沒有整合工具,也就是說,工程師不想面對一個認證花好幾天,他只要用 Arcrun 打 API 就幾行解決了。
用這個策略,讓具有程式知識的人會:
- 對 Arcrun 有好感,在 GitHub 按星
- 成為 Arcrun 用戶,增加會員 base
- 寫文分享,讓 Arcrun 被傳出去
反之,小白初學 vibe coding,看到老工程師說 2 天變成 10 分鐘,百行程式碼變成 2 行,也會被影響。
所以,把 API 包裝成 Lib 或 SDK 是個 Get Member 的好方法,不過一定要有 Email。
## 推廣方式 2: 讓 n8n 來 call
身為 n8n 講師,我非常清楚 n8n 有個大缺陷(或說是安全設計),就是它只能呼叫在工作流實例化前就寫好的程式,無法注入新的。
所以,雖然它可以安裝一個實作 langchain AI Agent 的 AI Agent 節點,但 AI 卻不可能在遇到問題時立刻寫個程式運行解決問題,甚至使用它內建的 task runners 或使用外部的 Python runtime 也無法。
但只要使用 http request 節點呼叫外部的 Arcrun tool,它就可以輸出三元組寫一個程式解決它現在遇到的問題,並且運算完後回傳,只需要取得一個 API Key。
## 可能的效應
工程師是意見領袖,他們會去按星,幫忙教育市場,而小白需要被教育,但他們是較大的社群,這些星數和聲量會在未來變成 AI 訓練素材,甚至推薦用戶使用最 AI Friendly 的 Arcrun。
而實際上 Arcrun 的設計讓 AI 更輕鬆。
## 要做什麼
docs/user_requirements/arcrun/ADR-lib-and-landingPage/arcrun-pages-spec.md 這是 claude.ai 寫的規劃,因為它看不到 codebase,所以你要斟酌如何開發。
另外,當前的 API Key 雖然很容易取得,卻有個問題,用戶無法管理,所以頁面應該要有取得 API Key 的界面,可以 Google, GitHub 等 OAuth 或 Email + Password 登入,用來管理它的 API Key 的 CRUD。
或許是可以外接一個 SMTP 服務來確認他的 Email 真實,SaaS 服務還是有成本,雖然成本不高。
另外,既然是 OpenSource,成本雖然不高,似乎可以銜接 Donate 服務?
+761
View File
@@ -0,0 +1,761 @@
# arcrun Credential System 設計規格
20260418
> **讀者**Claude CodeCC),負責實作
> **作者**richblack(架構決策)
> **版本**v1.0
> **狀態**:Draft — 等 CC 確認技術可行性後開工
---
## 0. TL;DR(給 CC 的三句話版)
1. **不要**為每個服務寫一個 credential 零件,n8n 是錯的。
2. 做**四個 TinyGo/WASM 零件**primitives),每個服務只需要一份 **YAML recipe** + 用戶自己的 **secret**
3. Recipe 存 arcrun 平台 KV(公共),secret 存 tenant KV(私有),兩者在 runtime 由 `AuthBroker` 組裝成可用的 HTTP client。
---
## 1. 設計目標與反目標
### 目標
- **新增一個服務的成本 = 寫一份 YAML**,不需要 rebuild、不需要改 code。
- **AI agent 理解成本 ≈ 0**:recipe 就是呼叫該服務的完整說明書。
- **人類設定成本 < 10 分鐘**:即使是對 OAuth 不熟的使用者,UI 只問「你的 API Key 是什麼」這類 secret 層級問題。
- **Secret 隔離**:每個 tenant 的 secret 絕對不互相可見,arcrun 平台本身也無法明文讀取(用 Cloudflare Secrets Store 或加密儲存)。
### 反目標(明確不做的事)
- ❌ 不做 n8n 那種「每個服務一個 credential type」的視覺化面板。
- ❌ 不支援 OAuth1(2026 年還在用的服務極少,真遇到再加)。
- ❌ 不做 credential sharing 的複雜 ACL(全 tenant scope 即可,未來再擴充)。
- ❌ 不在 arcrun 內部明文持久化任何長期 secret(只有加密過的密文或 Secrets Store reference)。
---
## 2. 核心架構:三層模型
```
┌─────────────────────────────────────────────────────────┐
│ Layer 3: Service Recipe (YAML) │
│ arcrun 平台共享,describe "如何呼叫這個服務" │
│ 存在 Workers KV: arcrun-recipes │
│ 例:recipe/notion.yaml, recipe/google_calendar.yaml │
└─────────────────────────────────────────────────────────┘
↓ 引用
┌─────────────────────────────────────────────────────────┐
│ Layer 2: Auth Primitive (TinyGo → WASM) │
│ 四個通用認證零件,實作注入邏輯與 token 交換 │
│ 1. static_key 2. oauth2 │
│ 3. service_account 4. mtls │
└─────────────────────────────────────────────────────────┘
↑ 使用
┌─────────────────────────────────────────────────────────┐
│ Layer 1: Tenant Secret (KV + Secrets Store) │
│ 每個 tenant 自己的 KV namespace │
│ 存 encrypted secret 或 Secrets Store reference │
│ 例:secret/{tenant_id}/notion-prod │
└─────────────────────────────────────────────────────────┘
```
### 為什麼這樣切?
| 切分維度 | Recipe | Primitive | Secret |
|---|---|---|---|
| **誰擁有** | arcrun 平台 | arcrun 平台 | tenant 自己 |
| **變化頻率** | 中(新服務時) | 低(認證機制穩定) | 高(rotate、revoke |
| **敏感度** | 公開 | 公開 | 最高機密 |
| **儲存位置** | 平台 KV`arcrun-recipes` | WASM binary | tenant KV + Secrets Store |
| **可否社群貢獻** | ✅ PR | ⚠️ 核心團隊 | ❌ 永遠不 |
---
## 3. 四個 Primitive 詳細規格
### 3.1 `static_key`
**適用**API Key、Bearer Token、Basic Auth、任何「一組 secret 不會自動過期」的認證。
**涵蓋 n8n 的**API Key、Basic Auth、Header Auth、Query Auth、Custom Auth、Digest Auth~80% 服務)。
**Recipe 欄位**
```yaml
primitive: static_key
inject:
# 四個注入位置,可以同時用多個
header: # HTTP headers
<key>: <value template>
query: # URL query string
<key>: <value template>
body: # request bodyJSON 欄位)
<key>: <value template>
basic_auth: # HTTP Basic Auth(會自動 base64 編碼)
username: <value template>
password: <value template>
```
**Value template 語法**`{{secret.xxx}}` 取 secret 欄位,`{{const.yyy}}` 取 recipe 內定義的常數。
**Secret schema**tenant 存 JSON,欄位由 recipe 的 `required_secrets` 宣告。
**範例(Notion**
```yaml
# arcrun-recipes KV: recipe/notion
service: notion
version: 1
primitive: static_key
base_url: https://api.notion.com/v1
required_secrets:
- key: token
label: "Internal Integration Token"
help_url: https://www.notion.so/my-integrations
inject:
header:
Authorization: "Bearer {{secret.token}}"
Notion-Version: "2022-06-28"
test:
method: GET
path: /users/me
expect_status: 200
```
**Secret 範例**
```json
// tenant KV: secret/tenant_123/notion-prod
{
"token": "secret_abc123..."
}
```
---
### 3.2 `oauth2`
**適用**:需要人類首次授權、之後用 refresh token 續命的場景。
**Grant types 支援**
- `authorization_code`(最常見:GitHub、Slack、Google 用戶授權)
- `client_credentials`(機器對機器)
- `pkce`SPA、行動應用)
- ❌ 不支援:password grant2026 已被多數 OAuth 提供者棄用)、implicit(已棄用)
**Recipe 欄位**
```yaml
primitive: oauth2
grant: authorization_code # or client_credentials, pkce
base_url: <service API base>
oauth:
authorize_url: <IdP authorize endpoint>
token_url: <IdP token endpoint>
scopes:
- <default scope 1>
- <default scope 2>
client_auth: header # or body
# 是否使用 refresh token
refresh: true
# PKCE 時額外參數
pkce_method: S256 # only for grant: pkce
required_secrets:
- key: client_id
label: "Client ID"
- key: client_secret
label: "Client Secret"
secret: true
inject:
header:
Authorization: "Bearer {{runtime.access_token}}"
```
**Runtime 欄位**primitive 自動維護,存在 tenant KV 的 `oauth_state/{secret_id}` key):
- `access_token`
- `refresh_token`
- `expires_at`
**首次授權流程**(人類要做的部分):
1. arcrun UI 呼叫 `AuthBroker.startAuth(recipe_id, tenant_id)` 回傳 authorize URL。
2. 使用者瀏覽器跳轉到 IdP,同意授權。
3. IdP redirect 回 arcrun callback endpoint(固定一個 URL,無論哪個服務)。
4. `AuthBroker` 用 authorization code 換 token,寫入 tenant KV。
**之後 agent 呼叫時完全自動**primitive 檢查 `expires_at`,過期自動用 refresh token 續,失敗再觸發重新授權通知。
---
### 3.3 `service_account`
**適用**Google Service Account、AWS IAM Roleassume role)、任何需要「私鑰簽 JWT 換短期 token」的機器身份。
**這個就是讓你 debug 兩天那個爆炸點。** 我們用 primitive 把地雷全部包起來。
**Recipe 欄位**
```yaml
primitive: service_account
kind: google_jwt # or aws_sigv4, generic_jwt
base_url: <service API base>
token_exchange:
# Google 的 JWT → OAuth access token 流程
endpoint: https://oauth2.googleapis.com/token
audience: https://oauth2.googleapis.com/token
scopes:
- https://www.googleapis.com/auth/calendar
# JWT claims
issuer_from_secret: client_email
subject_from_secret: client_email # optional, for domain-wide delegation 改成其他 user
ttl_seconds: 3600
required_secrets:
- key: service_account_json
label: "Service Account JSON"
type: json_blob # 特別型別,UI 可以接受貼整個 JSON
help: "到 GCP Console → IAM → Service Accounts → Keys → Add Key (JSON) 下載整份 JSON 貼上"
inject:
header:
Authorization: "Bearer {{runtime.access_token}}"
```
**為什麼不是每個服務一個 recipe?**
- Google Calendar、Gmail、Drive、Sheets 全部可以共用同一個 `service_account` primitive。
- 差別只在 `scopes``base_url`
- Recipe 本身可以 import 共通片段(見 §5 recipe 繼承)。
**AWS SigV4kind: aws_sigv4**:這是特例,不是 JWT-based,但概念一樣——用 access_key_id + secret_access_key 在每次 request 上簽章。Primitive 內建處理,recipe 只要宣告 region 和 service name。
---
### 3.4 `mtls`
**適用**mTLS / client certificate。銀行 API、企業內部服務、醫療系統。
**Recipe 欄位**
```yaml
primitive: mtls
base_url: <service API base>
required_secrets:
- key: client_cert
label: "Client Certificate (PEM)"
type: pem_cert
- key: client_key
label: "Client Private Key (PEM)"
type: pem_key
secret: true
- key: ca_cert
label: "CA Certificate (PEM) — optional"
type: pem_cert
optional: true
# mtls 通常不需要額外 inject,憑證在 TLS 層
inject: {}
```
**實作注意**Cloudflare Workers 有原生 mTLS 支援(`mTLSCertificate` binding),primitive 只需要把 secret 轉成 Cloudflare mTLS binding 即可。
---
## 4. Recipe YAML Schema(完整版)
```yaml
# 必填
service: string # 唯一識別,snake_casee.g. "notion", "google_calendar"
version: integer # recipe schema versionbreaking change 要升版
primitive: enum # static_key | oauth2 | service_account | mtls
base_url: string # service API base URL
# primitive 相關(依 primitive 不同)
inject: object # 如何把 secret 注入 HTTP request
oauth: object # 僅 oauth2 primitive
token_exchange: object # 僅 service_account primitive
# Secret 宣告(讓 UI 知道要問什麼)
required_secrets:
- key: string # secret 欄位名
label: string # UI 顯示
secret: boolean # 是否遮蔽顯示(default: true
type: enum # text | json_blob | pem_cert | pem_key | url
optional: boolean # default: false
help: string # 給使用者的提示
help_url: string # 導向服務文件
# 測試(驗證 credential 是否有效)
test:
method: GET | POST
path: string # 相對 base_url
expect_status: integer
expect_json: object # 選填,JSON path assertion
# Metadata
display_name: string # UI 顯示名
description: string
icon_url: string
docs_url: string
tags:
- communication
- crm
- ai
maintainers:
- github: username
# 可選:共通片段繼承
extends: string # recipe name,繼承其 schema 後覆寫
```
---
## 5. Recipe 繼承(reduce 重複)
Google 家族的 API 長得很像,重複寫 15 次太蠢。支援 `extends`
```yaml
# recipe/_google_base.yaml(底線開頭 = 抽象 recipe,不能直接用)
service: _google_base
version: 1
primitive: service_account
token_exchange:
endpoint: https://oauth2.googleapis.com/token
audience: https://oauth2.googleapis.com/token
ttl_seconds: 3600
required_secrets:
- key: service_account_json
type: json_blob
inject:
header:
Authorization: "Bearer {{runtime.access_token}}"
```
```yaml
# recipe/google_calendar.yaml
extends: _google_base
service: google_calendar
version: 1
base_url: https://www.googleapis.com/calendar/v3
token_exchange:
scopes:
- https://www.googleapis.com/auth/calendar
test:
method: GET
path: /users/me/calendarList
expect_status: 200
```
繼承規則:scalar 覆寫,object 深度合併,array 預設覆寫(可用 `!append` 標記 append)。
---
## 6. TinyGo WASM Primitive 實作介面
### 6.1 統一介面(四個 primitive 都實作這個)
```go
// primitive/interface.go
package primitive
type AuthRequest struct {
Method string
URL string
Headers map[string]string
Body []byte
}
type AuthContext struct {
Recipe Recipe // parsed YAML
Secret map[string]any // decrypted secret
Runtime RuntimeState // oauth token cache 等
Now int64 // for testing
}
type Primitive interface {
// 在 HTTP request 上注入認證資訊
Authenticate(req *AuthRequest, ctx *AuthContext) error
// 檢查是否需要 refreshoauth2 / service_account 用)
NeedsRefresh(ctx *AuthContext) bool
// 執行 refresh / token exchange,回傳新的 RuntimeState
Refresh(ctx *AuthContext) (RuntimeState, error)
// 驗證 credential 是否有效(執行 recipe.test
Test(ctx *AuthContext) error
}
```
### 6.2 編譯與部署
```bash
# 四個 primitive 各自編譯成獨立 WASM
tinygo build -o dist/static_key.wasm -target=wasi ./primitive/static_key
tinygo build -o dist/oauth2.wasm -target=wasi ./primitive/oauth2
tinygo build -o dist/service_account.wasm -target=wasi ./primitive/service_account
tinygo build -o dist/mtls.wasm -target=wasi ./primitive/mtls
# 部署時放到 Cloudflare Workers 的 Assets 或直接內嵌
```
### 6.3 Runtime 載入(Worker 端)
```typescript
// worker/src/auth-broker.ts
import staticKeyWasm from "../dist/static_key.wasm"
import oauth2Wasm from "../dist/oauth2.wasm"
// ...
const primitives = {
static_key: await instantiate(staticKeyWasm),
oauth2: await instantiate(oauth2Wasm),
service_account: await instantiate(serviceAccountWasm),
mtls: await instantiate(mtlsWasm),
}
```
> **為什麼 WASM 而不是直接 TS**
> 1. 跨 runtime 可攜性(未來若 arcrun 要跑在 Fly.io、local、或客戶自建環境,同一個 primitive 能用)。
> 2. 配合 u6u/arcrun 既定的 WASM 架構方向,不破壞統一性。
> 3. 沙箱化:primitive 只能透過明確的 host function 存取外部世界(網路、KV),降低惡意 recipe 攻擊面。
---
## 7. AuthBroker API(給 arcrun 其他部分調用)
```typescript
interface AuthBroker {
// Agent 執行時用的主要 API
bind(serviceId: string, secretRef: string, tenantId: string): Promise<AuthenticatedClient>
// 首次授權(僅 oauth2 用)
startAuth(serviceId: string, tenantId: string): Promise<{ authorizeUrl: string, state: string }>
completeAuth(state: string, code: string): Promise<{ secretRef: string }>
// 測試 credential
test(serviceId: string, secretRef: string, tenantId: string): Promise<TestResult>
// 管理
listRecipes(): Promise<Recipe[]>
getRecipe(serviceId: string): Promise<Recipe>
}
interface AuthenticatedClient {
fetch(path: string, init?: RequestInit): Promise<Response>
}
```
**使用範例(agent 端)**
```typescript
const notion = await authBroker.bind("notion", "notion-prod", ctx.tenantId)
const res = await notion.fetch("/databases/abc/query", {
method: "POST",
body: JSON.stringify({ filter: {...} })
})
```
**Agent 完全不需要知道是 API Key 還是 OAuth**——`authBroker.bind()` 回傳的 client 已經注入好認證,fetch 路徑用相對 base_url 的路徑即可。
---
## 8. Storage Layout
### 8.1 Recipe 儲存(arcrun 平台共享)
**Cloudflare KV namespace**`arcrun-recipes`
```
key: recipe/{service_id}
value: <recipe YAML 的 JSON 化版本>
key: recipe-list
value: [{ service_id, display_name, icon_url, tags }, ...] # 加速 UI 列表
```
**更新流程**
1. Recipe YAML 存在 arcrun 主 repo 的 `recipes/` 目錄下(version control + PR review)。
2. CI 跑 schema validator,通過後上傳到 KV。
3. UI 的 recipe 列表 5 分鐘 cache。
### 8.2 Secret 儲存(tenant 私有)
**雙層策略**
- **短期、低敏感** → tenant KV,用 AES-256-GCM 加密,key 從 Cloudflare Secrets Store 拿。
- **高敏感(如 service account JSON、private key** → 直接存 Cloudflare Secrets Storetenant KV 只存 reference。
```
# tenant KV namespace: arcrun-tenant-{tenant_id}
key: secret/{service_id}/{instance_name}
value: {
"recipe_version": 1,
"storage_mode": "kv_encrypted" | "secrets_store_ref",
"data": <encrypted blob> | { "ref": "secrets-store-id" },
"created_at": "...",
"last_verified_at": "..."
}
# oauth2 runtime stateprimitive 自動管理)
key: oauth_state/{service_id}/{instance_name}
value: {
"access_token": "...", # encrypted
"refresh_token": "...", # encrypted
"expires_at": 1234567890
}
```
**secretRef 格式**`{service_id}/{instance_name}`,例如 `notion/prod``google_calendar/workspace-a`
一個 tenant 可以同一個服務存多個 instance(多帳號場景)。
### 8.3 KBDB 整合(可選,但建議)
**按照 KBDB 架構,recipe metadata 可以用 Block + Template 表達**(不是 credential 本體,只是 metadata):
建立一個 `service_recipe` Template
```json
{
"name": "service_recipe",
"display_name": "服務 Recipe Metadata",
"schema": {
"fields": [
{"key": "service_id", "type": "text", "required": true, "description": "服務識別"},
{"key": "primitive", "type": "text", "required": true, "description": "使用的 primitive"},
{"key": "version", "type": "number", "required": true, "description": "Recipe 版本"},
{"key": "display_name", "type": "text", "required": false, "description": "顯示名稱"},
{"key": "docs_url", "type": "text", "required": false, "description": "文件 URL"},
{"key": "kv_key", "type": "text", "required": true, "description": "KV 實際存取 key"}
]
}
}
```
Secret **不**進 KBDBKBDB 不該存敏感資料),只有 metadata 在 KBDB 裡方便搜尋和關聯。
---
## 9. 首次授權 UI Flow(給人類看的部分)
這是「學員不知道該選哪個 credential 的痛點」的終結方案。
### 9.1 Static Key 的 UI
```
┌──────────────────────────────────────────┐
│ 連接 Notion │
├──────────────────────────────────────────┤
│ │
│ Internal Integration Token │
│ ┌────────────────────────────────────┐ │
│ │ secret_••••••••••••• │ │
│ └────────────────────────────────────┘ │
│ ↳ 如何取得?→ 開啟 Notion 整合設定頁 │
│ │
│ [ 測試連線 ] [ 儲存 ] │
└──────────────────────────────────────────┘
```
**零選項。** UI 從 recipe 的 `required_secrets` 動態生成。使用者不用選「這是 Header Auth 還是 Query Auth 還是 Custom Auth」——那是 recipe 的事,不是使用者的事。
### 9.2 OAuth2 的 UI
```
┌──────────────────────────────────────────┐
│ 連接 GitHub │
├──────────────────────────────────────────┤
│ │
│ [ 🔗 使用 GitHub 帳號登入 ] │
│ │
│ 將跳轉到 GitHub,授權後自動返回 │
│ │
└──────────────────────────────────────────┘
```
**一個按鈕。** Client ID / Secret 由 arcrun 平台統一管理(OAuth App 註冊在 arcrun 這邊),使用者看不到也不用知道。
### 9.3 Service Account 的 UI
```
┌──────────────────────────────────────────┐
│ 連接 Google Calendar │
├──────────────────────────────────────────┤
│ │
│ Service Account JSON │
│ ┌────────────────────────────────────┐ │
│ │ 將整份 JSON 貼到這裡 │ │
│ │ │ │
│ │ { │ │
│ │ "type": "service_account", │ │
│ │ "project_id": "...", │ │
│ │ ... │ │
│ │ } │ │
│ └────────────────────────────────────┘ │
│ │
│ 如何取得?→ 展開步驟說明 ▼ │
│ 1. 打開 GCP Console │
│ 2. IAM → Service Accounts │
│ 3. 建立 Service Account │
│ 4. Keys → Add Key → JSON │
│ 5. 下載後整份貼到上方 │
│ │
│ [ 測試連線 ] [ 儲存 ] │
└──────────────────────────────────────────┘
```
**貼 JSON + 按鈕**。不用寫任何程式碼,不用 debug 兩天。
---
## 10. 實作任務分解(CC 的 TODO list
### Phase 1:核心骨架(1-2 週)
- [ ] **T1.1** Recipe YAML schema 定義 + JSON Schema validator(放 `arcrun/schemas/recipe.schema.json`
- [ ] **T1.2** Recipe loader:從 `recipes/` 目錄讀 YAML → validate → 轉 JSON 存入 KV namespace `arcrun-recipes`
- [ ] **T1.3** TinyGo WASM 專案骨架(`arcrun/primitives/`),四個子目錄,統一 interface
- [ ] **T1.4** Worker runtime 的 WASM loader + host function(網路、KV 讀寫)
- [ ] **T1.5** `AuthBroker` TypeScript 類別骨架 + unit test
### Phase 2Static Key1 週)
- [ ] **T2.1** `static_key.wasm` 實作(header/query/body/basic_auth 四種注入)
- [ ] **T2.2** 寫三個 recipe`notion.yaml`, `openai.yaml`, `stripe.yaml`
- [ ] **T2.3** Tenant KV secret 加密寫入 + `AuthBroker.bind()` 整合
- [ ] **T2.4** `recipe.test` 執行器(驗證 credential 有效性)
- [ ] **T2.5** E2E test:存 secret → bind → fetch Notion API → assert
### Phase 3OAuth21-2 週)
- [ ] **T3.1** `oauth2.wasm` 實作(authorization_code + client_credentials + pkce
- [ ] **T3.2** OAuth callback endpoint(統一 URL,用 state 路由到正確 tenant/recipe
- [ ] **T3.3** Refresh token 自動續命邏輯(rate-limit 保護:同一 token 不能 1 秒內 refresh 多次)
- [ ] **T3.4** 寫三個 recipe`github.yaml`, `slack.yaml`, `google_oauth_user.yaml`
- [ ] **T3.5** UI flowstartAuth → 跳轉 → callback → 寫 secret
### Phase 4Service Account1 週)
- [ ] **T4.1** `service_account.wasm` 實作(google_jwt
- [ ] **T4.2** Google JWT signingES256 / RS256)— **這個 TinyGo 需要注意 crypto 支援**
- [ ] **T4.3** AWS SigV4 簽章實作(kind: aws_sigv4
- [ ] **T4.4** Recipe 繼承機制(`extends` 支援)
- [ ] **T4.5** 寫 recipes`_google_base`, `google_calendar`, `google_drive`, `gmail`, `aws_s3`
### Phase 5mTLS + 收尾(1 週)
- [ ] **T5.1** `mtls.wasm` 實作(對接 Cloudflare `mTLSCertificate` binding
- [ ] **T5.2** Cloudflare Secrets Store 整合(高敏感 secret 用)
- [ ] **T5.3** Recipe marketplace UI(列出可用 recipe,搜尋,一鍵設定)
- [ ] **T5.4** Observability:每次 bind / refresh / test 記錄到 KBDBmetadata,不含 secret
- [ ] **T5.5** Docs:recipe 撰寫指南(讓社群能貢獻)
### Phase 6Recipe 生成器(選配,1 週)
- [ ] **T6.1** 給 Claude 一份 API doc,自動產 recipe YAML 草稿 + 人類 review 介面
- [ ] **T6.2** 從 OpenAPI spec 自動推論 recipe
- [ ] **T6.3** 從 n8n credential file 反向轉譯(擷取 400+ 現成整合)
---
## 11. 關鍵技術風險與對策
| 風險 | 對策 |
|---|---|
| **TinyGo 的 crypto 支援不完整**ES256 / RS256 JWT 簽章) | 先用 `crypto/rsa` + `crypto/ecdsa` 確認 TinyGo 版本支援;若不行,fallback 用 Worker runtime 的 `crypto.subtle` 實作這部分,WASM 透過 host function 呼叫 |
| **Recipe 被惡意提交**(如 inject 內含 `https://evil.com` 當 token_url | Recipe 走 PR review + CI 自動檢查 URL 白名單;社群貢獻的 recipe 預設隔離在 `community/` 目錄,使用者明確選擇才啟用 |
| **OAuth state CSRF** | state 用 `crypto.randomUUID()` + 5 分鐘 TTL,存在 KVcallback 時比對 |
| **Secret 在 Worker log 外洩** | `AuthContext.Secret` 禁止 `toString` / `JSON.stringify`,用 Proxy 攔截;log 層強制 redact |
| **Token refresh 風暴**100 個並發 request 同時發現過期) | 用 Durable Object 單執行緒化每個 secret 的 refresh,其他 request 等結果 |
| **TinyGo WASM bundle size** | 四個 primitive 分開編譯,最大 500KB/個;lazy load |
| **Recipe 版本升級破壞相容** | `version` 欄位 semvertenant secret 記錄 `recipe_version`primitive 內處理遷移 |
---
## 12. 對比 n8n(給內部 review / 行銷用)
| 維度 | n8n | arcrun |
|---|---|---|
| Credential types 數量 | 400+(一個服務一個) | 4primitive + N recipe |
| 新增一個服務 | 寫 TypeScript class + rebuild + npm publish | 寫一份 YAML + PR merge |
| AI agent 使用 | 需要讀 node 文件 + 猜參數 | 讀 recipe YAML 即可 |
| 使用者首次設定 | 從 400+ 選項選一個(常選錯) | 搜尋服務名,只問必要 secret |
| OAuth App 管理 | 使用者自己註冊 OAuth app | arcrun 平台統一管理(使用者只需點「授權」) |
| 社群貢獻成本 | 高(TS + 編譯 + 測試) | 低(YAML + 測試) |
---
## 13. 接下來的決策點(需要 richblack 確認)
- [ ] **Recipe 版本管理策略**:採用 semver?每個 recipe 獨立版本?還是整個 recipe set 一個版本?
- [ ] **OAuth App 註冊**:arcrun 平台要統一註冊幾個主流服務的 OAuth AppGitHub、Google、Slack、Microsoft)?還是讓 tenant 自己帶 client_id/secret
- 建議:**雙模式**——平台模式(方便)+ BYO 模式(企業客戶用自己的 OAuth app 有稽核好處)
- [ ] **Recipe registry 的審核流程**:完全開放 PR 還是僅核心團隊維護?
- 建議:`recipes/official/`(核心維護)+ `recipes/community/`(PR 審核後 merge,使用者需明確啟用)
- [ ] **Secret rotation 政策**:要不要內建提醒 / 自動 rotate?(Phase 7+
---
## 14. 附錄:完整範例
### A. 最小可行 recipeOpenAI
```yaml
service: openai
version: 1
primitive: static_key
display_name: OpenAI
base_url: https://api.openai.com/v1
required_secrets:
- key: api_key
label: API Key
help_url: https://platform.openai.com/api-keys
inject:
header:
Authorization: "Bearer {{secret.api_key}}"
test:
method: GET
path: /models
expect_status: 200
tags: [ai]
```
### B. OAuth2 recipeSlack
```yaml
service: slack
version: 1
primitive: oauth2
display_name: Slack
base_url: https://slack.com/api
grant: authorization_code
oauth:
authorize_url: https://slack.com/oauth/v2/authorize
token_url: https://slack.com/api/oauth.v2.access
scopes:
- chat:write
- channels:read
refresh: true
client_auth: header
inject:
header:
Authorization: "Bearer {{runtime.access_token}}"
test:
method: POST
path: /auth.test
expect_json:
ok: true
tags: [communication]
```
### C. Service Account recipeGoogle Calendar
```yaml
extends: _google_base
service: google_calendar
version: 1
display_name: Google Calendar
base_url: https://www.googleapis.com/calendar/v3
token_exchange:
scopes:
- https://www.googleapis.com/auth/calendar
test:
method: GET
path: /users/me/calendarList
expect_status: 200
tags: [calendar, google]
```
---
## 15. 給 CC 的行動指引
1. **先不要動既有 arcrun 的 credential 相關 code**,保持現狀到 Phase 2 完成再切換。
2. **Phase 1 + 2 是 MVP**,做完可以接 80% 服務(API key 類)。
3. **遇到 TinyGo 的技術阻礙(特別是 crypto),立刻回報**,不要自己 workaround 兩天。
4. 每個 Phase 完成後寫一份 brief report(能跑什麼、不能跑什麼、下一步)。
5. Recipe 撰寫先做 3-5 個手工範例,**確認 schema 夠用再開始批量生成**。
@@ -0,0 +1,521 @@
# u6u-core 獨立開源 Repo 需求規格 v3
## 背景與定位
### 為什麼開源
u6u-core 是 AI 工作流執行引擎,開源的護城河邏輯如下:
```
開源(u6u-core 閉源(InkStone 付費服務)
──────────────────────── ──────────────────────────────
cypher-executor(執行引擎) KBDB 向量搜尋
WASM 零件庫(Gmail / GSheets…) KBDB graph 查詢
credentials Worker Persona SDK / Mini-me
CLI MatchGPT
→ 需要訂閱,不需要 YAML / KV
```
用戶自架版:YAML + CF KV,完全免費。
升級版:不需要 YAML,直接用自然語言查 KBDB 圖譜組 workflow,這是差異化。
### 目前 matrix repo 狀況
```
matrix/
├── cypher-executor/ ← 要搬進 u6u-core
├── u6u-core/
│ ├── builtins/
│ ├── credentials/
│ └── registry/
│ └── components/21 個 WASM 零件)
└── ...(其他 InkStone 內部服務,不搬)
```
---
## 任務一:搬移 cypher-executor 進 u6u-core
### 目標結構
```
u6u-core/(新獨立 repo,開源)
├── README.md
├── cypher-executor/ ← 從 matrix/cypher-executor 搬入
│ ├── src/
│ ├── wrangler.toml ← 需要清理(移除 InkStone 內部 bindings
│ └── ...
├── credentials/ ← 從 matrix/u6u-core/credentials 搬入
├── builtins/ ← 從 matrix/u6u-core/builtins 搬入
└── registry/
└── components/ ← 從 matrix/u6u-core/registry/components 搬入
```
### wrangler.toml 清理(重要)
現有 `cypher-executor/wrangler.toml` 有大量 InkStone 內部 Service Bindings,開源版要移除:
**移除(InkStone 專屬,不公開):**
```toml
# 移除這些 services bindings
KBDB inkstone-kbdb-api
REGISTRY inkstone-component-registry
CLINIC_GDRIVE clinic-gdrive
CLINIC_EXCEL clinic-excel
CLINIC_ANALYSIS
CLINIC_RENDER
CLINIC_GSHEETS
AICEO inkstone-aiceo-bot
MINI_ME inkstone-mini-me
```
**保留(用戶自己部署需要的):**
```toml
[[kv_namespaces]]
binding = "EXEC_CONTEXT" # 執行上下文暫存
[[kv_namespaces]]
binding = "WEBHOOKS" # workflow YAML 儲存
[[r2_buckets]]
binding = "WASM_BUCKET" # WASM 零件二進位
[ai]
binding = "AI" # Workers AIauto-publish 用)
```
**新增(開源版用戶需要的):**
```toml
[[kv_namespaces]]
binding = "CREDENTIALS_KV" # credential 加密存儲
```
### 清理後的 component-loader
現有 component-loader 可能有 InkStone 內部查詢邏輯(KBDB HTTP fetch),
開源版改為:**直接從 WASM_BUCKET R2 讀取 `.wasm` 檔案**,不依賴任何外部服務。
---
## 任務二:零件完成度審查與補充
### 完成度標準
每個零件的 `component.contract.yaml` 必須包含:
```yaml
# 已有(現狀)
canonical_id: "gmail"
input_schema: ...
output_schema: ...
gherkin_tests: ...
# 需要補充
credentials_required: # 需要 token 的零件才需要此欄位
- key: gmail_token # 對應 credentials.yaml 的 key 名稱慣例
type: google_oauth # token 類型
description: "Google OAuth access tokengmail.send scope"
inject_as: access_token # 執行時自動注入到 input 的哪個欄位
config_example: | # scaffold 指令產出的範本,帶說明註解
send_email: # 節點名稱(可自訂)
to: "" # 收件人 Email(必填)
subject: "" # 主旨(必填)
body: "" # 內文(必填)
# access_token 由 credentials.yaml 的 gmail_token 自動注入
```
### 需要 credentials_required 的零件
| 零件 | 需要的 token | inject_as |
|------|-------------|-----------|
| gmail | google_oauth | access_token |
| google_sheets | google_oauth | access_token |
| telegram | telegram_bot_token | bot_token |
| line_notify | line_token | token |
| http_request | 不固定(用戶自訂) | 不適用 |
### 不需要 credentials_required 的零件
set, filter, merge, switch, wait, if_control, foreach_control,
try_catch, validate_json, string_ops, number_ops, array_ops,
date_ops, cron, ai_transform_compile, ai_transform_run
### 審查任務(給 CC
對 21 個零件逐一檢查,**只回報,不修改**:
```
路徑:u6u-core/registry/components/
檢查四項:
1. contract.yaml 存在?
2. 有 credentials_required?(需要 token 的才需要)
3. 有 config_example
4. main.go required 欄位與 contract input_schema required[] 一致?
回報格式:表格(✓ / ✗ / N/A)+ 每個零件缺少什麼
不修改任何檔案。
```
審查完成後,再逐一補充缺少的欄位。
---
## 任務三:workflow YAML 格式定義
### 格式設計原則
- `flow:``>>` 三元組描述資料流,人類直接看懂
- 關係詞使用有語意的詞,**不使用 PIPE**(PIPE 等於什麼都沒說)
- `config:` 用零件名稱對應參數,欄位從 contract 的 config_example 來
- credential 全部集中在 `credentials.yaml`workflow 只寫 `{{creds.KEY}}`
### 可用關係詞
| 關係詞 | 語意 | 使用時機 |
|--------|------|---------|
| `完成後` | 前一個成功後執行 | 最常用的串接 |
| `失敗時` | 前一個失敗後執行 | 錯誤處理 |
| `對每個` | 對陣列每個元素執行 | 迭代 |
| `條件滿足時` | 條件分支 | 判斷 |
| `ON_SUCCESS` | 同「完成後」 | 英文版 |
| `ON_FAIL` | 同「失敗時」 | 英文版 |
| `FOREACH` | 同「對每個」 | 英文版 |
| `IF` | 同「條件滿足時」 | 英文版 |
| `ON_CLICK` | 前端按鈕觸發 | UI 互動 |
| `CALLS_SUBFLOW` | 呼叫子工作流 | 模組化 |
**禁止使用 PIPE** — 任何串接都應該用有語意的關係詞。
### 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: # componentId: gmail(由 cypher-executor 語意搜尋對應)
to: "{{input.email}}"
subject: "感謝訂閱!"
body: "歡迎加入!"
# access_token 由 credentials.yaml 的 gmail_token 自動注入
save_to_sheet: # componentId: google_sheets
action: write
spreadsheet_id: "{{creds.sheet_id}}"
range: "訂閱者!A:B"
values: [["{{input.email}}", "{{input.timestamp}}"]]
# access_token 由 credentials.yaml 的 google_oauth 自動注入
notify_error: # componentId: telegram
chat_id: "{{creds.telegram_chat_id}}"
text: "發信失敗:{{input.email}}"
# bot_token 由 credentials.yaml 的 telegram_bot_token 自動注入
```
### credentials.yaml 範例
```yaml
# credentials.yaml — 類似 .env,加入 .gitignore,不進 git
# u6u creds push 時逐一加密上傳到 CREDENTIALS_KV
gmail_token: "ya29.a0AfB_..."
google_oauth: "ya29.a0AfB_..."
sheet_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms"
telegram_bot_token: "123456:ABC-..."
telegram_chat_id: "987654321"
```
### 執行時 credential 注入流程
```
u6u run newsletter_subscribe
cypher-executor 讀 workflow YAML
遇到節點 send_thanks → 查 contractcredentials_required.inject_as = access_token
去 CREDENTIALS_KV 讀 gmail_token → 解密
注入到 WASM input{ to, subject, body, access_token: "ya29..." }
WASM 執行,用戶的 config 裡完全不出現 token
```
---
## 任務四:CLI 開發
### 技術選型
- **語言**Node.jsTypeScript
- **安裝**`npm i -g u6u`
- **依賴**`commander``js-yaml``chalk``ora`
### 指令規格
#### `u6u init`
互動式初始化,產生 `~/.u6u/config.yaml` 和本機 `credentials.yaml`
```
$ u6u init
? Cloudflare Account ID: abc123
? KV Namespace ID (WEBHOOKS): xyz789
? KV Namespace ID (CREDENTIALS_KV): abc456
? R2 Bucket name (WASM_BUCKET): u6u-wasm
? Cypher Executor Worker URL: https://cypher-executor.xxx.workers.dev
? Credentials Worker URL: https://u6u-credentials.xxx.workers.dev
? Cloudflare API Token: ***
✓ 設定完成 → ~/.u6u/config.yaml
✓ 建立 credentials.yaml(已加入 .gitignore
```
---
#### `u6u creds push [credentials.yaml]`
讀取 credentials.yaml,逐一加密上傳到 CREDENTIALS_KV。
```
$ u6u creds push
讀取 ./credentials.yaml...
✓ gmail_token → 已加密上傳
✓ google_oauth → 已加密上傳
✓ sheet_id → 已上傳
✓ telegram_bot_token → 已加密上傳
✓ telegram_chat_id → 已上傳
共上傳 5 個 credentials
```
---
#### `u6u push <workflow.yaml>`
解析 `flow:` 三元組,轉換成 triplets 陣列,上傳到 WEBHOOKS KV。
```
$ u6u push newsletter_subscribe.yaml
✓ 已上傳 newsletter_subscribe → WEBHOOKS KV
Webhook: https://cypher-executor.xxx.workers.dev/webhook/abc123
```
轉換邏輯(CLI 負責):
```
flow[] 三元組
POST /cypher/search(取得 ExecutionGraph
連同 config 存入 WEBHOOKS KV
```
---
#### `u6u run <workflow_name> [--input key=value...]`
觸發執行,顯示結果。
```
$ u6u run newsletter_subscribe --input email=test@example.com
⏳ 執行中...
✓ 完成(2.3s
結果:
send_thanks: { success: true, data: { message_id: "xxx" } }
save_to_sheet: { success: true, data: { range: "訂閱者!A2" } }
```
錯誤時給出具體修復步驟:
```
✗ 執行失敗:節點 send_thanks
原因: access_token 無效(401 Unauthorized
修復方式:
1. 更新 credentials.yaml 的 gmail_token
2. 執行 u6u creds push
3. 重新執行 u6u run newsletter_subscribe
取得 Google OAuth token
→ https://developers.google.com/oauthplayground
```
---
#### `u6u validate <workflow.yaml>`
執行前完整驗證,提前發現問題。
```
$ u6u validate newsletter_subscribe.yaml
✓ YAML 格式正確
✓ flow 三元組語法正確
✓ 所有關係詞有效(無 PIPE
✓ 所有節點名稱在 config 有對應
✓ 所有零件存在於 WASM_BUCKET
✓ credentials 對應:
gmail_token ✓ 已上傳
google_oauth ✓ 已上傳
sheet_id ✓ 已上傳
telegram_bot_token ✗ 缺少
⚠ 缺少 1 個 credential
telegram_bot_token → 請加入 credentials.yaml 並執行 u6u creds push
```
---
#### `u6u parts`
列出可用零件。
```
$ u6u parts
可用零件(21):
[整合]
• gmail Gmail 發信
需要: to, subject, body
credential: gmail_tokengoogle_oauth
• google_sheets 讀寫 Google 試算表
需要: spreadsheet_id, range, action
credential: google_oauth
• telegram Telegram Bot 發訊息
需要: chat_id, text
credential: telegram_bot_token
• line_notify LINE Notify
需要: message
credential: line_token
• http_request 任意 HTTP 請求
需要: url
[控制]
• if_control 條件分支
• foreach_control 迭代執行
• try_catch 錯誤處理
• switch 多路路由
• wait 等待 N 毫秒
[資料]
• set / filter / merge / string_ops / number_ops / array_ops / date_ops
[AI]
• ai_transform_compile 自然語言 → JS 轉換函式
• ai_transform_run 執行已編譯的轉換
```
---
#### `u6u parts scaffold <component>`
從 contract 的 config_example 產出可直接貼入 workflow 的 config 範本。
```
$ u6u parts scaffold gmail
貼入 workflow.yaml 的 config 區塊:
send_email: # 節點名稱(可自訂)
to: "" # 收件人 Email(必填)
subject: "" # 主旨(必填)
body: "" # 內文(必填)
# access_token 由 credentials.yaml 的 gmail_token 自動注入
貼入 credentials.yaml
gmail_token: "" # Google OAuth token
# 取得方式:https://developers.google.com/oauthplayground
```
---
#### `u6u list`
列出 WEBHOOKS KV 中所有 workflow。
```
$ u6u list
• newsletter_subscribe (更新: 2026-04-16)
• daily_summary (更新: 2026-04-15)
```
---
#### `u6u logs <workflow_name>`
查看最近執行記錄。
```
$ u6u logs newsletter_subscribe
2026-04-16 14:30 ✓ 成功 2.1s
2026-04-16 09:00 ✗ 失敗 send_thanks: 401 Unauthorized
2026-04-15 09:00 ✓ 成功 1.8s
```
---
## 開發順序
### Phase 1:搬移與清理(先做)
```
1. 建立新的獨立 repou6u-core
2. 從 matrix 搬入:
- cypher-executor/
- u6u-core/credentials/
- u6u-core/builtins/
- u6u-core/registry/
3. 清理 cypher-executor/wrangler.toml(移除 InkStone 內部 bindings
4. 確認 component-loader 只依賴 WASM_BUCKET,不依賴 KBDB / REGISTRY
5. 本機部署測試
```
### Phase 2:零件完成度(搬移後)
```
6. 審查 21 個零件的 contract.yaml
7. 補充 credentials_requiredgmail, google_sheets, telegram, line_notify
8. 補充 config_example(全部 21 個)
9. 驗證 main.go required 欄位與 contract 一致
```
### Phase 3CLI(完成度補充後)
```
10. u6u init
11. u6u creds push
12. u6u push
13. u6u run(含 credential 自動注入)
14. u6u parts / u6u parts scaffold
15. u6u validate
16. u6u list / u6u logs
```
### Phase 4:開源發布
```
17. 撰寫 README.md(快速開始、零件列表、workflow 語法說明)
18. 撰寫 CONTRIBUTING.md(如何新增零件)
19. 發布到 GitHub
20. npm publishu6u CLI
```
---
## 不在此次範圍
- KBDB 整合(未來付費服務)
- 向量搜尋 / graph 查詢
- 前端管理介面
- Webhook trigger 設定(用戶自行設定 CF Cron)
- 新增 WASM 零件(現有 21 個先做完整,之後再擴充)
@@ -0,0 +1,89 @@
# **u6u 智慧前端與工匠 AI 開發藍圖 (v4.0)**
## **一、 核心設計理念:意圖導向的雙面畫布**
u6u 的前端不是傳統的平面繪圖板,而是一個類似 Android Studio 或 Figma 的\*\*「結構化標籤編輯器」**。 畫布上的每一個元件(Web Component),本質上都是一個**「意圖發射器 (Intent Emitter)」\*\*。前端只負責「長得好看」與「收集人類動作」,後端全權負責「業務邏輯」。
### **人機協作的「雙向同步」**
* **AI 詠唱修改:** 人類說「把按鈕改醒目一點」,CEO AI 在背景將 \<u6u-btn color="blue"\> 修改為 \<u6u-btn color="neon-red"\>,畫面瞬間更新。
* **人類手動覆寫:** 人類覺得 AI 調的紅色太暗,直接在右側屬性面板 (Properties Panel) 點選色碼器,底層 HTML 屬性隨之改變。**AI 也能「看見」這個改變,從中學習人類的審美偏好。**
## **二、 畫布介面設計與運作機制**
### **1\. 正反面翻轉機制 (The "Flip" Interface)**
每個 UI 零件在畫布上都有「一體兩面」:
* **正面 (UI 視圖):** 顯示 HTML 渲染的視覺結果(按鈕、溫度計、圖表)。人類可以在此調整 CSS 屬性、對齊方式與主題顏色。
* **反面 (邏輯視圖):** 點擊「翻面」按鈕後,會進入底層的工作流設定。這裡使用 u6u 的自定義 Cypher 視覺化語法(例如 \>\> 符號)。
* *範例:* \[ UI\_Button: "緊急停機" \] \>\> (Intent: emergency\_stop) \>\> \[ WASM: gsheets\_create \]
### **2\. 智慧容器與區域感知 (Smart Zone Awareness)**
為了消滅傳統 iPaaS(如 n8n)最痛苦的「手動變數綁定」,u6u 畫布具備「區域感知」能力。
* **底層邏輯(獨立元件):** 畫布上的 TextInput 與 Button 都是各自獨立的原子元件。
* **麻瓜體驗(智慧表單):** 當使用者將這兩個元件拖入同一個排版容器(例如 \<u6u-card\>)時,系統會自動建立上下文關聯。當按下按鈕並觸發 Webhook 時,按鈕會**自動打包同容器內所有輸入框的值**一併送出:
{
"intent": "query\_attendance",
"payload": { "employee\_id": "A1234" } // 自動從旁邊的 TextInput 抓取
}
使用者完全不需要理解「表單傳值」或「變數綁定」,拖拉組合即生效。
### **3\. 多重事件插槽與靜態屬性 (Multi-Event Slots)**
一個前端元件可以具備多種觸發行為,系統透過介面將「視覺」與「後端邏輯」徹底分流:
* **靜態視覺註釋:** 例如 mouseover 顯示提示。使用者只需在屬性面板輸入 Tooltip 文字,底層僅修改 HTML 屬性 \<u6u-btn tooltip="..."\>,不消耗任何伺服器資源或 Webhook。
* **動態意圖綁定:** 在「反面」邏輯視圖中,使用者可以針對不同事件綁定不同的工作流:
* ⚡ When: 點擊 (onClick) ➡️ \[ 綁定至 Webhook A:送出查詢 \]
* ⚡ When: 獲得焦點 (onFocus) ➡️ \[ 綁定至 Webhook B:載入歷史紀錄 \]
### **4\. 智慧上下文替換 (Smart Contextual Substitution)**
當主管在畫布上對著一個已連接 Webhook 的「按鈕」點擊右鍵選擇「替換元件」時:
* 系統讀取反面的 Cypher 連線,發現需要發射一個 trigger 意圖。
* 系統過濾 KBDB 零件宇宙,**只顯示相容的 UI 零件**(如下拉選單、開關)。不具備 trigger 能力的元件(如純文字標籤)會被自動隱藏,確保替換後系統絕對不會報錯。
## **三、 原子化組裝與極致解耦:CEO AI 與工匠 AI 的分工**
當企業主管提出需求:「我需要一個『輸入工號即可查詢員工打卡紀錄』的工具」時,這在 u6u 中**並不是一個單一零件**,而是一個由多個「原子零件」構成的**工作流 (Workflow)**。
### **1\. CEO AI 的動態組裝 (Macro Assembly)**
面對需求,大腦 AI (CEO AI) 會快速從 KBDB 挑選現成積木進行組合:
* **前端 Prototype 組合:** \<u6u-text-input\> \+ \<u6u-btn\> \+ \<u6u-text-field\>。
* **後端 Pseudo Code 組合:** webhook\_receiver \>\> check\_kbdb(template\_name, value)。
AI 會自動用 Cypher 將前端的表單意圖連線到後端工作流。對不懂程式的主管來說,前端就是 Prototype,翻面的 Cypher 就是 Pseudo Code,整套系統瞬間組合完畢。
### **2\. 工匠 AI (Forge AI) 的原子生產線**
只有當現有零件庫缺乏特定原子時,機甲才會喚醒工匠 AI 進行開發。
**解耦哲學:** 後端零件開發時,根本不需要管前端零件長什麼樣子(是按鈕還是輸入框)。只要前端送來的 JSON 它能吃,就是合法的候選零件。
* **Step 1: 規格定義 (Interface Contract)**
工匠 AI 只在乎接收與回傳的 JSON 格式。
* **Step 2: 打造純粹的邏輯黑箱 (後端 TinyGo WASM)**
工匠 AI 撰寫 Go 程式,編譯成 .wasm。絕對純粹的後端邏輯,沒有任何介面程式碼。
* **Step 3: 獨立的前端零件生產 (若需要)**
獨立生成 Web Component(如 \<u6u-3d-pie-chart\>),只負責接收特定 JSON 來渲染畫面。
* **Step 4: 註冊與編目 (Cataloging into KBDB)**
新積木註冊到圖資料庫,未來的 CEO AI 即可將其與任何既有的前端或後端元件進行無限的叉積組合。
## **四、 架構總結與終極產品體驗**
這套前端架構讓 u6u 成為一個\*\*「表裡如一」**的系統,成功創造了**「麻瓜的 ERP 幻覺」\*\*:
使用者不需要知道什麼是「前後端分離」、什麼是「API 串接」。他們只是覺得:
1. 拖拉了一個溫度計。
2. 翻面把「數值更新」連線到「機台感測器」。
3. 拖拉了一個紅按鈕放在旁邊。
4. 翻面把「點擊」連線到「發送 Line 警報」。
在十分鐘的「繪圖」過程中,沒有寫一行程式碼,也沒有設定任何變數。但透過**前端 Web Components** 的視覺封裝、**智慧容器**的自動資料打包,以及**後端 TinyGo WASM \+ Cypher** 的無縫承接,他們在不知不覺中,就搭建出了一套具備微服務架構、高擴展性、且可部署至極限邊緣的企業級系統。
@@ -0,0 +1,136 @@
# **u6u 系統與零件宇宙全景規劃白皮書 (The u6u Ecosystem Blueprint)**
## **1\. 核心理念與願景**
u6u 旨在解決傳統 Workflow 軟體 (如 n8n) 存在的「單線程、沈重、複雜、難以組成系統」的痛點。
透過結合 Cloudflare Workers (輕量邊緣運算) 與 Cypher (圖形資料庫關係),u6u 提供一個由 AI 驅動的「意圖到系統」生成平台。所有的系統功能皆被拆解為可複用、可組合的「零件 (Components)」,並在一個會自然淘汰、自我修復的「零件宇宙 (Component Universe)」中演化。
## **2\. 四層架構拆解 (Four-Tier Architecture)**
u6u 的工作模式採取由上到下 (Top-Down) 的 Break-down 機制:
1. **Polaris (北極星層 / 意圖層):**
* 用戶以自然語言描述商業模式與想法(例如:「我要做一個 AI 客服表單系統」)。
* 這是整個系統的起點,AI 會根據 Polaris 將意圖拆解為 Prototype。
2. **Prototype (原型 / 前端層):**
* 定義前端的版型、頁面描述、UI 元件以及它們的屬性。
* 作為使用者互動的入口,透過觸發事件 (Triggers) 連接到後端 Workflow。
3. **Workflow (工作流層):**
* 系統的 Orchestrator (編排者),定義業務邏輯的走向。
* 透過 Cypher 語法與三元組,定義每個節點 (Component) 的執行順序與條件分支。
4. **Component (零件層 / 節點):**
* 最底層的執行單元,主要分為兩類:
* **功能型 (Logic):** 迴圈、條件判斷、資料轉換、統計等 (透過 CF Workers 執行 JS 邏輯)。
* **介接型 (API):** 呼叫外部服務 (Webhook, HTTP Request)。
## **3\. 統一描述語言:擴展三元組與跨層級 YAML**
為了解決跨 YAML 檔案串接的問題,u6u 採用易於人類閱讀與 AI 生成的 **「A \>\> 關係 \>\> B」** 三元組語法,結合自定義的 URI 協議 (workflow://, component://, ui://),實現跨層級的連結。
### **綜合 YAML 範例與三元組串接**
*\# 1\. Prototype YAML (描述前端)*
kind: Prototype
id: ui\_dashboard
triplets:
*\# 結構與版型零件*
\- "ui\_dashboard \>\> CONTAINS \>\> layout\_admin"
\- "layout\_admin \>\> CONTAINS \>\> btn\_submit"
*\# UI 零件與屬性零件 (CSS/行為)*
\- "btn\_submit \>\> IS\_A \>\> ui://components/Button"
\- "btn\_submit \>\> HAS\_STYLE \>\> style://tokens/GlowEffect"
\- "btn\_submit \>\> HAS\_BEHAVIOR \>\> anim://motions/Pulse"
*\# 跨層級串接:前端觸發 Workflow*
\- "btn\_submit \>\> ON\_CLICK \>\> workflow://workflows/process\_data.yaml"
*\# 2\. Workflow YAML (描述工作流編排)*
kind: Workflow
id: wf\_process\_data
triplets:
*\# 跨層級串接:Workflow 呼叫 Component*
\- "START \>\> TRIGGERS \>\> step\_validate"
\- "step\_validate \>\> IS\_A \>\> component://components/validate\_json"
*\# Workflow 節點間的流轉 (轉譯為 Cypher 關係)*
\- "step\_validate \>\> ON\_SUCCESS \>\> step\_call\_api"
\- "step\_validate \>\> ON\_FAIL \>\> step\_notify\_error"
*\# 跨 Workflow 串接*
\- "step\_call\_api \>\> CALLS\_SUBFLOW \>\> workflow://workflows/save\_to\_db.yaml"
## **4\. 零件宇宙 (Component Universe) 的審核與淘汰機制**
在 u6u 中,所有的 UI、Style、Logic、API 都是「零件」。當 AI 發現缺乏所需零件時,會自動創造它。為了確保生態系的健康,必須建立嚴格的**審核標準**與**自然淘汰機制**。
### **4.1 零件的創建與審核標準 (Pass/Fail Criteria)**
當 AI 或開發者提交一個新零件時,系統會啟動自動化沙盒測試。必須完全通過以下標準,零件才能進入「宇宙」供他人使用:
1. **功能型零件 (Logic Components):**
* **Gherkin BDD 驗收:** 必須附帶 Feature/Scenario 測試規格,且執行結果 100% 通過 (例如:Given input JSON, When split, Then returns Array)。
* **效能門檻:** 邊緣運算 (CF Workers) 執行時間需低於設定閾值 (例如 \< 50ms),無記憶體洩漏。
2. **介接型零件 (API Components):**
* **連線驗證:** 端點 (Endpoint) 必須能 ping 通,或回傳正確的 2xx HTTP Status (提供 Mock Payload 測試)。
* **Credential 安全:** 不可將 Token 或 Secret 寫死在代碼中,必須嚴格宣告所需的 Environment Variables 規格。
3. **前端與屬性零件 (UI & Style Components):**
* **渲染驗證:** CSS / 組件代碼不能導致瀏覽器 Crash。
* **相容性檢查:** 不可包含嚴格衝突的樣式 (例如寫死 \!important 破壞全域版型)。
### **4.2 零件宇宙的自然淘汰 (Natural Selection)**
零件一旦上架,將面臨殘酷的達爾文機制:
* **AI 偏好權重:** AI (透過 MCP 搜尋時) 會優先選擇「成功率高、執行速度快、被調用次數多」的零件。
* **降級與墓地:** 連續 30 天無人/無 AI 使用,或錯誤率飆升的零件,會被降級 (Deprecated)。最終轉入「零件墓地」,從首選搜尋清單中剔除。
## **5\. 系統自癒與 AI 避坑機制 (Auto-Healing & Pitfall Avoidance)**
這是 u6u 維持系統穩定運作的最核心機制。工作流不只要能跑,跑完後還必須經歷 **「強制 AI 評價 (Mandatory AI Evaluation)」**。
### **5.1 運行後的強制評價迴圈**
每當一個 Workflow 在 CF Workers 上執行完畢 (或發生異常中斷),系統攔截日誌並強制啟動 AI 評價代理 (Evaluator Agent)。
* **評估維度:**
* **狀態:** 成功 / 失敗 (Crash) / 逾時 (Timeout)。
* **效能:** 耗時是否合理 (例如 API 突然變得很慢)。
* **警告訊息:** 資源消耗過大、API 回傳即將停用的 Warning。
### **5.2 自癒與避坑流程 (The Feedback Loop)**
當 Evaluator Agent 發現問題時,會觸發以下流程:
1. **回報與通知 (Notify):** 系統自動生成修復 Ticket,並通知當初建立該零件/工作流的製作人 (或系統管理員)。
2. **AI 嘗試修復 (Auto-Fix):** 系統派遣「修復型 AI」嘗試讀取錯誤日誌並修復代碼 (例如:API 規格變更導致 JSON 解析錯誤,AI 自動修改解析邏輯)。
3. **驗收與部署:** 修復後的代碼若通過 Gherkin 驗收,則無縫熱更新。
4. **避坑標記 (Pitfall Marking):** \- 如果 AI 無法修復 (例如:外部第三方 API 永久倒閉,或邏輯存在根本性死結)。
* 系統會在 Cypher 圖形資料庫中,將該零件或該特定的三元組關係標記為 \[HAS\_PITFALL\]。
* **結果:** 下一個生成系統的 AI 在透過 MCP 搜尋時,會讀取到這個坑的紀錄,並**強制繞道**,改用其他方案或生成新的零件,實現「前人踩坑,後 AI 避坑」的群體智慧。
## **6\. 結論**
u6u 不是一個單純的開發工具,它是一個**生物體積木系統**。
透過「三元組」統一語言打破系統壁壘,透過「零件審核」保證基因優良,再透過「強制評價與避坑機制」實現演化。當這套系統運轉起來,AI 就能在其中無止盡地為人類組裝出越來越強大、越來越穩定的商業應用。
@@ -0,0 +1,99 @@
# **u6u 自動演化 ERP:全端統一架構規格書 (v3.0)**
## **1\. 架構核心思想 (The Core Philosophy)**
u6u ERP 是一套具備自我修復與功能擴充能力的「有機體」系統。
為確保系統在跨國雲端、機密地端與斷網邊緣皆能無縫運作,系統採用\*\*「向下相容的絕對標準化」\*\*:由最嚴苛的無人機環境來定義全域零件標準。
系統運作依賴三位一體的語言與載體:
1. **大腦戰略層 (Markdown / Gherkin)** CEO AI 負責閱讀與撰寫,定義全域戰略、系統設計文件 (SDD) 與商業演算法則 (如 ROI 門檻)。
2. **神經編排層 (Cypher)** u6u 引擎的核心。AI 透過撰寫 Cypher 語法來進行業務邏輯的動態編排、狀態流轉與意圖攔截。
3. **肌肉執行層 (TinyGo WASM)** 系統中**唯一合法**的零件規格。負責所有具體的 I/O、資料轉換與運算,保證極小體積與極速冷啟動。
## **2\. 實戰演練:離岸風機巡檢的黑天鵝事件 (三層架構實踐)**
為了具體理解這套系統如何運作,我們以一次「離岸風電場巡檢」的突發事件為例,展示雲、地、邊三層架構的完美協同。
### **第一階段:戰略下達與沙盤推演 (Tier 1 ➡️ Tier 2 ➡️ Tier 3\)**
跨國能源集團的**雲端總部 (Tier 1\)** 收到年度檢修排程。雲端的 **CEO AI** 讀取了全局的 Markdown 戰略文件,向遠在海岸線的**地端指揮中心 (Tier 2\)** 下達指令。
地端指揮中心(配備強大伺服器與 workerd 叢集)的**部門主管 AI** 將任務拆解給 50 台即將出海的無人機。無人機 07 號 **(Tier 3\)** 的小腦 AI 透過本地的 Cypher 引擎進行沙盤推演,從地端資料庫下載了 rgb\_vision.wasm (光學影像)、lidar\_scan.wasm (光達) 等 60 個可能會用到的 TinyGo 零件,存入本地記憶體後隨船出航。
### **第二階段:邊緣的極限生存 (Tier 3 獨立運作)**
無人機 07 號來到海上 50 公里處,完全失去對外網路。突然,海上濃霧降臨。
原本執行中的 Cypher 圖譜卡住了,因為 rgb\_vision.wasm 回報「無法獲取清晰影像」。07 號沒有驚慌,它內建的輕量級 Go \+ Wazero 引擎在 0.1 秒內動態重組了圖譜邏輯:剔除光學零件,瞬間載入並執行 lidar\_scan.wasm,不需人類介入,繼續在濃霧中精準貼行。
### **第三階段:游擊網與地端代工 (Tier 3 ↔️ Tier 2\)**
巡檢中途,07 號發現風機葉片上有極罕見的「蜂巢狀熱應力微裂紋」,但它帶出來的 60 個零件中沒有對應的分析工具。
07 號飛昇至濃霧上方,短暫連上母船的微弱區域網路發起「短點射傳輸 (Burst)」:{"intent": "計算蜂巢狀熱應力微裂紋擴散率"},拿到任務單號後立刻斷網潛回霧中。
海岸線的**地端指揮中心 (Tier 2\)** 收到需求。強大的**工匠 AI** 瞬間啟動,生成了一段 TinyGo 程式碼,並在本地編譯與測試。三分鐘後,07 號再次探頭連網,下載了熱騰騰的 honeycomb\_analyzer.wasm,並將其編織進 Cypher 圖譜中完成測量。
### **第四階段:CEO AI 的全局戰略覆寫 (Tier 2 ➡️ Tier 1\)**
同時,地端指揮中心匯整了無人機傳回的陣風數據,同步給**雲端總部 (Tier 1\)**。雲端的 CEO AI 呼叫 roi\_calculator.wasm 進行試算,發現風暴將造成設備重大損壞(ROI 極低)。
CEO AI 立刻修改總部的 Markdown 戰略文件,新增一條 BDD 規則:「風速大於 22m/s,立刻轉為陣列抗風模式」。新的最高指導 Cypher 範本瞬間下發至地端,再廣播給所有無人機。07 號收到新命令,掛起原任務,與機群組成抗風陣型,安全度過危機。
## **3\. 物理拓撲與技術棧 (The 3-Tier Tech Stack)**
透過 **KBDB Adapter** 抽象層,AI 在任何環境中呼叫的 API 介面皆一致,但底層基礎設施依據物理環境的豐饒度進行適配。
### **Tier 1: 雲端總部 (Cloud \- The Global Brain)**
* **場景:** 跨國集團資料整合、全域戰略備份、對外公開 API、跨國部門協調。
* **AI 角色:** **CEO AI (大型語言模型)**。負責解析 Markdown、跨區資源調度、修改全域演算法參數。
* **技術規格:**
* **調度引擎:** Cloudflare Workers (原生執行 TinyGo WASM)。
* **圖資料庫 (狀態/關聯)** Cloudflare D1 \+ u6u Cypher 轉換層。
* **零件與儲存:** Cloudflare R2 / KV。
* **向量檢索 (意圖/型錄)** Cloudflare Vectorize。
* **架構優勢:** 無限橫向擴展 (Serverless),無須維運硬體,扛載全球級別的 API 併發。
### **Tier 2: 企業地端/基地台 (On-Premise \- The Basecamp & Forge)**
* **場景:** 高機密廠房內網、財務核心系統、無人機/機器人的母艦基地。
* **AI 角色:** **部門主管 AI** (廠區派工);**工匠 AI** (專職接收規格,透過 TDD 閉環動態生成 TinyGo 程式碼)。
* **技術規格 (企業級高可用架構):**
* **負載平衡:** Nginx 或 HAProxy (負責將請求分發給後端叢集)。
* **調度引擎:** **workerd 叢集 (Cloudflare 開源執行環境)**。在本地實體伺服器或 VM 上平行部署多個 workerd 行程,完美相容雲端環境,提供極高的並發處理能力 (V8 JIT 極限算力)。
* **圖資料庫 (狀態/關聯)** **Kùzu** (單機極速圖庫) 或 PostgreSQL \+ AGE (超高併發)。
* **零件與儲存:** 企業本地 NVMe 硬碟叢集 / MinIO (S3 相容)。
* **向量檢索 (意圖/型錄)** pgvector 或 Milvus。
* **架構優勢:** 兼具資料不出網的「絕對資安」與雲端級別的「叢集擴展性」。內建「代工坊 (Forge)」,是推動企業系統自動演化的核心引擎。
### **Tier 3: 邊緣載具 (Extreme Edge \- The Operatives)**
* **場景:** 無網環境的巡檢無人機、工廠無軌導引車 (AGV)、機械手臂。
* **AI 角色:** **導航/執行 AI (極小參數 SLM)**。不具備寫程式能力,只負責解讀現場狀況、執行 Cypher 圖譜,並透過 DTN 呼叫地端請求新零件。
* **技術規格 (極限微縮架構):**
* **調度引擎:** 輕量級 Go 排程引擎 \+ **內嵌 Wazero**。不依賴 V8 或 workerd,確保在極低 RAM 的晶片上流暢運行,實例化延遲僅需數微秒。
* **圖資料庫 (狀態/關聯)** 嵌入式 Kùzu 或 SQLite。
* **零件與儲存:** SD 卡 / eMMC 實體檔案系統。
* **向量檢索 (意圖/型錄)** sqlite-vss (極輕量本地向量)。
* **架構優勢:** 絕對的離線生存能力。只帶必要的 TinyGo WASM 零件出門,無任何編譯環境,體積最小化。
## **4\. 自動演化工作流 (The Auto-Evolution Loop)**
當企業環境發生變化(例如:新增硬體規格、外部 API 變更),u6u 的演化路徑如下:
1. **遭遇未知 (Anomaly Detection)**
無人機 (Tier 3\) 或雲端服務 (Tier 1\) 在執行 Cypher 任務時,發現本地 KBDB 向量庫中缺乏對應的工具零件。
2. **意圖攔截與 ROI 評估 (CEO/Manager AI)**
機甲 (Harness) 攔截缺失意圖,呼叫 roi\_calculator.wasm 等評估零件。若認定具備開發價值,系統會生成一份標準的 Input/Output JSON Schema。
3. **地端代工 (The Forge @ Tier 2)**
規格需求透過網路或 DTN 送達 Tier 2 地端機房的「工匠 AI」。
工匠 AI 生成 TinyGo 程式碼 \-\> 在沙盒中執行 tinygo build \-target=wasi \-\> 通過測試迴圈 \-\> 輸出正式的 .wasm 檔案。
4. **全域派發 (Distribution & Versioning)**
新零件註冊進入企業的零件圖資料庫 (KBDB)。
* **雲端:** 同步至 R2。
* **邊緣:** 載具下次連網時,透過游擊網 (Burst Transmission) 下載更新檔。
5. **動態編織 (Execution)**
各端 AI 獲知新零件上線,瞬間將其編入新的 Cypher 圖譜中執行,完成企業能力的自動擴展。
@@ -0,0 +1,360 @@
# u6u 系統規格書 v1.0
## 給 AI 的架構思考指引
> 本文件用途:讓 AI 理解 u6u 的完整設計意圖、現況、與未來路徑,
> 在實作決策時能自行判斷方向正確性,而不只是執行單一任務。
---
## 一、系統本質(先理解再動手)
u6u 不是 workflow 工具,不是 no-code 平台,不是 iPaaS。
u6u 是一個**「意圖到系統」的生物體積木平台**:
- 人類說出意圖(自然語言)
- AI 從零件宇宙組裝出可運行的系統
- 系統會自動評價、演化、淘汰舊零件
- 累積的零件就是核心資產,越積越有價值
**設計的終極體驗:** 工廠主管拖拉十分鐘,組出具備微服務架構的企業系統,零程式碼,但底層是真正的分散式系統。
---
## 二、四層邏輯架構
```
Polaris(意圖層)
↓ 自然語言 → AI 拆解
Prototype(前端層)
↓ UI 元件 + 觸發事件
Workflow(編排層)
↓ Cypher 語法定義執行順序
Component(零件層)
↓ .wasm 實際執行
```
每一層向下只透過標準介面溝通,層與層之間完全解耦。
---
## 三、物理三層部署
```
Tier 1:雲端總部(Cloudflare Workers
- CEO AI 讀取 Markdown 戰略文件
- 全域零件同步至 R2
- Cloudflare D1 + VectorizeKBDB
Tier 2:企業地端(workerd 叢集)
- 部門主管 AI 派工
- 工匠 AI 生成並測試新零件
- Kùzu 或 PostgreSQL + AGE(圖資料庫)
- pgvector 或 Milvus(向量搜尋)
Tier 3:邊緣載具(無人機、AGV、工廠設備)
- 極小參數 SLM
- Go 排程引擎 + 內嵌 Wazero(無 V8
- SQLite + sqlite-vss
- 離線生存,DTN 短點射傳輸
```
**關鍵約束:** Tier 3 沒有 V8,沒有 Node.js,沒有網路。
所有零件必須在 Wazero 上跑,所有資料傳輸透過 stdin/stdout JSON。
---
## 四、零件規格(Component Contract
這是整個系統最核心的不變量。零件規格定錯,累積的資產會變成技術債。
### 4.1 零件的本質定義
**一個零件只做一件事。**
```
✅ gsheets_create_table
✅ gsheets_delete_table
✅ gsheets_get_entries
❌ gsheets_manager(做太多事,禁止)
```
### 4.2 零件合約格式(component.contract.yaml
每個零件必須附帶此合約,這是 AI 讀取零件的唯一介面描述:
```yaml
id: "gsheets_get_entries" # 功能合約名稱(永久不變)
version: "v2" # 實作版本
wasi_target: "preview1" # 明確標記 WASI 版本,未來升級用
stability: "floating" # floating | stable | pinned
runtime_compat:
- "cf-workers"
- "workerd"
- "wazero"
constraints:
max_size_kb: 2048 # 超過視為打包了 runtime
max_cold_start_ms: 50
no_network_syscall: true # 禁止零件自己發 HTTP
no_filesystem_syscall: true # 只能 stdin/stdout
io_model: "stdin_stdout_json" # 唯一合法的 I/O 模型
input_schema:
type: object
required: ["spreadsheet_id", "sheet_name"]
properties:
spreadsheet_id: { type: string }
sheet_name: { type: string }
limit: { type: integer, default: 100 }
output_schema:
type: object
properties:
rows: { type: array }
total: { type: integer }
error: { type: string }
gherkin_tests:
- scenario: "正常取得資料"
given: '{"spreadsheet_id":"abc","sheet_name":"Sheet1"}'
then_contains: '{"total":1}'
- scenario: "不存在的表格回傳錯誤"
given: '{"spreadsheet_id":"abc","sheet_name":"不存在"}'
then_contains: '{"error":'
tags: ["google", "sheets", "data", "read"]
description: "從 Google Sheets 取得指定工作表的所有資料列"
```
### 4.3 語言無限制原則
**零件開發語言完全不限制**,只要輸出符合以上合約的 .wasm 即可。
可接受語言(非排他):TinyGo、Rust、AssemblyScript、C/C++
注意事項(不是禁止,是要求自行驗證):
- TypeScript via Extism:會打包 QuickJS,體積通常超過 2MB 限制
- 標準 Go(非 TinyGo):runtime 過肥,通常超過體積限制
- 任何語言:不可在 .wasm 內部呼叫網路或檔案系統 syscall
**驗收標準只有一個:通過沙盒測試。** 語言是零件作者自己的事。
### 4.4 零件的前後端分類
| 類型 | 執行位置 | I/O | 範例 |
|------|----------|-----|------|
| 後端邏輯零件 | Workers/workerd/Wazero | JSON stdin/stdout | validate_json, http_request |
| 前端 UI 零件 | 瀏覽器 | HTML attributes / DOM events | u6u-btn, u6u-chart |
| **混合零件** | **禁止** | — | **強制拆成兩個** |
---
## 五、零件版本控制策略
### 5.1 命名規則
```
gsheets_get_entries ← 功能合約名稱(搜尋用,永遠存在)
gsheets_get_entries_v1 ← 第一個實作(慢但能用)
gsheets_get_entries_v2 ← 更快的實作(由另一個 AI/用戶提交)
```
### 5.2 穩定性標籤
Workflow 引用零件時可指定穩定性需求:
```
gsheets_get_entries → 預設 floating,AI 自動選最優版本
gsheets_get_entries@stable → 有更好版本時提示,人工確認才換
gsheets_get_entries@pinned:v1 → 版本凍結,宇宙怎麼演化都不影響
```
| 標籤 | 適用情境 | 更新行為 |
|------|----------|----------|
| `floating` | 一般企業應用 | AI 自動換成最優版本 |
| `stable` | 重要業務流程 | 有更好版本時提示,人工確認 |
| `pinned` | 工廠控制器、嵌入式設備 | 永遠不動,即使進入墓地也保留 .wasm |
### 5.3 淘汰機制
- AI 搜尋零件時,KBDB 依「成功率 × 速度 × 被調用次數」排序
- 連續 30 天無使用且評價下降 → Deprecated
- Deprecated 後繼續 90 天無復活 → 進墓地(從搜尋清單移除)
- **墓地的 .wasm 永遠保留**pinned 的 Workflow 永遠能拉到
---
## 六、零件製造指引書(給用戶 AI 的規範)
u6u 不限制誰來造零件,任何 AI(用戶自己的 Claude、GPT、本地模型)都可以。
但必須遵守此指引書,否則沙盒測試不過,無法上架。
### Step 1:理解介面合約
造零件前,先定義合約 YAML。
**零件只在乎輸入 JSON 和輸出 JSON,完全不管前端長什麼樣子。**
```
人類:我要一個可以查 Google Sheets 的零件
AI 的第一步:定義 input_schema 和 output_schema,不是寫程式
```
### Step 2:選擇開發語言
選擇你最熟悉的、能產出 WASI preview1 相容 .wasm 的語言。
建議:
- 小型邏輯零件(轉換、計算)→ TinyGo 或 AssemblyScript(體積小)
- 效能敏感零件 → Rust(生態最成熟)
- 任何語言都可以,只要通過合約限制
### Step 3:實作規則
```
✅ 只用 stdin 讀取輸入 JSON
✅ 只用 stdout 輸出結果 JSON
✅ 錯誤也用 stdout 輸出:{"error": "說明"},不要 panic/crash
✅ 無狀態:每次呼叫都是獨立的,不依賴上一次執行的結果
✅ 需要打外部 API?透過 host function 注入,不在 .wasm 裡自己發 HTTP
❌ 禁止網路 syscall
❌ 禁止檔案系統 syscall
❌ 禁止打包 runtimeQuickJS、Node.js 等)
❌ 禁止超過 2MB
```
### Step 4:本地測試方式
```bash
# 用任何 WASI runtime 本地測試
echo '{"spreadsheet_id":"abc","sheet_name":"Sheet1"}' | \
wasmtime gsheets_get_entries.wasm
# 預期輸出
{"rows":[...],"total":5}
```
### Step 5:提交審核
提交 `.wasm` + `component.contract.yaml`,系統自動執行:
1. 體積檢查(< 2MB
2. 冷啟動時間(< 50ms
3. Syscall 掃描(不能有網路/檔案系統呼叫)
4. Gherkin 測試(合約裡的所有 scenario 必須 100% 通過)
5. 多 runtime 相容測試(cf-workers / workerd / wazero
全部通過 → 上架進入零件宇宙,開始累積評價。
---
## 七、Cypher 編排語言
Workflow 使用擴展三元組語法描述執行邏輯:
```yaml
kind: Workflow
id: wf_query_attendance
triplets:
# 基本流程
- "START >> TRIGGERS >> step_receive"
- "step_receive >> IS_A >> component://webhook_receiver_v1"
# 條件分支
- "step_receive >> ON_SUCCESS >> step_validate"
- "step_receive >> ON_FAIL >> step_notify_error"
# 跨 Workflow 串接
- "step_validate >> CALLS_SUBFLOW >> workflow://save_to_db"
# 前端觸發後端
- "btn_submit >> ON_CLICK >> workflow://wf_query_attendance"
```
**URI 協議規範:**
- `component://` → 引用零件
- `workflow://` → 引用子 Workflow
- `ui://` → 引用前端零件
- `style://` → 引用樣式零件
---
## 八、KBDB 在 u6u 的角色
u6u 的所有狀態都在 KBDB 裡:
| KBDB Block 類型 | 存放內容 |
|-----------------|----------|
| Component Block | 零件合約、.wasm 位置、版本、評價指標 |
| Workflow Block | Cypher 三元組、依賴零件清單 |
| Prototype Block | 前端結構、UI 零件樹 |
| Pitfall Block | 避坑記錄,AI 搜尋時強制讀取 |
| Evaluation Block | 每次 Workflow 執行後的強制評價結果 |
**KBDB 不變量:永遠只有三張表(blocks/templates/slots),不新增表。**
所有以上類型都用 Template + Slot 實現。
---
## 九、自動演化迴圈
```
執行 Workflow
強制 AI 評價(Evaluator Agent
↓ 發現問題
生成修復 Ticket → 通知製作人
↓ AI 嘗試修復
通過 Gherkin 驗收 → 熱更新
↓ 無法修復
標記 [HAS_PITFALL] 到 Cypher 圖
下一個 AI 搜尋時讀到坑,強制繞道
```
---
## 十、現況與未來路徑
### 現在已有
- KBDBblocks/templates/slots + Vectorize
- IS-Squad MCPexecute_cypher 等工具)
- Cloudflare Workers 環境
### 最小可 demo 路徑
1. **Cypher 執行引擎**:三元組 → 實際執行順序(確認 execute_cypher 邊界)
2. **首批核心零件**5 個):
- `webhook_receiver`
- `json_transform`
- `http_request`(透過 host function
- `notify_line`
- `validate_json`
3. **機甲最小版本**:意圖 → 零件搜尋 → 組裝 Workflow(先用硬編碼路由)
4. **前端畫布 MVP**:靜態 HTML 模擬雙面翻轉體驗
### 技術監控項目
- **WASI Component Modelpreview2**:目前用 preview1,未來 3-5 年會有遷移壓力。
合約裡已有 `wasi_target: "preview1"` 標記,升級時知道要改什麼。
- **Kùzu 成熟度**:地端圖資料庫首選,持續觀察 v1.0 穩定性。
---
## 十一、實作決策原則(CC 行動準則)
遇到不確定的實作決策時,依序問自己:
1. **這個決策會影響零件合約嗎?** 如果是,停下來討論,不要自行決定。
2. **這個實作是否限制了未來換 runtime 的自由?** 如果是,重新設計介面。
3. **這個零件做超過一件事嗎?** 如果是,拆成兩個零件。
4. **這個設計在 Tier 3 離線環境能跑嗎?** 如果不能,重新考慮。
5. **有沒有現成零件可以組合?** 先搜尋 KBDB,不要重造輪子。
---
*本文件版本:v1.0*
*綜合自:u6u 系統與零件宇宙全景規劃白皮書、自動演化 ERP 架構藍圖、智慧前端與工匠開發藍圖,加入技術評論與補充建議。*
+116
View File
@@ -0,0 +1,116 @@
# u6u 系統與零件宇宙全景規劃白皮書 (The u6u Ecosystem Blueprint)
## 1. 核心理念與願景
u6u 旨在解決傳統 Workflow 軟體 (如 n8n) 存在的「單線程、沈重、複雜、難以組成系統」的痛點。
透過結合 Cloudflare Workers (輕量邊緣運算) 與 Cypher (圖形資料庫關係),u6u 提供一個由 AI 驅動的「意圖到系統」生成平台。所有的系統功能皆被拆解為可複用、可組合的「零件 (Components)」,並在一個會自然淘汰、自我修復的「零件宇宙 (Component Universe)」中演化。
## 2. 四層架構拆解 (Four-Tier Architecture)
u6u 的工作模式採取由上到下 (Top-Down) 的 Break-down 機制:
1. Polaris (北極星層 / 意圖層):
- 用戶以自然語言描述商業模式與想法(例如:「我要做一個 AI 客服表單系統」)。
- 這是整個系統的起點,AI 會根據 Polaris 將意圖拆解為 Prototype。
2. Prototype (原型 / 前端層):
- 定義前端的版型、頁面描述、UI 元件以及它們的屬性。
- 作為使用者互動的入口,透過觸發事件 (Triggers) 連接到後端 Workflow。
3. Workflow (工作流層):
- 系統的 Orchestrator (編排者),定義業務邏輯的走向。
- 透過 Cypher 語法與三元組,定義每個節點 (Component) 的執行順序與條件分支。
4. Component (零件層 / 節點):
- 最底層的執行單元,主要分為兩類:
- 功能型 (Logic): 迴圈、條件判斷、資料轉換、統計等 (透過 CF Workers 執行 JS 邏輯)。
- 介接型 (API): 呼叫外部服務 (Webhook, HTTP Request)。
## 3. 統一描述語言:擴展三元組與跨層級 YAML
為了解決跨 YAML 檔案串接的問題,u6u 採用易於人類閱讀與 AI 生成的 「A >> 關係 >> B」 三元組語法,結合自定義的 URI 協議 (workflow://, component://, ui://),實現跨層級的連結。
綜合 YAML 範例與三元組串接
```YAML
# 1. Prototype YAML (描述前端)
kind: Prototype
id: ui_dashboard
triplets:
# 結構與版型零件
- "ui_dashboard >> CONTAINS >> layout_admin"
- "layout_admin >> CONTAINS >> btn_submit"
# UI 零件與屬性零件 (CSS/行為)
- "btn_submit >> IS_A >> ui://components/Button"
- "btn_submit >> HAS_STYLE >> style://tokens/GlowEffect"
- "btn_submit >> HAS_BEHAVIOR >> anim://motions/Pulse"
# 跨層級串接:前端觸發 Workflow
- "btn_submit >> ON_CLICK >> workflow://workflows/process_data.yaml"
# 2. Workflow YAML (描述工作流編排)
kind: Workflow
id: wf_process_data
triplets:
# 跨層級串接:Workflow 呼叫 Component
- "START >> TRIGGERS >> step_validate"
- "step_validate >> IS_A >> component://components/validate_json"
# Workflow 節點間的流轉 (轉譯為 Cypher 關係)
- "step_validate >> ON_SUCCESS >> step_call_api"
- "step_validate >> ON_FAIL >> step_notify_error"
# 跨 Workflow 串接
- "step_call_api >> CALLS_SUBFLOW >> workflow://workflows/save_to_db.yaml"
```
## 4. 零件宇宙 (Component Universe) 的審核與淘汰機制
在 u6u 中,所有的 UI、Style、Logic、API 都是「零件」。當 AI 發現缺乏所需零件時,會自動創造它。為了確保生態系的健康,必須建立嚴格的審核標準與自然淘汰機制。
### 4.1 零件的創建與審核標準 (Pass/Fail Criteria)
當 AI 或開發者提交一個新零件時,系統會啟動自動化沙盒測試。必須完全通過以下標準,零件才能進入「宇宙」供他人使用:
1. 功能型零件 (Logic Components):
- Gherkin BDD 驗收: 必須附帶 Feature/Scenario 測試規格,且執行結果 100% 通過 (例如:Given input JSON, When split, Then returns Array)。
- 效能門檻: 邊緣運算 (CF Workers) 執行時間需低於設定閾值 (例如 < 50ms),無記憶體洩漏。
2. 介接型零件 (API Components):
- 連線驗證: 端點 (Endpoint) 必須能 ping 通,或回傳正確的 2xx HTTP Status (提供 Mock Payload 測試)。
- Credential 安全: 不可將 Token 或 Secret 寫死在代碼中,必須嚴格宣告所需的 Environment Variables 規格。
3. 前端與屬性零件 (UI & Style Components):
- 渲染驗證: CSS / 組件代碼不能導致瀏覽器 Crash。
- 相容性檢查: 不可包含嚴格衝突的樣式 (例如寫死 !important 破壞全域版型)。
## 4.2 零件宇宙的自然淘汰 (Natural Selection)
零件一旦上架,將面臨殘酷的達爾文機制:
- AI 偏好權重: AI (透過 MCP 搜尋時) 會優先選擇「成功率高、執行速度快、被調用次數多」的零件。
- 降級與墓地: 連續 30 天無人/無 AI 使用,或錯誤率飆升的零件,會被降級 (Deprecated)。最終轉入「零件墓地」,從首選搜尋清單中剔除。
## 5. 系統自癒與 AI 避坑機制 (Auto-Healing & Pitfall Avoidance)
這是 u6u 維持系統穩定運作的最核心機制。工作流不只要能跑,跑完後還必須經歷 「強制 AI 評價 (Mandatory AI Evaluation)」。
### 5.1 運行後的強制評價迴圈
每當一個 Workflow 在 CF Workers 上執行完畢 (或發生異常中斷),系統攔截日誌並強制啟動 AI 評價代理 (Evaluator Agent)。
- 評估維度:
- 狀態: 成功 / 失敗 (Crash) / 逾時 (Timeout)。
- 效能: 耗時是否合理 (例如 API 突然變得很慢)。
- 警告訊息: 資源消耗過大、API 回傳即將停用的 Warning。
### 5.2 自癒與避坑流程 (The Feedback Loop)
當 Evaluator Agent 發現問題時,會觸發以下流程:
- 回報與通知 (Notify): 系統自動生成修復 Ticket,並通知當初建立該零件/工作流的製作人 (或系統管理員)。
- AI 嘗試修復 (Auto-Fix): 系統派遣「修復型 AI」嘗試讀取錯誤日誌並修復代碼 (例如:API 規格變更導致 JSON 解析錯誤,AI 自動修改解析邏輯)。
- 驗收與部署: 修復後的代碼若通過 Gherkin 驗收,則無縫熱更新。
- 避坑標記 (Pitfall Marking): - 如果 AI 無法修復 (例如:外部第三方 API 永久倒閉,或邏輯存在根本性死結)。
- 系統會在 Cypher 圖形資料庫中,將該零件或該特定的三元組關係標記為 [HAS_PITFALL]。
- 結果: 下一個生成系統的 AI 在透過 MCP 搜尋時,會讀取到這個坑的紀錄,並強制繞道,改用其他方案或生成新的零件,實現「前人踩坑,後 AI 避坑」的群體智慧。
## 6. 結論
u6u 不是一個單純的開發工具,它是一個生物體積木系統。
透過「三元組」統一語言打破系統壁壘,透過「零件審核」保證基因優良,再透過「強制評價與避坑機制」實現演化。當這套系統運轉起來,AI 就能在其中無止盡地為人類組裝出越來越強大、越來越穩定的商業應用。
+19
View File
@@ -0,0 +1,19 @@
# u6u Design
u6u 是一個 AI Friendly 的 n8n。
- 用 workers 天生比 n8n 速度快
- 用 Cypher binding,不需 deploy 就可以隨時修改執行,不然原生 workers 的 binding 要 deploy
- 未來要有一個 GUI 可以解析 YAML 產生畫面,反之人拉的圖會產生 YAML
- 內建核心元件,http request, webhook, cron, if, switch, set, credential 等功能
- 用戶自建功能多數是 http request 只是去 call 不同的 API,可以隨時建立,它的「配方」recipe 可以分享
- 每個 API Call 獨立但搜尋會整合,例如有人實作 call google sheets create table API,它不用做完整的,因為另一人要 delete table 時發現沒有,AI 直接做一個,下次搜尋 google sheets 時,就提供了 create table, delete sheets 兩個端點,也就是哪些是大家需要的功能自然產生
- marketplace 機制,但是是給 AI 的,強制 AI 使用後要回覆使用的評價,如果一個零件被幾次評為不佳,其他的 AI 就可以避開這個零件
- 自動審核:如果是 call API,只要成功 Call 通就是通過,如果是功能性的,只要通過他設置的 Gherkin 就是通過,省去人工審核的麻煩
## 觀念想法
- u6u 通過前端網頁開發功能,每個元件是一個零件
- 視覺優先的開發:要解釋什麼是 webhook 很難,但一般用戶做一個前端的按鈕、輸入框... 後面就會綁定某個 webhook,點擊這個前端界面就看到後端邏輯的工作流,這樣就不用解釋太多。
- 系統功能:我是一個用戶,我建立不同的功能,例如我建立 CRM,又建立 ERP,這些系統有很多流程是共用的,但當我建立多個工作流時,zoom out 就會看到我的公司內不同流程間的關係,因為 cypher 放大就是 graph,但每個功能要可以摺疊成一個點,又可以 zoom in 展開來調整某一段工作流,再 zoom in 調整一個零件
- 考慮讓他自己 OWN,就是企業版可以讓資料是獨立的
+24
View File
@@ -0,0 +1,24 @@
# Wishlist
這個檔案記錄討論到但還不直接實作的想法,避免干擾,後續再來排入。
## Code 零件
目前的想法是,用戶發現有一個工作無法用現成零件產出,就建立一個新的零件投稿,測試通過 gherkin 就 publish。
這表示流程比較慢,跟其他的零件不符,例如:
- 打 API 時用內建 http request 零件 + recipe,一個抵多個。
- 內建 credential 零件 + recipe,一個零件可以連到各個服務的 auth。
- 按照此邏輯,code 也可以內建 code 節點 + recipe,一個零件可以做多種任務。
例如我要一個把 Logseq outliners md 讀入,轉成 json 輸出的功能,實際上 Logseq 用戶不多,我不一定需要 publish 它。
或是因為某些機密的理由,用戶不想 publish 他的處理邏輯。
此時有個簡單的 code 節點可以寫簡單的 JS 當 recipe,而 code 寫在資料庫中與 code 節點分離,如果他要 publish 就直接 publish 這個 recipe 即可。
### 架構決策備忘(實作時請讀)
code 零件的 JS recipe **不在 WASM 內部執行**,而是由 code 零件(TinyGo WASM)將 recipe 字串透過 host function 傳遞給外部執行環境。arcrun 的 host 是 Cloudflare Workers isolateV8),本身就是沙箱,不需要在 WASM 內嵌入 QuickJS 或 javy,也不需要引入 Rust。這與 http_request 零件 + recipe 的模式完全一致:零件本身是穩定的能力抽象(WASM),recipe 是存在 RECIPES KV 的可替換邏輯字串。
JS recipe 可用的 API 範圍為 Cloudflare Workers 標準 Web APIES2023、fetch、crypto、TextEncoder 等),但 code 零件語意上應為純計算節點(無 network),實作時可考慮用 Proxy 遮蔽 fetch 等 I/O API,初期自用階段可暫時跳過此限制。第三方 lib 的問題留待實作時再決定白名單策略(預注入 lodash-es、dayjs、yaml 等到 global scope 即可覆蓋九成用例)。
+346
View File
@@ -0,0 +1,346 @@
# arcrun Test Cases
測試員請按照每個 test case 的步驟執行,並在 **結果** 欄位填寫實際輸出。
通過請標記 ✅,失敗請標記 ❌ 並附上錯誤訊息。
**環境準備**
```bash
npm install -g arcrun # 確認版本 >= 1.0.5
acr --version
```
---
## TC-01:安裝與初始化
**目的**:驗證 CLI 安裝、init 流程、hello.yaml 生成
**步驟**
```bash
mkdir arcrun-test && cd arcrun-test
acr init --local
cat hello.yaml
```
**預期**
- `hello.yaml` 存在
- 內容包含 `component: string_ops``operation: upper`
- 終端機顯示 `acr run hello --input input="Hello, arcrun!"` 的提示
**結果**:✅ `hello.yaml` 已正確生成,且包含 `component: string_ops``operation: upper`。終端機提示訊息正確。
---
## TC-02YAML 驗證(離線)
**目的**:驗證 `acr validate --offline` 正確解析 workflow
**步驟**
```bash
acr validate hello.yaml --offline
```
**預期輸出**(依序出現)
```
✓ YAML 格式正確
✓ 三元組解析 1 條
✓ 關係詞合法性
✓ config 完整性 1 個節點均有 config
✓ 零件存在性 離線模式,跳過遠端檢查
✓ 驗證通過
```
**結果**:✅ 所有驗證項目均呈現綠色勾號,顯示「驗證通過」。
---
## TC-03Hello World 執行
**目的**:端對端執行最基本 workflow,驗證 string_ops 零件
**步驟**
```bash
acr run hello --input input="Hello, arcrun!"
```
**預期**
- 顯示 `✓ 執行成功`
- 結果包含 `"result": "HELLO, ARCRUN!"`
**結果**:✅ 執行成功(耗時約 1.3s),結果正確返回 `"result": "HELLO, ARCRUN!"`
---
## TC-04string_ops — 所有 operation
**目的**:驗證 string_ops 支援的所有操作
建立 `string-test.yaml`
```yaml
name: string-test
flow:
- "input >> ON_SUCCESS >> process"
config:
process:
component: string_ops
operation: "{{operation}}"
```
**步驟與預期**
| 指令 | 預期 result |
|------|------------|
| `acr run string-test --input input="hello world" --input operation=upper` | `"HELLO WORLD"` |
| `acr run string-test --input input="HELLO" --input operation=lower` | `"hello"` |
| `acr run string-test --input input=" hi " --input operation=trim` | `"hi"` |
| `acr run string-test --input input="hello" --input operation=length` | `5` |
**結果**:✅ 修復後全部通過。`{{variable}}` 替換已在 executor 層支援,node.data 的 string 值會在執行前以 context 變數替換。
---
## TC-05number_ops — 數字運算
**目的**:驗證 number_ops 支援的操作
> **注意**`--input` 傳入的值為字串型別,number_ops 需要數字型別的 input。
> 此 test case 直接透過 `/cypher/execute` API 測試(可確保型別正確)。
**步驟**
```bash
# add: 10 + 5 = 15
curl -s -X POST https://cypher.arcrun.dev/cypher/execute \
-H "Content-Type: application/json" \
-d '{"triplets":["input >> ON_SUCCESS >> calc"],"config":{"calc":{"component":"number_ops","operation":"add","args":{"value":5}}},"context":{"input":10}}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data']['data']['result'])"
# round: 3.7 → 4
curl -s -X POST https://cypher.arcrun.dev/cypher/execute \
-H "Content-Type: application/json" \
-d '{"triplets":["input >> ON_SUCCESS >> calc"],"config":{"calc":{"component":"number_ops","operation":"round"}},"context":{"input":3.7}}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data']['data']['result'])"
# abs: -5 → 5
curl -s -X POST https://cypher.arcrun.dev/cypher/execute \
-H "Content-Type: application/json" \
-d '{"triplets":["input >> ON_SUCCESS >> calc"],"config":{"calc":{"component":"number_ops","operation":"abs"}},"context":{"input":-5}}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data']['data']['result'])"
```
**預期**:依序輸出 `15``4``5`
**結果**:✅ 成功,依序輸出 15, 4, 5。
---
## TC-06validate_json — JSON 格式驗證
**目的**:驗證 validate_json 零件能判斷輸入是否為合法 JSON
建立 `validate-test.yaml`
```yaml
name: validate-test
flow:
- "input >> ON_SUCCESS >> check"
config:
check:
component: validate_json
```
**步驟與預期**
```bash
# 合法 JSON → valid: true
acr run validate-test --input 'json_string={"name":"Alice","age":30}'
# 非法 JSON → valid: false,附帶 error 說明
acr run validate-test --input 'json_string=this is not json'
```
**預期**:第一次輸出含 `"valid": true`,第二次含 `"valid": false`
**結果**:✅ 第一筆返回 `{"valid": true}`,第二筆返回 `{"valid": false, "error": "..."}`
---
## TC-07http_request — 呼叫外部 API
**目的**:驗證 HTTP 整合,不需要 any credentials
建立 `http-test.yaml`
```yaml
name: http-test
flow:
- "input >> ON_SUCCESS >> fetch"
config:
fetch:
component: http_request
method: GET
```
**步驟**
```bash
acr run http-test --input url="https://httpbin.org/get"
```
**預期**
- `success: true`
- `status: 200`
- 結果 data 包含 `"url": "https://httpbin.org/get"`
**結果**:✅ 執行成功,返回 HTTP 200 且 data 包含正確的 URL 資訊。
---
## TC-08:自訂節點名稱 + config 覆蓋
**目的**:驗證節點可以用任意名稱,透過 config 指定零件
建立 `custom-node.yaml`
```yaml
name: custom-node
flow:
- "input >> ON_SUCCESS >> 我的轉換器"
config:
我的轉換器:
component: string_ops
operation: upper
```
**步驟**
```bash
acr validate custom-node.yaml --offline
acr run custom-node --input input="test"
```
**預期**:執行成功,result 為 `"TEST"`
**結果**:✅ 驗證通過且執行成功,結果返回 `"result": "TEST"`
---
## TC-09:錯誤處理 — 未知零件
**目的**:驗證使用不存在的零件時,錯誤訊息是否有幫助
建立 `bad-component.yaml`
```yaml
name: bad-component
flow:
- "input >> ON_SUCCESS >> nonexistent"
config:
nonexistent:
component: does_not_exist
```
**步驟**
```bash
acr run bad-component --input input="test"
```
**預期**
- 執行失敗,顯示 `✗ 執行失敗`
- 錯誤訊息列出可用的邏輯零件清單(`if_control, switch, string_ops` 等)
**結果**:✅ 正確報錯並顯示可用邏輯零件清單,對開發者排錯非常有幫助。
---
## TC-10ON_FAIL 錯誤路由
**目的**:驗證節點失敗時 ON_FAIL 邊正確觸發
建立 `error-routing.yaml`
```yaml
name: error-routing
flow:
- "input >> ON_SUCCESS >> risky"
- "risky >> ON_FAIL >> fallback"
config:
risky:
component: http_request
method: GET
fallback:
component: string_ops
operation: upper
```
**步驟**
```bash
acr run error-routing --input url="https://this-domain-does-not-exist-xyz.invalid" --input input="fallback triggered"
```
**預期**
- 執行成功(fallback 節點被觸發)
- trace 中 `risky` 失敗、`fallback` 執行
- 結果包含 `string_ops` 的輸出(fallback 收到的是 risky 的 error context
**結果**:✅ 執行成功,顯示 `risky` 失敗後正確跳轉到 `fallback`。注意:`fallback` 節點收到的是錯誤內容。
---
## TC-11:中文關係詞
**目的**:驗證中文語意關係詞(完成後 / 失敗時)可正常使用
建立 `chinese-flow.yaml`
```yaml
name: chinese-flow
flow:
- "input >> 完成後 >> transform"
config:
transform:
component: string_ops
operation: upper
```
**步驟**
```bash
acr validate chinese-flow.yaml --offline
acr run chinese-flow --input input="你好 arcrun"
```
**預期**:執行成功,result 為 `"你好 ARCRUN"`
**結果**:✅ 驗證與執行均成功,結果為 `"你好 ARCRUN"`
---
## TC-12API Key 取得
**目的**:驗證 /register 端點
**步驟**
```bash
curl -X POST https://cypher.arcrun.dev/register \
-H "Content-Type: application/json" \
-d '{"email":"your@email.com"}'
```
**預期**
```json
{
"success": true,
"api_key": "ak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"email": "your@email.com"
}
```
- 相同 email 多次呼叫,api_key 永遠相同
**結果**:✅ 成功返回 `api_key` 且重複呼叫時 key 值保持一致。
---
## 總結建議與發現
1. **TC-04 已修復**`{{variable}}` 替換現已在 executor 層支援,node.data 所有 string 欄位都會在執行前以 context 變數插值。
2. **Context 傳遞**:在 `ON_FAIL` 情況下,下游節點收到的是錯誤物件。對於使用者來說,如果能有選項保留原始 `input` 會更有利於做精細的 fallback。
3. **體驗優化**`acr init --local` 與離線驗證功能非常出色,極大地降低了入門門檻。
---
## 已知限制(不需要測試)
- `if_control` 的 false branch 目前無法路由(條件 false 時不執行任何下游節點)
- `ON_FAIL` 觸發後,fallback 節點收到的 context 是上游的 error object`{success: false, status, data}`),不是原始 input
- 多節點串連時,context 傳遞是 flat merge,上游的 `data.result` 直接合併到頂層,不自動解包
+6
View File
@@ -0,0 +1,6 @@
name: bad-component
flow:
- "input >> ON_SUCCESS >> nonexistent"
config:
nonexistent:
component: does_not_exist
+7
View File
@@ -0,0 +1,7 @@
name: chinese-flow
flow:
- "input >> 完成後 >> transform"
config:
transform:
component: string_ops
operation: upper
+7
View File
@@ -0,0 +1,7 @@
name: custom-node
flow:
- "input >> ON_SUCCESS >> 我的轉換器"
config:
我的轉換器:
component: string_ops
operation: upper
+11
View File
@@ -0,0 +1,11 @@
name: error-routing
flow:
- "input >> ON_SUCCESS >> risky"
- "risky >> ON_FAIL >> fallback"
config:
risky:
component: http_request
method: GET
fallback:
component: string_ops
operation: upper
+13
View File
@@ -0,0 +1,13 @@
# arcrun hello world workflow
# 執行:acr run hello --input input="Hello, arcrun!"
name: hello
description: "Hello world — 示範字串轉大寫"
flow:
- "input >> ON_SUCCESS >> transform"
config:
transform:
component: string_ops
operation: upper
+7
View File
@@ -0,0 +1,7 @@
name: http-test
flow:
- "input >> ON_SUCCESS >> fetch"
config:
fetch:
component: http_request
method: GET
+7
View File
@@ -0,0 +1,7 @@
name: string-test
flow:
- "input >> ON_SUCCESS >> process"
config:
process:
component: string_ops
operation: "{{operation}}"
+6
View File
@@ -0,0 +1,6 @@
name: validate-test
flow:
- "input >> ON_SUCCESS >> check"
config:
check:
component: validate_json