依 autoflow-agent workspace v2 設計把 PRD / 設計 / 架構 / 交付類 共享文件從個人層 .autoflow/(ignored)搬到 docs/autoflow/(進 git), 讓團隊可共享產品與架構文件,個人層只留 progress / review / testing 等 per-branch 筆記。 - 02-prd/ 21 個檔(PRD、features、market-analysis 等) - 03-design/ 18 個檔(design-spec、wireframes、flows 等) - 04-architecture/ 31 個檔(TDD、design-doc、ADR×14、API 規格等) - 07-delivery/ 3 個檔(project-summary、phase-0.6-handover、stage-deployment-setup) 合計 73 檔。原檔已從 .autoflow/ 移除(migration 工具執行 git mv, 但因 .autoflow/ 在 .gitignore 中、git 將此操作視為新增、無 rename history)。
11 KiB
8. 介面契約(Interface Contracts) — visionA Cloud
父文件:PRD.md
本章節是 Phase 0 的核心產出之一。Phase 0 有很多 TODO,介面契約讓未來能「無痛換實作」。
8.1 為什麼介面這麼重要
Phase 0 是雛形階段,很多子系統用 stub 實作,但:
- 雛形不實作 ≠ 雛形不設計
- 介面(interface / contract)現在就要定義清楚
- 未來只換實作,不動業務邏輯
五大關鍵介面:
- AuthProvider — 會員系統(Phase 0 stub → Phase 1 JWT)
- SessionStore — Tunnel session 狀態(Phase 0 in-memory → Phase 1 Redis)
- ObjectStorage — 模型檔儲存(Phase 0 local fs → Phase 1 S3/MinIO)
- ConverterClient — 轉檔服務(Phase 0 stub → Phase 2 真實 API)
- BillingProvider — 計費(Phase 0 / 1 都不做 → Phase 2+)
實際 Go interface 定義由 Architect Agent 寫進 TDD。本文件定義的是需求與合約。
8.2 AuthProvider 介面
目的
讓 visionA-backend 的所有 Auth 相關功能(登入、註冊、驗 token、登出)透過一個 interface 抽象,Phase 0 接 stub,Phase 1 接真實 Auth。
必須提供的能力
| 能力 | 說明 | Phase 0 | Phase 1 |
|---|---|---|---|
| Register | 建立新 user(email + password) | stub(僅接受固定 demo-user) | DB |
| Login | 驗證 user,發 session token | 接受任何帳密,一律回 demo-user + stub token | JWT |
| ValidateToken | 驗證 token,回傳 user | 查 in-memory map | 驗簽 + 查 DB |
| Logout | 撤銷 token | 刪 in-memory entry | 加 blacklist |
| GetUser | 查 user 資訊 | in-memory(固定 demo-user) | DB |
| — 以下為 Phase 1 才做 — | |||
| RefreshToken | Refresh token rotation | ❌(Phase 1) | ✅ |
| RequestPasswordReset | 寄 email 重設 | ❌(Phase 1) | ✅ |
| ConfirmPasswordReset | 確認重設 | ❌(Phase 1) | ✅ |
| Delete | 刪除帳號 + 所有相關資源 | ❌(Phase 1) | ✅ |
Phase 0 雛形實作:StaticAuthProvider
Phase 0 不做真實會員系統,改用 StaticAuthProvider:
- Login:不驗證 email/password,一律接受並回
demo-user的 stub token - ValidateToken:只認得
StaticAuthProvider自己發的 stub token,驗過回demo-user - Logout:從 in-memory map 刪 token
- GetUser:固定回
demo-user(email:demo@visiona.cloud) - Register:Phase 0 可回
ErrNotImplemented或直接回 demo-user(Architect 決定) - Phase 1 方法(RefreshToken / RequestPasswordReset / ConfirmPasswordReset / Delete):stub,直接回
ErrNotImplemented
這個設計讓前端登入流程能跑通(任何帳密都能登入),但不涉及真實會員資料。Phase 1 換成 JWTAuthProvider(綁 DB + JWT 簽章)時,所有業務邏輯不用動。
錯誤類型
必須定義:ErrUserNotFound、ErrInvalidCredentials、ErrTokenExpired、ErrTokenInvalid、ErrUserAlreadyExists、ErrNotImplemented(Phase 0 stub 用)。
使用方式
api-server 啟動時透過 DI 注入 AuthProvider 實作:
// Phase 0
authProvider := stub.NewStaticAuthProvider()
// Phase 1
authProvider := jwt.NewJWTAuthProvider(db, jwtSecret)
router := api.NewRouter(authProvider, ...)
Middleware 設計:
router.Use(auth.RequireAuth(authProvider)) // 需登入的路由
詳細介面定義
見 features/feature-auth.md § 技術細節。
8.3 SessionStore 介面
目的
Tunnel session 狀態管理。api-server 和 remote-proxy 都需要查詢「某 user/pairing token 對應哪個 tunnel session」,Phase 0 兩個 binary 共用記憶體(同一個 process 或 shared memory);Phase 1 用 Redis 跨節點共享。
必須提供的能力
| 能力 | 說明 |
|---|---|
| CreatePairingToken | 建立一次性 pairing token,綁 user_id,15 min TTL |
| ConsumePairingToken | 驗證並消耗 pairing token,回傳對應 user_id |
| CreateSession | 建立 tunnel session,發 session token |
| GetSession | 從 session token 查 session(回 user_id、device_id、created_at 等) |
| RevokeSession | 撤銷 session(登出或使用者要求撤銷) |
| ListSessionsByUser | 查使用者所有活躍 session |
| UpdateSessionHeartbeat | 更新 session 最後心跳時間 |
| CleanupExpired | 清過期 session |
Phase 0 實作:MemorySessionStore
- 單一 process 內用 sync.Map / mutex
- api-server 和 remote-proxy 必須跑在同一個 process(用 goroutine 起兩個 server)OR
- 共享 memory store 透過 gRPC / HTTP(remote-proxy 呼叫 api-server 的 internal API 查 session)
Architect 需決策:Phase 0 的 api-server 和 remote-proxy 是一個 process 還是兩個? 兩個 binary 就是兩個 process,不能共享記憶體。 方案 A:兩個 process + internal gRPC 同步 session;方案 B:單 binary 啟動時透過參數選擇角色,Phase 0 都跑在一起。 建議 Phase 0 用方案 A(符合最終架構),即便兩個 process 都在本機跑,介面也走 HTTP/gRPC。
Phase 1 實作:RedisSessionStore
- 所有狀態存 Redis
- api-server 和 remote-proxy 都連同一個 Redis
- 支援多節點部署
資料模型(邏輯)
PairingToken:
- token (string, primary)
- user_id (string)
- created_at (timestamp)
- expires_at (timestamp)
- consumed (bool)
- consumed_at (timestamp, nullable)
Session:
- session_token (string, primary)
- user_id (string)
- device_id (string)
- pairing_token_used (string, FK)
- created_at (timestamp)
- last_heartbeat_at (timestamp)
- revoked (bool)
- revoked_at (timestamp, nullable)
- proxy_node_id (string, Phase 1 for routing)
8.4 ObjectStorage 介面
目的
統一模型檔(.nef)、未來使用者上傳檔(影片、圖片)、推論結果等二進位資料的儲存。
必須提供的能力
| 能力 | 說明 |
|---|---|
| Upload | 上傳檔案到指定 key |
| Download | 下載檔案 |
| Delete | 刪除檔案 |
| List | 列出某 prefix 下的所有 key |
| Exists | 檢查 key 是否存在 |
| GetSize | 取得檔案大小 |
| GetDownloadURL | 取得下載 URL(Phase 0 走自己的 API,Phase 1 用 presigned) |
| GetUploadURL | (Phase 1)presigned upload URL |
Key 規範
採統一的 key schema,讓未來換 S3 不需要 migrate:
models/{user_id}/{model_id}.nef ← 使用者上傳
models/system/{model_id}.nef ← 預設模型(所有 user 共享)
uploads/{user_id}/{upload_id}.{ext} ← 使用者臨時上傳(影片、圖片等)
converter-inputs/{job_id}/{filename} ← 轉檔的來源檔(臨時)
converter-outputs/{job_id}/model.nef ← 轉檔結果
Phase 0 實作:LocalFSStorage
- 存在
./data/storage/{key} GetDownloadURL回傳/api/storage/download?key=...,api-server 自己 stream 出去- 需做 key 合法性檢查(不能 path traversal)
Phase 1 實作:S3Storage / MinIOStorage
- 標準 AWS S3 SDK 或 minio-go
- 原生 presigned URL
- Bucket 策略:
visiona-models:公開讀權限 forsystem/prefix,其他要 presignedvisiona-uploads:全私有
檔案大小限制
- Phase 0:100 MB(記憶體限制)
- Phase 1:500 MB(model)、5 GB(video upload)
8.5 ConverterClient 介面
目的
呼叫 kneron_model_converter 服務做格式轉檔。Phase 0 / 1 用 stub,Phase 2 接真實 API。
介面能力
見 features/feature-converter-integration.md 的完整 API 合約。
核心能力:
Submit(ConvertRequest) → ConvertJobGetStatus(jobID) → ConvertJobDownload(jobID) → io.ReadCloserCancel(jobID) → error
現況:converter API 尚未存在
visionA-backend 先定義介面,對 converter 團隊提出 spec 需求。流程:
- Phase 0:visionA-backend 定義
ConverterClientinterface +StubConverterClient實作 - Phase 0:PM Agent 把 API spec(
feature-converter-integration.md)正式交付給 converter 團隊 - Phase 0-1:converter 團隊依 spec 實作 API
- Phase 2:visionA-backend 實作
HTTPConverterClient,換掉 stub
Stub 行為
StubConverterClient:
- Submit 回 fake job_id + status = queued
- GetStatus 第一次回 queued,第二次回 processing,第三次之後回 completed(給 fake download URL)
- Download 回一個內建的 fake
.nef(就是某個預設模型) - 讓前端能跑完整流程 UI 開發
8.6 BillingProvider 介面(Phase 2)
目的
抽象計費與訂閱管理,方便 Phase 2 接 Stripe / Paddle / 其他。
介面能力
| 能力 | 說明 |
|---|---|
| CreateCustomer | 建立計費客戶 |
| CreateSubscription | 建立訂閱 |
| CancelSubscription | 取消訂閱 |
| UpdateSubscription | 升降級 |
| ReportUsage | 上報使用量(usage-based pricing) |
| CreateCheckoutSession | 建立結帳 session(hosted checkout) |
| HandleWebhook | 處理 provider webhook(付款成功、失敗等) |
| GetInvoices | 列出 invoice |
Phase 0 / 1 都不做
Phase 0 / 1 visionA-backend 不存在任何 billing 相關程式碼。Phase 2 再從頭加。
8.7 給外部團隊的 API 合約清單
Phase 0 期間,需要對外溝通的契約:
對 kneron_model_converter 團隊
交付文件:features/feature-converter-integration.md
對方 Action Items:
- 審閱 API spec
- 評估實作時程
- 提供 dev 環境與 test API key
- 決定 webhook 簽章格式
- 決定 rate limit 數字
對 local-tool 團隊(同 repo)
Phase 0 期間 local-tool 完全不動。Phase 1 才考慮整合:
- local-tool 新增「配對到 visionA Cloud」功能
- 要共享哪些 Go 套件(tunnel client 可能可共用)
對 Innovedus IT / DevOps
Phase 0 後期,若要部署 staging 環境,需要:
- 一個子域名(例如
cloud.visiona.dev) - TLS 憑證(Let's Encrypt)
- Docker host 或 AWS account
- DNS 管理權
8.8 API 版本策略
URL 版本
所有 visionA Cloud API 採 URL 版本策略:
/api/v1/... ← Phase 0 從 v1 開始
/api/v2/... ← 未來 breaking change 時
相容性
- 同一 major version 內必須向後相容
- 遇到必須 breaking 時開新 major version,舊版保留至少 6 個月
- Phase 0 內部使用,允許 v1 小幅變動(不視為 breaking)
WebSocket URL
wss://api.visiona.cloud/api/v1/ws/...
wss://relay.visiona.cloud/v1/tunnel/connect