docs: add SDD specs + user requirements + tests
- .agents/specs/: spec-driven-dev docs for arcrun MVP, auth-recipe, credential-primitives-wasm (active refactor), landing-page, sdk-and-website, u6u-core-mvp, u6u-platform-evolution. - .agents/steerings/tech.md: detailed tech stack rationale. - docs/user_requirements/: long-form requirements incl. credential primitives, pages spec, py strategy analysis. - tests/: end-to-end harness scaffolding. These are the durable context backing CLAUDE.md's SDD protocol. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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 | 初始發布 |
|
||||
@@ -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<string, string>; // "Authorization": "Bearer {{secret.token}}"
|
||||
query?: Record<string, string>;
|
||||
body?: Record<string, string>;
|
||||
};
|
||||
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
```
|
||||
|
||||
**Template 語法**:
|
||||
- `{{secret.KEY}}` → 從 tenant 的 CREDENTIALS_KV 解密取值
|
||||
- `{{runtime.access_token}}` → service_account JWT exchange 後取得的短期 token
|
||||
|
||||
---
|
||||
|
||||
## 四、KV 儲存
|
||||
|
||||
沿用現有 `RECIPES` KV namespace,不新增 binding。
|
||||
|
||||
```
|
||||
auth_recipe:{service} → AuthRecipeDefinition JSON
|
||||
```
|
||||
|
||||
與現有 `recipe:{id}` / `idx:{hash}` 的 key 不衝突。
|
||||
|
||||
---
|
||||
|
||||
## 五、執行流程
|
||||
|
||||
### 5.1 static_key(涵蓋 ~80% 服務)
|
||||
|
||||
```
|
||||
trigger → graph-executor
|
||||
→ injectCredentials(componentId, input, env, apiKey)
|
||||
→ resolveAuthRecipe("notion", RECIPES KV)
|
||||
→ 取得 required_secrets: [{key: "notion_token", ...}]
|
||||
→ 從 CREDENTIALS_KV 讀 "{api_key}:cred:notion_token"
|
||||
→ AES-GCM 解密
|
||||
→ 展開 inject.header templates ({{secret.notion_token}} → 實際值)
|
||||
→ 注入 _auth_headers, _auth_query, _auth_body 到 input
|
||||
→ makeAuthRecipeRunner(recipe)
|
||||
→ 合併 _auth_headers 到 fetch headers
|
||||
→ 呼叫 recipe.base_url + input._path
|
||||
→ 回傳結果
|
||||
```
|
||||
|
||||
### 5.2 service_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<string, string>` — 要合併進 fetch headers |
|
||||
| `_auth_query` | `Record<string, string>` — 要附加到 URL query string |
|
||||
| `_auth_body` | `Record<string, string>` — 要合併進 request body |
|
||||
|
||||
`makeAuthRecipeRunner` 在發出 fetch 前讀取這三個欄位,之後從 body 中剔除(不迴傳給下游)。
|
||||
|
||||
---
|
||||
|
||||
## 七、向後相容
|
||||
|
||||
- 現有 `BUILTIN_API_RECIPES`(gmail, google_sheets, telegram, line_notify)**不動**
|
||||
- 現有 `BUILTIN_CREDENTIALS_MAP` **不動**
|
||||
- auth recipe 解析在 component-loader step 5.5(新增),在 step 6 KV recipe 和 step 7 builtin 之前
|
||||
- 若 `auth_recipe:{service}` 不存在 → 繼續往下走,行為與現在完全相同
|
||||
|
||||
---
|
||||
|
||||
## 八、新增/修改的檔案
|
||||
|
||||
| 檔案 | 類型 | 說明 |
|
||||
|---|---|---|
|
||||
| `cypher-executor/src/routes/recipes.ts` | 修改 | 加 `AuthRecipeDefinition` 型別、`resolveAuthRecipe`、`/auth-recipes` CRUD routes |
|
||||
| `cypher-executor/src/actions/credential-injector.ts` | 修改 | 加 auth recipe 分支:static_key + service_account |
|
||||
| `cypher-executor/src/lib/jwt-signer.ts` | 新增 | Google JWT signing via crypto.subtle |
|
||||
| `cypher-executor/src/lib/component-loader.ts` | 修改 | step 5.5 auth recipe lookup + `makeAuthRecipeRunner` |
|
||||
| `cypher-executor/src/lib/auth-recipe-seeds.ts` | 新增 | 20 個常用服務的 auth recipe 定義 |
|
||||
| `cli/src/commands/auth-recipe.ts` | 新增 | `acr auth-recipe list/info/scaffold` |
|
||||
| `cli/src/commands/parts.ts` | 修改 | `cmdPartsScaffold` fallback 到 auth recipe |
|
||||
| `cli/src/index.ts` | 修改 | 註冊 auth-recipe 指令 |
|
||||
|
||||
---
|
||||
|
||||
## 九、封測前預計的 Auth Recipe 清單(20 個)
|
||||
|
||||
### static_key 類(~80% 服務)
|
||||
|
||||
| service | 認證方式 | credential key |
|
||||
|---|---|---|
|
||||
| `notion` | Bearer token (header) | `notion_token` |
|
||||
| `slack` | Bot Token (Bearer) | `slack_bot_token` |
|
||||
| `github` | PAT (Bearer) | `github_token` |
|
||||
| `openai` | API key (Bearer) | `openai_api_key` |
|
||||
| `anthropic` | API key (x-api-key) | `anthropic_api_key` |
|
||||
| `airtable` | PAT (Bearer) | `airtable_token` |
|
||||
| `discord` | Bot token ("Bot TOKEN") | `discord_bot_token` |
|
||||
| `stripe` | Secret key (Bearer) | `stripe_secret_key` |
|
||||
| `twilio` | AccountSid + AuthToken (Basic Auth) | `twilio_account_sid`, `twilio_auth_token` |
|
||||
| `sendgrid` | API key (Bearer) | `sendgrid_api_key` |
|
||||
| `hubspot` | Private App token (Bearer) | `hubspot_token` |
|
||||
| `linear` | API key (Bearer) | `linear_api_key` |
|
||||
| `shopify` | Admin API token (X-Shopify-Access-Token) | `shopify_access_token` |
|
||||
| `resend` | API key (Bearer) | `resend_api_key` |
|
||||
| `supabase` | Service role key (Bearer + apikey) | `supabase_service_key` |
|
||||
| `typeform` | PAT (Bearer) | `typeform_token` |
|
||||
| `jira` | API token + email (Basic Auth) | `jira_api_token`, `jira_email` |
|
||||
|
||||
### service_account 類(Google 家族,JWT signing)
|
||||
|
||||
| service | scopes | credential key |
|
||||
|---|---|---|
|
||||
| `google_sheets_sa` | spreadsheets | `google_service_account` |
|
||||
| `google_gmail_sa` | gmail.send | `google_service_account` |
|
||||
| `google_drive_sa` | drive | `google_service_account` |
|
||||
|
||||
> 注意:三個 Google 服務可共用同一個 `google_service_account` credential,只是 scope 不同。
|
||||
|
||||
---
|
||||
|
||||
## 十、實作進度
|
||||
|
||||
### Server (cypher-executor)
|
||||
|
||||
- [x] `AuthRecipeDefinition` 型別 + `resolveAuthRecipe`
|
||||
- [x] `/auth-recipes` CRUD routes
|
||||
- [x] `injectFromAuthRecipe` — static_key primitive
|
||||
- [x] `lib/jwt-signer.ts` — Google JWT via crypto.subtle
|
||||
- [x] `injectFromAuthRecipe` — service_account primitive
|
||||
- [x] `makeAuthRecipeRunner` in component-loader
|
||||
- [x] step 5.5 in createComponentLoader
|
||||
- [x] auth-recipe-seeds.ts (20 services)
|
||||
- [x] seed script / deploy seeds to 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 邏輯
|
||||
@@ -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 內完成
|
||||
@@ -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 <openai_key>`
|
||||
- [ ] 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
|
||||
@@ -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)
|
||||
|
||||
- 嵌入 `<SwaggerUIBundle>` 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 = "<to be created>"
|
||||
|
||||
[[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=` 覆寫 |
|
||||
@@ -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
|
||||
```
|
||||
@@ -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<unknown>`
|
||||
- `client.auth.listServices()` / `setup()` / `bind()` / `getToken()`
|
||||
- `client.creds.push()` / `list()` / `delete()`
|
||||
- `client.workflows.run()` / `push()` / `list()` / `delete()`
|
||||
3. THE SDK 的 credential 加密 SHALL 使用 Web Crypto API(`crypto.subtle` AES-GCM),相容 Node 18+、browsers、Cloudflare Workers、Deno。
|
||||
4. THE `auth.bind()` SHALL 回傳一個有 `get/post/put/delete/patch` 方法的 `AuthenticatedClient`,base URL + auth headers 已配置。
|
||||
5. THE SDK SHALL 使用原生 `fetch()` API,不依賴外部 HTTP client 套件。
|
||||
6. THE SDK 位置 SHALL 為 `arcrun/js-sdk/`,build 用 `tsup`(ESM + CJS + DTS),`tsconfig.json` target ES2020 + NodeNext module。
|
||||
7. THE JS SDK 套件名與 CLI 套件名衝突(都叫 `arcrun`),SHALL 使用 `@arcrun/sdk` 或由 richblack 決定套件名。
|
||||
|
||||
---
|
||||
|
||||
### Requirement 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()` ✅
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user