From 9e29ebf767087511f85d1757ce3204b2528b9069 Mon Sep 17 00:00:00 2001 From: jim800121chen Date: Sat, 16 May 2026 19:16:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(visionA-backend):=20Phase=200.8b=20v0.6=20?= =?UTF-8?q?T4=20=E2=80=94=20config=20FAA=20=E6=AC=84=E4=BD=8D=E7=A0=8D=20+?= =?UTF-8?q?=20.env=20=E6=B8=85=20+=20i18n/godoc=20polish?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 對齊 ADR-016 / conversion.md v0.6.1 §3.1:visionA 端不再需要 FAA 設定(v0.5 T1 加的 FAAAPIKey/FAABaseURL 撤回)。 config 砍除: - internal/config/config.go: ConversionConfig.FAABaseURL + FAAAPIKey 兩欄位 - internal/config/load.go: VISIONA_FAA_BASE_URL + VISIONA_FAA_API_KEY 兩 env 讀取 - Enabled() 簡化為「ConverterBaseURL + ConverterAPIKey 兩個非空」 - internal/config/load_test.go: TestLoad_ConversionEnabled 從 6 case 簡化為 4 case (all_set / missing_converter_url / missing_converter_key / all_empty) .env*.example 對齊(3 個檔): - visionA-backend/.env.example: 砍 2 個 FAA env row + 註解;header 改「2 欄位啟用」 - .env.stage.example: 同上;VISIONA_CONVERTER_API_KEY 保留 CHANGE_ME_OPENSSL_RAND_HEX_32 placeholder - .env.dev.example: 註解區塊統一對齊 T3 review polish: - m-2 internal/api/conversion.go: i18n message map 砍 4 個 dead case (download_token_failed / mc_token_unavailable / idp_misconfigured / idp_unavailable) — 對應 v0.5 mc_token_client 撤回時砍的 sentinel;落入 default「內部錯誤」、行為不變 - m-3 internal/conversion/util.go: hashObjectKey godoc 補「設計約束(重要)」段 + 3 條「不應做的事」(不出現在 response body/header / 不組 URL / 不寫進 user-facing 錯誤訊息)— 明示用途限定於 slog 欄位內、避免 misuse vector - cmd/api-server/main.go: godoc 對齊 T4 完成狀態 驗證: - B 層 verification 主動跑(T3 reviewer 接受暫緩、backend 主動跑避免 reviewer 二次要求): * 跨檔 grep: production code 0 functional 命中(殘留全是註解 audit trail / test fixture name) * 17 packages race -count=3 全綠 * 3 個 .env 環境一致性驗證 - go build ./... exit 0 - go test -race -count=3 ./... 17 packages 全綠 - Reviewer 5 軸(v0.6-t4-review)✅ 通過(0 Critical / 0 Major / 2 Minor / 4 Suggestion) v0.6 對齊改造事實上完工: - T1 ConverterClient.GetResult method - T2 flow.go DownloadStream/PromoteToModels 改用 GetResult + e2e endpoint - T3 faa_client 整檔砍 + ErrFAA* sentinel 清 + s-3/s-4/s-5 必補 + mockFAA regression-only - T4 config FAA 欄位砍 + .env 清 + i18n/godoc polish main.go startup log 已是「converter_api_key_set only」、無 FAA 殘留 / 無 tenant_id(T2-T3 已處理)。e2e regression 防護由 mockFAA negative assertion 守住(T3)。 下一步: - visionA backend 端 ADR-016 對齊完工,等使用者跨 repo 加 converter GET /api/v1/jobs/{id}/result endpoint - stage redeploy + e2e 完整測試 Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.dev.example | 12 ++-- .env.stage.example | 20 +++--- visionA-backend/.env.example | 25 +++----- visionA-backend/cmd/api-server/main.go | 9 +-- visionA-backend/internal/api/conversion.go | 14 ++--- visionA-backend/internal/config/config.go | 39 +++++------- visionA-backend/internal/config/load.go | 8 ++- visionA-backend/internal/config/load_test.go | 65 ++++++++------------ visionA-backend/internal/conversion/util.go | 9 +++ 9 files changed, 91 insertions(+), 110 deletions(-) diff --git a/.env.dev.example b/.env.dev.example index fe15287..931dd95 100644 --- a/.env.dev.example +++ b/.env.dev.example @@ -79,16 +79,18 @@ VISIONA_PAIRING_TOKEN= # ============================================================ # Phase 0.8 / 0.8b — 轉檔功能整合(dev 通常不啟用,留空即可) # ============================================================ -# 對齊 .autoflow/04-architecture/conversion.md §3 + ADR-015。 +# 對齊 docs/autoflow/04-architecture/conversion.md §3 + ADR-015 + ADR-016。 # -# 4 個欄位全部非空才會啟用 conversion 模組;dev 全空 = sidebar tab 顯示但 endpoint 不註冊。 -# 若要在 dev 連 stage 的 converter / FAA 測整合,依 .env.stage.example 模板填入。 +# 2 個欄位(ConverterBaseURL / ConverterAPIKey)全部非空才會啟用 conversion 模組; +# dev 全空 = sidebar tab 顯示但 endpoint 不註冊。 +# 若要在 dev 連 stage 的 converter 測整合,依 .env.stage.example 模板填入。 +# +# Phase 0.8b v0.6 T4:原 FAA 相關 env(VISIONA_FAA_BASE_URL / VISIONA_FAA_API_KEY) +# 已撤回(ADR-016:visionA 端不再直接呼叫 FAA;download / promote 改走 converter.GetResult)。 # # 產生 API key:openssl rand -hex 32 # VISIONA_CONVERTER_BASE_URL=http://192.168.0.130:9501 -# VISIONA_FAA_BASE_URL=http://192.168.0.130:5081 # VISIONA_CONVERTER_API_KEY= -# VISIONA_FAA_API_KEY= # ============================================================ # 進階:port 衝突時可改 diff --git a/.env.stage.example b/.env.stage.example index 6d04085..0b6a0ad 100644 --- a/.env.stage.example +++ b/.env.stage.example @@ -115,33 +115,29 @@ VISIONA_SEED_DEMO_DATA=false # 留註解作為 audit trail;stage 部署不需設定 VISIONA_STATIC_USER_ID。 # ============================================================ -# Phase 0.8 / 0.8b — 轉檔功能整合(converter / FAA pre-shared API key) +# Phase 0.8 / 0.8b — 轉檔功能整合(converter pre-shared API key) # ============================================================ -# 對齊 .autoflow/04-architecture/conversion.md §3 + ADR-015。 +# 對齊 docs/autoflow/04-architecture/conversion.md §3 + ADR-015 + ADR-016。 # # Phase 0.8b 變更:服務間認證從 OAuth client_credentials 改為 pre-shared API key。 # -# 啟用判定:4 個欄位(ConverterBaseURL / FAABaseURL / ConverterAPIKey / FAAAPIKey) -# **全部非空**才視為啟用;任一缺 → 5 個 /api/conversion/* endpoint 不註冊。 +# 啟用判定:2 個欄位(ConverterBaseURL / ConverterAPIKey)**全部非空**才視為啟用; +# 任一缺 → 5 個 /api/conversion/* endpoint 不註冊。 # # Phase 0.8b 移除(不再讀取,也別再放進 .env.stage): # - VISIONA_OIDC_SERVICE_CLIENT_ID / _SECRET(服務間認證取消 MC client_credentials) # - VISIONA_CONVERSION_TENANT_ID / VISIONA_OIDC_TENANT_ID(取消 tenant 概念) # - VISIONA_FAA_DELEGATED_TTL_SECONDS(取消 delegated download token 機制;改 server-side stream proxy) +# +# Phase 0.8b v0.6 T4 移除(不再讀取,也別再放進 .env.stage;ADR-016 撤回 v0.5 設計缺口): +# - VISIONA_FAA_BASE_URL(visionA 端不再直接呼叫 FAA) +# - VISIONA_FAA_API_KEY(同上;download / promote 改走 converter.GetResult) # kneron_model_converter task-scheduler base URL(stage 公司內網) VISIONA_CONVERTER_BASE_URL=http://192.168.0.130:9501 -# File Access Agent base URL -VISIONA_FAA_BASE_URL=http://192.168.0.130:5081 - # Pre-shared API key — visionA → converter 服務間認證(Phase 0.8b 新增;ADR-015 §3) # **必須換掉**:openssl rand -hex 32 產生 64 字元 hex;與 converter 端 CONVERTER_API_KEY 對齊 # 雙方獨立持有、不共用、嚴格分環境(dev / stage / prod 各自獨立 key) # log 永遠不印此值全文;部署時用 AWS Secrets Manager / Vault 注入 VISIONA_CONVERTER_API_KEY=CHANGE_ME_OPENSSL_RAND_HEX_32 - -# Pre-shared API key — visionA → FAA 服務間認證(Phase 0.8b 新增;ADR-015 §3) -# **必須換掉**:openssl rand -hex 32;與 FAA 端 FAA_API_KEY 對齊(warrenchen 配置) -# 與 ConverterAPIKey **不共用**(每條 trust boundary 各自獨立,避免一處洩漏連坐) -VISIONA_FAA_API_KEY=CHANGE_ME_OPENSSL_RAND_HEX_32 diff --git a/visionA-backend/.env.example b/visionA-backend/.env.example index 9195dec..b13cc63 100644 --- a/visionA-backend/.env.example +++ b/visionA-backend/.env.example @@ -80,7 +80,8 @@ VISIONA_FRONTEND_URL=http://localhost:3000 # 服務間認證從 OAuth client_credentials 改為 pre-shared API key(見 ADR-015、conversion.md §3)。 # 兩個 service client env 不再讀取(OIDCConfig.ServiceClientID/Secret struct 欄位 # 為了 backward compat 暫保留、但 conversion 模組不再依賴)。 -# 取代設定見下方 Phase 0.8 / 0.8b 區塊的 VISIONA_CONVERTER_API_KEY / VISIONA_FAA_API_KEY。 +# 取代設定見下方 Phase 0.8 / 0.8b 區塊的 VISIONA_CONVERTER_API_KEY +# (Phase 0.8b v0.6 T4 起 visionA 端不再直接呼叫 FAA、原 VISIONA_FAA_API_KEY 已撤回)。 # Cookie HMAC 簽章 secret(⚠️ 至少 32 byte 隨機字串;prod 用 openssl rand -hex 32) VISIONA_SESSION_SECRET=CHANGE_ME_TO_RANDOM_64_BYTES_in_production @@ -157,40 +158,34 @@ VISIONA_PAIRING_TOKEN= # ============================================================ -# Phase 0.8 / 0.8b — 轉檔功能整合(converter / FAA pre-shared API key) +# Phase 0.8 / 0.8b — 轉檔功能整合(converter pre-shared API key) # ============================================================ -# 對齊 .autoflow/04-architecture/conversion.md §3 + ADR-015。 +# 對齊 docs/autoflow/04-architecture/conversion.md §3 + ADR-015 + ADR-016。 # # Phase 0.8b 變更:服務間認證從 OAuth client_credentials 改為 pre-shared API key。 # -# 啟用判定:4 個欄位(ConverterBaseURL / FAABaseURL / ConverterAPIKey / FAAAPIKey) -# **全部非空**才視為啟用;任一缺 → 5 個 /api/conversion/* endpoint 不註冊 / 回 501。 +# 啟用判定:2 個欄位(ConverterBaseURL / ConverterAPIKey)**全部非空**才視為啟用; +# 任一缺 → 5 個 /api/conversion/* endpoint 不註冊 / 回 501。 # # Phase 0.8b 移除(不再讀取): # - VISIONA_OIDC_SERVICE_CLIENT_ID / _SECRET(OAuth client_credentials 機制取消) # - VISIONA_OIDC_TENANT_ID(取消 tenant 概念,converter 端的 user_id 仍由 visionA 灌入) # - VISIONA_FAA_DELEGATED_TTL_SECONDS(delegated download token 機制取消,改 server-side stream proxy) +# +# Phase 0.8b v0.6 T4 移除(不再讀取;ADR-016 撤回 v0.5 設計缺口): +# - VISIONA_FAA_BASE_URL(visionA 端不再直接呼叫 FAA) +# - VISIONA_FAA_API_KEY(同上;download / promote 改走 converter.GetResult) # kneron_model_converter task-scheduler base URL # dev/stage:http://192.168.0.130:9501 # prod:https://converter.visiona.cloud VISIONA_CONVERTER_BASE_URL= -# File Access Agent base URL -# dev/stage:http://192.168.0.130:5081 -# prod:https://faa.innovedus.com -VISIONA_FAA_BASE_URL= - # Pre-shared API key — visionA → converter 服務間認證(Phase 0.8b 新增;ADR-015 §3) # 產生:openssl rand -hex 32(64 字元 hex) # 與 converter 端 CONVERTER_API_KEY env 對齊(雙方獨立持有,嚴格分環境 dev / stage / prod) # ⚠️ 不可 commit;prod 用 Secrets Manager / Vault;log 永遠不印此值全文 VISIONA_CONVERTER_API_KEY= -# Pre-shared API key — visionA → FAA 服務間認證(Phase 0.8b 新增;ADR-015 §3) -# 產生方式同上;與 FAA 端 FAA_API_KEY env 對齊(warrenchen 配置) -# 與 ConverterAPIKey **不共用**(每條 trust boundary 各自獨立,避免一處洩漏連坐) -VISIONA_FAA_API_KEY= - # 上傳模型檔大小上限(MB)— 與 converter 端 limit 對齊 VISIONA_CONVERTER_MAX_MODEL_SIZE_MB=500 diff --git a/visionA-backend/cmd/api-server/main.go b/visionA-backend/cmd/api-server/main.go index f029ddc..ff37178 100644 --- a/visionA-backend/cmd/api-server/main.go +++ b/visionA-backend/cmd/api-server/main.go @@ -141,16 +141,17 @@ func main() { // 對齊 docs/autoflow/04-architecture/conversion.md、ADR-015、ADR-016。 // // 啟用條件:cfg.Conversion.Enabled() — - // 由 ConverterBaseURL + ConverterAPIKey 決定(FAABaseURL / FAAAPIKey 由 T4 砍除 env 校驗)。 + // 由 ConverterBaseURL + ConverterAPIKey 決定(v0.6 T4 起 visionA 端不再需要 FAA env)。 // 不啟用時 deps.Conversion 為 nil,5 個 endpoint 自動回 501(registerConversionRoutes 處理)。 // // **Phase 0.8b T5**:完全切換至 pre-shared API key 認證 — 不再 wire MCTokenClient、 // 不再讀 OIDCConfig.ServiceClientID/Secret、不再有 tenant_id / delegated_ttl_sec // 概念。參見 ADR-015 §6 變更影響清單。 // - // **Phase 0.8b v0.6 T3**:撤回 visionA → FAA 直接呼叫(ADR-016 撤回 v0.5 設計缺口)。 - // faa_client.go / FAAClient interface / FlowOpts.FAA 全部砍除;download / promote 流程 - // 改走 converter.GetResult。FAABaseURL / FAAAPIKey env 仍保留在 config 直到 T4 一併砍。 + // **Phase 0.8b v0.6 T3 + T4**:撤回 visionA → FAA 直接呼叫(ADR-016 撤回 v0.5 設計缺口)。 + // T3 砍 faa_client.go / FAAClient interface / FlowOpts.FAA;T4 砍 ConversionConfig + // FAABaseURL / FAAAPIKey 兩欄位與對應 env(VISIONA_FAA_BASE_URL / VISIONA_FAA_API_KEY), + // `Enabled()` 簡化為只判 converter 兩欄位。download / promote 流程改走 converter.GetResult。 var conversionService conversion.Service if cfg.Conversion.Enabled() { // 不再檢查 ServiceClientID/Secret —— Phase 0.8b 起 conversion 不依賴 OIDC service client。 diff --git a/visionA-backend/internal/api/conversion.go b/visionA-backend/internal/api/conversion.go index 70244eb..00f8ec5 100644 --- a/visionA-backend/internal/api/conversion.go +++ b/visionA-backend/internal/api/conversion.go @@ -508,17 +508,15 @@ func errorMessageFor(code string) string { case "converter_unavailable": return "轉檔服務暫時無法使用" case "faa_unavailable": + // converter promote 內部 PUT FAA 失敗時透傳,由 SRE 區分 converter 不可達 vs + // converter→FAA push 失敗(ADR-016:visionA 端不再直接呼叫 FAA)。 return "檔案存取服務暫時無法使用" - case "download_token_failed": - return "無法取得下載授權" - case "mc_token_unavailable": - return "無法取得下載授權,請重試" - case "idp_misconfigured": - return "系統設定錯誤,請聯絡支援" - case "idp_unavailable": - return "認證服務暫時無法使用" case "service_busy": return "系統繁忙,請稍後再試" + // Phase 0.8b v0.6 T3 / T4:以下 i18n key 對應的 sentinel 已砍除,不再會被產生: + // - download_token_failed / mc_token_unavailable(v0.5 mc_token_client 撤回) + // - idp_misconfigured / idp_unavailable(同上) + // 故 case 直接省略;errorMessageFor 落入 default。 default: return "內部錯誤" } diff --git a/visionA-backend/internal/config/config.go b/visionA-backend/internal/config/config.go index bea2f8b..95d386d 100644 --- a/visionA-backend/internal/config/config.go +++ b/visionA-backend/internal/config/config.go @@ -182,30 +182,31 @@ type LoggerConfig struct { // ConversionConfig 控制 Phase 0.8 / 0.8b 轉檔功能整合。 // -// 對齊 .autoflow/04-architecture/conversion.md §3、`adr/adr-015-server-to-server-api-key.md`。 +// 對齊 docs/autoflow/04-architecture/conversion.md §3、 +// `adr/adr-015-server-to-server-api-key.md`、`adr/adr-016-download-via-converter.md`。 // -// 啟用判定(由 Enabled() 給 main.go 用):Phase 0.8b 起,4 個欄位(ConverterBaseURL / -// FAABaseURL / ConverterAPIKey / FAAAPIKey)**全部非空**才視為啟用;任一缺即視為未啟用, +// 啟用判定(由 Enabled() 給 main.go 用):Phase 0.8b v0.6 起,2 個必要欄位 +// (ConverterBaseURL / ConverterAPIKey)**全部非空**才視為啟用;任一缺即視為未啟用, // 5 個 /api/conversion/* endpoint 不會 wire(main.go 在 wire 階段跳過、log warn)。 // -// **Phase 0.8b 變更**:服務間認證從 OAuth client_credentials 改為 pre-shared API key(ADR-015); -// `Enabled()` 加入兩個 API key 非空檢查。 +// **Phase 0.8b 變更**:服務間認證從 OAuth client_credentials 改為 pre-shared API key(ADR-015)。 // // **Phase 0.8b T5 完成**(見 conversion.md §3.2 / ADR-015 §5 §7):原暫留欄位 // TenantID / DelegatedTTLSeconds 已移除 — MC 認證鏈與 delegated download token 機制 // 都不存在了,兩個欄位連同對應 env(VISIONA_OIDC_TENANT_ID / // VISIONA_FAA_DELEGATED_TTL_SECONDS)一併清除。 +// +// **Phase 0.8b v0.6 T4 完成**(見 ADR-016 §2 / conversion.md v0.6.1 §3.1):原 FAA 相關 +// 欄位 FAABaseURL / FAAAPIKey 已移除 — visionA 端不再直接呼叫 FAA,download / promote +// 流程改走 converter.GetResult(ADR-016 撤回 v0.5 設計缺口),兩個欄位連同對應 env +// (VISIONA_FAA_BASE_URL / VISIONA_FAA_API_KEY)一併清除;`Enabled()` 也簡化為只判 +// converter 兩欄位。 type ConversionConfig struct { // ConverterBaseURL 是 kneron_model_converter task-scheduler 服務的 base URL。 // 例:http://192.168.0.130:9501(dev / stage) / https://converter.visiona.cloud(prod) // 對齊 VISIONA_CONVERTER_BASE_URL;留空 = 不啟用 Phase 0.8 轉檔功能。 ConverterBaseURL string - // FAABaseURL 是 File Access Agent 的 base URL。 - // 例:http://192.168.0.130:5081(dev / stage) / https://faa.innovedus.com(prod) - // 對齊 VISIONA_FAA_BASE_URL;留空 = 不啟用 Phase 0.8 轉檔功能。 - FAABaseURL string - // ConverterAPIKey 是 visionA → converter 服務間認證的 pre-shared API key(Phase 0.8b 新增)。 // 對齊 VISIONA_CONVERTER_API_KEY;以 `Authorization: Bearer ` 形式帶上。 // 雙方獨立產生(`openssl rand -hex 32`),visionA 端的值必須與 converter 端的 @@ -216,12 +217,6 @@ type ConversionConfig struct { // 部署用 AWS Secrets Manager / Vault;嚴格分環境(dev / stage / prod 各自獨立 key)。 ConverterAPIKey string - // FAAAPIKey 是 visionA → FAA 服務間認證的 pre-shared API key(Phase 0.8b 新增)。 - // 對齊 VISIONA_FAA_API_KEY;以 `Authorization: Bearer ` 形式帶上。 - // 與 ConverterAPIKey **不共用**(每條 trust boundary 各自獨立,避免一處洩漏連坐 — ADR-015 §3); - // 對應 FAA 端的 `FAA_API_KEY` env,由 warrenchen 配置(跨 repo 同步)。 - FAAAPIKey string - // MaxModelSizeMB 是 visionA-backend 端對上傳模型檔的大小上限(MB)。 // 與 converter 端 limit 對齊(converter 預設 500 MB)。 // 對齊 VISIONA_CONVERTER_MAX_MODEL_SIZE_MB;預設 500。 @@ -230,14 +225,12 @@ type ConversionConfig struct { // Enabled 回傳 Phase 0.8 / 0.8b conversion 是否啟用。 // -// **Phase 0.8b 變更**(ADR-015 §6):除既有的 ConverterBaseURL / FAABaseURL 外, -// 加入 ConverterAPIKey / FAAAPIKey 非空檢查;4 個欄位皆非空才算啟用。 -// 任一缺 → 視為未啟用,main.go 不會 wire conversion.Service(5 個 endpoint 回 501 / 不註冊)。 +// **Phase 0.8b v0.6 T4 簡化**(ADR-016 §2 / conversion.md v0.6.1 §3.1):visionA 端撤回 +// 對 FAA 的直接呼叫,download / promote 改走 converter.GetResult;只剩 ConverterBaseURL +// 與 ConverterAPIKey 兩個欄位需非空。任一缺 → 視為未啟用,main.go 不會 wire +// conversion.Service(5 個 endpoint 回 501 / 不註冊)。 func (c ConversionConfig) Enabled() bool { - return c.ConverterBaseURL != "" && - c.FAABaseURL != "" && - c.ConverterAPIKey != "" && - c.FAAAPIKey != "" + return c.ConverterBaseURL != "" && c.ConverterAPIKey != "" } // CORSConfig 控制 api-server 對瀏覽器的 CORS 白名單。 diff --git a/visionA-backend/internal/config/load.go b/visionA-backend/internal/config/load.go index 9a68160..39badf9 100644 --- a/visionA-backend/internal/config/load.go +++ b/visionA-backend/internal/config/load.go @@ -68,15 +68,17 @@ func Load() *Config { CORS: CORSConfig{ AllowedOrigins: getEnvStringSlice("VISIONA_CORS_ALLOWED_ORIGINS", nil), }, - // Phase 0.8 / 0.8b conversion (見 .autoflow/04-architecture/conversion.md §3、ADR-015) + // Phase 0.8 / 0.8b conversion (見 docs/autoflow/04-architecture/conversion.md §3、 + // ADR-015、ADR-016) // Phase 0.8b T5:原暫留欄位 TenantID / DelegatedTTLSeconds 與對應 env // (VISIONA_OIDC_TENANT_ID / VISIONA_FAA_DELEGATED_TTL_SECONDS)已移除 — // MC 認證鏈與 delegated download token 機制不存在了。 + // Phase 0.8b v0.6 T4:原 FAA 相關欄位 FAABaseURL / FAAAPIKey 與對應 env + // (VISIONA_FAA_BASE_URL / VISIONA_FAA_API_KEY)已移除 — ADR-016 撤回 v0.5 + // 設計缺口,visionA 端不再直接呼叫 FAA、download/promote 改走 converter.GetResult。 Conversion: ConversionConfig{ ConverterBaseURL: getEnvString("VISIONA_CONVERTER_BASE_URL", ""), - FAABaseURL: getEnvString("VISIONA_FAA_BASE_URL", ""), ConverterAPIKey: getEnvString("VISIONA_CONVERTER_API_KEY", ""), - FAAAPIKey: getEnvString("VISIONA_FAA_API_KEY", ""), MaxModelSizeMB: getEnvInt("VISIONA_CONVERTER_MAX_MODEL_SIZE_MB", 500), }, } diff --git a/visionA-backend/internal/config/load_test.go b/visionA-backend/internal/config/load_test.go index 69a0f4d..d3cc119 100644 --- a/visionA-backend/internal/config/load_test.go +++ b/visionA-backend/internal/config/load_test.go @@ -268,15 +268,19 @@ func TestLoad_CORSAllowedOrigins(t *testing.T) { // TestLoad_ConversionDefaults 驗證 Phase 0.8 / 0.8b conversion 欄位的預設行為。 // -// 對齊 .autoflow/04-architecture/conversion.md §3 + ADR-015:留空時 Enabled() 為 false, -// 5 個 endpoint 不會 wire(main.go 在 wire 階段會跳過)。 +// 對齊 docs/autoflow/04-architecture/conversion.md §3 + ADR-015 + ADR-016:留空時 +// Enabled() 為 false,5 個 endpoint 不會 wire(main.go 在 wire 階段會跳過)。 // // Phase 0.8b T5:原暫留欄位 TenantID / DelegatedTTLSeconds 已從 ConversionConfig 移除 // (MC 認證鏈與 delegated download token 機制不存在了);本 test 不再驗這兩欄位。 +// +// Phase 0.8b v0.6 T4:原 FAA 相關欄位 FAABaseURL / FAAAPIKey 已移除(ADR-016 撤回 +// v0.5 設計缺口,visionA 端不再直接呼叫 FAA);本 test 不再驗這兩欄位,對應的 +// VISIONA_FAA_BASE_URL / VISIONA_FAA_API_KEY env 也不再 clear。 func TestLoad_ConversionDefaults(t *testing.T) { for _, k := range []string{ - "VISIONA_CONVERTER_BASE_URL", "VISIONA_FAA_BASE_URL", - "VISIONA_CONVERTER_API_KEY", "VISIONA_FAA_API_KEY", + "VISIONA_CONVERTER_BASE_URL", + "VISIONA_CONVERTER_API_KEY", "VISIONA_CONVERTER_MAX_MODEL_SIZE_MB", } { t.Setenv(k, "") @@ -284,49 +288,36 @@ func TestLoad_ConversionDefaults(t *testing.T) { cfg := Load() assert.Empty(t, cfg.Conversion.ConverterBaseURL) - assert.Empty(t, cfg.Conversion.FAABaseURL) assert.Empty(t, cfg.Conversion.ConverterAPIKey, "Phase 0.8b:API key 預設留空") - assert.Empty(t, cfg.Conversion.FAAAPIKey, "Phase 0.8b:API key 預設留空") assert.Equal(t, 500, cfg.Conversion.MaxModelSizeMB, "預設 500 MB(與 converter 對齊)") assert.False(t, cfg.Conversion.Enabled(), "全空 → 不啟用") } -// TestLoad_ConversionEnabled 驗證 Conversion.Enabled() 的判定邏輯(Phase 0.8b 修訂)。 +// TestLoad_ConversionEnabled 驗證 Conversion.Enabled() 的判定邏輯。 // -// Phase 0.8b 變更:4 個欄位(Converter URL / FAA URL / Converter API key / FAA API key) -// 全部非空才視為啟用;任一缺即 disable。 +// Phase 0.8b v0.6 T4 簡化(ADR-016):visionA 端撤回對 FAA 的直接呼叫後, +// 只剩 ConverterBaseURL + ConverterAPIKey 兩個欄位需非空;任一缺即 disable。 func TestLoad_ConversionEnabled(t *testing.T) { cases := []struct { name string converterURL string - faaURL string converterKey string - faaKey string wantEnabled bool }{ {"all_set_enables", - "http://converter:9501", "http://faa:5081", + "http://converter:9501", "converter-key-32-bytes-hex-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "faa-key-32-bytes-hex-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", true}, {"missing_converter_url_disabled", - "", "http://faa:5081", - "converter-key", "faa-key", - false}, - {"missing_faa_url_disabled", - "http://converter:9501", "", - "converter-key", "faa-key", + "", + "converter-key", false}, {"missing_converter_key_disabled", - "http://converter:9501", "http://faa:5081", - "", "faa-key", - false}, - {"missing_faa_key_disabled", - "http://converter:9501", "http://faa:5081", - "converter-key", "", + "http://converter:9501", + "", false}, {"all_empty_disabled", - "", "", "", "", + "", "", false}, } @@ -334,9 +325,7 @@ func TestLoad_ConversionEnabled(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { t.Setenv("VISIONA_CONVERTER_BASE_URL", tc.converterURL) - t.Setenv("VISIONA_FAA_BASE_URL", tc.faaURL) t.Setenv("VISIONA_CONVERTER_API_KEY", tc.converterKey) - t.Setenv("VISIONA_FAA_API_KEY", tc.faaKey) cfg := Load() assert.Equal(t, tc.wantEnabled, cfg.Conversion.Enabled()) }) @@ -347,39 +336,35 @@ func TestLoad_ConversionEnabled(t *testing.T) { // // Phase 0.8b T5:原暫留欄位 TenantID / DelegatedTTLSeconds 已移除,本 test // 不再驗這兩欄位(對應 env 也不再讀取)。 +// +// Phase 0.8b v0.6 T4:原 FAA 相關欄位 FAABaseURL / FAAAPIKey 已移除(ADR-016), +// 本 test 不再驗這兩欄位、對應 env 也不再讀取。 func TestLoad_ConversionAllSet(t *testing.T) { const fakeConverterKey = "fake-converter-api-key-for-test-do-not-use-in-prod" - const fakeFAAKey = "fake-faa-api-key-for-test-do-not-use-in-prod" t.Setenv("VISIONA_CONVERTER_BASE_URL", "http://192.168.0.130:9501") - t.Setenv("VISIONA_FAA_BASE_URL", "http://192.168.0.130:5081") t.Setenv("VISIONA_CONVERTER_API_KEY", fakeConverterKey) - t.Setenv("VISIONA_FAA_API_KEY", fakeFAAKey) t.Setenv("VISIONA_CONVERTER_MAX_MODEL_SIZE_MB", "300") cfg := Load() assert.Equal(t, "http://192.168.0.130:9501", cfg.Conversion.ConverterBaseURL) - assert.Equal(t, "http://192.168.0.130:5081", cfg.Conversion.FAABaseURL) assert.Equal(t, fakeConverterKey, cfg.Conversion.ConverterAPIKey) - assert.Equal(t, fakeFAAKey, cfg.Conversion.FAAAPIKey) assert.Equal(t, 300, cfg.Conversion.MaxModelSizeMB) assert.True(t, cfg.Conversion.Enabled()) } -// TestLoad_ConversionAPIKeysOnly:Phase 0.8b T5 — 4 個必要欄位齊全即 Enabled。 +// TestLoad_ConversionAPIKeysOnly:Phase 0.8b v0.6 T4 — 2 個必要欄位齊全即 Enabled。 // -// 此 test 在 T1-T4 期間驗證「廢棄 env 不設也能 Enabled」;T5 完成後該邏輯 -// 由本 test 與 TestLoad_ConversionAllSet 共同覆蓋(因為廢棄 env 已徹底移除)。 +// 本 test 與 TestLoad_ConversionAllSet 共同覆蓋 Enabled() 的最小啟用條件: +// 只要 ConverterBaseURL + ConverterAPIKey 兩個欄位都非空、即視為啟用, +// MaxModelSizeMB 不影響啟用判定。 func TestLoad_ConversionAPIKeysOnly(t *testing.T) { const fakeConverterKey = "fake-converter-api-key-only-test" - const fakeFAAKey = "fake-faa-api-key-only-test" t.Setenv("VISIONA_CONVERTER_BASE_URL", "http://192.168.0.130:9501") - t.Setenv("VISIONA_FAA_BASE_URL", "http://192.168.0.130:5081") t.Setenv("VISIONA_CONVERTER_API_KEY", fakeConverterKey) - t.Setenv("VISIONA_FAA_API_KEY", fakeFAAKey) cfg := Load() assert.True(t, cfg.Conversion.Enabled(), - "Phase 0.8b T5:4 個必要欄位齊全即 Enabled") + "Phase 0.8b v0.6 T4:2 個必要欄位齊全即 Enabled") } diff --git a/visionA-backend/internal/conversion/util.go b/visionA-backend/internal/conversion/util.go index 6188674..3103845 100644 --- a/visionA-backend/internal/conversion/util.go +++ b/visionA-backend/internal/conversion/util.go @@ -30,6 +30,15 @@ func truncate(s string, max int) string { // - 目前 visionA 的 object_key 不直接含 user 敏感資訊,但保險起見統一 hash // - 16 chars hex(64-bit)對 visionA 內部 job 數量來說碰撞機率極低,足以追蹤單一 request // +// **設計約束(重要):此 hash 僅供內部 log / observability 用、不應對外暴露**。 +// 具體不應: +// - 出現在 HTTP response body / header(frontend 不需要、洩漏內部 storage 結構) +// - 拿來組 presigned URL / download URL 的 path(hash 不可逆、不能用來定位物件) +// - 寫進使用者可見的錯誤訊息(用 request_id 追蹤即可) +// +// 用途限定於 slog 欄位(如 `slog.String("object_key_hash", hashObjectKey(...))`), +// 配合 request_id 在 log aggregator 內串接同一 NEF 物件在多個 service 之間的流向。 +// // Phase 0.8b v0.6(T3):原本定義在 faa_client.go;砍 faa_client.go 時搬到 util.go // (flow.go DownloadStream / PromoteToModels 仍用 hashObjectKey 為 log 加 target_object_key // 標記,方便跨 service 追蹤同一個 NEF 物件)。