Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。 此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在 richblack/arcrun 與本地 backup 分支)。含: - acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe) - recipe push 把關(資料外流提醒 + 打通檢查) - 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1) - CLI / cypher-executor / registry / 完整 SDD Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
29 KiB
arcrun Credential System 設計規格
20260418
讀者:Claude Code(CC),負責實作 作者:richblack(架構決策) 版本:v1.0 狀態:Draft — 等 CC 確認技術可行性後開工
0. TL;DR(給 CC 的三句話版)
- 不要為每個服務寫一個 credential 零件,n8n 是錯的。
- 做四個 TinyGo/WASM 零件(primitives),每個服務只需要一份 YAML recipe + 用戶自己的 secret。
- 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 欄位:
primitive: static_key
inject:
# 四個注入位置,可以同時用多個
header: # HTTP headers
<key>: <value template>
query: # URL query string
<key>: <value template>
body: # request body(JSON 欄位)
<key>: <value template>
basic_auth: # HTTP Basic Auth(會自動 base64 編碼)
username: <value template>
password: <value template>
Value template 語法:{{secret.xxx}} 取 secret 欄位,{{const.yyy}} 取 recipe 內定義的常數。
Secret schema:tenant 存 JSON,欄位由 recipe 的 required_secrets 宣告。
範例(Notion):
# 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 範例:
// 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 欄位:
primitive: oauth2
grant: authorization_code # or client_credentials, pkce
base_url: <service API base>
oauth:
authorize_url: <IdP authorize endpoint>
token_url: <IdP token endpoint>
scopes:
- <default scope 1>
- <default scope 2>
client_auth: header # or body
# 是否使用 refresh token
refresh: true
# PKCE 時額外參數
pkce_method: S256 # only for grant: pkce
required_secrets:
- key: client_id
label: "Client ID"
- key: client_secret
label: "Client Secret"
secret: true
inject:
header:
Authorization: "Bearer {{runtime.access_token}}"
Runtime 欄位(primitive 自動維護,存在 tenant KV 的 oauth_state/{secret_id} key):
access_tokenrefresh_tokenexpires_at
首次授權流程(人類要做的部分):
- arcrun UI 呼叫
AuthBroker.startAuth(recipe_id, tenant_id)回傳 authorize URL。 - 使用者瀏覽器跳轉到 IdP,同意授權。
- IdP redirect 回 arcrun callback endpoint(固定一個 URL,無論哪個服務)。
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 欄位:
primitive: service_account
kind: google_jwt # or aws_sigv4, generic_jwt
base_url: <service API base>
token_exchange:
# Google 的 JWT → OAuth access token 流程
endpoint: https://oauth2.googleapis.com/token
audience: https://oauth2.googleapis.com/token
scopes:
- https://www.googleapis.com/auth/calendar
# JWT claims
issuer_from_secret: client_email
subject_from_secret: client_email # optional, for domain-wide delegation 改成其他 user
ttl_seconds: 3600
required_secrets:
- key: service_account_json
label: "Service Account JSON"
type: json_blob # 特別型別,UI 可以接受貼整個 JSON
help: "到 GCP Console → IAM → Service Accounts → Keys → Add Key (JSON) 下載整份 JSON 貼上"
inject:
header:
Authorization: "Bearer {{runtime.access_token}}"
為什麼不是每個服務一個 recipe?
- Google Calendar、Gmail、Drive、Sheets 全部可以共用同一個
service_accountprimitive。 - 差別只在
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 欄位:
primitive: mtls
base_url: <service API base>
required_secrets:
- key: client_cert
label: "Client Certificate (PEM)"
type: pem_cert
- key: client_key
label: "Client Private Key (PEM)"
type: pem_key
secret: true
- key: ca_cert
label: "CA Certificate (PEM) — optional"
type: pem_cert
optional: true
# mtls 通常不需要額外 inject,憑證在 TLS 層
inject: {}
實作注意:Cloudflare Workers 有原生 mTLS 支援(mTLSCertificate binding),primitive 只需要把 secret 轉成 Cloudflare mTLS binding 即可。
4. Recipe YAML Schema(完整版)
# 必填
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:
# 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}}"
# 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 都實作這個)
// 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 編譯與部署
# 四個 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 端)
// 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?
- 跨 runtime 可攜性(未來若 arcrun 要跑在 Fly.io、local、或客戶自建環境,同一個 primitive 能用)。
- 配合 u6u/arcrun 既定的 WASM 架構方向,不破壞統一性。
- 沙箱化:primitive 只能透過明確的 host function 存取外部世界(網路、KV),降低惡意 recipe 攻擊面。
7. AuthBroker API(給 arcrun 其他部分調用)
interface AuthBroker {
// Agent 執行時用的主要 API
bind(serviceId: string, secretRef: string, tenantId: string): Promise<AuthenticatedClient>
// 首次授權(僅 oauth2 用)
startAuth(serviceId: string, tenantId: string): Promise<{ authorizeUrl: string, state: string }>
completeAuth(state: string, code: string): Promise<{ secretRef: string }>
// 測試 credential
test(serviceId: string, secretRef: string, tenantId: string): Promise<TestResult>
// 管理
listRecipes(): Promise<Recipe[]>
getRecipe(serviceId: string): Promise<Recipe>
}
interface AuthenticatedClient {
fetch(path: string, init?: RequestInit): Promise<Response>
}
使用範例(agent 端):
const notion = await authBroker.bind("notion", "notion-prod", ctx.tenantId)
const res = await notion.fetch("/databases/abc/query", {
method: "POST",
body: JSON.stringify({ filter: {...} })
})
Agent 完全不需要知道是 API Key 還是 OAuth——authBroker.bind() 回傳的 client 已經注入好認證,fetch 路徑用相對 base_url 的路徑即可。
8. Storage Layout
8.1 Recipe 儲存(arcrun 平台共享)
Cloudflare KV namespace:arcrun-recipes
key: recipe/{service_id}
value: <recipe YAML 的 JSON 化版本>
key: recipe-list
value: [{ service_id, display_name, icon_url, tags }, ...] # 加速 UI 列表
更新流程:
- Recipe YAML 存在 arcrun 主 repo 的
recipes/目錄下(version control + PR review)。 - CI 跑 schema validator,通過後上傳到 KV。
- 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": <encrypted blob> | { "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:
{
"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 namespacearcrun-recipes - T1.3 TinyGo WASM 專案骨架(
arcrun/primitives/),四個子目錄,統一 interface - T1.4 Worker runtime 的 WASM loader + host function(網路、KV 讀寫)
- T1.5
AuthBrokerTypeScript 類別骨架 + 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實作(對接 CloudflaremTLSCertificatebinding) - 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)
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)
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)
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 的行動指引
- 先不要動既有 arcrun 的 credential 相關 code,保持現狀到 Phase 2 完成再切換。
- Phase 1 + 2 是 MVP,做完可以接 80% 服務(API key 類)。
- 遇到 TinyGo 的技術阻礙(特別是 crypto),立刻回報,不要自己 workaround 兩天。
- 每個 Phase 完成後寫一份 brief report(能跑什麼、不能跑什麼、下一步)。
- Recipe 撰寫先做 3-5 個手工範例,確認 schema 夠用再開始批量生成。