依 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)。
331 lines
11 KiB
Markdown
331 lines
11 KiB
Markdown
# 8. 介面契約(Interface Contracts) — visionA Cloud
|
||
|
||
> 父文件:[PRD.md](PRD.md)
|
||
>
|
||
> **本章節是 Phase 0 的核心產出之一**。Phase 0 有很多 TODO,介面契約讓未來能「無痛換實作」。
|
||
|
||
---
|
||
|
||
## 8.1 為什麼介面這麼重要
|
||
|
||
Phase 0 是雛形階段,很多子系統用 **stub 實作**,但:
|
||
|
||
- **雛形不實作 ≠ 雛形不設計**
|
||
- 介面(interface / contract)現在就要定義清楚
|
||
- 未來只換實作,不動業務邏輯
|
||
|
||
五大關鍵介面:
|
||
|
||
1. **AuthProvider** — 會員系統(Phase 0 stub → Phase 1 JWT)
|
||
2. **SessionStore** — Tunnel session 狀態(Phase 0 in-memory → Phase 1 Redis)
|
||
3. **ObjectStorage** — 模型檔儲存(Phase 0 local fs → Phase 1 S3/MinIO)
|
||
4. **ConverterClient** — 轉檔服務(Phase 0 stub → Phase 2 真實 API)
|
||
5. **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` 實作:
|
||
|
||
```go
|
||
// Phase 0
|
||
authProvider := stub.NewStaticAuthProvider()
|
||
|
||
// Phase 1
|
||
authProvider := jwt.NewJWTAuthProvider(db, jwtSecret)
|
||
|
||
router := api.NewRouter(authProvider, ...)
|
||
```
|
||
|
||
**Middleware 設計**:
|
||
```go
|
||
router.Use(auth.RequireAuth(authProvider)) // 需登入的路由
|
||
```
|
||
|
||
### 詳細介面定義
|
||
|
||
見 [features/feature-auth.md § 技術細節](features/feature-auth.md#技術細節給-architect-參考)。
|
||
|
||
---
|
||
|
||
## 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`:公開讀權限 for `system/` prefix,其他要 presigned
|
||
- `visiona-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](features/feature-converter-integration.md) 的完整 API 合約。
|
||
|
||
**核心能力**:
|
||
|
||
- `Submit(ConvertRequest) → ConvertJob`
|
||
- `GetStatus(jobID) → ConvertJob`
|
||
- `Download(jobID) → io.ReadCloser`
|
||
- `Cancel(jobID) → error`
|
||
|
||
### 現況:converter API 尚未存在
|
||
|
||
**visionA-backend 先定義介面,對 converter 團隊提出 spec 需求**。流程:
|
||
|
||
1. Phase 0:visionA-backend 定義 `ConverterClient` interface + `StubConverterClient` 實作
|
||
2. Phase 0:PM Agent 把 API spec(`feature-converter-integration.md`)正式交付給 converter 團隊
|
||
3. Phase 0-1:converter 團隊依 spec 實作 API
|
||
4. 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](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
|
||
```
|
||
|
||
---
|
||
|
||
## 連結
|
||
|
||
- 上一章:[非功能性需求](nonfunctional.md)
|
||
- 下一章:[成功指標](success-metrics.md)
|
||
- 跳回:[PRD 索引](PRD.md)
|