diff --git a/.agents/specs/arcrun/arcrun.md b/.agents/specs/arcrun/arcrun.md new file mode 100644 index 0000000..66aaf08 --- /dev/null +++ b/.agents/specs/arcrun/arcrun.md @@ -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 token(gmail / google_sheets 等) +5. `acr push` 部署 workflow,取得 Webhook URL +6. 網頁 POST /webhooks/named/{name}/trigger,結果存 Google Sheets + +--- + +## 二、場景各步驟驗證狀態 + +### Step 1:acr init → api_key +- [x] `acr init` Standard 模式完成,api_key 存入 `~/.arcrun/config.yaml` +- [x] 已驗證:`mode: standard, api_key: ak_...` 正確 + +### Step 2:acr 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 3:acr 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 4:acr 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 5:acr 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_key,acr init 自動存入 config(CLI 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 KV(CRUD)、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_key;acr init 自動儲存;creds push 不需手動設環境變數 | +| 1.0.8 | acr push → webhooks/named;config 套入 graph;acr 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 | 初始發布 | diff --git a/.agents/specs/arcrun/auth-recipe.md b/.agents/specs/arcrun/auth-recipe.md new file mode 100644 index 0000000..be10f66 --- /dev/null +++ b/.agents/specs/arcrun/auth-recipe.md @@ -0,0 +1,254 @@ +# Auth Recipe System — SDD + +> 文件類型:SDD(Software 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; // "Authorization": "Bearer {{secret.token}}" + query?: Record; + body?: Record; + }; + + 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_account(Google 家族) + +``` +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` — 要合併進 fetch headers | +| `_auth_query` | `Record` — 要附加到 URL query string | +| `_auth_body` | `Record` — 要合併進 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 KV(2026-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 publish(arcrun@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 邏輯 diff --git a/.agents/specs/arcrun/credential-primitives-wasm/design.md b/.agents/specs/arcrun/credential-primitives-wasm/design.md new file mode 100644 index 0000000..1e45f85 --- /dev/null +++ b/.agents/specs/arcrun/credential-primitives-wasm/design.md @@ -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 primitive(crypto.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) + +``` +stdin(Worker → 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_account)crypto_sign_rs256(jwt, pkcs8) + http_request 換 token + 5. 展開 recipe.inject 的 {{secret.X}} / {{runtime.X}} 模板 + +stdout(WASM → 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 signing(RS256:PEM→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 Functions(WASM ↔ 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 內完成 diff --git a/.agents/specs/arcrun/credential-primitives-wasm/tasks.md b/.agents/specs/arcrun/credential-primitives-wasm/tasks.md new file mode 100644 index 0000000..54b9b7d --- /dev/null +++ b/.agents/specs/arcrun/credential-primitives-wasm/tasks.md @@ -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_id,6 個 API 零件 + 4 個 auth primitive)+ `wasmWorkerUrl()` URL 慣例輔助函數;解析鏈新增為第 8 層(放在 `BUILTIN_API_RECIPES` fallback 之後,避免 Phase 3 尚未完成時 API 零件 Worker 未部署造成 404;Phase 3 刪除 `BUILTIN_API_RECIPES` 後,API 零件會自然落到此層)。auth primitive 從此層進入。`tsc --noEmit` 通過。 + +--- + +## Phase 1:auth_static_key WASM(優先,涵蓋 80% 服務) + +方案 B:WASM 自行讀 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 ` +- [ ] 1.8 端對端測試:twilio recipe(Basic Auth)→ 成功注入 +- [ ] 1.9 **刪除 `credential-injector.ts` 整檔**(`decryptCredential` / `decryptSecrets` / `interpolateTemplate` / `BUILTIN_CREDENTIALS_MAP` 全刪) + +--- + +## Phase 2:auth_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(純 Go,base64 decode + 去 header/footer) + - 組 JWT header + payload(base64url),呼叫 `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 runner(HTTP 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 4:auth_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.6(host functions)+ 0.7(WASM 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 diff --git a/.agents/specs/arcrun/landing-page.md b/.agents/specs/arcrun/landing-page.md new file mode 100644 index 0000000..0ad27c8 --- /dev/null +++ b/.agents/specs/arcrun/landing-page.md @@ -0,0 +1,325 @@ +# arcrun.dev Landing Page — SDD + +> **目標**:給工程師一個門面,可以取得 API Key、管理 Key、探索 API(Swagger),同時藉此獲得會員 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.js(App 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 Pages(Next.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 session,key = `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)**: +- Google(google_drive recipe 的 OAuth App,或另建 arcrun-login Google App) +- GitHub(github 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 callback(Pages Function) +├── /dashboard API Key 管理(需登入) +├── /api Swagger UI(嵌入 swagger.json) +└── /integrations 服務目錄(靜態,20 個 recipe) +``` + +--- + +## 3. 登入 / OAuth 流程 + +### 3.1 流程圖 + +``` +用戶點「Google 登入」 + → GET /auth/google/start(Worker 端) + → redirect 到 Google OAuth(state = random, 存 SESSIONS_KV sess:state:{state} = {provider, redirect_back}) + → 用戶同意 + → GET /auth/callback?code=...&state=...(Worker 端) + → 驗 state + → 用 code 換 access_token(POST google token endpoint) + → 用 token 取 userinfo(GET google userinfo) + → upsert USERS_KV user:{provider}:{provider_id} = {email, display_name, api_key, ...} + → 若新用戶:呼叫現有 /register?email=... 取得 arcrun API Key + → 建立 session:SESSIONS_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 / HTTP(n8n 用戶) + +[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) + +- 嵌入 `` JS(CDN) +- `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 = "" + +[[kv_namespaces]] +binding = "SESSIONS_KV" +id = "455d0505c7534883a4d4985ab8295857" +``` + +--- + +## 7. 環境變數 / Secrets + +### cypher-executor(Worker 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_id(read:user + user:email scope) +GITHUB_CLIENT_SECRET GitHub OAuth App client_secret +SESSION_SECRET 隨機 32 bytes,用於 HMAC session ID(或直接用 UUID) +``` + +### landing(Pages Environment Variables) + +``` +NEXT_PUBLIC_API_BASE https://cypher.arcrun.dev +``` + +--- + +## 8. 實作步驟(Checklist) + +### Phase 1:cypher-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 2:Next.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 Key,Rotate / 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=` 覆寫 | diff --git a/.agents/specs/arcrun/sdk-and-website/design.md b/.agents/specs/arcrun/sdk-and-website/design.md new file mode 100644 index 0000000..c645ac1 --- /dev/null +++ b/.agents/specs/arcrun/sdk-and-website/design.md @@ -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 + +### 系統關係圖 + +``` +使用者程式碼 + ├── CLI(acr) → cypher.arcrun.dev(HTTP API) + ├── Python SDK(arcrun) → cypher.arcrun.dev(HTTP API) + └── JS SDK(arcrun / @arcrun/sdk) → cypher.arcrun.dev(HTTP API) + +arcrun.dev 網站(Next.js / Cloudflare Pages) + ├── /login → /auth/google/start, /auth/github/start(cypher.arcrun.dev) + ├── /dashboard → /me, /me/api-key/rotate(cypher.arcrun.dev) + ├── /integrations → /auth-recipes(cypher.arcrun.dev) + └── /components → /recipes + 靜態零件清單(embedded) + +cypher.arcrun.dev(Cloudflare 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 ← CredentialsClient(push/list/delete) + ├── auth.py ← AuthClient(setup/bind/get_token/list_services) + └── workflows.py ← WorkflowClient(run/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 TBD(arcrun vs @arcrun/sdk),tsup build +├── tsconfig.json ← ES2020, NodeNext +└── src/ + ├── index.ts ← export class Arcrun + ├── crypto.ts ← Web Crypto API AES-GCM encrypt(client 端) + ├── creds.ts ← CredentialsClient(push/list/delete) + ├── auth.ts ← AuthClient(setup/bind/getToken/listServices) + └── workflows.ts ← WorkflowClient(run/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 Demo(Python/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_schema(required / optional 欄位) + │ ├── output_schema + │ ├── credentials_required(if any) + │ └── config_example(YAML 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 1:Python SDK 重建 + 測試 + 1.1 重建 arcrun/python-sdk/(按本 SDD 的結構) + 1.2 修正上次的 bug:recipe 回應 wrapper、inject key "header" vs "headers"、secret key mapping + 1.3 對 cypher.arcrun.dev live 測試全部 API + 1.4 本地安裝測試(pip install -e .) + +Phase 2:JS SDK 重建 + 測試 + 2.1 重建 arcrun/js-sdk/(按本 SDD 的結構) + 2.2 同步修正 Python SDK 發現的所有 recipe 格式問題 + 2.3 build(tsup)+ 本地測試 + +Phase 3:arcrun.dev 網站補完 + 3.1 新增 /components 頁面 + 3.2 更新首頁 code demo(三種使用方式) + 3.3 OAuth secrets 設定(需 richblack 操作 GCP / GitHub) + 3.4 登入流程驗證 + +Phase 4:GitHub README + 發布 + 4.1 更新 arcrun/README.md — 三種 Quick Start + 4.2 pip publish(arcrun) + 4.3 npm publish(TBD 套件名) + 4.4 最終驗證:從零開始 pip install / npm install / 打 API +``` diff --git a/.agents/specs/arcrun/sdk-and-website/requirements.md b/.agents/specs/arcrun/sdk-and-website/requirements.md new file mode 100644 index 0000000..d4eff9f --- /dev/null +++ b/.agents/specs/arcrun/sdk-and-website/requirements.md @@ -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 Worker(workflow 執行、credential 管理、auth recipe、webhook) +- `u6u-core/credentials`:credential Worker(AES-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 token(escape hatch,給官方 SDK 用) +- **workflows.run()**:觸發已部署的 workflow +- **workflows.push()**:上傳 workflow 定義 +- **Recipe**:描述「如何對某服務認證」的 YAML 設定,存在 RECIPES KV + +--- + +## Requirements + +### Requirement 1:Python 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=)` — 建構 client,api_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 client(async 版使用 `httpx.AsyncClient`)。 +6. THE SDK 位置 SHALL 為 `arcrun/python-sdk/`,build 系統用 `hatchling`(`pyproject.toml`)。 + +--- + +### Requirement 2:JavaScript/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 對等的 API(camelCase 版): + - `new Arcrun({ apiKey?, baseUrl? })` — 讀 `process.env.ARCRUN_API_KEY` + - `client.health()` — 回傳 `Promise` + - `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 3:arcrun.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 包含三個 tab:Python、JavaScript、HTTP/curl,展示三種使用方式。 + +--- + +### Requirement 4:GitHub 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 UI(API 文件)。 + +--- + +### Requirement 5:SDK 發布 + +**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()` ✅ diff --git a/.agents/specs/arcrun/sdk-and-website/tasks.md b/.agents/specs/arcrun/sdk-and-website/tasks.md new file mode 100644 index 0000000..663e1d6 --- /dev/null +++ b/.agents/specs/arcrun/sdk-and-website/tasks.md @@ -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 1:Python 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)、list(GET /credentials)、delete + - [ ] 1.5 `arcrun/auth.py`:AuthClient — setup(fetch recipe → match secrets → encrypt → push)、bind(fetch recipe → resolve headers from cache → return AuthenticatedClient)、get_token、list_services + - [ ] 1.6 `arcrun/workflows.py`:WorkflowClient — run(POST /webhooks/named/{name}/trigger)、push(POST /webhooks/named)、list(GET /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 2:JS/TS SDK + +- [ ] 4. 建立 `arcrun/js-sdk/` 目錄 + - [ ] 4.1 `package.json`:name TBD(arcrun vs @arcrun/sdk),deps=devDeps only(tsup, 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 3:arcrun.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 4:README + 發布 + +- [ ] 11. 更新 `arcrun/README.md` + - [ ] 11.1 三種 Quick Start(CLI / 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 diff --git a/.agents/specs/u6u-core-mvp/design.md b/.agents/specs/u6u-core-mvp/design.md new file mode 100644 index 0000000..ede7b37 --- /dev/null +++ b/.agents/specs/u6u-core-mvp/design.md @@ -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.wasm(stdin = 含 access_token 的完整 input) + ↓ +回傳結果 +``` + +### credential-injector 實作位置 + +放在 `cypher-executor/src/actions/credential-injector.ts`(新增),由 `graph-executor.ts` 在每個節點執行前呼叫。 + +```typescript +async function injectCredentials( + componentId: string, + input: Record, + env: Env +): Promise> { + 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_required(gmail 範例) + +```yaml +credentials_required: + - key: gmail_token + type: google_oauth + description: "Google OAuth access token(gmail.send scope)" + inject_as: access_token +``` + +### config_example(gmail 範例) + +```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 模式架構(用戶自己的 KV,arcrun.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-worker(api.arcrun.dev) │ +│ POST /register → { api_key, tenant_id } │ +│ ACCOUNTS_KV: { tenant_id, cf_api_token, api_key_hash } │ +│ ※ 不儲存用戶 credential 或 workflow 內容 │ +│ │ +│ cypher-executor(cypher.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 registry(registry.arcrun.dev) │ +│ GET /components → 零件清單 + 統計 + author + visibility │ +│ POST /submit → 接收零件,沙盒驗收後設 author_only │ +│ POST /analytics/record → 執行統計(非同步) │ +└──────────────────────────────────────────────────────────────┘ + + ↕ CF KV API(用戶的 cf_api_token,KV 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 KV,arcrun 不會儲存它們。 +``` + +### 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_rate(DESC) +``` + +--- + +## 零件貢獻流程設計 + +### `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: + source: + wasm: + 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_required(4 個零件) + 2.3 補充 config_example(全部 21 個) + 2.4 驗證 main.go required 與 contract 一致 + +Phase 3:credential 注入(Requirement 3) + 3.1 新增 credential-injector.ts + 3.2 整合進 graph-executor 節點執行前 + 3.3 測試 gmail 零件端對端(credentials.yaml → push → run) + +Phase 4:CLI(Requirement 4) + 4.1 acr init(--hosted / --self-hosted 分支) + 4.2 acr creds push(Hosted 走 API,Self-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 6:Hosted SaaS(Requirement 6) + 6.1 建立 auth-worker(api.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 指令 +``` diff --git a/.agents/specs/u6u-core-mvp/requirements.md b/.agents/specs/u6u-core-mvp/requirements.md new file mode 100644 index 0000000..4753155 --- /dev/null +++ b/.agents/specs/u6u-core-mvp/requirements.md @@ -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 2:component.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 3:workflow 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 4:CLI(arcrun,指令 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 ` 指令 SHALL 解析 `flow:` 三元組,轉換成執行圖,連同 `config:` 存入 `WEBHOOKS KV`,並輸出 webhook URL。 +5. THE `acr run [--input key=value...]` 指令 SHALL 觸發 cypher-executor 執行指定 workflow,顯示各節點執行結果;失敗時顯示具體節點、原因與修復步驟。 +6. THE `acr validate ` 指令 SHALL 在執行前驗證:YAML 格式、關係詞合法性(無 PIPE)、所有節點在 config 中有對應、所有零件存在於 WASM_BUCKET、所有 credentials 已上傳至 CREDENTIALS_KV。 +7. THE `acr parts` 指令 SHALL 列出所有可用零件(按類型分組),顯示每個零件的必填欄位與所需 credential。 +8. THE `acr parts scaffold ` 指令 SHALL 從 contract 的 `config_example` 輸出可直接貼入 workflow.yaml 的 config 範本,以及對應的 credentials.yaml 欄位範本。 +9. THE `acr list` 指令 SHALL 列出 WEBHOOKS KV 中所有已上傳的 workflow,顯示名稱與更新時間。 +10. THE `acr logs ` 指令 SHALL 顯示最近執行記錄,包含時間、成功/失敗狀態、執行時間,失敗時顯示失敗節點與原因。 + +--- + +### Requirement 5:README 與開源發布準備 + +**User Story:** As a 外部開發者, I want 看到清楚的 README,5 分鐘內能完成部署, 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 6:Standard 模式 — 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 ` 指令 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 ` 指令 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,讓貢獻者清楚知道零件的可用範圍。 diff --git a/.agents/specs/u6u-core-mvp/tasks.md b/.agents/specs/u6u-core-mvp/tasks.md new file mode 100644 index 0000000..477a015 --- /dev/null +++ b/.agents/specs/u6u-core-mvp/tasks.md @@ -0,0 +1,206 @@ +# Implementation Plan: arcrun MVP + +## Overview + +依照 Design 的七個 Phase 實作。原則:最小異動,不重寫現有邏輯,只 cherry-pick + carve-out + supplement。 +所有 Phase 1–3 工作在 `matrix` repo 對應目錄驗證後再搬到新 repo。 + +**PR #2(claude/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 Bindings(KBDB、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 3:Credential 注入整合 + +- [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 4:CLI 開發 + +- [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 加密 fallback(base64)已移除,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 6:Standard 模式 — 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 mock,Phase 7 補充 +- cold-start 測量(sandbox 步驟 b)為 Phase 0 mock,Phase 2 補充 +- CF API Token 永遠不離開用戶本機,arcrun.dev 只收 email + account_id + kv_namespace_id diff --git a/.agents/specs/u6u-platform-evolution/design.md b/.agents/specs/u6u-platform-evolution/design.md new file mode 100644 index 0000000..9ca32b7 --- /dev/null +++ b/.agents/specs/u6u-platform-evolution/design.md @@ -0,0 +1,822 @@ +# Design Document: u6u Platform Evolution + +## Overview + +u6u 平台演進的核心目標是將現有的「HTTP endpoint 零件 + 單一 Cloudflare 部署」架構,演進為「WASM 零件模型 + 三層物理部署 + 雙面翻轉畫布」的完整平台。 + +設計的最高原則是 **Dogfooding**:每一層都是下一層的第一個用戶。底層先建立最小可運行的能力,再用自己的方式往上蓋。這確保每個設計決策都被真實使用場景驗證,而非紙上談兵。 + +### Bootstrap 順序(不可跳過) + +``` +Phase 0:最小 WASM 執行核心 + → Component Dispatcher 能在 CF Workers 執行一個 .wasm(stdin/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 2:Cypher 語意擴展 + 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
GraphExecutor] + CD[Component Dispatcher
路由層] + CR[Component Registry
KBDB HTTP API] + KBDB[(KBDB
blocks/templates/slots
+ Vectorize)] + R2[(R2
.wasm 二進位)] + CE --> CD + CD --> CR + CR --> KBDB + CR --> R2 + end + + subgraph "Tier 2 — workerd self-hosted(企業地端)" + T2D[Tier 2 Dispatcher
同 wasi-shim,不同部署] + T2R[本地 Registry 快取] + T2D --> T2R + end + + subgraph "Tier 3 — 邊緣載具" + T3E[Go 排程引擎] + Wazero[Wazero Runtime] + SQLite[(SQLite
本地 KBDB)] + DTN[DTN 佇列] + T3E --> Wazero + T3E --> SQLite + T3E --> DTN + end + + subgraph "前端" + Canvas[雙面翻轉畫布
React 19 + Web Components] + WC[Web Components 零件庫
u6u-btn / u6u-card / ...] + Canvas --> WC + end + + CD -->|WASM 執行| Tier1WASM[.wasm 執行] + CD -->|Cypher binding| ExtSvc[外部服務
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
取得合約} + B --> C{component_type?} + C -->|wasm| E{當前 Tier?} + C -->|service_binding| SB{有 CF Service Binding?} + SB -->|是| SBExec[CF Service Binding 執行
需 deploy,效能最佳] + SB -->|否| SBErr[回傳錯誤:binding 未宣告] + E -->|Tier 1 / Tier 2| I[workerd WASM
WebAssembly.instantiate
+ WASI shim(兩者相同)] + E -->|Tier 3| L[Wazero IPC
stdin/stdout,完全離線] + I --> RC{runtime_compat
包含 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` 二進位存 R2,KBDB 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` | 相容 runtime(JSON 陣列) | `["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 Schema(JSON 字串) | `{"type":"object",...}` | +| `output_schema` | JSON Schema(JSON 字串) | `{"type":"object",...}` | +| `gherkin_tests` | 測試案例(JSON 字串) | `[{"scenario":"..."}]` | +| `wasm_r2_key` | R2 物件鍵(wasm 模式) | `components/validate_json/v1.wasm` | +| `service_binding_key` | CF binding key(service_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 回傳 ENOSYS(76) + 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, kvNamespace); + result = { ...(result as Record), ...subResult.data as Record }; + break; +} +``` + +### 4. Web Components 零件庫(`u6u-core/web-components/`) + +Web Components 以原生 Custom Elements API 實作,不依賴任何框架。 + +**`` 介面:** + +```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; + }; +} +``` + +**`` 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 = {}; + 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 Block(KBDB 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 Block(KBDB 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 input,WASM 執行路徑的輸出 SHALL 與 HTTP 執行路徑的輸出語意等效。 + +**Validates: Requirements 3.1, 3.5, 3.6** + +--- + +### Property 5: Dispatcher 錯誤結構完整性 + +*For any* (component_id, tier) 組合,若該零件的 `runtime_compat` 不包含當前 tier,Component_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 設定於 `` 的 `workflow` attribute,使用者點擊後發出的 `u6u:trigger` 事件 detail 中的 `workflowId` SHALL 與 URI 中的 id 完全一致;*For any* 一組具名 `` 元件置於 `` 內,觸發事件後收集到的 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": "發現禁止的 syscall:sock_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 錯誤邊界 + +`` 在 `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 0(WASM 執行核心):** +- Property 4:WASM 執行路徑 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 2(Cypher 擴展):** +- Property 9:Confluence(三元組順序無關性,fast-check shuffle) +- Property 6:URI 解析 round-trip +- Property 7:版本選擇策略(floating 最高分、pinned 固定版本) +- Property 5:錯誤結構完整性 + +**Phase 3(前端畫布):** +- Property 10:Web 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 解析的邊界案例(空字串、特殊字元) +- `` Smart Container:巢狀容器的事件冒泡行為 diff --git a/.agents/specs/u6u-platform-evolution/requirements.md b/.agents/specs/u6u-platform-evolution/requirements.md new file mode 100644 index 0000000..d9fd31e --- /dev/null +++ b/.agents/specs/u6u-platform-evolution/requirements.md @@ -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 1(Cloudflare Workers)、Tier 2(workerd 地端叢集)、Tier 3(Go + 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**:畫布上的排版容器(如 ``),自動打包同容器內所有輸入元件的值並附加至觸發事件的 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 Networking,Tier 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_Registry,THE 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_Registry,THE 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 1(Cloudflare Workers)環境中,以 `workerd` 內建的 WASM 執行能力執行 WASI preview1 零件。 +4. WHEN 一個零件的 `runtime_compat` 不包含當前執行環境的 Tier,THE 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 5:Cypher 語意關係擴展 + +**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 6:Component Dispatcher 多 Tier 路由 + +**User Story:** As a 平台架構師, I want Cypher Executor 透過統一的 Component Dispatcher 介面呼叫跨 Tier 零件, so that Workflow 設計者不需要知道零件部署在哪個 Tier。 + +#### Acceptance Criteria + +1. THE Component_Dispatcher SHALL 根據以下優先序決定呼叫路徑:(1)Tier 1:Cloudflare Service Binding(若零件部署為 Worker)或 WASM 直接執行;(2)Tier 2:workerd 叢集 HTTP endpoint;(3)Tier 3:Wazero IPC(stdin/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 7:Tier 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_2,THE 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:``(按鈕)、``(文字輸入)、``(多行文字)、``(圖表)、``(智慧容器)。 +2. THE `` SHALL 支援以下 HTML attributes:`label`(顯示文字)、`color`(主題色)、`tooltip`(滑鼠懸停提示,純靜態,不觸發 Webhook)、`workflow`(綁定的 Workflow URI,格式為 `workflow://id`)。 +3. WHEN `` 的 `workflow` attribute 被設定且使用者點擊按鈕,THE `` SHALL 發出 `u6u:trigger` 自訂事件,事件 detail 包含 `{ workflowId, payload }`。 +4. THE `` SHALL 在接收到子元件的 `u6u:trigger` 事件時,自動收集同容器內所有 `` 與 `` 的當前值,合併至事件的 `payload` 後再向上冒泡。 +5. FOR ALL Web Components,THE 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 零件拖入同一個 `` 容器,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、打包 runtime(QuickJS、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 12:KBDB 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 資料不變的情況下,對相同查詢參數回傳相同結果(查詢冪等性)。 diff --git a/.agents/specs/u6u-platform-evolution/tasks.md b/.agents/specs/u6u-platform-evolution/tasks.md new file mode 100644 index 0000000..f568ec1 --- /dev/null +++ b/.agents/specs/u6u-platform-evolution/tasks.md @@ -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 keys(canonical_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` 至 R2,slot `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 一律回傳 ENOSYS(76) + - 不引入任何外部依賴(不使用 `@cloudflare/workers-wasi`) + - _Requirements: 3.1, 3.3_ + + - [x]* 2.2 寫單元測試 for WASI shim + - 測試 `fd_read` 正確讀取 stdin buffer(含多次讀取、邊界條件) + - 測試 `fd_write` 正確寫入 stdout buffer(fd=1)與 stderr buffer(fd=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_tests(happy path + error path) + - `runtime_compat: ["cf-workers","workerd","wazero"]` + - _Requirements: 1.1, 1.2, 1.4_ + + - [ ]* 3.3 寫單元測試 for validate_json(Gherkin 場景驗證) + - 測試合法 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 2:Cypher 語意擴展 + 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 實作 `` Custom Element + - 支援 attributes:`label`、`color`、`tooltip`、`workflow`、`disabled` + - `workflow` 設定且點擊時發出 `u6u:trigger` CustomEvent(`{ workflowId, payload }`) + - `workflow` 未設定時點擊不發出事件,console 輸出警告 + - _Requirements: 8.2, 8.3_ + + - [x] 14.2 實作 `` 與 `` Custom Elements + - 支援 `name`、`placeholder`、`value` attributes + - `value` property 可被 `` 讀取 + - _Requirements: 8.1_ + + - [x] 14.3 實作 `` Smart Container + - 攔截子元件的 `u6u:trigger` 事件(`stopPropagation`) + - 收集同容器內所有 `` / `` 的 name-value 對 + - 合併至 payload 後重新發出 `u6u:trigger`(`bubbles: true, composed: true`) + - _Requirements: 8.4_ + + - [x] 14.4 實作 `` Custom Element(基礎版) + - 支援 `data` attribute(JSON 字串)、基本折線圖渲染 + - _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 拖放與自動打包關係顯示 + - 拖入同一 `` 時,邏輯視圖自動顯示 CONTAINS 關係與自動打包說明 + - _Requirements: 9.4_ + + - [x] 15.5 實作零件替換過濾器 + - 替換已綁定 Workflow 的 UI 零件時,只顯示具備 `u6u:trigger` 能力的候選零件 + - _Requirements: 9.5_ + + - [x] 15.6 在畫布中整合 u6u Web Components(Dogfooding) + - 畫布 UI 本身使用 ``、``、`` 組裝 + - 驗證 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 + - 存入本地 SQLite(AES-GCM 加密,key 存於設備安全儲存) + - 離線時從本地快取讀取,過期時加入 DTN 佇列等待更新 + - _Requirements: 7.1, 7.3_ + +- [ ] 18. Go Cypher Executor(Tier 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 1(wasi-shim.ts)和 Tier 3(Wazero)執行結果一致 + +--- + +## Notes + +- 標記 `*` 的子任務為選填,可跳過以加速 MVP 交付 +- 每個任務都引用具體的 Requirements 條款以確保可追溯性 +- Checkpoint 任務確保每個 Phase 完成後有明確的驗收點 +- Property tests 使用 fast-check,每個屬性最少執行 100 次迭代 +- 所有跨服務呼叫只透過 KBDB HTTP API,不直接操作 D1 SQL +- TinyGo 零件只使用白名單 import(`os`、`io`、`encoding/json`) + +## Phase 5:u6u-mcp 對齊新 Registry + u6u-gui 前端 + +> 壓測前必須完成,讓 AI(u6u-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) diff --git a/.agents/steerings/tech.md b/.agents/steerings/tech.md new file mode 100644 index 0000000..7404bed --- /dev/null +++ b/.agents/steerings/tech.md @@ -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` diff --git a/docs/user_requirements/ADR-lib-and-landingPage/arcrun-pages-spec.md b/docs/user_requirements/ADR-lib-and-landingPage/arcrun-pages-spec.md new file mode 100644 index 0000000..1f1ba11 --- /dev/null +++ b/docs/user_requirements/ADR-lib-and-landingPage/arcrun-pages-spec.md @@ -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 param,Cloudflare 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) +├── 只取 token(escape 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 D1(API 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 Worker:API call 時寫入 D1 +- [ ] 每小時聚合 Worker:D1 → 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 都有效。 diff --git a/docs/user_requirements/ADR-lib-and-landingPage/arcrun-py-strategy-analysis.md b/docs/user_requirements/ADR-lib-and-landingPage/arcrun-py-strategy-analysis.md new file mode 100644 index 0000000..45783e7 --- /dev/null +++ b/docs/user_requirements/ADR-lib-and-landingPage/arcrun-py-strategy-analysis.md @@ -0,0 +1,486 @@ +# arcrun-py:Python 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 +選項 A:google-api-python-client(官方,但使用者要先去 GCP 設 OAuth) +選項 B:arcrun-auth(pip 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 C:arcrun 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 到 localhost,lib 接到 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,開源 SDK,hosted service 付費。SDK 下載數 = 付費轉換引流。 +- **PostHog**:product analytics,同樣策略。 +- **Supabase**:auth + db,開源 self-hosted,hosted 服務付費。 + +共同特徵: +1. SDK 免費下載、單機/本地能完整 work。 +2. 從本地升級到 hosted 是「多人/多機/審計」的自然需求。 +3. SDK 本身就是最強的 marketing(開發者口耳相傳 + 訓練資料擴散)。 + +--- + +## 6. 為什麼 AI 會主動選 arcrun-auth(機率分析) + +讓我給你一個分層的答案。 + +### 6.1 會主動選的場景(高機率) + +| 場景 | 原因 | +|---|---| +| 使用者在 prompt 說「用最簡單的方式認證」 | AI 權衡 UX,arcrun-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:核心 lib(2-3 週) + +- [ ] `arcrun-auth` PyPI 骨架(pyproject.toml + src layout) +- [ ] Recipe loader(從 GitHub 或平台 API 拉 YAML) +- [ ] `auth.bind(service_id, instance?)` → httpx Client +- [ ] Static key primitive(Notion / OpenAI / Stripe 當試金石) +- [ ] Keyring 整合 + 本地加密檔 fallback +- [ ] CLI:`arcrun setup `, `arcrun list`, `arcrun test` + +### Phase 2:OAuth2(2 週) + +- [ ] OAuth2 primitive(authorization_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 自動化 +- [ ] Recipe:Google Drive / Gmail / GitHub / Slack + +### Phase 3:Service Account(1-2 週) + +- [ ] Google Service Account(JWT signing) +- [ ] AWS SigV4 +- [ ] Recipe 繼承(`extends: _google_base`) + +### Phase 4:雲端同步(2 週) + +- [ ] `arcrun login` → 綁定雲端帳號 +- [ ] Secret sync 協議(本地加密後上傳,平台只存密文) +- [ ] 多機同步 +- [ ] Audit log + +### Phase 5:AI 生態整合(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 卡關,有 fallback:lib 自動引導使用者建自己的 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 Secret,lib 支援直接讀這些來源。 + +### 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,低階,還是要自己組。 +- **各家 SDK(google-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-2(static key + OAuth2),私人 alpha。 +- **Month 5**:公開 release,寫部落格、SEO、社群推廣。 +- **Month 6+**:雲端同步、MCP、AI 生態整合。 + +### 9.3 一句話總結 + +> **arcrun-auth 不是「arcrun 的 Python 綁定」,是「OAuth 地獄的解藥」**。SaaS 是延伸。這個敘事才能在 AI 寫程式的時代站住腳。 + diff --git a/docs/user_requirements/ADR-lib-and-landingPage/strategy.md b/docs/user_requirements/ADR-lib-and-landingPage/strategy.md new file mode 100644 index 0000000..a22fb7b --- /dev/null +++ b/docs/user_requirements/ADR-lib-and-landingPage/strategy.md @@ -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 服務? diff --git a/docs/user_requirements/credential_parts.md b/docs/user_requirements/credential_parts.md new file mode 100644 index 0000000..babdedc --- /dev/null +++ b/docs/user_requirements/credential_parts.md @@ -0,0 +1,761 @@ +# arcrun Credential System 設計規格 +20260418 + +> **讀者**:Claude Code(CC),負責實作 +> **作者**: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 + : + query: # URL query string + : + body: # request body(JSON 欄位) + : + basic_auth: # HTTP Basic Auth(會自動 base64 編碼) + username: + password: +``` + +**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 grant(2026 已被多數 OAuth 提供者棄用)、implicit(已棄用) + +**Recipe 欄位**: +```yaml +primitive: oauth2 +grant: authorization_code # or client_credentials, pkce +base_url: +oauth: + authorize_url: + token_url: + scopes: + - + - + 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 Role(assume role)、任何需要「私鑰簽 JWT 換短期 token」的機器身份。 + +**這個就是讓你 debug 兩天那個爆炸點。** 我們用 primitive 把地雷全部包起來。 + +**Recipe 欄位**: +```yaml +primitive: service_account +kind: google_jwt # or aws_sigv4, generic_jwt +base_url: +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 SigV4(kind: 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: +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_case,e.g. "notion", "google_calendar" +version: integer # recipe schema version,breaking 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 + + // 檢查是否需要 refresh(oauth2 / 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 + + // 首次授權(僅 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 + + // 管理 + listRecipes(): Promise + getRecipe(serviceId: string): Promise +} + +interface AuthenticatedClient { + fetch(path: string, init?: RequestInit): Promise +} +``` + +**使用範例(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: + +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 Store,tenant 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": | { "ref": "secrets-store-id" }, + "created_at": "...", + "last_verified_at": "..." +} + +# oauth2 runtime state(primitive 自動管理) +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 **不**進 KBDB(KBDB 不該存敏感資料),只有 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 2:Static Key(1 週) + +- [ ] **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 3:OAuth2(1-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 flow:startAuth → 跳轉 → callback → 寫 secret + +### Phase 4:Service Account(1 週) + +- [ ] **T4.1** `service_account.wasm` 實作(google_jwt) +- [ ] **T4.2** Google JWT signing(ES256 / 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 5:mTLS + 收尾(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 記錄到 KBDB(metadata,不含 secret) +- [ ] **T5.5** Docs:recipe 撰寫指南(讓社群能貢獻) + +### Phase 6:Recipe 生成器(選配,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,存在 KV,callback 時比對 | +| **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` 欄位 semver,tenant secret 記錄 `recipe_version`,primitive 內處理遷移 | + +--- + +## 12. 對比 n8n(給內部 review / 行銷用) + +| 維度 | n8n | arcrun | +|---|---|---| +| Credential types 數量 | 400+(一個服務一個) | 4(primitive) + 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 App(GitHub、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. 最小可行 recipe(OpenAI) + +```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 recipe(Slack) + +```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 recipe(Google 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 夠用再開始批量生成**。 \ No newline at end of file diff --git a/docs/user_requirements/simplify_mvp/u6u-core-spec-v3.md b/docs/user_requirements/simplify_mvp/u6u-core-spec-v3.md new file mode 100644 index 0000000..78afedc --- /dev/null +++ b/docs/user_requirements/simplify_mvp/u6u-core-spec-v3.md @@ -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 AI(auto-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 token(gmail.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 → 查 contract:credentials_required.inject_as = access_token + ↓ +去 CREDENTIALS_KV 讀 gmail_token → 解密 + ↓ +注入到 WASM input:{ to, subject, body, access_token: "ya29..." } + ↓ +WASM 執行,用戶的 config 裡完全不出現 token +``` + +--- + +## 任務四:CLI 開發 + +### 技術選型 + +- **語言**:Node.js(TypeScript) +- **安裝**:`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 ` + +解析 `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 [--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 ` + +執行前完整驗證,提前發現問題。 + +``` +$ 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_token(google_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 ` + +從 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 ` + +查看最近執行記錄。 + +``` +$ 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. 建立新的獨立 repo:u6u-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_required(gmail, google_sheets, telegram, line_notify) +8. 補充 config_example(全部 21 個) +9. 驗證 main.go required 欄位與 contract 一致 +``` + +### Phase 3:CLI(完成度補充後) + +``` +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 publish(u6u CLI) +``` + +--- + +## 不在此次範圍 + +- KBDB 整合(未來付費服務) +- 向量搜尋 / graph 查詢 +- 前端管理介面 +- Webhook trigger 設定(用戶自行設定 CF Cron) +- 新增 WASM 零件(現有 21 個先做完整,之後再擴充) diff --git a/docs/user_requirements/u6u-long-term/SourceDocs/u6u 智慧前端與工匠開發藍圖.md b/docs/user_requirements/u6u-long-term/SourceDocs/u6u 智慧前端與工匠開發藍圖.md new file mode 100644 index 0000000..826f97a --- /dev/null +++ b/docs/user_requirements/u6u-long-term/SourceDocs/u6u 智慧前端與工匠開發藍圖.md @@ -0,0 +1,89 @@ +# **u6u 智慧前端與工匠 AI 開發藍圖 (v4.0)** + +## **一、 核心設計理念:意圖導向的雙面畫布** + +u6u 的前端不是傳統的平面繪圖板,而是一個類似 Android Studio 或 Figma 的\*\*「結構化標籤編輯器」**。 畫布上的每一個元件(Web Component),本質上都是一個**「意圖發射器 (Intent Emitter)」\*\*。前端只負責「長得好看」與「收集人類動作」,後端全權負責「業務邏輯」。 + +### **人機協作的「雙向同步」** + +* **AI 詠唱修改:** 人類說「把按鈕改醒目一點」,CEO AI 在背景將 \ 修改為 \,畫面瞬間更新。 +* **人類手動覆寫:** 人類覺得 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 都是各自獨立的原子元件。 +* **麻瓜體驗(智慧表單):** 當使用者將這兩個元件拖入同一個排版容器(例如 \)時,系統會自動建立上下文關聯。當按下按鈕並觸發 Webhook 時,按鈕會**自動打包同容器內所有輸入框的值**一併送出: + { + "intent": "query\_attendance", + "payload": { "employee\_id": "A1234" } // 自動從旁邊的 TextInput 抓取 + } + + 使用者完全不需要理解「表單傳值」或「變數綁定」,拖拉組合即生效。 + +### **3\. 多重事件插槽與靜態屬性 (Multi-Event Slots)** + +一個前端元件可以具備多種觸發行為,系統透過介面將「視覺」與「後端邏輯」徹底分流: + +* **靜態視覺註釋:** 例如 mouseover 顯示提示。使用者只需在屬性面板輸入 Tooltip 文字,底層僅修改 HTML 屬性 \,不消耗任何伺服器資源或 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 組合:** \ \+ \ \+ \。 +* **後端 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(如 \),只負責接收特定 JSON 來渲染畫面。 +* **Step 4: 註冊與編目 (Cataloging into KBDB)** + 新積木註冊到圖資料庫,未來的 CEO AI 即可將其與任何既有的前端或後端元件進行無限的叉積組合。 + +## **四、 架構總結與終極產品體驗** + +這套前端架構讓 u6u 成為一個\*\*「表裡如一」**的系統,成功創造了**「麻瓜的 ERP 幻覺」\*\*: + +使用者不需要知道什麼是「前後端分離」、什麼是「API 串接」。他們只是覺得: + +1. 拖拉了一個溫度計。 +2. 翻面把「數值更新」連線到「機台感測器」。 +3. 拖拉了一個紅按鈕放在旁邊。 +4. 翻面把「點擊」連線到「發送 Line 警報」。 + +在十分鐘的「繪圖」過程中,沒有寫一行程式碼,也沒有設定任何變數。但透過**前端 Web Components** 的視覺封裝、**智慧容器**的自動資料打包,以及**後端 TinyGo WASM \+ Cypher** 的無縫承接,他們在不知不覺中,就搭建出了一套具備微服務架構、高擴展性、且可部署至極限邊緣的企業級系統。 \ No newline at end of file diff --git a/docs/user_requirements/u6u-long-term/SourceDocs/u6u 系統與零件宇宙全景規劃白皮書.md b/docs/user_requirements/u6u-long-term/SourceDocs/u6u 系統與零件宇宙全景規劃白皮書.md new file mode 100644 index 0000000..087ba71 --- /dev/null +++ b/docs/user_requirements/u6u-long-term/SourceDocs/u6u 系統與零件宇宙全景規劃白皮書.md @@ -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 就能在其中無止盡地為人類組裝出越來越強大、越來越穩定的商業應用。 \ No newline at end of file diff --git a/docs/user_requirements/u6u-long-term/SourceDocs/u6u 自動演化 ERP 架構藍圖.md b/docs/user_requirements/u6u-long-term/SourceDocs/u6u 自動演化 ERP 架構藍圖.md new file mode 100644 index 0000000..5904067 --- /dev/null +++ b/docs/user_requirements/u6u-long-term/SourceDocs/u6u 自動演化 ERP 架構藍圖.md @@ -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 圖譜中執行,完成企業能力的自動擴展。 \ No newline at end of file diff --git a/docs/user_requirements/u6u-long-term/u6u_system_spec.md b/docs/user_requirements/u6u-long-term/u6u_system_spec.md new file mode 100644 index 0000000..965a2a4 --- /dev/null +++ b/docs/user_requirements/u6u-long-term/u6u_system_spec.md @@ -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 + Vectorize(KBDB) + +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 +❌ 禁止打包 runtime(QuickJS、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 搜尋時讀到坑,強制繞道 +``` + +--- + +## 十、現況與未來路徑 + +### 現在已有 + +- KBDB(blocks/templates/slots + Vectorize) +- IS-Squad MCP(execute_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 Model(preview2)**:目前用 preview1,未來 3-5 年會有遷移壓力。 + 合約裡已有 `wasi_target: "preview1"` 標記,升級時知道要改什麼。 +- **Kùzu 成熟度**:地端圖資料庫首選,持續觀察 v1.0 穩定性。 + +--- + +## 十一、實作決策原則(CC 行動準則) + +遇到不確定的實作決策時,依序問自己: + +1. **這個決策會影響零件合約嗎?** 如果是,停下來討論,不要自行決定。 +2. **這個實作是否限制了未來換 runtime 的自由?** 如果是,重新設計介面。 +3. **這個零件做超過一件事嗎?** 如果是,拆成兩個零件。 +4. **這個設計在 Tier 3 離線環境能跑嗎?** 如果不能,重新考慮。 +5. **有沒有現成零件可以組合?** 先搜尋 KBDB,不要重造輪子。 + +--- + +*本文件版本:v1.0* +*綜合自:u6u 系統與零件宇宙全景規劃白皮書、自動演化 ERP 架構藍圖、智慧前端與工匠開發藍圖,加入技術評論與補充建議。* diff --git a/docs/user_requirements/u6u-plan.md b/docs/user_requirements/u6u-plan.md new file mode 100644 index 0000000..8387f6e --- /dev/null +++ b/docs/user_requirements/u6u-plan.md @@ -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 就能在其中無止盡地為人類組裝出越來越強大、越來越穩定的商業應用。 \ No newline at end of file diff --git a/docs/user_requirements/u6u_design.md b/docs/user_requirements/u6u_design.md new file mode 100644 index 0000000..8d43604 --- /dev/null +++ b/docs/user_requirements/u6u_design.md @@ -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,就是企業版可以讓資料是獨立的 \ No newline at end of file diff --git a/docs/user_requirements/wishlist.md b/docs/user_requirements/wishlist.md new file mode 100644 index 0000000..0c158d2 --- /dev/null +++ b/docs/user_requirements/wishlist.md @@ -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 isolate(V8),本身就是沙箱,不需要在 WASM 內嵌入 QuickJS 或 javy,也不需要引入 Rust。這與 http_request 零件 + recipe 的模式完全一致:零件本身是穩定的能力抽象(WASM),recipe 是存在 RECIPES KV 的可替換邏輯字串。 + +JS recipe 可用的 API 範圍為 Cloudflare Workers 標準 Web API(ES2023、fetch、crypto、TextEncoder 等),但 code 零件語意上應為純計算節點(無 network),實作時可考慮用 Proxy 遮蔽 fetch 等 I/O API,初期自用階段可暫時跳過此限制。第三方 lib 的問題留待實作時再決定白名單策略(預注入 lodash-es、dayjs、yaml 等到 global scope 即可覆蓋九成用例)。 \ No newline at end of file diff --git a/tests/TEST_CASES.md b/tests/TEST_CASES.md new file mode 100644 index 0000000..cf0372b --- /dev/null +++ b/tests/TEST_CASES.md @@ -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-02:YAML 驗證(離線) + +**目的**:驗證 `acr validate --offline` 正確解析 workflow + +**步驟** +```bash +acr validate hello.yaml --offline +``` + +**預期輸出**(依序出現) +``` +✓ YAML 格式正確 +✓ 三元組解析 1 條 +✓ 關係詞合法性 +✓ config 完整性 1 個節點均有 config +✓ 零件存在性 離線模式,跳過遠端檢查 +✓ 驗證通過 +``` + +**結果**:✅ 所有驗證項目均呈現綠色勾號,顯示「驗證通過」。 + +--- + +## TC-03:Hello World 執行 + +**目的**:端對端執行最基本 workflow,驗證 string_ops 零件 + +**步驟** +```bash +acr run hello --input input="Hello, arcrun!" +``` + +**預期** +- 顯示 `✓ 執行成功` +- 結果包含 `"result": "HELLO, ARCRUN!"` + +**結果**:✅ 執行成功(耗時約 1.3s),結果正確返回 `"result": "HELLO, ARCRUN!"`。 + +--- + +## TC-04:string_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-05:number_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-06:validate_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-07:http_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-10:ON_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-12:API 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` 直接合併到頂層,不自動解包 diff --git a/tests/arcrun-test/bad-component.yaml b/tests/arcrun-test/bad-component.yaml new file mode 100644 index 0000000..53ad4b2 --- /dev/null +++ b/tests/arcrun-test/bad-component.yaml @@ -0,0 +1,6 @@ +name: bad-component +flow: + - "input >> ON_SUCCESS >> nonexistent" +config: + nonexistent: + component: does_not_exist diff --git a/tests/arcrun-test/chinese-flow.yaml b/tests/arcrun-test/chinese-flow.yaml new file mode 100644 index 0000000..89bb533 --- /dev/null +++ b/tests/arcrun-test/chinese-flow.yaml @@ -0,0 +1,7 @@ +name: chinese-flow +flow: + - "input >> 完成後 >> transform" +config: + transform: + component: string_ops + operation: upper diff --git a/tests/arcrun-test/custom-node.yaml b/tests/arcrun-test/custom-node.yaml new file mode 100644 index 0000000..0a20e7e --- /dev/null +++ b/tests/arcrun-test/custom-node.yaml @@ -0,0 +1,7 @@ +name: custom-node +flow: + - "input >> ON_SUCCESS >> 我的轉換器" +config: + 我的轉換器: + component: string_ops + operation: upper diff --git a/tests/arcrun-test/error-routing.yaml b/tests/arcrun-test/error-routing.yaml new file mode 100644 index 0000000..9fea36a --- /dev/null +++ b/tests/arcrun-test/error-routing.yaml @@ -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 diff --git a/tests/arcrun-test/hello.yaml b/tests/arcrun-test/hello.yaml new file mode 100644 index 0000000..addaa81 --- /dev/null +++ b/tests/arcrun-test/hello.yaml @@ -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 diff --git a/tests/arcrun-test/http-test.yaml b/tests/arcrun-test/http-test.yaml new file mode 100644 index 0000000..af7c750 --- /dev/null +++ b/tests/arcrun-test/http-test.yaml @@ -0,0 +1,7 @@ +name: http-test +flow: + - "input >> ON_SUCCESS >> fetch" +config: + fetch: + component: http_request + method: GET diff --git a/tests/arcrun-test/string-test.yaml b/tests/arcrun-test/string-test.yaml new file mode 100644 index 0000000..f39caac --- /dev/null +++ b/tests/arcrun-test/string-test.yaml @@ -0,0 +1,7 @@ +name: string-test +flow: + - "input >> ON_SUCCESS >> process" +config: + process: + component: string_ops + operation: "{{operation}}" diff --git a/tests/arcrun-test/validate-test.yaml b/tests/arcrun-test/validate-test.yaml new file mode 100644 index 0000000..7417253 --- /dev/null +++ b/tests/arcrun-test/validate-test.yaml @@ -0,0 +1,6 @@ +name: validate-test +flow: + - "input >> ON_SUCCESS >> check" +config: + check: + component: validate_json