visionA/visionA-backend/.env.example
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

190 lines
7.8 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# visionA-backend 環境變數範本
#
# 使用方式:
# cp .env.example .env
# # 視情況修改 .env 內的值(尤其 VISIONA_STORAGE_SIGNING_SECRET 與 VISIONA_PAIRING_TOKEN
#
# ⚠️ 不要把 .env commit 進 git已在 .gitignore 中排除)
# 相關文件:
# - .autoflow/04-architecture/build-deploy.md §9變數對照表
# - internal/config/config.go每個欄位的定義
# ============================================================
# 共用
# ============================================================
# 日誌等級debug / info / warn / error
VISIONA_LOG_LEVEL=info
# ============================================================
# api-server
# ============================================================
# 對前端的 REST / WebSocket port對齊 local-tool 的 base URL 預設)
VISIONA_API_PORT=3721
# api-server 連 remote-proxy 的 internal HTTP base URL
# 本機 go run 時用 localhostdocker-compose 內部會被 compose 覆寫為 http://remote-proxy:3801
VISIONA_PROXY_INTERNAL_URL=http://localhost:3801
# Static user — Phase 0.7 security audit 後僅供 dev seedVISIONA_SEED_DEMO_DATA=true
# 與 unit test fixture 用;不再注入 api.Deps、stage/prod 留空無影響。
# 見 .autoflow/05-implementation/review/phase-0.7-security-audit.md C1。
VISIONA_STATIC_USER_ID=demo-user
# 啟動時 seed 示範資料device + model + pairing token方便前端 demo
VISIONA_SEED_DEMO_DATA=true
# CORS 白名單(逗號分隔)— 預設允許 frontend dev serverhttp://localhost:3000
VISIONA_CORS_ALLOWED_ORIGINS=http://localhost:3000
# ============================================================
# OIDC必填 — OB5 起 OIDC 是唯一認證路徑A1 起支援 public PKCE-only client
# ============================================================
# 必填欄位缺任何一項main.go 啟動時會 fatal log 退出。
#
# 對應 Innovedus Member Center 的 OIDC client 設定:
# - 在 Member Center 註冊一個 OAuth clientconfidential 或 public 皆可)
# - 取得 client_idpublic client 沒有 client_secret
# - 將 RedirectURL 加入 Member Center 的白名單
# Member Center 的 issuer不帶結尾斜線MC 的 issuer 末尾斜線必要時請保留)
# dev: http://localhost:5050
# stage: https://stage-9527.innovedus.com:7850/
# prod: https://members.innovedus.com
VISIONA_OIDC_ISSUER_URL=http://localhost:5050
# 在 Member Center 註冊的 OAuth client_id
VISIONA_OIDC_CLIENT_ID=visiona-cloud
# Client secretA1選填 — public PKCE-only client 留空)
# - 有值 → confidential client modeclient_secret + PKCE 雙保險)
# - 留空 → public PKCE-only client mode依靠 PKCE 防 code interception
# ⚠️ 不可 commitprod 用 Secrets Manager。Stage MC 配的 client `b8093fea...` 是 public留空。
VISIONA_OIDC_CLIENT_SECRET=
# Backend callback URL — 必須與 Member Center 註冊值完全一致
# dev: http://localhost:3721/api/auth/callback
# stage: https://stage-9527.innovedus.com:9527/api/auth/callback
# prod: https://api.visiona.cloud/api/auth/callback
VISIONA_OIDC_REDIRECT_URL=http://localhost:3721/api/auth/callback
# Frontend base URL — callback 完成後 302 redirect 的目的地
# dev: http://localhost:3000
# stage: https://stage-9527.innovedus.com:9527
# prod: https://app.visiona.cloud
VISIONA_FRONTEND_URL=http://localhost:3000
# Service clientclient_credentials grant— A1 預留欄位,**目前不啟用**。
# 將來 visionA-backend 需以服務身份呼叫 MC API 時(例如查詢使用者組織、推送通知)
# 才會接這條路。留空代表「不啟用」main.go 不會 wire。
# 對應 Stage 的 service client<see stage .env.stage>
VISIONA_OIDC_SERVICE_CLIENT_ID=
VISIONA_OIDC_SERVICE_CLIENT_SECRET=
# Cookie HMAC 簽章 secret 至少 32 byte 隨機字串prod 用 openssl rand -hex 32
VISIONA_SESSION_SECRET=CHANGE_ME_TO_RANDOM_64_BYTES_in_production
# Cookie 設定dev 預設 host-only / non-secureprod 改 .visiona.cloud + Secure=true
VISIONA_SESSION_COOKIE_NAME=visiona_session
VISIONA_SESSION_COOKIE_DOMAIN=
VISIONA_SESSION_COOKIE_SECURE=false
# Session TTL — 預設 7 天 absolute / 24h idle
VISIONA_SESSION_ABSOLUTE_TTL=168h
VISIONA_SESSION_IDLE_TTL=24h
# Relay 對外可達 URLagent tunnel 用)— POST /api/pairing/exchange 會回給 agent。
# 雛形為空時會 fallback 到 wss://relay.visionA.cloudplaceholder
# 實機請設為實際可達的 WSS URLwss://relay.visionA.cloud
VISIONA_RELAY_PUBLIC_URL=
# ============================================================
# remote-proxy
# ============================================================
# 對 local agent 的 WebSocket tunnel port
VISIONA_TUNNEL_PORT=3800
# 對 api-server 的 internal HTTP port不對外暴露
VISIONA_PROXY_INTERNAL_PORT=3801
# ============================================================
# Tunnel 心跳 / 掉線判定(對齊 tunnel.md §4.2
# ============================================================
VISIONA_TUNNEL_HEARTBEAT_INTERVAL=10s
VISIONA_TUNNEL_IDLE_TIMEOUT=30s
# ============================================================
# StorageLocalFS — Phase 0 雛形Phase 1 會改 S3
# ============================================================
# 儲存根目錄容器內docker-compose 已 mount 成 volume
VISIONA_STORAGE_BACKEND=localfs
VISIONA_STORAGE_LOCALFS_ROOT=./data/storage
# 瀏覽器 / 上傳 client 看到的 presigned URL base
# 本機開發http://localhost:3721/storage
# docker-compose demo同上透過 host port mapping
VISIONA_STORAGE_BASE_URL=http://localhost:3721/storage
VISIONA_STORAGE_LOCALFS_BASE_URL=http://localhost:3721/storage
# HMAC 簽章 secret — 用於 LocalFS presigned URL 與Phase 1pairing token hash
# ⚠️ 生產環境必改openssl rand -hex 32 產生 64 字元 hex
VISIONA_STORAGE_SIGNING_SECRET=CHANGE_ME_IN_PRODUCTION_use_openssl_rand_hex_32
# ============================================================
# Model 上傳限制
# ============================================================
# 單檔上限MB— Phase 0 規範 100 MBPRD §8.4
VISIONA_MODEL_MAX_SIZE_MB=100
# ============================================================
# Pairinglocal agent ↔ remote-proxy 配對)
# ============================================================
# 格式vAc_ + 32 hex見 security.md §1.3
# 建議用vAc_$(openssl rand -hex 16)
# 留空代表雛形 InMemoryPairingStore 會動態配發(前端呼叫 POST /api/pairing/token
VISIONA_PAIRING_TOKEN=
# ============================================================
# Phase 0.8 — 轉檔功能整合converter / FAA / Member Center service token
# ============================================================
# 對齊 .autoflow/04-architecture/conversion.md §5.3
#
# 啟用判定:當 VISIONA_CONVERTER_BASE_URL 與 VISIONA_FAA_BASE_URL 都非空時,
# main.go 才會 wire conversion.Service其中之一留空 → 5 個 /api/conversion/* endpoint 回 501。
#
# 啟用時 VISIONA_OIDC_SERVICE_CLIENT_ID/SECRET 必須非空(轉檔依賴 service token 機制)。
# kneron_model_converter task-scheduler base URL
# dev/stagehttp://192.168.0.130:9501
# prodhttps://converter.visiona.cloud
VISIONA_CONVERTER_BASE_URL=
# File Access Agent base URL
# dev/stagehttp://192.168.0.130:5081
# prodhttps://faa.innovedus.com
VISIONA_FAA_BASE_URL=
# visionA 在 Member Center 的 tenant id單一 tenant
# 跟 MC 換 delegated download token 時當 tenant_id 欄位用
VISIONA_OIDC_TENANT_ID=
# Delegated download token TTL— FAA 直連下載用
# 預設 3005 分鐘),可調整範圍 60-900
VISIONA_FAA_DELEGATED_TTL_SECONDS=300
# 上傳模型檔大小上限MB— 與 converter 端 limit 對齊
VISIONA_CONVERTER_MAX_MODEL_SIZE_MB=500