feat(visionA-backend): Phase 0.8b v0.6 T4 — config FAA 欄位砍 + .env 清 + i18n/godoc polish

對齊 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) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-05-16 19:16:28 +08:00
parent 6024c294d3
commit 9e29ebf767
9 changed files with 91 additions and 110 deletions

View File

@ -79,16 +79,18 @@ VISIONA_PAIRING_TOKEN=
# ============================================================ # ============================================================
# Phase 0.8 / 0.8b — 轉檔功能整合dev 通常不啟用,留空即可) # 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 不註冊。 # 2 個欄位ConverterBaseURL / ConverterAPIKey全部非空才會啟用 conversion 模組;
# 若要在 dev 連 stage 的 converter / FAA 測整合,依 .env.stage.example 模板填入。 # dev 全空 = sidebar tab 顯示但 endpoint 不註冊。
# 若要在 dev 連 stage 的 converter 測整合,依 .env.stage.example 模板填入。
#
# Phase 0.8b v0.6 T4原 FAA 相關 envVISIONA_FAA_BASE_URL / VISIONA_FAA_API_KEY
# 已撤回ADR-016visionA 端不再直接呼叫 FAAdownload / promote 改走 converter.GetResult
# #
# 產生 API keyopenssl rand -hex 32 # 產生 API keyopenssl rand -hex 32
# VISIONA_CONVERTER_BASE_URL=http://192.168.0.130:9501 # 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_CONVERTER_API_KEY=
# VISIONA_FAA_API_KEY=
# ============================================================ # ============================================================
# 進階port 衝突時可改 # 進階port 衝突時可改

View File

@ -115,33 +115,29 @@ VISIONA_SEED_DEMO_DATA=false
# 留註解作為 audit trailstage 部署不需設定 VISIONA_STATIC_USER_ID。 # 留註解作為 audit trailstage 部署不需設定 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。 # Phase 0.8b 變更:服務間認證從 OAuth client_credentials 改為 pre-shared API key。
# #
# 啟用判定:4 個欄位ConverterBaseURL / FAABaseURL / ConverterAPIKey / FAAAPIKey # 啟用判定:2 個欄位ConverterBaseURL / ConverterAPIKey**全部非空**才視為啟用;
# **全部非空**才視為啟用;任一缺 → 5 個 /api/conversion/* endpoint 不註冊。 # 任一缺 → 5 個 /api/conversion/* endpoint 不註冊。
# #
# Phase 0.8b 移除(不再讀取,也別再放進 .env.stage # Phase 0.8b 移除(不再讀取,也別再放進 .env.stage
# - VISIONA_OIDC_SERVICE_CLIENT_ID / _SECRET服務間認證取消 MC client_credentials # - VISIONA_OIDC_SERVICE_CLIENT_ID / _SECRET服務間認證取消 MC client_credentials
# - VISIONA_CONVERSION_TENANT_ID / VISIONA_OIDC_TENANT_ID取消 tenant 概念) # - VISIONA_CONVERSION_TENANT_ID / VISIONA_OIDC_TENANT_ID取消 tenant 概念)
# - VISIONA_FAA_DELEGATED_TTL_SECONDS取消 delegated download token 機制;改 server-side stream proxy # - VISIONA_FAA_DELEGATED_TTL_SECONDS取消 delegated download token 機制;改 server-side stream proxy
#
# Phase 0.8b v0.6 T4 移除(不再讀取,也別再放進 .env.stageADR-016 撤回 v0.5 設計缺口):
# - VISIONA_FAA_BASE_URLvisionA 端不再直接呼叫 FAA
# - VISIONA_FAA_API_KEY同上download / promote 改走 converter.GetResult
# kneron_model_converter task-scheduler base URLstage 公司內網) # kneron_model_converter task-scheduler base URLstage 公司內網)
VISIONA_CONVERTER_BASE_URL=http://192.168.0.130:9501 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 # Pre-shared API key — visionA → converter 服務間認證Phase 0.8b 新增ADR-015 §3
# **必須換掉**openssl rand -hex 32 產生 64 字元 hex與 converter 端 CONVERTER_API_KEY 對齊 # **必須換掉**openssl rand -hex 32 產生 64 字元 hex與 converter 端 CONVERTER_API_KEY 對齊
# 雙方獨立持有、不共用、嚴格分環境dev / stage / prod 各自獨立 key # 雙方獨立持有、不共用、嚴格分環境dev / stage / prod 各自獨立 key
# log 永遠不印此值全文;部署時用 AWS Secrets Manager / Vault 注入 # log 永遠不印此值全文;部署時用 AWS Secrets Manager / Vault 注入
VISIONA_CONVERTER_API_KEY=CHANGE_ME_OPENSSL_RAND_HEX_32 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

View File

@ -80,7 +80,8 @@ VISIONA_FRONTEND_URL=http://localhost:3000
# 服務間認證從 OAuth client_credentials 改為 pre-shared API key見 ADR-015、conversion.md §3 # 服務間認證從 OAuth client_credentials 改為 pre-shared API key見 ADR-015、conversion.md §3
# 兩個 service client env 不再讀取OIDCConfig.ServiceClientID/Secret struct 欄位 # 兩個 service client env 不再讀取OIDCConfig.ServiceClientID/Secret struct 欄位
# 為了 backward compat 暫保留、但 conversion 模組不再依賴)。 # 為了 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 # Cookie HMAC 簽章 secret 至少 32 byte 隨機字串prod 用 openssl rand -hex 32
VISIONA_SESSION_SECRET=CHANGE_ME_TO_RANDOM_64_BYTES_in_production 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。 # Phase 0.8b 變更:服務間認證從 OAuth client_credentials 改為 pre-shared API key。
# #
# 啟用判定:4 個欄位ConverterBaseURL / FAABaseURL / ConverterAPIKey / FAAAPIKey # 啟用判定:2 個欄位ConverterBaseURL / ConverterAPIKey**全部非空**才視為啟用;
# **全部非空**才視為啟用;任一缺 → 5 個 /api/conversion/* endpoint 不註冊 / 回 501。 # 任一缺 → 5 個 /api/conversion/* endpoint 不註冊 / 回 501。
# #
# Phase 0.8b 移除(不再讀取): # Phase 0.8b 移除(不再讀取):
# - VISIONA_OIDC_SERVICE_CLIENT_ID / _SECRETOAuth client_credentials 機制取消) # - VISIONA_OIDC_SERVICE_CLIENT_ID / _SECRETOAuth client_credentials 機制取消)
# - VISIONA_OIDC_TENANT_ID取消 tenant 概念converter 端的 user_id 仍由 visionA 灌入) # - VISIONA_OIDC_TENANT_ID取消 tenant 概念converter 端的 user_id 仍由 visionA 灌入)
# - VISIONA_FAA_DELEGATED_TTL_SECONDSdelegated download token 機制取消,改 server-side stream proxy # - VISIONA_FAA_DELEGATED_TTL_SECONDSdelegated download token 機制取消,改 server-side stream proxy
#
# Phase 0.8b v0.6 T4 移除不再讀取ADR-016 撤回 v0.5 設計缺口):
# - VISIONA_FAA_BASE_URLvisionA 端不再直接呼叫 FAA
# - VISIONA_FAA_API_KEY同上download / promote 改走 converter.GetResult
# kneron_model_converter task-scheduler base URL # kneron_model_converter task-scheduler base URL
# dev/stagehttp://192.168.0.130:9501 # dev/stagehttp://192.168.0.130:9501
# prodhttps://converter.visiona.cloud # prodhttps://converter.visiona.cloud
VISIONA_CONVERTER_BASE_URL= VISIONA_CONVERTER_BASE_URL=
# File Access Agent base URL
# dev/stagehttp://192.168.0.130:5081
# prodhttps://faa.innovedus.com
VISIONA_FAA_BASE_URL=
# Pre-shared API key — visionA → converter 服務間認證Phase 0.8b 新增ADR-015 §3 # Pre-shared API key — visionA → converter 服務間認證Phase 0.8b 新增ADR-015 §3
# 產生openssl rand -hex 3264 字元 hex # 產生openssl rand -hex 3264 字元 hex
# 與 converter 端 CONVERTER_API_KEY env 對齊(雙方獨立持有,嚴格分環境 dev / stage / prod # 與 converter 端 CONVERTER_API_KEY env 對齊(雙方獨立持有,嚴格分環境 dev / stage / prod
# ⚠️ 不可 commitprod 用 Secrets Manager / Vaultlog 永遠不印此值全文 # ⚠️ 不可 commitprod 用 Secrets Manager / Vaultlog 永遠不印此值全文
VISIONA_CONVERTER_API_KEY= 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 對齊 # 上傳模型檔大小上限MB— 與 converter 端 limit 對齊
VISIONA_CONVERTER_MAX_MODEL_SIZE_MB=500 VISIONA_CONVERTER_MAX_MODEL_SIZE_MB=500

View File

@ -141,16 +141,17 @@ func main() {
// 對齊 docs/autoflow/04-architecture/conversion.md、ADR-015、ADR-016。 // 對齊 docs/autoflow/04-architecture/conversion.md、ADR-015、ADR-016。
// //
// 啟用條件cfg.Conversion.Enabled() — // 啟用條件cfg.Conversion.Enabled() —
// 由 ConverterBaseURL + ConverterAPIKey 決定(FAABaseURL / FAAAPIKey 由 T4 砍除 env 校驗)。 // 由 ConverterBaseURL + ConverterAPIKey 決定(v0.6 T4 起 visionA 端不再需要 FAA env)。
// 不啟用時 deps.Conversion 為 nil5 個 endpoint 自動回 501registerConversionRoutes 處理)。 // 不啟用時 deps.Conversion 為 nil5 個 endpoint 自動回 501registerConversionRoutes 處理)。
// //
// **Phase 0.8b T5**:完全切換至 pre-shared API key 認證 — 不再 wire MCTokenClient、 // **Phase 0.8b T5**:完全切換至 pre-shared API key 認證 — 不再 wire MCTokenClient、
// 不再讀 OIDCConfig.ServiceClientID/Secret、不再有 tenant_id / delegated_ttl_sec // 不再讀 OIDCConfig.ServiceClientID/Secret、不再有 tenant_id / delegated_ttl_sec
// 概念。參見 ADR-015 §6 變更影響清單。 // 概念。參見 ADR-015 §6 變更影響清單。
// //
// **Phase 0.8b v0.6 T3**:撤回 visionA → FAA 直接呼叫ADR-016 撤回 v0.5 設計缺口)。 // **Phase 0.8b v0.6 T3 + T4**:撤回 visionA → FAA 直接呼叫ADR-016 撤回 v0.5 設計缺口)。
// faa_client.go / FAAClient interface / FlowOpts.FAA 全部砍除download / promote 流程 // T3 砍 faa_client.go / FAAClient interface / FlowOpts.FAAT4 砍 ConversionConfig
// 改走 converter.GetResult。FAABaseURL / FAAAPIKey env 仍保留在 config 直到 T4 一併砍。 // FAABaseURL / FAAAPIKey 兩欄位與對應 envVISIONA_FAA_BASE_URL / VISIONA_FAA_API_KEY
// `Enabled()` 簡化為只判 converter 兩欄位。download / promote 流程改走 converter.GetResult。
var conversionService conversion.Service var conversionService conversion.Service
if cfg.Conversion.Enabled() { if cfg.Conversion.Enabled() {
// 不再檢查 ServiceClientID/Secret —— Phase 0.8b 起 conversion 不依賴 OIDC service client。 // 不再檢查 ServiceClientID/Secret —— Phase 0.8b 起 conversion 不依賴 OIDC service client。

View File

@ -508,17 +508,15 @@ func errorMessageFor(code string) string {
case "converter_unavailable": case "converter_unavailable":
return "轉檔服務暫時無法使用" return "轉檔服務暫時無法使用"
case "faa_unavailable": case "faa_unavailable":
// converter promote 內部 PUT FAA 失敗時透傳,由 SRE 區分 converter 不可達 vs
// converter→FAA push 失敗ADR-016visionA 端不再直接呼叫 FAA
return "檔案存取服務暫時無法使用" return "檔案存取服務暫時無法使用"
case "download_token_failed":
return "無法取得下載授權"
case "mc_token_unavailable":
return "無法取得下載授權,請重試"
case "idp_misconfigured":
return "系統設定錯誤,請聯絡支援"
case "idp_unavailable":
return "認證服務暫時無法使用"
case "service_busy": case "service_busy":
return "系統繁忙,請稍後再試" return "系統繁忙,請稍後再試"
// Phase 0.8b v0.6 T3 / T4以下 i18n key 對應的 sentinel 已砍除,不再會被產生:
// - download_token_failed / mc_token_unavailablev0.5 mc_token_client 撤回)
// - idp_misconfigured / idp_unavailable同上
// 故 case 直接省略errorMessageFor 落入 default。
default: default:
return "內部錯誤" return "內部錯誤"
} }

View File

@ -182,30 +182,31 @@ type LoggerConfig struct {
// ConversionConfig 控制 Phase 0.8 / 0.8b 轉檔功能整合。 // 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 / // 啟用判定(由 Enabled() 給 main.go 用Phase 0.8b v0.6 起2 個必要欄位
// FAABaseURL / ConverterAPIKey / FAAAPIKey**全部非空**才視為啟用;任一缺即視為未啟用, // ConverterBaseURL / ConverterAPIKey**全部非空**才視為啟用;任一缺即視為未啟用,
// 5 個 /api/conversion/* endpoint 不會 wiremain.go 在 wire 階段跳過、log warn // 5 個 /api/conversion/* endpoint 不會 wiremain.go 在 wire 階段跳過、log warn
// //
// **Phase 0.8b 變更**:服務間認證從 OAuth client_credentials 改為 pre-shared API keyADR-015 // **Phase 0.8b 變更**:服務間認證從 OAuth client_credentials 改為 pre-shared API keyADR-015
// `Enabled()` 加入兩個 API key 非空檢查。
// //
// **Phase 0.8b T5 完成**(見 conversion.md §3.2 / ADR-015 §5 §7原暫留欄位 // **Phase 0.8b T5 完成**(見 conversion.md §3.2 / ADR-015 §5 §7原暫留欄位
// TenantID / DelegatedTTLSeconds 已移除 — MC 認證鏈與 delegated download token 機制 // TenantID / DelegatedTTLSeconds 已移除 — MC 認證鏈與 delegated download token 機制
// 都不存在了,兩個欄位連同對應 envVISIONA_OIDC_TENANT_ID / // 都不存在了,兩個欄位連同對應 envVISIONA_OIDC_TENANT_ID /
// VISIONA_FAA_DELEGATED_TTL_SECONDS一併清除。 // 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 端不再直接呼叫 FAAdownload / promote
// 流程改走 converter.GetResultADR-016 撤回 v0.5 設計缺口),兩個欄位連同對應 env
// VISIONA_FAA_BASE_URL / VISIONA_FAA_API_KEY一併清除`Enabled()` 也簡化為只判
// converter 兩欄位。
type ConversionConfig struct { type ConversionConfig struct {
// ConverterBaseURL 是 kneron_model_converter task-scheduler 服務的 base URL。 // ConverterBaseURL 是 kneron_model_converter task-scheduler 服務的 base URL。
// 例http://192.168.0.130:9501dev / stage / https://converter.visiona.cloudprod // 例http://192.168.0.130:9501dev / stage / https://converter.visiona.cloudprod
// 對齊 VISIONA_CONVERTER_BASE_URL留空 = 不啟用 Phase 0.8 轉檔功能。 // 對齊 VISIONA_CONVERTER_BASE_URL留空 = 不啟用 Phase 0.8 轉檔功能。
ConverterBaseURL string ConverterBaseURL string
// FAABaseURL 是 File Access Agent 的 base URL。
// 例http://192.168.0.130:5081dev / stage / https://faa.innovedus.comprod
// 對齊 VISIONA_FAA_BASE_URL留空 = 不啟用 Phase 0.8 轉檔功能。
FAABaseURL string
// ConverterAPIKey 是 visionA → converter 服務間認證的 pre-shared API keyPhase 0.8b 新增)。 // ConverterAPIKey 是 visionA → converter 服務間認證的 pre-shared API keyPhase 0.8b 新增)。
// 對齊 VISIONA_CONVERTER_API_KEY以 `Authorization: Bearer <key>` 形式帶上。 // 對齊 VISIONA_CONVERTER_API_KEY以 `Authorization: Bearer <key>` 形式帶上。
// 雙方獨立產生(`openssl rand -hex 32`visionA 端的值必須與 converter 端的 // 雙方獨立產生(`openssl rand -hex 32`visionA 端的值必須與 converter 端的
@ -216,12 +217,6 @@ type ConversionConfig struct {
// 部署用 AWS Secrets Manager / Vault嚴格分環境dev / stage / prod 各自獨立 key // 部署用 AWS Secrets Manager / Vault嚴格分環境dev / stage / prod 各自獨立 key
ConverterAPIKey string ConverterAPIKey string
// FAAAPIKey 是 visionA → FAA 服務間認證的 pre-shared API keyPhase 0.8b 新增)。
// 對齊 VISIONA_FAA_API_KEY以 `Authorization: Bearer <key>` 形式帶上。
// 與 ConverterAPIKey **不共用**(每條 trust boundary 各自獨立,避免一處洩漏連坐 — ADR-015 §3
// 對應 FAA 端的 `FAA_API_KEY` env由 warrenchen 配置(跨 repo 同步)。
FAAAPIKey string
// MaxModelSizeMB 是 visionA-backend 端對上傳模型檔的大小上限MB // MaxModelSizeMB 是 visionA-backend 端對上傳模型檔的大小上限MB
// 與 converter 端 limit 對齊converter 預設 500 MB // 與 converter 端 limit 對齊converter 預設 500 MB
// 對齊 VISIONA_CONVERTER_MAX_MODEL_SIZE_MB預設 500。 // 對齊 VISIONA_CONVERTER_MAX_MODEL_SIZE_MB預設 500。
@ -230,14 +225,12 @@ type ConversionConfig struct {
// Enabled 回傳 Phase 0.8 / 0.8b conversion 是否啟用。 // Enabled 回傳 Phase 0.8 / 0.8b conversion 是否啟用。
// //
// **Phase 0.8b 變更**ADR-015 §6除既有的 ConverterBaseURL / FAABaseURL 外, // **Phase 0.8b v0.6 T4 簡化**ADR-016 §2 / conversion.md v0.6.1 §3.1visionA 端撤回
// 加入 ConverterAPIKey / FAAAPIKey 非空檢查4 個欄位皆非空才算啟用。 // 對 FAA 的直接呼叫download / promote 改走 converter.GetResult只剩 ConverterBaseURL
// 任一缺 → 視為未啟用main.go 不會 wire conversion.Service5 個 endpoint 回 501 / 不註冊)。 // 與 ConverterAPIKey 兩個欄位需非空。任一缺 → 視為未啟用main.go 不會 wire
// conversion.Service5 個 endpoint 回 501 / 不註冊)。
func (c ConversionConfig) Enabled() bool { func (c ConversionConfig) Enabled() bool {
return c.ConverterBaseURL != "" && return c.ConverterBaseURL != "" && c.ConverterAPIKey != ""
c.FAABaseURL != "" &&
c.ConverterAPIKey != "" &&
c.FAAAPIKey != ""
} }
// CORSConfig 控制 api-server 對瀏覽器的 CORS 白名單。 // CORSConfig 控制 api-server 對瀏覽器的 CORS 白名單。

View File

@ -68,15 +68,17 @@ func Load() *Config {
CORS: CORSConfig{ CORS: CORSConfig{
AllowedOrigins: getEnvStringSlice("VISIONA_CORS_ALLOWED_ORIGINS", nil), 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 // Phase 0.8b T5原暫留欄位 TenantID / DelegatedTTLSeconds 與對應 env
// VISIONA_OIDC_TENANT_ID / VISIONA_FAA_DELEGATED_TTL_SECONDS已移除 — // VISIONA_OIDC_TENANT_ID / VISIONA_FAA_DELEGATED_TTL_SECONDS已移除 —
// MC 認證鏈與 delegated download token 機制不存在了。 // 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{ Conversion: ConversionConfig{
ConverterBaseURL: getEnvString("VISIONA_CONVERTER_BASE_URL", ""), ConverterBaseURL: getEnvString("VISIONA_CONVERTER_BASE_URL", ""),
FAABaseURL: getEnvString("VISIONA_FAA_BASE_URL", ""),
ConverterAPIKey: getEnvString("VISIONA_CONVERTER_API_KEY", ""), ConverterAPIKey: getEnvString("VISIONA_CONVERTER_API_KEY", ""),
FAAAPIKey: getEnvString("VISIONA_FAA_API_KEY", ""),
MaxModelSizeMB: getEnvInt("VISIONA_CONVERTER_MAX_MODEL_SIZE_MB", 500), MaxModelSizeMB: getEnvInt("VISIONA_CONVERTER_MAX_MODEL_SIZE_MB", 500),
}, },
} }

View File

@ -268,15 +268,19 @@ func TestLoad_CORSAllowedOrigins(t *testing.T) {
// TestLoad_ConversionDefaults 驗證 Phase 0.8 / 0.8b conversion 欄位的預設行為。 // TestLoad_ConversionDefaults 驗證 Phase 0.8 / 0.8b conversion 欄位的預設行為。
// //
// 對齊 .autoflow/04-architecture/conversion.md §3 + ADR-015留空時 Enabled() 為 false // 對齊 docs/autoflow/04-architecture/conversion.md §3 + ADR-015 + ADR-016:留空時
// 5 個 endpoint 不會 wiremain.go 在 wire 階段會跳過)。 // Enabled() 為 false5 個 endpoint 不會 wiremain.go 在 wire 階段會跳過)。
// //
// Phase 0.8b T5原暫留欄位 TenantID / DelegatedTTLSeconds 已從 ConversionConfig 移除 // Phase 0.8b T5原暫留欄位 TenantID / DelegatedTTLSeconds 已從 ConversionConfig 移除
// MC 認證鏈與 delegated download token 機制不存在了);本 test 不再驗這兩欄位。 // 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) { func TestLoad_ConversionDefaults(t *testing.T) {
for _, k := range []string{ for _, k := range []string{
"VISIONA_CONVERTER_BASE_URL", "VISIONA_FAA_BASE_URL", "VISIONA_CONVERTER_BASE_URL",
"VISIONA_CONVERTER_API_KEY", "VISIONA_FAA_API_KEY", "VISIONA_CONVERTER_API_KEY",
"VISIONA_CONVERTER_MAX_MODEL_SIZE_MB", "VISIONA_CONVERTER_MAX_MODEL_SIZE_MB",
} { } {
t.Setenv(k, "") t.Setenv(k, "")
@ -284,49 +288,36 @@ func TestLoad_ConversionDefaults(t *testing.T) {
cfg := Load() cfg := Load()
assert.Empty(t, cfg.Conversion.ConverterBaseURL) assert.Empty(t, cfg.Conversion.ConverterBaseURL)
assert.Empty(t, cfg.Conversion.FAABaseURL)
assert.Empty(t, cfg.Conversion.ConverterAPIKey, "Phase 0.8bAPI key 預設留空") assert.Empty(t, cfg.Conversion.ConverterAPIKey, "Phase 0.8bAPI key 預設留空")
assert.Empty(t, cfg.Conversion.FAAAPIKey, "Phase 0.8bAPI key 預設留空")
assert.Equal(t, 500, cfg.Conversion.MaxModelSizeMB, "預設 500 MB與 converter 對齊)") assert.Equal(t, 500, cfg.Conversion.MaxModelSizeMB, "預設 500 MB與 converter 對齊)")
assert.False(t, cfg.Conversion.Enabled(), "全空 → 不啟用") 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 // Phase 0.8b v0.6 T4 簡化ADR-016visionA 端撤回對 FAA 的直接呼叫後,
// 全部非空才視為啟用;任一缺即 disable。 // 只剩 ConverterBaseURL + ConverterAPIKey 兩個欄位需非空;任一缺即 disable。
func TestLoad_ConversionEnabled(t *testing.T) { func TestLoad_ConversionEnabled(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
converterURL string converterURL string
faaURL string
converterKey string converterKey string
faaKey string
wantEnabled bool wantEnabled bool
}{ }{
{"all_set_enables", {"all_set_enables",
"http://converter:9501", "http://faa:5081", "http://converter:9501",
"converter-key-32-bytes-hex-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "converter-key-32-bytes-hex-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"faa-key-32-bytes-hex-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
true}, true},
{"missing_converter_url_disabled", {"missing_converter_url_disabled",
"", "http://faa:5081", "",
"converter-key", "faa-key", "converter-key",
false},
{"missing_faa_url_disabled",
"http://converter:9501", "",
"converter-key", "faa-key",
false}, false},
{"missing_converter_key_disabled", {"missing_converter_key_disabled",
"http://converter:9501", "http://faa:5081", "http://converter:9501",
"", "faa-key", "",
false},
{"missing_faa_key_disabled",
"http://converter:9501", "http://faa:5081",
"converter-key", "",
false}, false},
{"all_empty_disabled", {"all_empty_disabled",
"", "", "", "", "", "",
false}, false},
} }
@ -334,9 +325,7 @@ func TestLoad_ConversionEnabled(t *testing.T) {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Setenv("VISIONA_CONVERTER_BASE_URL", tc.converterURL) 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_CONVERTER_API_KEY", tc.converterKey)
t.Setenv("VISIONA_FAA_API_KEY", tc.faaKey)
cfg := Load() cfg := Load()
assert.Equal(t, tc.wantEnabled, cfg.Conversion.Enabled()) 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 // Phase 0.8b T5原暫留欄位 TenantID / DelegatedTTLSeconds 已移除,本 test
// 不再驗這兩欄位(對應 env 也不再讀取)。 // 不再驗這兩欄位(對應 env 也不再讀取)。
//
// Phase 0.8b v0.6 T4原 FAA 相關欄位 FAABaseURL / FAAAPIKey 已移除ADR-016
// 本 test 不再驗這兩欄位、對應 env 也不再讀取。
func TestLoad_ConversionAllSet(t *testing.T) { func TestLoad_ConversionAllSet(t *testing.T) {
const fakeConverterKey = "fake-converter-api-key-for-test-do-not-use-in-prod" 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_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_CONVERTER_API_KEY", fakeConverterKey)
t.Setenv("VISIONA_FAA_API_KEY", fakeFAAKey)
t.Setenv("VISIONA_CONVERTER_MAX_MODEL_SIZE_MB", "300") t.Setenv("VISIONA_CONVERTER_MAX_MODEL_SIZE_MB", "300")
cfg := Load() cfg := Load()
assert.Equal(t, "http://192.168.0.130:9501", cfg.Conversion.ConverterBaseURL) 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, fakeConverterKey, cfg.Conversion.ConverterAPIKey)
assert.Equal(t, fakeFAAKey, cfg.Conversion.FAAAPIKey)
assert.Equal(t, 300, cfg.Conversion.MaxModelSizeMB) assert.Equal(t, 300, cfg.Conversion.MaxModelSizeMB)
assert.True(t, cfg.Conversion.Enabled()) assert.True(t, cfg.Conversion.Enabled())
} }
// TestLoad_ConversionAPIKeysOnlyPhase 0.8b T5 — 4 個必要欄位齊全即 Enabled。 // TestLoad_ConversionAPIKeysOnlyPhase 0.8b v0.6 T4 — 2 個必要欄位齊全即 Enabled。
// //
// 此 test 在 T1-T4 期間驗證「廢棄 env 不設也能 Enabled」T5 完成後該邏輯 // 本 test 與 TestLoad_ConversionAllSet 共同覆蓋 Enabled() 的最小啟用條件:
// 由本 test 與 TestLoad_ConversionAllSet 共同覆蓋(因為廢棄 env 已徹底移除)。 // 只要 ConverterBaseURL + ConverterAPIKey 兩個欄位都非空、即視為啟用,
// MaxModelSizeMB 不影響啟用判定。
func TestLoad_ConversionAPIKeysOnly(t *testing.T) { func TestLoad_ConversionAPIKeysOnly(t *testing.T) {
const fakeConverterKey = "fake-converter-api-key-only-test" 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_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_CONVERTER_API_KEY", fakeConverterKey)
t.Setenv("VISIONA_FAA_API_KEY", fakeFAAKey)
cfg := Load() cfg := Load()
assert.True(t, cfg.Conversion.Enabled(), assert.True(t, cfg.Conversion.Enabled(),
"Phase 0.8b T54 個必要欄位齊全即 Enabled") "Phase 0.8b v0.6 T42 個必要欄位齊全即 Enabled")
} }

View File

@ -30,6 +30,15 @@ func truncate(s string, max int) string {
// - 目前 visionA 的 object_key 不直接含 user 敏感資訊,但保險起見統一 hash // - 目前 visionA 的 object_key 不直接含 user 敏感資訊,但保險起見統一 hash
// - 16 chars hex64-bit對 visionA 內部 job 數量來說碰撞機率極低,足以追蹤單一 request // - 16 chars hex64-bit對 visionA 內部 job 數量來說碰撞機率極低,足以追蹤單一 request
// //
// **設計約束(重要):此 hash 僅供內部 log / observability 用、不應對外暴露**。
// 具體不應:
// - 出現在 HTTP response body / headerfrontend 不需要、洩漏內部 storage 結構)
// - 拿來組 presigned URL / download URL 的 pathhash 不可逆、不能用來定位物件)
// - 寫進使用者可見的錯誤訊息(用 request_id 追蹤即可)
//
// 用途限定於 slog 欄位(如 `slog.String("object_key_hash", hashObjectKey(...))`
// 配合 request_id 在 log aggregator 內串接同一 NEF 物件在多個 service 之間的流向。
//
// Phase 0.8b v0.6T3原本定義在 faa_client.go砍 faa_client.go 時搬到 util.go // Phase 0.8b v0.6T3原本定義在 faa_client.go砍 faa_client.go 時搬到 util.go
// flow.go DownloadStream / PromoteToModels 仍用 hashObjectKey 為 log 加 target_object_key // flow.go DownloadStream / PromoteToModels 仍用 hashObjectKey 為 log 加 target_object_key
// 標記,方便跨 service 追蹤同一個 NEF 物件)。 // 標記,方便跨 service 追蹤同一個 NEF 物件)。