依 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)。
513 lines
25 KiB
Markdown
513 lines
25 KiB
Markdown
# Design Doc — visionA Cloud
|
||
|
||
## Metadata
|
||
- **作者**:Architect Agent
|
||
- **狀態**:Draft(待三方交叉審閱)
|
||
- **最後更新**:2026-04-21
|
||
- **範圍**:visionA-frontend + visionA-backend 的雲端版架構;定位為「POC 升格為產品」的雛形骨架
|
||
- **相關文件**:
|
||
- 健檢:`.autoflow/00-onboarding/health-check.md`
|
||
- PRD:`.autoflow/02-prd/PRD.md`(PM 主導,平行產出中)
|
||
- 設計規格:`.autoflow/03-design/`(Design 主導,平行產出中)
|
||
- TDD:`.autoflow/04-architecture/TDD.md`(本文檔的姊妹檔,實作細節)
|
||
- ADRs:`.autoflow/04-architecture/adr/adr-*.md`
|
||
|
||
---
|
||
|
||
## 1. 系統總覽(High-Level Overview)
|
||
|
||
visionA Cloud 把「使用者的 Kneron 裝置」透過雲端反向代理提供給瀏覽器操作。核心流向:
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────────────────┐
|
||
│ 使用者瀏覽器 │
|
||
│ visionA-frontend (Next.js) │
|
||
└─────────────────┬────────────────────────────┬────────────────────────┘
|
||
│ HTTPS REST │ WSS (訂閱)
|
||
│ │
|
||
┌─────────────────▼────────────────┐ ┌─────────▼──────────────────────┐
|
||
│ visionA-backend / api-server │ │ visionA-backend / api-server │
|
||
│ (無狀態, 水平擴展) │ │ (WebSocket 訂閱, 同 binary) │
|
||
└─────────────────┬────────────────┘ └─────────┬──────────────────────┘
|
||
│ Session 查詢(in-mem 同進程 / Redis 跨進程) │
|
||
▼ ▼
|
||
┌────────────────────────────────────────────────────────────────────────┐
|
||
│ 共享 Session Store(routing table) │
|
||
│ 雛形:in-memory(單進程) / Phase 1:Redis │
|
||
└─────────────────────────────┬──────────────────────────────────────────┘
|
||
│ 查到該 token 的 tunnel 在哪個 proxy 節點
|
||
▼
|
||
┌────────────────────────────────────────────────────────────────────────┐
|
||
│ visionA-backend / remote-proxy (有狀態) │
|
||
│ 持有 yamux.Session map[token]→Session │
|
||
└────────────────────────────┬───────────────────────────────────────────┘
|
||
│ WebSocket + yamux(長連線)
|
||
▼
|
||
┌────────────────────────────────────────────────────────────────────────┐
|
||
│ 使用者電腦 local agent │
|
||
│ (local-tool 或 tunnel-only client;連 remote-proxy 出站) │
|
||
│ 本機 HTTP server :3721 │
|
||
└────────────────────────────┬───────────────────────────────────────────┘
|
||
│ USB
|
||
▼
|
||
┌──────────────────┐
|
||
│ Kneron 裝置 │
|
||
│ KL520 / KL720 │
|
||
└──────────────────┘
|
||
|
||
外部依賴(均透過介面,雛形 stub):
|
||
├─ kneron_model_converter (`/api/converter/*` stub, TODO)
|
||
├─ S3-compatible 物件儲存 (LocalFSStore for prototype)
|
||
└─ PostgreSQL + Auth provider (in-memory for prototype, ADR-005)
|
||
```
|
||
|
||
**關鍵概念**:
|
||
- **Session key = pairing token**,一個 token 對應一個 yamux tunnel。
|
||
- **API Server 把收到的請求**,查 session store 找到對應 token 的 tunnel,**轉發**到該 tunnel 上的 remote-proxy 節點,**由 proxy 打 yamux stream** 送進 local agent,拿到 response 再回傳瀏覽器。
|
||
- **WebSocket 訂閱**同理:api-server 升級 WS 後,在 session store 裡找到 tunnel,把 WS 幀透過 yamux stream 送給 local agent 的 `/ws/*`。
|
||
|
||
---
|
||
|
||
## 1.9 Non-Goals(雛形明確不做的事,2026-04-22 M-8)
|
||
|
||
以下項目在 **Phase 0 雛形階段刻意不支援**,列出以避免誤解:
|
||
|
||
| # | Non-Goal | 原因 | 對應 Phase |
|
||
|---|----------|------|-----------|
|
||
| N1 | **多 instance 部署** — 雛形僅支援單一 `api-server` instance + 單一 `remote-proxy` instance | 無共享 state 機制(無 Redis、無 DB session metadata);開兩個 `api-server` 會因為 session lookup 散到不同節點而壞掉;開兩個 `remote-proxy` 會讓 tunnel 註冊到 A 節點、但 api-server 的 `ProxyClientStore` 配置只連 B,查無 session | Phase 1 |
|
||
| N2 | **真實使用者註冊 / 登入** — `StaticAuthProvider` 無論帳密都回 demo-user | Auth 不是 POC → 產品驗證的核心風險;整合 Clerk / 自建 OIDC 需 2-4 週,放 Phase 1 | Phase 1 |
|
||
| N3 | **真實 DB 持久化** — 所有 repository 走 in-memory map | ADR-005 明定;Phase 1 實作 `Postgres*Repository` | Phase 1 |
|
||
| N4 | **真實 Converter 整合** — `/api/converter/*` 走 Stub | Converter 團隊 API 規格未定;見 `api/api-converter-contract.md` | Phase 1 |
|
||
| N5 | **Pairing Token 兩階段升級** — 雛形 Pairing 與 Session 合併為單一 token | 單 token 比對流程足以驗證 tunnel;兩階段 DB transaction 需 Postgres | Phase 1(必須在公開 release 前完成)|
|
||
| N6 | **HTTPS / WSS** — 雛形走明文 HTTP / WS | TLS termination 由 Phase 1 LB / reverse proxy 處理 | Phase 1 |
|
||
| N7 | **Rate limiting / Audit log** — 雛形無 | 雛形只跑 dev / 內測 | Phase 1 |
|
||
| N8 | **Observability(metrics / trace / alert)** — 雛形只有 stdout log | 雛形不追求 SLO | Phase 1 |
|
||
| N9 | **Local agent 自己的 binary(visionA/local-agent/)** — 雛形用 POC `edge-ai-server` 當 tunnel client 暫代 | 讓雛形專注在 backend 協定正確性;Phase 1 從 local-tool 複製起步 | Phase 1 |
|
||
| N10 | **`cmd/dev-all-in-one` 單進程合併模式** | 會讓部署拓撲與 Production 不一致,反而掩蓋 bug | 永不做 |
|
||
|
||
**重要後果**:
|
||
- 開第二個 `api-server`(例如為了測試無狀態性)會因 `ProxyClientStore` 只配一個 `VISIONA_PROXY_INTERNAL_URL`,導致兩個 api-server 都指向同一個 remote-proxy,這**可以**運作(真實多 instance 場景)。但開兩個 `remote-proxy` 會壞 — tunnel 連上的是 A,但 api-server 可能查到 B(視 LB 分配),B 沒有 session 就回 404。
|
||
- 雛形的部署指引明確:**恰好 1 個 api-server + 恰好 1 個 remote-proxy**。
|
||
- Phase 1 解多 `remote-proxy` 節點時會補 session metadata 共享機制(見 `tunnel.md` §5.4 + 待產出的新 ADR)。
|
||
|
||
---
|
||
|
||
## 2. 系統邊界與組件
|
||
|
||
### 2.1 visionA-frontend(客戶端)
|
||
|
||
| 屬性 | 值 |
|
||
|------|-----|
|
||
| 技術 | Next.js 16 App Router + React 19 + TypeScript + Tailwind 4 + Radix + Zustand 5 + Lucide + Recharts + driver.js |
|
||
| 部署 | 靜態打包 + CDN(雛形用 Next.js 本地 dev server;Phase 1 上 Vercel / Cloudflare Pages / S3+CloudFront)|
|
||
| 狀態 | 無(純前端,所有資料 via API)|
|
||
| Auth | JWT / session cookie(雛形 stub;Phase 1 真實) |
|
||
| 連線對象 | **只連 `visionA-backend/api-server`**,**不直連** remote-proxy 或 local agent |
|
||
|
||
**從 local-tool 搬來的內容**:所有頁面、components、stores、i18n、UI styling;改 API base URL,移除 localhost hardcode。
|
||
|
||
**新增**:
|
||
- `/login`、`/register`(雛形骨架,Auth stub)
|
||
- `/account`(雛形骨架)
|
||
- `/devices/pair`(輸入 / 產生 pairing token)
|
||
- `/clusters`(從 POC 搬)
|
||
|
||
### 2.2 visionA-backend / api-server(cmd/api-server)
|
||
|
||
| 屬性 | 值 |
|
||
|------|-----|
|
||
| Binary | `cmd/api-server/main.go` |
|
||
| 狀態 | **無狀態**(所有狀態在 DB / session store / 物件儲存)|
|
||
| 水平擴展 | ✅ 多實例 + 前置 L7 load balancer |
|
||
| 對外 | HTTP :3001(雛形預設);正式上 443 |
|
||
| 職責 | REST API、WebSocket 升級、Auth、權限、把請求 proxy 到 remote-proxy、Storage presigned URL、Converter 呼叫 |
|
||
|
||
### 2.3 visionA-backend / remote-proxy(cmd/remote-proxy)
|
||
|
||
| 屬性 | 值 |
|
||
|------|-----|
|
||
| Binary | `cmd/remote-proxy/main.go` |
|
||
| 狀態 | **有狀態**(持有 yamux.Session)|
|
||
| 水平擴展 | 可多實例;session 路由交給 session store |
|
||
| 對外(local agent) | WS :3800(POC 預設)|
|
||
| 對內(api-server) | HTTP :3801(內部 API,不對外)|
|
||
| 職責 | 接受 local agent 的 tunnel 連線、維護 yamux session、轉發 api-server 送來的請求 |
|
||
|
||
### 2.4 共享狀態:Session Store(2026-04-22 Q1 裁決 C + ADR-006:雛形即雙 binary,無 Redis)
|
||
|
||
| 雛形實作 | Phase 1 實作 |
|
||
|---------|-------------|
|
||
| Session state **完全由 remote-proxy 持有**(in-memory);api-server 無狀態,透過 internal HTTP 向 remote-proxy 查詢 | 多 remote-proxy 節點間的 metadata 共享機制**待 Phase 1 評估**(Redis / gossip / sticky LB 等 — 不預設採用 Redis) |
|
||
|
||
**雛形設計**:
|
||
- `cmd/remote-proxy`:**唯一**持有 `*yamux.Session` 的 process;以 `InMemoryStore` 儲存
|
||
- `cmd/api-server`:無狀態;`internal/session` 載入 `ProxyClientStore`(internal HTTP client)
|
||
- 兩 binary 透過 `internal/forward/*` 和 `internal/session/*` endpoints 溝通(詳見 `api/api-internal.md`)
|
||
|
||
**Non-Goal(刻意不做)**:
|
||
- **不做 `cmd/dev-all-in-one` 單進程合併模式** — 會讓部署拓撲與 Production 不一致,反而讓 bug 在雛形階段無法暴露
|
||
- **不引入 Redis** — POC 從未用過(見 ADR-006)
|
||
|
||
詳細見 TDD §2 與 `tunnel.md` §5。
|
||
|
||
### 2.5 local agent(使用者電腦)
|
||
|
||
三種形態:
|
||
|
||
1. **local-tool**(桌面版 Wails 應用):原本離線用,加一個 config 選項「啟用雲端模式」後,額外啟動 tunnel client 連 remote-proxy。**local-tool 本身不動**;雲端模式是新增 opt-in 模組,不影響離線使用。
|
||
2. **tunnel-only client**(未來):專為「headless / server 使用者」提供的輕量 Go binary,只跑 tunnel client + 最小 HTTP server,不含 Wails / UI。雛形不做。
|
||
3. **既有 edge-ai-platform**:POC 的 tunnel client 可直接繼續用來做技術驗證。
|
||
|
||
### 2.6 外部依賴
|
||
|
||
| 依賴 | 雛形 | Phase 1 |
|
||
|------|------|--------|
|
||
| `kneron_model_converter` | Stub:`/api/converter/*` 回假資料 | 真實打 converter API |
|
||
| 物件儲存 | `LocalFSStore`(本地檔案)| `S3Store`(AWS S3 / Cloudflare R2 / MinIO)|
|
||
| 資料庫 | in-memory repositories | PostgreSQL |
|
||
| Auth | `StaticAuthService`(永遠 demo-user)| 真實 Auth |
|
||
| Observability | stdout log | Prometheus + Loki + OpenTelemetry |
|
||
|
||
---
|
||
|
||
## 3. 12-Factor 合規策略
|
||
|
||
| # | Factor | 雛形做法 | Phase 1 做法 |
|
||
|---|--------|---------|-------------|
|
||
| 1 | Codebase | ✅ Monorepo (`visionA/visionA-frontend` + `visionA/visionA-backend`) | 同 |
|
||
| 2 | Dependencies | ✅ `go.mod` + `package.json` 明確宣告;無系統隱式依賴 | 同 |
|
||
| 3 | Config | ✅ 全走 env vars(`VISIONA_*` prefix);`internal/config` 讀取 | 同 + 密鑰用 Secrets Manager |
|
||
| 4 | Backing services | ✅ Storage / DB / Auth 全走 interface | 同(實作 swap)|
|
||
| 5 | Build / Release / Run | ⚠️ 雛形只有 `make build` + `docker-compose` | Build:CI 產出 image;Release:tag + manifest;Run:K8s / ECS deploy |
|
||
| 6 | Processes | ✅ api-server 無狀態;state 全走 Store interface | 同 |
|
||
| 7 | Port binding | ✅ api-server `:3001`,remote-proxy 對 agent `:3800`,對 api `:3801` | 同(port 由 env 指定)|
|
||
| 8 | Concurrency | ✅ 單進程多 goroutine;跨進程靠 SessionStore | 多實例 + auto-scale |
|
||
| 9 | Disposability | ⚠️ 雛形 SIGTERM graceful shutdown;tunnel 斷線有重連 | 同 + drain period |
|
||
| 10 | Dev/Prod Parity | ✅ Docker 一致化 | 同 |
|
||
| 11 | Logs | ⚠️ 雛形 stdout(已有 broadcaster WebSocket 看 log)| 結構化 JSON log + 集中收集 |
|
||
| 12 | Admin processes | ⚠️ 雛形手動 | 後台 CLI / admin API |
|
||
|
||
**雛形妥協**:log 未結構化、缺 metrics、缺 trace。記錄為 TODO,不影響功能驗證。
|
||
|
||
---
|
||
|
||
## 4. 水平擴展策略
|
||
|
||
### 4.1 api-server(無狀態)
|
||
|
||
- 多實例,前置 L7 load balancer(ALB / nginx / Cloud Run 自動)
|
||
- Sticky session **不需要**(無狀態)
|
||
- 擴展訊號:CPU > 60% 或 p95 延遲 > 300ms
|
||
|
||
### 4.2 remote-proxy(有狀態)
|
||
|
||
remote-proxy 的狀態是 `map[token]*yamux.Session`。擴展的挑戰:
|
||
|
||
- Local agent 連 proxy 時,會連到某一個**具體節點**(由 LB 分派,例:ALB + IP hash 或 DNS round-robin)
|
||
- 後續 api-server 要送請求到該 token 的 tunnel 時,需要**知道這條 tunnel 在哪個 proxy 節點**
|
||
|
||
**解決方案:Session Store 記錄 routing table**
|
||
|
||
```
|
||
Session Store 儲存(Redis Hash):
|
||
session:{token} = {
|
||
proxy_node_id: "proxy-2",
|
||
proxy_internal_url: "http://proxy-2.internal:3801",
|
||
connected_at: "...",
|
||
last_seen: "..."
|
||
}
|
||
```
|
||
|
||
api-server 送請求的流程:
|
||
|
||
```
|
||
1. 收到 API 請求(例:GET /api/devices + X-Pairing-Token: xxx)
|
||
2. 查 Session Store:token → proxy_internal_url
|
||
3. 呼叫該 proxy 的 internal endpoint:POST http://proxy-2.internal:3801/forward
|
||
- body = 原請求(method, path, headers, body)
|
||
4. proxy-2 收到後,從自己 in-memory map 取 yamux.Session
|
||
5. 開 stream,傳 HTTP request
|
||
6. 等 response → 回給 api-server → 回給瀏覽器
|
||
```
|
||
|
||
### 4.3 雛形簡化(單節點)
|
||
|
||
雛形期 api-server 與 remote-proxy **同進程**,SessionStore 就是本機 map,**跳過跨節點轉發**:
|
||
|
||
```
|
||
1. 查 SessionStore(local map)取 yamux.Session
|
||
2. 直接開 stream 送請求
|
||
```
|
||
|
||
Phase 1 時把 SessionStore 換 Redis、引入「proxy internal HTTP API」,不改 API handler 程式碼。
|
||
|
||
### 4.4 擴展邊界
|
||
|
||
| 資源 | 估算 |
|
||
|------|------|
|
||
| remote-proxy 單節點 tunnel 數上限 | ~10K(受檔案描述符 / 記憶體限制,每個 tunnel ~100KB)|
|
||
| api-server 單節點 QPS | ~5K(Gin + Go 典型值)|
|
||
| Session Store(Redis)單節點 QPS | ~100K |
|
||
|
||
雛形單節點即可撐 demo / 早期內測。Phase 1 再引入多節點。
|
||
|
||
---
|
||
|
||
## 5. 資料流
|
||
|
||
### 5.1 前端呼叫 REST API(最常見情境)
|
||
|
||
```
|
||
[瀏覽器] GET /api/devices
|
||
↓ HTTPS + Cookie/JWT
|
||
[api-server] authMiddleware 驗 user
|
||
↓ 從 user 找對應 pairing token(雛形 env 寫死;Phase 1 查 DB)
|
||
[api-server] 查 SessionStore(token) 取 proxy location
|
||
↓
|
||
(單節點雛形:直接拿到 yamux.Session)
|
||
(多節點:轉發 http://proxy-X.internal:3801/forward)
|
||
↓
|
||
[remote-proxy] 從 session open yamux stream
|
||
↓ 把 HTTP request 寫進 stream(沿用 POC 方法)
|
||
[local agent] tunnel client accept stream
|
||
↓ 轉發到本機 127.0.0.1:3721
|
||
[local HTTP server] 處理 /api/devices → 回 response
|
||
↓ 經 yamux stream 回到 remote-proxy
|
||
↓ remote-proxy 回 api-server
|
||
↓ api-server 回瀏覽器
|
||
[瀏覽器] 得到裝置列表
|
||
```
|
||
|
||
### 5.2 前端開 WebSocket 訂閱(例:裝置事件串流)
|
||
|
||
```
|
||
[瀏覽器] WS /ws/devices/events (connect)
|
||
↓
|
||
[api-server] authMiddleware → 升級 WS
|
||
↓ 查 SessionStore → 取 yamux session
|
||
↓ 開 stream,把 HTTP upgrade request 寫進 stream(沿用 POC proxyWebSocket 邏輯)
|
||
[local agent] handleStream 偵測 upgrade → 連本機 raw TCP → 雙向 copy
|
||
[local HTTP server] /ws/devices/events 升級 → 開始推事件
|
||
↓ 事件 frame → yamux stream → api-server → 瀏覽器
|
||
```
|
||
|
||
POC 已完整實作此流程,visionA 直接沿用。
|
||
|
||
### 5.3 模型上傳(**不走 tunnel**)
|
||
|
||
```
|
||
[瀏覽器] 點「上傳模型」
|
||
↓
|
||
[api-server] POST /api/models/upload-url
|
||
↓ 呼叫 Storage.PresignedPutURL → 回給瀏覽器
|
||
[瀏覽器] 直接 PUT 檔案到 S3 / LocalFS(不走 tunnel,省 tunnel 頻寬)
|
||
↓ 上傳完成後
|
||
[瀏覽器] POST /api/models/register (告知 api-server 上傳完成 + metadata)
|
||
↓
|
||
[api-server] 寫入 model repository
|
||
```
|
||
|
||
**為何不走 tunnel?**
|
||
- 模型檔可達百 MB,走 tunnel = 占用使用者本地頻寬兩次(上傳 → 雲端 → 下載)
|
||
- S3 presigned URL 讓瀏覽器直連,scalable
|
||
- 需要時 api-server 再叫 local agent「下載這個 model 的 URL」,由 local agent 用自己網路下載
|
||
|
||
### 5.4 轉檔呼叫(Phase 1)
|
||
|
||
```
|
||
[瀏覽器] POST /api/converter/convert {source_url, target_chip: "kl520"}
|
||
↓
|
||
[api-server] 驗 user → 呼叫 kneron_model_converter API
|
||
↓ (非同步)取得 job_id
|
||
[api-server] 回 job_id
|
||
↓ 輪詢 /api/converter/jobs/{id} 或 WS 訂閱進度
|
||
```
|
||
|
||
雛形 api-server 提供端點,但回 stub(job_id 假的,狀態永遠 `queued`)。
|
||
|
||
---
|
||
|
||
## 6. 安全架構
|
||
|
||
### 6.1 通訊加密
|
||
|
||
- **瀏覽器 ↔ api-server**:HTTPS(Phase 1);雛形開發用 HTTP
|
||
- **api-server ↔ remote-proxy 內部**:雛形同進程無需加密;多節點 Phase 1 考慮 mTLS 或 VPC-only
|
||
- **local agent ↔ remote-proxy**:WSS(Phase 1);雛形 WS。TLS 由前端反代(ALB / nginx)終止
|
||
|
||
### 6.2 認證 / 授權
|
||
|
||
| 物件 | 雛形 | Phase 1 |
|
||
|------|------|--------|
|
||
| 使用者登入 | `StaticAuthService` 永遠過 | OIDC / SAML / 自建 |
|
||
| 前端 call API | 無 token 也接受(stub)| JWT / session cookie |
|
||
| local agent 連 proxy | `VISIONA_PAIRING_TOKEN` env 寫死 | DB-backed Pairing Token(見 ADR-003)|
|
||
| API handler 授權 | 一律通過 | RBAC:`user 只能看自己的 device` |
|
||
|
||
### 6.3 CORS / CSRF
|
||
|
||
- **CORS**:api-server 雛形設 `Access-Control-Allow-Origin: *`(dev);Phase 1 白名單
|
||
- **CSRF**:用 JWT in header(非 cookie)可天然免疫;若用 session cookie 則需 CSRF token
|
||
- **WebSocket Origin**:本 repo 的 `local-tool/server/internal/api/ws/origin.go` 已有成熟 allowlist,搬過來用
|
||
|
||
### 6.4 Pairing Token 安全
|
||
|
||
詳見 ADR-003。重點:
|
||
- 雛形:env 寫死
|
||
- Phase 1:DB 存 `sha256(token)`,不存明文;15min expiry;可 revoke;綁 user_id + device_id
|
||
|
||
### 6.5 輸入驗證
|
||
|
||
- 模型檔案格式驗證(雛形僅 extension;Phase 1 加 magic bytes + file size limit)
|
||
- API body schema 驗證(用 struct tags `binding:"required"` 或 `go-playground/validator`)
|
||
|
||
### 6.6 STRIDE 分析(重點項目)
|
||
|
||
| 威脅 | 防護 | 雛形 / Phase 1 |
|
||
|------|------|--------------|
|
||
| 偽冒(Spoofing):他人冒用 token | `sha256(token)` 存 DB + rate limit;雛形 env 隔離 | Phase 1 必做 |
|
||
| 竄改(Tampering):中間人改 request | TLS | Phase 1 |
|
||
| 否認(Repudiation) | Audit log | Phase 1 |
|
||
| 資訊洩露(Information Disclosure) | TLS + least privilege log | Phase 1 |
|
||
| DoS | rate limit on `/tunnel/connect` + API | Phase 1 |
|
||
| 提權(EoP) | RBAC | Phase 1 |
|
||
|
||
**雛形期驗證技術,不預期公開部署**,以上多數 Phase 1 才補全。
|
||
|
||
---
|
||
|
||
## 7. 部署架構
|
||
|
||
### 7.1 雛形(開發 / 內測)
|
||
|
||
**方案 A:本機一鍵啟動**
|
||
|
||
```
|
||
docker-compose up
|
||
```
|
||
|
||
`docker-compose.yml`:
|
||
```
|
||
services:
|
||
api-server:
|
||
build: { context: ., dockerfile: docker/Dockerfile.api-server }
|
||
ports: ["3001:3001"]
|
||
environment:
|
||
VISIONA_SESSION_BACKEND: inmemory
|
||
VISIONA_STORAGE_BACKEND: localfs
|
||
VISIONA_AUTH_MODE: static
|
||
VISIONA_PAIRING_TOKEN: dev-token-abc123
|
||
volumes: ["./data:/data"]
|
||
|
||
remote-proxy:
|
||
build: { context: ., dockerfile: docker/Dockerfile.remote-proxy }
|
||
ports: ["3800:3800", "3801:3801"]
|
||
environment:
|
||
VISIONA_SESSION_BACKEND: inmemory
|
||
VISIONA_PAIRING_TOKEN: vAc_<32 hex> # 格式見 security.md §1.3
|
||
|
||
frontend:
|
||
build: { context: visionA-frontend }
|
||
ports: ["3000:3000"]
|
||
environment:
|
||
NEXT_PUBLIC_API_BASE: http://localhost:3001
|
||
```
|
||
|
||
**雛形交付物(Phase 0)**:上述雙 binary + docker-compose 即完整雛形。**不提供** `cmd/dev-all-in-one`(見 §2.4 Non-Goal)。
|
||
|
||
### 7.2 Phase 1(正式部署,草圖)
|
||
|
||
```
|
||
┌──────────┐
|
||
│ CDN │ (Cloudflare / CloudFront)
|
||
└────┬─────┘
|
||
│ static frontend
|
||
┌───────────────┴────────────────┐
|
||
│ │
|
||
┌────▼─────┐ ┌──────▼────┐
|
||
│ ALB │ │ NLB │ (for WebSocket, TCP pass-through)
|
||
└────┬─────┘ └──────┬────┘
|
||
│ HTTPS /api/*, /ws/* │ WSS /tunnel/connect
|
||
│ │
|
||
┌────▼───────────────┐ ┌────▼──────────────────┐
|
||
│ api-server cluster │◄──────────►│ remote-proxy cluster │
|
||
│ (K8s / ECS) │ Redis │ (K8s / ECS, StatefulSet│
|
||
│ stateless, N pods │ Session │ or DaemonSet) │
|
||
└────┬───────────────┘ Store └────┬──────────────────┘
|
||
│ │
|
||
├─► PostgreSQL (RDS) │
|
||
├─► S3 / R2 (object store) │
|
||
└─► Converter API │
|
||
│ WSS + yamux
|
||
▼
|
||
使用者電腦 local agent
|
||
```
|
||
|
||
**Cloud-agnostic**:所有組件(Redis、PG、S3)都有對應的 interface;可用 AWS / GCP / 自建。
|
||
|
||
### 7.3 CI/CD(雛形目標)
|
||
|
||
- `make build` → 兩個 binary
|
||
- `make docker-build` → 兩個 image
|
||
- `make docker-compose-up` → 本機跑起來
|
||
- GitHub Actions / GitLab CI(Phase 1)→ 自動 build + push registry + deploy
|
||
|
||
---
|
||
|
||
## 8. 觀測(Observability)
|
||
|
||
### 8.1 雛形
|
||
|
||
| 項目 | 做法 |
|
||
|------|------|
|
||
| Log | stdout;沿用 local-tool 的 log broadcaster(WS 看 log)|
|
||
| Metrics | 無;`/api/system/health` 回 ok 即可 |
|
||
| Trace | 無 |
|
||
| Alert | 無 |
|
||
|
||
### 8.2 Phase 1(TODO)
|
||
|
||
| 項目 | 做法 |
|
||
|------|------|
|
||
| Log | 結構化 JSON → Loki / CloudWatch Logs |
|
||
| Metrics | Prometheus export:`qps`, `p95_latency`, `tunnel_count`, `session_active_count` |
|
||
| Trace | OpenTelemetry SDK → Tempo / X-Ray |
|
||
| SLO | API 可用性 99.9%,p95 < 500ms;Tunnel 連線穩定度 99%(7 天滾動)|
|
||
| Alert | Grafana / PagerDuty |
|
||
|
||
---
|
||
|
||
## 9. ADR 索引
|
||
|
||
| 編號 | 標題 | 狀態 |
|
||
|------|------|------|
|
||
| [ADR-001](adr/adr-001-two-binaries.md) | 單一 Go 專案 / 雙 binary 結構 | Accepted |
|
||
| [ADR-002](adr/adr-002-tunnel-protocol.md) | 沿用 POC 的 Tunnel 協定(WebSocket + yamux)| Accepted |
|
||
| [ADR-003](adr/adr-003-pairing-token.md) | 以 Pairing Token 取代 SHA256(MAC) Token | Accepted |
|
||
| [ADR-004](adr/adr-004-storage-interface.md) | 模型儲存採用 S3-Compatible 介面 | Accepted |
|
||
| [ADR-005](adr/adr-005-no-db-auth-in-prototype.md) | 雛形階段不接真實 DB 與 Auth | Accepted |
|
||
|
||
---
|
||
|
||
## 10. 三方交叉審閱檢核清單
|
||
|
||
### 給 PM 審閱
|
||
- [ ] 所有 PRD 中列出的 P0 功能,是否在本 Design Doc 都有對應的技術路徑?
|
||
- [ ] 「雲端能操作使用者本地裝置」的核心價值主張是否由架構支撐?
|
||
- [ ] 雛形 vs Phase 1 的差異,PM 能否向使用者解釋「現在能做什麼、未來會做什麼」?
|
||
|
||
### 給 Design 審閱
|
||
- [ ] 「pairing token 輸入」頁面是否技術可行(API 已定義)?
|
||
- [ ] 模型上傳流程的 presigned URL 對 UX 是否清楚?
|
||
- [ ] tunnel 斷線時瀏覽器要怎麼顯示?(已在 §5.2 指出走 POC 同機制;需 Design 規劃 UI)
|
||
|
||
### 給 Architect 自我檢查
|
||
- [x] 每個 ADR 都有 Context / Decision / Consequences
|
||
- [x] 所有雛形實作都有明確的 Phase 1 替換計畫
|
||
- [x] 擴展策略說明完整(§4)
|
||
- [x] 安全架構覆蓋 STRIDE(§6.6)
|
||
|
||
---
|
||
|
||
**下一步**:閱讀 `TDD.md`(實作細節),以及各 ADR。
|