visionA/visionA-backend
jim800121chen 1231bf0ed2 feat(visionA-backend): Phase 0.8 conversion package — 5 endpoint + 8 個內部模組
Phase 0.8 把 kneron_model_converter 的轉檔功能整合進 visionA Cloud。
visionA backend 當 streaming proxy(upload)+ delegated download token broker(download)+
ownership trust boundary,converter / FAA / MC 三方零修改。

新增 internal/conversion/ 套件(8 個檔,~10,000 行 prod+test,117+ test cases,race -count=3 全綠):

- conversion.go:Service interface 5 method、Job/PromoteResult/InitJobInput types
- errors.go:13+ sentinel errors + ErrorCode/HTTPStatus mapping,對齊 conversion.md §6
- mc_token_client.go:service-to-service token (client_credentials grant) + DCL cache
  (exp - 15s 重取,per-scope cache),IssueDelegatedDownload(MC delegated download token)
  錯誤分 idp_misconfigured (4xx) / idp_unavailable (5xx) / download_token_failed / mc_token_unavailable
- converter_client.go:對 converter scheduler 4 method(InitJob multipart streaming /
  GetJob / Promote / ListInProgressJobs),InitJob 不 retry 5xx(streaming body 無法 replay)
- faa_client.go:對 FAA GET /files/{key} server-to-server pull,Phase A retry(GET 無 body
  可 replay)對齊 §9.1 retry 矩陣,streaming io.ReadCloser 透傳避 OOM
- ownership.go:in-memory job_id → user_id map + per-user mutex 防 thundering herd lazy rebuild
  (不同 user 平行 fetch,同 user 100 caller 收斂成 1 次),visionA 重啟靠 converter
  ListInProgressJobs(user) 重建
- flow.go:Service interface 整合層(5 method 串接 converter/FAA/MC/ownership)
  - InitJob 用 io.Pipe + multipart.Reader/Writer 重組 streaming proxy(黑名單 client user_id
    + 灌入 OIDC sub)
  - DownloadRedirectURL 自動觸發 promote(spec §1 Stage 3b),用 ensurePromoted helper
  - PromoteToModels 冪等(modelStore.FindBySourceJobID 為 source-of-truth)
  - OwnershipMismatch → ErrJobNotFound 不 forbidden(§7.2 防枚舉)
  - storage / modelStore 失敗包 ErrStorageUnavailable / ErrModelStoreUnavailable
    (視為 visionA 自身 500 而非 502 gateway,SRE alarm 才打對 team)

新增 internal/api/conversion.go(5 endpoint handler + main.go wire):
- POST /api/conversion/init(multipart streaming proxy,不呼叫 c.MultipartForm())
- GET  /api/conversion/active(lazy rebuild ownership)
- GET  /api/conversion/{job_id}(poll status)
- POST /api/conversion/{job_id}/promote-to-models(FAA pull → models 三段式)
- GET  /api/conversion/{job_id}/download(server-side HTTP 302 → FAA,token 不過 frontend
  JS,仿 FAA TestSite DownloadFileDirect pattern;Cache-Control: no-store)

5 個 endpoint 全部走 OIDC AuthMiddleware;user_id 從 cookie session 灌(trust boundary),
從不接受 client multipart form / JSON / query 的 user_id。
TestAllAPIEndpointsRequire401WithoutCookie 自動覆蓋新 5 endpoint regression 防呆。

新增 cmd/api-server/conversion_e2e_test.go(4 個 e2e 場景):
- TestConversionE2E_StreamingProxy(10MB body + trust boundary regression)
- TestConversionE2E_LazyRebuildAfterRestart(visionA 重啟仍能 /active)
- TestConversionE2E_Download302Redirect(驗 302 + Location header + token 不在 body)
- TestConversionE2E_ActiveJobConflict(409 + active_job 詳情)

修改 internal/config/{config,load}.go:新增 ConversionConfig 5 欄位
(ConverterBaseURL / FAABaseURL / TenantID / ServiceClientID / ServiceClientSecret)+
Enabled() helper(雙非空判定)。
修改 cmd/api-server/main.go:條件 wire(cfg.Conversion.Enabled() 為 true 才建 client + Service;
否則 Deps.Conversion=nil,handler 自動回 501)。
修改 .env.example:新增 Phase 0.8 區塊註解。
新增 cmd/api-server/conversion_adapters.go:narrow interface adapter(接既有
internal/model.Repository / internal/storage.Store → conversion.ModelStore / Storage,避免 import cycle)。

驗證:go test -race -count=3 ./... 17 packages 全綠 / go vet 0 warning / go build 成功。

對齊文件:
- .autoflow/04-architecture/adr/adr-014-conversion-integration.md
- .autoflow/04-architecture/conversion.md (TDD)
- .autoflow/04-architecture/api/api-conversion.md
- .autoflow/02-prd/features/feature-converter-integration.md
- .autoflow/03-design/wireframes/wireframe-conversion.md
- .autoflow/03-design/flows/flow-conversion.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:56:07 +08:00
..

visionA-backend

visionA Cloud 的後端服務。由 api-server(無狀態 REST/WS APIremote-proxy(有狀態 tunnel server兩個 binary 組成。


⚠️ Phase 0 雛形警告

這是雛形prototype版本不是生產交付物。 主要限制:

  • 單一 user永遠回 demo-user,無真正認證)
  • 所有狀態 in-memory重啟即消失無 DB / Redis
  • Storage 走 LocalFS無 S3
  • WebSocket proxy 尚未實作(所有 /ws/* 皆回 501
  • 單一 instance無水平擴展

完整限制見下方 雛形範圍與限制


架構總覽

                       ┌─────────────────────┐
                       │   Browser / curl    │
                       └──────────┬──────────┘
                                  │ REST / WS (3721)
                                  ▼
                   ┌──────────────────────────────┐
                   │   api-server                 │
                   │   (cmd/api-server)           │
                   │                              │
                   │   - REST + WS handler         │
                   │   - Auth middlewarestatic│
                   │   - ProxyClientStore          │
                   │     (查詢 session          │
                   │   - Forwarder                 │
                   │     (轉發 HTTP 到 tunnel   │
                   │   - LocalFS storage (/storage)│
                   │   無狀態,可水平擴展          │
                   └──────────┬───────────────────┘
                              │ internal HTTP (3801)
                              ▼
                   ┌──────────────────────────────┐
                   │   remote-proxy               │
                   │   (cmd/remote-proxy)         │
                   │                              │
                   │   - Tunnel server (ws://3800) │
                   │   - yamux session store       │
                   │   - /internal/forward/raw    │
                   │   - /internal/session/:token │
                   │                              │
                   │   ⚠️ 有狀態(單 instance    │
                   └──────────┬───────────────────┘
                              │ WebSocket + yamux (3800)
                              ▼
                   ┌──────────────────────────────┐
                   │   Local Agent                │
                   │   (在客戶端機器上跑)          │
                   │                              │
                   │   目前 demo 用 POC 的        │
                   │   edge-ai-server 當 client   │
                   └──────────────────────────────┘

詳細設計見:


技術堆疊

層級 技術 備註
語言 Go 1.26 go.mod 鎖定
HTTP framework Gin + gin-contrib/cors B4 導入
Tunnel 傳輸 gorilla/websocket + hashicorp/yamux 沿用 POC edge-ai-platform
Logging log/slogstdlib JSON handler結構化輸出
ID 生成 google/uuid request-id / demo seed
單元測試 stretchr/testify B2 導入
配置 12-Factor App 全走 env不寫死

快速啟動10 分鐘起步)

前置

  • Go 1.26+(本機 run或 Docker 27+(容器 run
  • macOS / LinuxWindows 未測試)

方式 A本機 go run(最快,適合開發)

cd visionA-backend

# 1) 一鍵跑 remote-proxy + api-server任一 Ctrl+C 兩個都會停)
make dev

# 另開 terminal 驗證:
curl http://localhost:3721/healthz
# {"status":"ok"}

curl -X POST http://localhost:3721/api/auth/login \
     -H "Content-Type: application/json" \
     -d '{"email":"demo@visionA.local","password":"any"}'
# {"success":true,"data":{"user":{"id":"demo-user",...},"access_token":"demo-access-token",...}}

方式 BDocker Compose接近生產拓撲

cd visionA-backend

# 1) 複製環境變數範本
cp .env.example .env
# (視情況編輯 .env — 通常預設就能跑)

# 2) 建 image + 啟動
make docker-compose-up

# 3) 驗證
curl http://localhost:3721/healthz
curl -X POST http://localhost:3721/api/auth/login \
     -H "Content-Type: application/json" \
     -d '{"email":"demo@visionA.local","password":"any"}'

# 4) 跟 logs
make docker-compose-logs

# 5) 停
make docker-compose-down

Ports

  • 3721api-server REST + WS對前端
  • 3800remote-proxy tunnel WS對 local agent
  • 3801remote-proxy internal HTTPcompose 內部,不對外)

如何用 POC edge-ai-server 驗證 tunnel

雛形不包含 local agentQ3 決策local agent 模組 Phase 1 才做)。要驗證 tunnel 整條鏈路,用 POC edge-ai-platform/edge-ai-server 當 tunnel client。

步驟

# Terminal 1 — 起 visionA-backend
cd visionA-backend
make dev
# 或 docker compose up

# Terminal 2 — 申請 pairing token雛形POST 就會配發一個)
curl -X POST http://localhost:3721/api/pairing/token \
     -H "Content-Type: application/json" \
     -d '{"name":"demo"}'
# { "success": true, "data": { "token": "vAc_...", "expires_at": "..." } }

# Terminal 3 — POC edge-ai-server 當 tunnel client 接上來
cd /path/to/edge-ai-platform
./dist/edge-ai-server \
  --relay-url=ws://localhost:3800/tunnel/connect \
  --relay-token=vAc_<貼上上一步的 token>

# Terminal 2 — 驗證 tunnel 已連上
curl -H "Authorization: Bearer demo-access-token" \
     http://localhost:3721/api/pairing/status
# { "success": true, "data": { "connected": true, ... } }

# 打到 local agent會被 forward 過 tunnel
curl -H "Authorization: Bearer demo-access-token" \
     http://localhost:3721/api/devices/scan

或:用 B3 的 fake tunnel client 寫小 demo

若不想起 POCcmd/api-server/b5_integration_test.go 裡的 startFakeTunnelClient 是現成的 60 行範例,直接複製到 cmd/tunnel-demo/main.go 就能跑。


目錄結構

visionA-backend/
├── cmd/
│   ├── api-server/         # REST/WS API server無狀態
│   │   ├── main.go
│   │   ├── seed.go         # --seed-demo-data 用的示範資料
│   │   ├── integration_test.go    # B4 端到端測試
│   │   └── b5_integration_test.go # B5 端到端測試(含 tunnel forward + model upload
│   └── remote-proxy/       # tunnel server有狀態持有 session in-memory
│       └── main.go
├── internal/
│   ├── api/                # API handlers + Gin router + middlewareB4 + B5
│   ├── auth/               # AuthService / AuthProvider / PairingStore雛形 Static + InMemory
│   ├── session/            # Store / Handle / Forwarder / ProxyClient
│   ├── device/             # Device domain + InMemoryRepository
│   ├── model/              # Model domain + InMemoryRepository
│   ├── cluster/            # Cluster domainPOC 複製dispatcher 留 TODO
│   ├── relay/              # tunnel server + internal forward APIPOC 改造)
│   ├── wsconn/             # WebSocket ↔ net.Conn adapterPOC 複製)
│   ├── converter/          # StubClientPhase 2 才實作)
│   ├── storage/            # Store interface + LocalFSStoreHMAC presigned URL
│   ├── config/             # Config + Load()12-Factor
│   └── logger/             # slog JSON logger wrapper
├── docker/
│   ├── Dockerfile.api-server     # multi-stagenon-roothealthcheck
│   ├── Dockerfile.remote-proxy
│   └── docker-compose.yml
├── .env.example            # 環境變數範本commit
├── .gitignore              # 已排除 .env / bin/ / data/
├── Makefile                # build / dev / test / docker-* 等 targets
├── go.mod / go.sum
└── README.md               # 本檔

API 端點摘要

完整規格見 .autoflow/04-architecture/api/api-spec.md

群組 端點 說明
System GET /healthz liveness/readiness無需認證
System GET /api/system/health tunnel / agent 連線狀態
System GET /api/system/info 版本 + build 資訊
Auth POST /api/auth/login 雛形永遠回 demo-user
Auth POST /api/auth/register 雛形 501
Auth GET /api/auth/me 當前 user 資訊
Pairing POST /api/pairing/token 申請 pairing tokenvAc_ + 32 hex
Pairing GET /api/pairing/status 目前 user 的 tunnel 狀態
Pairing GET /api/pairing/tokens 列出已簽發的 token
Pairing DELETE /api/pairing/tokens/:id revoke token
Devices GET /api/devices 列出裝置
Devices POST /api/devices/scan 觸發 local agent 掃 USB透過 tunnel
Devices DELETE /api/devices/:id unpair同時關 tunnel
Models POST /api/models/init 兩階段上傳 step 1拿 presigned PUT URL
Models PUT /storage/:signed 實際上傳檔案HMAC 驗簽)
Models POST /api/models/:id/finalize 兩階段上傳 step 2marked ready
Models GET /api/models 列出模型
Clusters GET /api/clusters 列出 cluster骨架
Storage GET /storage/:signed presigned download
Camera / Inference /api/cameras/*/api/inference/* proxy 到 local agent
WebSocket /ws/* 雛形 501B7 之後補)

錯誤格式(統一)

{
  "success": false,
  "error": {
    "code": "TUNNEL_DISCONNECTED",
    "message": "local agent 未連線或 tunnel 斷開",
    "request_id": "req_abc123"
  }
}

錯誤碼清單見 internal/api/errors.go


環境變數

詳見 .env.example。常用:

變數 預設 說明
VISIONA_API_PORT 3721 api-server listen port
VISIONA_TUNNEL_PORT 3800 remote-proxy 對 local agent 的 WS port
VISIONA_PROXY_INTERNAL_PORT 3801 remote-proxy 對 api-server 的內部 HTTP port
VISIONA_PROXY_INTERNAL_URL http://localhost:3801 api-server 連 remote-proxy 用docker compose 會覆為 http://remote-proxy:3801
VISIONA_SEED_DEMO_DATA false 啟動時塞示範資料device + model + pairing
VISIONA_STORAGE_SIGNING_SECRET dev-signing-secret-... presigned URL HMAC secret生產必改
VISIONA_STATIC_USER_ID demo-user 雛形 static auth 的 user id
VISIONA_MODEL_MAX_SIZE_MB 100 模型上傳大小上限
VISIONA_CORS_ALLOWED_ORIGINS http://localhost:3000 CORS 白名單(逗號分隔)
VISIONA_LOG_LEVEL info debug / info / warn / error

測試

# 所有單元測試 + integration test + race detector
make test-race

# 僅 go vet / gofmt check
make lint

# 詳細輸出
make test

覆蓋面:

  • 單元測試:internal/{auth,session,device,model,config,storage,api,relay,wsconn,logger,converter}
  • Integration
    • cmd/api-server/integration_test.goB4api-server → remote-proxy → fake tunnel
    • cmd/api-server/b5_integration_test.goB5完整端到端 — login / scan / model upload / tunnel disconnect

開發者指南

我想做的事 看哪裡
新增一個 REST endpoint internal/api/ 找類似的 handler 複製proxy 類用 newProxyHandler
改動 API 規格 先改 .autoflow/04-architecture/api/api-spec.md 再改 code
加環境變數 internal/config/config.go + load.go + .env.example + 本 README
改 tunnel 協定 .autoflow/04-architecture/tunnel.md + internal/relay/ + internal/session/forwarder.go
追蹤某個 Review 問題 .autoflow/05-implementation/review/

Known Issues

Tunnel client 不在本 repo

visionA-backend 只實作 tunnel server 端(internal/relay/ + internal/wsconn/tunnel client 由 visionA Agent 實作(從 POC edge-ai-platform/server/internal/tunnel/client.go 複製)。本 repo 過去曾保留一份 internal/tunnel/ 副本,但因從未被 import 且會造成「兩處需要同步修補」的維護負擔,已於 2026-04-21 刪除(決策見 .autoflow/04-architecture/adr/adr-008-tunnel-client-reuse.md)。

POC client.gobackoff() 有單位 mix bugattempt >= 1 時永遠回 30 秒visionA Agent 建立後需在自己的 repo 修復;本 repo 不再追蹤此 issue。

WebSocket proxy 未實作

所有 /ws/* endpoint 目前回 501 Not Implemented。原因:Forwarder.ForwardWebSocket 需要 http.Hijacker + 雙向 io.Copy 架構B7 範圍外。

前端呼叫 /ws/* 時會收到 JSON body { "success": false, "error": { "code": "NOT_IMPLEMENTED", ... } },瀏覽器 WebSocket 會 fail upgrade。


雛形範圍與限制

是什麼

  • 雙 binary 架構驗證api-server 無狀態 + remote-proxy in-memory session
  • REST + Tunnel 完整鏈路browser → api-server → internal HTTP → remote-proxy → yamux → local agent
  • 兩階段模型上傳init → PUT presigned → finalize
  • Docker image + docker-compose 可跑

不是什麼

項目 雛形 Phase 1+
Auth static一律 demo-user OIDC / Clerk
資料庫 無(全 in-memory PostgreSQL
Session 存放 remote-proxy 進程內 Redis支援水平擴展
檔案儲存 LocalFS./data/storage S3
支援多 user
Rate limiting
Audit log
WebSocket proxy 501 stub
TLS http only ALB / NLB termination
水平擴展 api-server 可remote-proxy 需加 shared session store

重啟即消失

make docker-compose-down 或 restart 後:

  • 所有 pairing token 消失
  • 所有 device / model 紀錄消失(除非 VISIONA_SEED_DEMO_DATA=true
  • Storage 檔案保留(./data/storage 被 mount 為 volume

Phase 1 路線圖

  • 真 authOIDC via Clerk / Auth0
  • PostgreSQL + Redis參考 .autoflow/04-architecture/database.md
  • S3 / R2 storage backend替換 LocalFSStore
  • WebSocket proxyhijack + 雙向 io.Copy
  • 多裝置 / 多 cluster 支援
  • Rate limiting + audit log
  • K8s / ECS deployment參考 .autoflow/04-architecture/build-deploy.md §7
  • CI/CD pipelineGitHub Actions
  • Local agent 模組(獨立 binary取代 POC edge-ai-server 當 tunnel client

目前實作進度

  • B1 專案初始化go.mod、目錄骨架、Makefile、.gitignore
  • B2 共用 internal/ 模組core interface + in-memory 實作 + 單元測試)
  • B3 cmd/remote-proxy + relay / tunnel / wsconn / cluster
  • B4 cmd/api-server + internal/api 骨架 + Forwarder + ProxyClient
  • B5 API handlers 雛形20+ endpoint 實作、兩階段上傳、tunnel forward
  • B6 Docker image + docker-composemulti-stage + non-root + healthcheck
  • B7 README + .env.example + Makefile 補完

完整任務紀錄見 .autoflow/progress.md