Files
Arcrun/docs/user_requirements/credential_parts.md
T
uncle6me-web 922a57fe34 arcrun — AI workflow execution engine (clean history)
Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:52:38 +08:00

29 KiB
Raw Blame History

arcrun Credential System 設計規格

20260418

讀者Claude CodeCC),負責實作 作者richblack(架構決策) 版本v1.0 狀態:Draft — 等 CC 確認技術可行性後開工


0. TL;DR(給 CC 的三句話版)

  1. 不要為每個服務寫一個 credential 零件,n8n 是錯的。
  2. 四個 TinyGo/WASM 零件(primitives),每個服務只需要一份 YAML recipe + 用戶自己的 secret
  3. Recipe 存 arcrun 平台 KV(公共),secret 存 tenant KV(私有),兩者在 runtime 由 AuthBroker 組裝成可用的 HTTP client。

1. 設計目標與反目標

目標

  • 新增一個服務的成本 = 寫一份 YAML,不需要 rebuild、不需要改 code。
  • AI agent 理解成本 ≈ 0:recipe 就是呼叫該服務的完整說明書。
  • 人類設定成本 < 10 分鐘:即使是對 OAuth 不熟的使用者,UI 只問「你的 API Key 是什麼」這類 secret 層級問題。
  • Secret 隔離:每個 tenant 的 secret 絕對不互相可見,arcrun 平台本身也無法明文讀取(用 Cloudflare Secrets Store 或加密儲存)。

反目標(明確不做的事)

  • 不做 n8n 那種「每個服務一個 credential type」的視覺化面板。
  • 不支援 OAuth1(2026 年還在用的服務極少,真遇到再加)。
  • 不做 credential sharing 的複雜 ACL(全 tenant scope 即可,未來再擴充)。
  • 不在 arcrun 內部明文持久化任何長期 secret(只有加密過的密文或 Secrets Store reference)。

2. 核心架構:三層模型

┌─────────────────────────────────────────────────────────┐
│  Layer 3: Service Recipe (YAML)                         │
│  arcrun 平台共享,describe "如何呼叫這個服務"            │
│  存在 Workers KV: arcrun-recipes                        │
│  例:recipe/notion.yaml, recipe/google_calendar.yaml    │
└─────────────────────────────────────────────────────────┘
                         ↓ 引用
┌─────────────────────────────────────────────────────────┐
│  Layer 2: Auth Primitive (TinyGo → WASM)                │
│  四個通用認證零件,實作注入邏輯與 token 交換              │
│  1. static_key    2. oauth2                             │
│  3. service_account 4. mtls                            │
└─────────────────────────────────────────────────────────┘
                         ↑ 使用
┌─────────────────────────────────────────────────────────┐
│  Layer 1: Tenant Secret (KV + Secrets Store)            │
│  每個 tenant 自己的 KV namespace                        │
│  存 encrypted secret 或 Secrets Store reference         │
│  例:secret/{tenant_id}/notion-prod                     │
└─────────────────────────────────────────────────────────┘

為什麼這樣切?

切分維度 Recipe Primitive Secret
誰擁有 arcrun 平台 arcrun 平台 tenant 自己
變化頻率 中(新服務時) 低(認證機制穩定) 高(rotate、revoke
敏感度 公開 公開 最高機密
儲存位置 平台 KVarcrun-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 bodyJSON 欄位)
    <key>: <value template>
  basic_auth: # HTTP Basic Auth(會自動 base64 編碼)
    username: <value template>
    password: <value template>

Value template 語法{{secret.xxx}} 取 secret 欄位,{{const.yyy}} 取 recipe 內定義的常數。

Secret schematenant 存 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(機器對機器)
  • pkceSPA、行動應用)
  • 不支援:password grant2026 已被多數 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_token
  • refresh_token
  • expires_at

首次授權流程(人類要做的部分):

  1. arcrun UI 呼叫 AuthBroker.startAuth(recipe_id, tenant_id) 回傳 authorize URL。
  2. 使用者瀏覽器跳轉到 IdP,同意授權。
  3. IdP redirect 回 arcrun callback endpoint(固定一個 URL,無論哪個服務)。
  4. AuthBroker 用 authorization code 換 token,寫入 tenant KV。

之後 agent 呼叫時完全自動primitive 檢查 expires_at,過期自動用 refresh token 續,失敗再觸發重新授權通知。


3.3 service_account

適用Google Service Account、AWS IAM Roleassume role)、任何需要「私鑰簽 JWT 換短期 token」的機器身份。

這個就是讓你 debug 兩天那個爆炸點。 我們用 primitive 把地雷全部包起來。

Recipe 欄位

primitive: service_account
kind: google_jwt  # or aws_sigv4, generic_jwt
base_url: <service API base>
token_exchange:
  # Google 的 JWT → OAuth access token 流程
  endpoint: https://oauth2.googleapis.com/token
  audience: https://oauth2.googleapis.com/token
  scopes:
    - https://www.googleapis.com/auth/calendar
  # JWT claims
  issuer_from_secret: client_email
  subject_from_secret: client_email  # optional, for domain-wide delegation 改成其他 user
  ttl_seconds: 3600
required_secrets:
  - key: service_account_json
    label: "Service Account JSON"
    type: json_blob  # 特別型別,UI 可以接受貼整個 JSON
    help: "到 GCP Console → IAM → Service Accounts → Keys → Add Key (JSON) 下載整份 JSON 貼上"
inject:
  header:
    Authorization: "Bearer {{runtime.access_token}}"

為什麼不是每個服務一個 recipe

  • Google Calendar、Gmail、Drive、Sheets 全部可以共用同一個 service_account primitive。
  • 差別只在 scopesbase_url
  • Recipe 本身可以 import 共通片段(見 §5 recipe 繼承)。

AWS SigV4kind: aws_sigv4:這是特例,不是 JWT-based,但概念一樣——用 access_key_id + secret_access_key 在每次 request 上簽章。Primitive 內建處理,recipe 只要宣告 region 和 service name。


3.4 mtls

適用mTLS / client certificate。銀行 API、企業內部服務、醫療系統。

Recipe 欄位

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_casee.g. "notion", "google_calendar"
version: integer             # recipe schema versionbreaking change 要升版
primitive: enum              # static_key | oauth2 | service_account | mtls
base_url: string             # service API base URL

# primitive 相關(依 primitive 不同)
inject: object               # 如何把 secret 注入 HTTP request
oauth: object                # 僅 oauth2 primitive
token_exchange: object       # 僅 service_account primitive

# Secret 宣告(讓 UI 知道要問什麼)
required_secrets:
  - key: string              # secret 欄位名
    label: string            # UI 顯示
    secret: boolean          # 是否遮蔽顯示(default: true
    type: enum               # text | json_blob | pem_cert | pem_key | url
    optional: boolean        # default: false
    help: string             # 給使用者的提示
    help_url: string         # 導向服務文件

# 測試(驗證 credential 是否有效)
test:
  method: GET | POST
  path: string               # 相對 base_url
  expect_status: integer
  expect_json: object        # 選填,JSON path assertion

# Metadata
display_name: string         # UI 顯示名
description: string
icon_url: string
docs_url: string
tags:
  - communication
  - crm
  - ai
maintainers:
  - github: username

# 可選:共通片段繼承
extends: string              # recipe name,繼承其 schema 後覆寫

5. Recipe 繼承(reduce 重複)

Google 家族的 API 長得很像,重複寫 15 次太蠢。支援 extends

# 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

    // 檢查是否需要 refreshoauth2 / service_account 用)
    NeedsRefresh(ctx *AuthContext) bool

    // 執行 refresh / token exchange,回傳新的 RuntimeState
    Refresh(ctx *AuthContext) (RuntimeState, error)

    // 驗證 credential 是否有效(執行 recipe.test
    Test(ctx *AuthContext) error
}

6.2 編譯與部署

# 四個 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

  1. 跨 runtime 可攜性(未來若 arcrun 要跑在 Fly.io、local、或客戶自建環境,同一個 primitive 能用)。
  2. 配合 u6u/arcrun 既定的 WASM 架構方向,不破壞統一性。
  3. 沙箱化: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 namespacearcrun-recipes

key: recipe/{service_id}
value: <recipe YAML 的 JSON 化版本>

key: recipe-list
value: [{ service_id, display_name, icon_url, tags }, ...]  # 加速 UI 列表

更新流程

  1. Recipe YAML 存在 arcrun 主 repo 的 recipes/ 目錄下(version control + PR review)。
  2. CI 跑 schema validator,通過後上傳到 KV。
  3. UI 的 recipe 列表 5 分鐘 cache。

8.2 Secret 儲存(tenant 私有)

雙層策略

  • 短期、低敏感 → tenant KV,用 AES-256-GCM 加密,key 從 Cloudflare Secrets Store 拿。
  • 高敏感(如 service account JSON、private key → 直接存 Cloudflare Secrets Storetenant KV 只存 reference。
# tenant KV namespace: arcrun-tenant-{tenant_id}
key: secret/{service_id}/{instance_name}
value: {
  "recipe_version": 1,
  "storage_mode": "kv_encrypted" | "secrets_store_ref",
  "data": <encrypted blob> | { "ref": "secrets-store-id" },
  "created_at": "...",
  "last_verified_at": "..."
}

# oauth2 runtime stateprimitive 自動管理)
key: oauth_state/{service_id}/{instance_name}
value: {
  "access_token": "...",   # encrypted
  "refresh_token": "...",  # encrypted
  "expires_at": 1234567890
}

secretRef 格式{service_id}/{instance_name},例如 notion/prodgoogle_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 namespace arcrun-recipes
  • T1.3 TinyGo WASM 專案骨架(arcrun/primitives/),四個子目錄,統一 interface
  • T1.4 Worker runtime 的 WASM loader + host function(網路、KV 讀寫)
  • T1.5 AuthBroker TypeScript 類別骨架 + unit test

Phase 2Static Key1 週)

  • T2.1 static_key.wasm 實作(header/query/body/basic_auth 四種注入)
  • T2.2 寫三個 recipenotion.yaml, openai.yaml, stripe.yaml
  • T2.3 Tenant KV secret 加密寫入 + AuthBroker.bind() 整合
  • T2.4 recipe.test 執行器(驗證 credential 有效性)
  • T2.5 E2E test:存 secret → bind → fetch Notion API → assert

Phase 3OAuth21-2 週)

  • T3.1 oauth2.wasm 實作(authorization_code + client_credentials + pkce
  • T3.2 OAuth callback endpoint(統一 URL,用 state 路由到正確 tenant/recipe
  • T3.3 Refresh token 自動續命邏輯(rate-limit 保護:同一 token 不能 1 秒內 refresh 多次)
  • T3.4 寫三個 recipegithub.yaml, slack.yaml, google_oauth_user.yaml
  • T3.5 UI flowstartAuth → 跳轉 → callback → 寫 secret

Phase 4Service Account1 週)

  • T4.1 service_account.wasm 實作(google_jwt
  • T4.2 Google JWT signingES256 / RS256)— 這個 TinyGo 需要注意 crypto 支援
  • T4.3 AWS SigV4 簽章實作(kind: aws_sigv4
  • T4.4 Recipe 繼承機制(extends 支援)
  • T4.5 寫 recipes_google_base, google_calendar, google_drive, gmail, aws_s3

Phase 5mTLS + 收尾(1 週)

  • T5.1 mtls.wasm 實作(對接 Cloudflare mTLSCertificate binding
  • T5.2 Cloudflare Secrets Store 整合(高敏感 secret 用)
  • T5.3 Recipe marketplace UI(列出可用 recipe,搜尋,一鍵設定)
  • T5.4 Observability:每次 bind / refresh / test 記錄到 KBDBmetadata,不含 secret
  • T5.5 Docs:recipe 撰寫指南(讓社群能貢獻)

Phase 6Recipe 生成器(選配,1 週)

  • T6.1 給 Claude 一份 API doc,自動產 recipe YAML 草稿 + 人類 review 介面
  • T6.2 從 OpenAPI spec 自動推論 recipe
  • T6.3 從 n8n credential file 反向轉譯(擷取 400+ 現成整合)

11. 關鍵技術風險與對策

風險 對策
TinyGo 的 crypto 支援不完整ES256 / RS256 JWT 簽章) 先用 crypto/rsa + crypto/ecdsa 確認 TinyGo 版本支援;若不行,fallback 用 Worker runtime 的 crypto.subtle 實作這部分,WASM 透過 host function 呼叫
Recipe 被惡意提交(如 inject 內含 https://evil.com 當 token_url Recipe 走 PR review + CI 自動檢查 URL 白名單;社群貢獻的 recipe 預設隔離在 community/ 目錄,使用者明確選擇才啟用
OAuth state CSRF state 用 crypto.randomUUID() + 5 分鐘 TTL,存在 KVcallback 時比對
Secret 在 Worker log 外洩 AuthContext.Secret 禁止 toString / JSON.stringify,用 Proxy 攔截;log 層強制 redact
Token refresh 風暴100 個並發 request 同時發現過期) 用 Durable Object 單執行緒化每個 secret 的 refresh,其他 request 等結果
TinyGo WASM bundle size 四個 primitive 分開編譯,最大 500KB/個;lazy load
Recipe 版本升級破壞相容 version 欄位 semvertenant secret 記錄 recipe_versionprimitive 內處理遷移

12. 對比 n8n(給內部 review / 行銷用)

維度 n8n arcrun
Credential types 數量 400+(一個服務一個) 4primitive + N recipe
新增一個服務 寫 TypeScript class + rebuild + npm publish 寫一份 YAML + PR merge
AI agent 使用 需要讀 node 文件 + 猜參數 讀 recipe YAML 即可
使用者首次設定 從 400+ 選項選一個(常選錯) 搜尋服務名,只問必要 secret
OAuth App 管理 使用者自己註冊 OAuth app arcrun 平台統一管理(使用者只需點「授權」)
社群貢獻成本 高(TS + 編譯 + 測試) 低(YAML + 測試)

13. 接下來的決策點(需要 richblack 確認)

  • Recipe 版本管理策略:採用 semver?每個 recipe 獨立版本?還是整個 recipe set 一個版本?
  • OAuth App 註冊:arcrun 平台要統一註冊幾個主流服務的 OAuth AppGitHub、Google、Slack、Microsoft)?還是讓 tenant 自己帶 client_id/secret
    • 建議:雙模式——平台模式(方便)+ BYO 模式(企業客戶用自己的 OAuth app 有稽核好處)
  • Recipe registry 的審核流程:完全開放 PR 還是僅核心團隊維護?
    • 建議:recipes/official/(核心維護)+ recipes/community/(PR 審核後 merge,使用者需明確啟用)
  • Secret rotation 政策:要不要內建提醒 / 自動 rotate?(Phase 7+

14. 附錄:完整範例

A. 最小可行 recipeOpenAI

service: openai
version: 1
primitive: static_key
display_name: OpenAI
base_url: https://api.openai.com/v1
required_secrets:
  - key: api_key
    label: API Key
    help_url: https://platform.openai.com/api-keys
inject:
  header:
    Authorization: "Bearer {{secret.api_key}}"
test:
  method: GET
  path: /models
  expect_status: 200
tags: [ai]

B. OAuth2 recipeSlack

service: slack
version: 1
primitive: oauth2
display_name: Slack
base_url: https://slack.com/api
grant: authorization_code
oauth:
  authorize_url: https://slack.com/oauth/v2/authorize
  token_url: https://slack.com/api/oauth.v2.access
  scopes:
    - chat:write
    - channels:read
  refresh: true
  client_auth: header
inject:
  header:
    Authorization: "Bearer {{runtime.access_token}}"
test:
  method: POST
  path: /auth.test
  expect_json:
    ok: true
tags: [communication]

C. Service Account recipeGoogle Calendar

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 夠用再開始批量生成