jim800121chen dab13ed984 docs(autoflow): ADR-016 — visionA download 改走 converter GetResult,撤回 FAA delegated token 鏈
致命發現(grep MC + FAA source 確認):
- MC source 沒有 issue delegated download token endpoint
- MC source 沒有 validate delegated download token endpoint
- FAA MemberCenterDelegatedDownloadTokenValidator.cs 假設的 MC introspection endpoint 不存在
- ADR-014 §2 從 5/2 寫完到現在這條鏈一直是斷的、只是因為從未實際 e2e 跑通過所以沒被發現

使用者拍板硬約束:不動 MC + 不動 FAA

新增 ADR-016:
- visionA download 改用 converter GET /api/v1/jobs/{id}/result(新 endpoint)
- visionA backend 用既有 ConverterAPIKey 認證(不需新增 secret)
- 維持 T4 已實作的 stream proxy 結構(io.CopyN + Content-Disposition + size cap)
- promote 仍 PUT FAA(converter 內部用自己的 OAuth、與 visionA 無關)
- 不需動 MC + FAA + warrenchen
- 6 個替代方案逐一說明排除理由

修訂既有文件:
- ADR-014 v1.1 → v1.2:§2 download flow 標註被 ADR-016 部分 supersede
- ADR-015 v2.0 → v2.1:§2 visionA → FAA delegated token 設計(v2.0 從 v1.x 撤回的設計)再次撤回;§9 env 表撤回 v2.0 加回的 OIDC ServiceClient* / TenantID / FAABaseURL;visionA 端 server-to-server 只剩 ConverterAPIKey 一把
- conversion.md v0.5 → v0.6:§1 sequence diagram 重畫(移除 MC node)、§2 模組設計(mc_token_client.go 整檔刪除確認、faa_client.go 改名 converter_result_client.go)、§3.2 visionA → FAA 整段標撤回、§4.1 download handler 改 converter.GetResult、§6 錯誤碼撤回 mc/faa 三個 code 加 result_not_found / result_expired
- api-conversion.md v0.5 → v0.6:檔頭 Auth 段落改寫、§4 download endpoint 改述、error code 表撤回 mc_token_unavailable / download_token_failed
- oidc-tdd.md v0.3 → v0.4:§13.1 環境變數表 OIDC ServiceClient* / TenantID / FAABaseURL 從「重新啟用」改回「再次廢棄」、§13.1.1 stage env 範例移除 service client / tenant_id / FAA URL、§13.1.3 改寫為「v0.4 單線設計」說明

整體影響:
- 不需復活 mc_token_client.go(commit 86b7175 砍除狀態維持)
- 不需復活 OIDCConfig.ServiceClientID/Secret/TenantID(commit 86b7175 移除狀態維持)
- visionA backend faa_client.go 要改名為 converter_result_client.go、改呼叫 converter.GetResult
- visionA backend flow.go DownloadStream / PromoteToModels 改用 converter.GetResult
- jimchen 跨 repo 任務:converter scheduler 加 GET /api/v1/jobs/{id}/result endpoint

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:30:46 +08:00

22 KiB
Raw Blame History

API — Conversion轉檔功能Phase 0.8 / Phase 0.8b v0.6

base URLhttps://stage-9527.innovedus.com:9527/stage / http://localhost:3721dev Authuser → visionAOIDC cookie sessionvisiona_session),參見 oidc-tdd.md — 與 Phase 0.8 完全一致,未變 服務間認證visionA → converterPhase 0.8b v1.0 改為 pre-shared API key(取代 OAuth client_credentials— 對 frontend 透明v0.6 新增同一把 key 也用於 download 路徑converter GET /api/v1/jobs/{id}/result)。詳見 conversion.md §3.1 + ADR-015 §1 服務間認證visionA → FAA / MCPhase 0.8b v0.6 整段撤回v1.x 加的 API key 撤回 / v2.0 改回 MC service token + delegated download token 也撤回)— visionA 端不再有任何 visionA → FAA / visionA → MC 路徑。download 改走 converter 中轉converter 從自己的 MinIO stream NEF 回 visionA。詳見 ADR-016 + conversion.md §3 同層api/api-spec.md(總覽)、conversion.md(內部設計)、adr/adr-014-conversion-integration.md§2 被 ADR-016 supersede§1 upload / §3 半自動分流原則 / §4 模組劃分 / §6 user_id trust boundary 仍有效)、adr/adr-015-server-to-server-api-key.md v2.1§2 被 ADR-016 supersede§1 visionA → converter API key 仍有效)、adr/adr-016-download-via-converter.mdv0.6 上位) 角色:給 visionA-frontend 實作時的 API 契約


通用約定

項目
通用回應格式 { "success": true, "data": {...} } / { "success": false, "error": {code, message, details?} }
Auth 走 cookiefrontend 用 credentials: "include"
Request ID header X-Request-IdvisionA-backend 沒收到會自動產生)
Content-Type initmultipart/form-data 外,其他 JSON

1. POST /api/conversion/init

啟動轉檔 job — 把 multipart body streaming proxy 到 converter。

Request

POST /api/conversion/init HTTP/1.1
Cookie: visiona_session=...
Content-Type: multipart/form-data; boundary=----xyz

multipart fields注意:不要帶 user_idbackend 會從 cookie 灌

Field Type 必填 說明
model file .onnx / .tflite,≤ 500MB
ref_images[] file × N 可 0100 張,每張 ≤ 10MB
model_id text 165535使用者自訂編號converter 要求)
version text v1.0.0
platform text 520 / 720
enable_evaluate text true/false,預設 false
enable_sim_fp text 同上
enable_sim_fixed text 同上
enable_sim_hw text 同上

Response 200

{
  "success": true,
  "data": {
    "job_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "running",
    "stage": "onnx",
    "progress": 0,
    "stage_progress": 0,
    "created_at": "2026-04-30T12:00:00Z",
    "expires_at": "2026-05-07T12:00:00Z"
  }
}

expires_at = created_at + 7dconverter 7 天 GC 截止時間。frontend 用於顯示倒數與切「已過期」狀態。詳見 conversion.md §2.6.2。

錯誤

HTTP code 來源 處理建議
400 validation_failed converter 顯示 details.fields
401 unauthorized visionA redirect /login
409 active_job_exists visionA pre-check / converter 顯示「你已有進行中任務」+ details.job
413 payload_too_large converter 提示檔案大小限制
502 converter_unavailable visionA 提示「轉檔服務暫時無法使用」+ 重試按鈕
502 converter_auth_failed visionAPhase 0.8b v1.0 新增v2.0 維持) 同上文字 — frontend 看不出差別SRE 從 log 排查 converter API key 同步
503 service_busy converter 提示稍後重試

2. GET /api/conversion/{job_id}

查 job 狀態。Frontend 用 polling建議間隔 2 秒。

Response 200

{
  "success": true,
  "data": {
    "job_id": "550e8400-...",
    "status": "running",
    "stage": "bie",
    "progress": 45,
    "stage_progress": 60,
    "created_at": "2026-04-30T12:00:00Z",
    "updated_at": "2026-04-30T12:05:30Z",
    "expires_at": "2026-05-07T12:00:00Z",
    "source_filename": "yolov5s.onnx",
    "target_chip": "720",
    "error_code": null,
    "error_message": null
  }
}

status enumcreated / running / completed / failed stage enumonnx / bie / nef

欄位 用途
expires_at created_at + 7dfrontend 顯示倒數
source_filename 原始檔名(顯示用,例 wireframe success card 「yolov5s.onnx → yolov5s_kl720.nef」
target_chip 從 init 時的 platform 欄回傳(520 / 720 / 630 / 730

錯誤

HTTP code 處理
403 forbidden job 不屬於當前 user
404 not_found job_id 不存在 / 已過期
502 converter_unavailable 持續失敗 → 提示重試

Polling 建議

  • Frontend 收到 status=running → 2s 後再 poll
  • status=completed / failed → 停止 polling
  • 連續 5 次 5xx → 停止 polling 並顯示錯誤

3. POST /api/conversion/{job_id}/promote-to-models

「加到模型庫」 — 完整流程promote → FAA pull → 寫進 visionA model store。

Request

POST /api/conversion/{job_id}/promote-to-models
Content-Type: application/json

{
  "name": "yolov5s_kl720"
}
Field 必填 說明
name 在 model 庫顯示的名字。Design Phase 0.8 wireframe §7.1 要求此欄位,預設 {job.source_filename_stem}_{target_chip.lower()}
description Phase 0.8 不送,留 Phase 1— backend 接受但忽略Phase 1 開放

與 Design 對齊(議題 #4Phase 0.8 wireframe §7.1 的 import Dialog 只有名稱欄位不含描述backend Phase 0.8 也只用 namedescription 雖在 schema 內但不顯示給使用者填寫。Phase 1 Design 開放描述欄位時 backend 已 ready無需改 API。

Response 201

{
  "success": true,
  "data": {
    "model_id": "abc-123",
    "source": "converted",
    "source_job_id": "550e8400-...",
    "name": "YOLOv5 Face KL520",
    "target_chip": "kl520",
    "file_size": 12345678,
    "status": "ready",
    "created_at": "2026-04-30T12:30:00Z"
  }
}

格式註記:這個 response 是既有 internal/model.Model schema沿用target_chip"kl520" 小寫格式。 跟 §2 / §5 conversion job 的 target_chip"720"converter platform enum不同欄位、不同來源

  • conversion job來自 converter scheduler 的 platform 欄位("520" / "630" / "720" / "730"
  • model.target_chipvisionA 既有 model schema"kl520" / "kl720" / etc

visionA-frontend 統一 normalize 成 UI 內部形式 KL520 / KL720 顯示(見 lib/api/conversion.ts normalizeTargetChip)。 Phase 1 評估是否值得在 backend 把兩邊統一(可能影響既有 model store 多處 caller動範圍大

錯誤

HTTP code 處理
403 forbidden 不是該 user 的 job
404 not_found job_id 不存在visionA ownership 找不到、或 converter 端 GET result 回 404 result_not_found
409 job_not_completed job 還沒 completed不能 promote
410 result_expired v0.6 新增job completed 但 converter MinIO 內 NEF 已過 7 天 expires_at 被 GC無法 promote
502 converter_unavailable promote 失敗 / converter GET result 5xx可重試
502 converter_auth_failed converter API key 不同步(運維事件)

冪等性:對同一 job_id 重複呼叫;若已建過 model record回 200 + 既有 model 詳情(不重新建)。

v0.6 註:撤回 v0.5 的 mc_token_unavailable / download_token_failed / faa_unavailable 三個 codev0.5 規劃要回收給 FAA 線用。visionA 端 v0.6 不再有 visionA → MC / visionA → FAA 直接呼叫promote 內部 NEF pull 改走 converter.GetResult,失敗模式收斂為 converter_unavailable / converter_auth_failed / result_not_found / result_expired。converter → FAApromote 時 converter 內部 PUT是 converter 自己的事、與 visionA 無關converter 端失敗會在 promote response 中體現為 5xx


4. GET /api/conversion/{job_id}/download

「下載」 — Phase 0.8b v0.6visionA-backend server-side stream proxy from converter GET /api/v1/jobs/{id}/result。流程演進:

  • Phase 0.8 (ADR-014)「302 redirect to FAA delegated URL」browser 直連 FAA
  • Phase 0.8b v0.4 (ADR-015 v1.x)server-side stream proxy從 FAA pull NEFtoken 用 visionA API key
  • Phase 0.8b v0.5 (ADR-015 v2.0):保留 server-side stream proxytoken 來源改回 MC delegated download token對 MC source 驗證後確認設計 fictional、未實際 e2e 跑通
  • Phase 0.8b v0.6 (ADR-016):保留 server-side stream proxystream 來源從 FAA 改 converter GET /api/v1/jobs/{id}/resultvisionA 端不再經 MC / FAA

詳見 ADR-016conversion.md §3 / §4.1。

對 frontend 而言呼叫方式完全一致<a href> / window.location.hrefresponse 仍是「200 + NEF binary stream + Content-Disposition: attachment」與 v0.4 / v0.5 一致;只是 visionA backend 內部抓 stream 的下游 endpoint 改變frontend 無感)。

Request

GET /api/conversion/{job_id}/download HTTP/1.1
Cookie: visiona_session=...

無 query string、無 body。

Response 200成功 — Phase 0.8b 變更)

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 12345678
Content-Disposition: attachment; filename="yolov5s_kl720.nef"
Cache-Control: no-store, no-cache, must-revalidate, max-age=0

<NEF binary stream...>

browser 收到 Content-Disposition: attachment 自動觸發下載對話框 / 直接存到 Downloads。

Frontend 使用方式(與 Phase 0.8 完全一致)

<!-- 推薦anchor tagbrowser 自動處理 attachment download -->
<a href={`/api/conversion/${jobId}/download`} download>下載</a>

或:

// 程式化觸發
window.location.href = `/api/conversion/${jobId}/download`;

Frontend 不需處理 token、不需處理 redirectContent-Disposition: attachment 觸發 browser 原生 download 行為。

Phase 0.8b 不需要 FAA CORSvisionA backend → FAA 是 server-side 同進程 outbound HTTP call完全不適用 CORSCORS 只管 browser JS fetch / XHR。同源 endpoint + server-side stream + attachment header = 無 CORS 議題。

錯誤(依 Accept header 回 JSON 或 HTML 錯誤頁)

HTTP code 處理
401 unauthorized 沒登入redirect /login前端攔截
403 forbidden 不是該 user 的 job
404 not_found job_id 不存在 / 已過期visionA ownership 找不到,或 converter GET result 回 404 result_not_found
409 job_not_completed job 還沒 completed不能下載
410 result_expired v0.6 新增job completed 但 converter MinIO 內 NEF 已過 7 天 expires_at 被 GCfrontend 顯示「轉檔結果已過期,請重新轉檔」並提供重新轉檔 CTA
502 converter_unavailable promote 失敗(首次下載且尚未 promote 過時可能發生)/ converter GET result 5xx
502 converter_auth_failed converter API key 不同步運維事件frontend 不需區分)

錯誤回應格式:依 Accept header

  • Accept: application/json{success:false, error:{code, message}}
  • Accept: text/html(一般 anchor 觸發) → HTML 錯誤頁browser 直接顯示

注意

  • 每次「下載」按鈕都直接打 /download endpoint不要前端 cache 任何中間狀態
  • Phase 0.8bv0.4 / v0.5 / v0.6server-side proxy 模式下visionA backend 變 streaming bottleneck — Phase 1+ 量大評估升級converter Phase 2 download-tokens 讓 browser 直連 converter或 FAA HMAC token
  • 不會與 promote-to-models 衝突;兩者內部都會 ensurePromoted冪等兩條路徑都從 converter GET result 拉同一份 NEF stream
  • v0.6 後 promote-to-models 也走 converter.GetResult(與 download 共用同一條 path不再有 delegated token / FAA pull 任何概念)

v0.6 註:撤回 v0.5 加的 mc_token_unavailable / download_token_failed / faa_unavailable 三個 code。對 frontend 文字仍維持一致(轉檔服務暫時無法使用),用 converter_unavailable 統一表達;result_expired410是新增的明確 case給 frontend 「過期」精確提示。


5. GET /api/conversion/active

查當前 user 是否有 active job — 給 frontend 在跳出「上傳」UI 前 pre-check。

Response 200有 active

{
  "success": true,
  "data": {
    "has_active": true,
    "job": {
      "job_id": "550e8400-...",
      "status": "running",
      "stage": "bie",
      "progress": 45,
      "created_at": "2026-04-30T12:00:00Z",
      "expires_at": "2026-05-07T12:00:00Z",
      "source_filename": "yolov5s.onnx",
      "target_chip": "720"
    }
  }
}

此 endpoint 與 GET /api/conversion/{job_id} 回傳同一個 Job shapewireframe §3.3、flow-conversion.md §5.1 依賴此 shape 做「進入頁面就直接落 processing 畫面」的恢復邏輯。

重啟恢復行為Phase 0.8 強化):當 visionA-backend 重啟導致 in-memory ownership 丟失時,此 endpoint 會 fallback 對 converter 查 GET /api/v1/jobs?user_id=<sub>&status=in_progress 並重建 ownershiplazy rebuild。對 frontend 完全透明(同樣 endpoint、同樣 response shape。詳見 conversion.md §2.6.1。

Response 200無 active

{
  "success": true,
  "data": {
    "has_active": false,
    "job": null
  }
}

用法

Frontend 在「轉檔」入口的 /conversion 頁載入時打這個 endpoint

  • has_active=true → 顯示「你目前有進行中的任務」+ 跳轉到該 job 的進度頁
  • has_active=false → 顯示上傳表單

錯誤碼總覽

對齊 conversion.md §6。前端 i18n key 統一 conversion.error.<short-name>

Phase 0.8b v0.6 變更:撤回 v0.5「回收 MC 相關 code」決定。visionA 端不再有任何 visionA → MC / visionA → FAA 直接呼叫,mc_token_unavailable / download_token_failed / faa_unavailable 三個 code 全部移除。新增 result_not_found404result_expired410對應 converter GET result endpoint 的新失敗模式。

code HTTP i18n key 預設訊息zh-TW
validation_failed 400 conversion.error.validation 上傳的內容不符合要求
unauthorized 401 common.error.unauthorized 請先登入
forbidden 403 conversion.error.forbidden 你無權存取此任務
not_found 404 conversion.error.not_found 任務不存在
result_not_found 404 conversion.error.not_found 任務不存在v0.6 新增converter GET result 回 404i18n 與 not_found 共用文字、SRE 從 log 區分)
active_job_exists 409 conversion.error.active_job 你目前已有進行中的轉檔任務
job_not_completed 409 conversion.error.not_completed 任務尚未完成(promote-to-modelsdownload 共用)
result_expired 410 conversion.error.result_expired 轉檔結果已過期請重新轉檔v0.6 新增converter GET result 回 410frontend 顯示重新轉檔 CTA
payload_too_large 413 conversion.error.too_large 檔案超過大小限制
converter_unavailable 502 conversion.error.converter_down 轉檔服務暫時無法使用
converter_auth_failed 502 conversion.error.converter_down 轉檔服務暫時無法使用v1.0 新增v2.0 / v0.6 維持 — frontend 文字同 converter_unavailable
service_busy 503 conversion.error.busy 系統繁忙,請稍後再試

Phase 0.8b v0.6 撤回v0.5 加的、v0.6 移除)

已撤回 原 HTTP 原語意v0.5 v0.6 替代
mc_token_unavailable 502 MC /oauth/token 4xx / 5xx 不需要visionA 端不再打 MC
download_token_failed 502 MC /file-access/download-tokens 4xx / 5xx 不需要visionA 端不再 issue delegated token
faa_unavailable 502 FAA pull 失敗 不需要visionA 端不再直接打 FAAconverter GET result 失敗統一收斂進 converter_unavailable

Phase 0.8b v0.4 → v0.5 → v0.6 演進摘要

code v0.4 狀態 v0.5 狀態 v0.6 狀態
converter_auth_failed 新增 維持 維持(同一把 key 也用於 GetResult
converter_unavailable 維持 維持 維持(含 GET result 5xx 收斂)
result_not_found 新增converter GET result 404
result_expired 新增converter GET result 410
faa_auth_failed 新增 撤回 維持撤回
faa_unavailable 使用中 使用中 撤回
mc_token_unavailable 移除 回收 撤回
download_token_failed 移除 回收 撤回
idp_misconfigured 移除 維持移除 維持移除
idp_unavailable 移除 維持移除 維持移除

frontend i18n 字典:

  • v0.6 後 conversion.error.faa_down i18n key 可保留但不再被任何 code 引用(向下相容;舊版 backend 不會發 faa_* / mc_* 三個 code
  • 新增 i18n keyconversion.error.result_expired —「轉檔結果已過期,請重新轉檔」(給 410 用、與其他 502 文字明確區分)
  • result_not_foundnot_found 共用 conversion.error.not_found i18n keyuser 角度兩者等價:任務不存在)

版本記錄

日期 版本 變更
2026-04-30 0.1 初稿Phase 0.8 MVP 範圍)
2026-04-30 0.2 §4 download endpoint 從 POST /{job}/download-token(回 JSON {download_url, expires_at})改為 GET /{job}/downloadHTTP 302 redirect仿 FAA TestSite DownloadFileDirect patterntoken 不過 frontend JS、不需 FAA CORSjob_not_completed HTTP code 從 400 改為 409 + 補 mc_token_unavailable
2026-04-30 0.3 Phase 0.8 三方交叉審閱回饋整合Job response shape 補 expires_at / source_filename / target_chip(議題 #7/api/conversion/active 行為文件化 lazy rebuild 機制(議題 #2 重啟恢復);promote-to-models request body 對齊 Design 單欄位(議題 #4description 留 Phase 1
2026-05-11 0.4 Phase 0.8b 對應 ADR-015(1) Header / 文件 metadata 標示服務間認證改 pre-shared API key對 frontend 透明);(2) §4 download endpoint response 從「302 Location」改為「200 + NEF binary + Content-Disposition: attachment」— frontend 呼叫方式(<a href> / window.location.href)完全一致;(3) 錯誤碼總覽:移除 4 個 MC 相關 codeidp_misconfigured / idp_unavailable / download_token_failed / mc_token_unavailable),新增 2 個 *_auth_failedconverter_auth_failed / faa_auth_failed)對應 API key 不同步的運維事件
2026-05-16 0.5 對應 ADR-015 v2.0 範圍縮限:撤回 v0.4「visionA → FAA 改 API key」FAA 線回到 ADR-014 §2 原設計MC service token + delegated download tokenvisionA → converter API key 路線v0.4維持。主要變更:(1) Header metadata 區分 converter 線 / FAA 線兩條 server-to-server 認證;(2) §3 promote-to-models 與 §4 download endpoint 錯誤碼回收 mc_token_unavailable / download_token_failed,撤回 v0.4 加的 faa_auth_failed(3) 錯誤碼總覽表更新對應;(4) §4 download endpoint 描述更新「v0.4 server-side proxy + v0.5 token 來源改回 delegated download token」演進。Frontend 行為對 user 完全透明response shape / call pattern / 錯誤文字一律不變;只是 visionA backend 內部 token 來源演進。
2026-05-16 0.6 對應 ADR-016:撤回 v0.5「visionA → FAA 改回 MC service token + delegated download token」全部規劃。原因:對 MC source 全 grep 驗證後確認 MC 沒有 POST /file-access/download-tokens endpoint、也沒有 FAA IDelegatedDownloadTokenValidator assume 的 introspection endpoint—— v0.5 設計是 fictional。v0.6 新設計visionA download 改走 converter 新增的 GET /api/v1/jobs/{id}/result + visionA stream 中轉visionA 端不再有任何 visionA → FAA / visionA → MC 路徑、server-to-server 認證收斂為單條 visionA → converterAPI key。主要變更(1) Header metadata 區段改寫「服務間認證visionA → FAA / MC」段落整段撤回(2) §3 promote-to-models 錯誤表移除 mc_token_unavailable / download_token_failed / faa_unavailable,新增 result_expired410(3) §4 download endpoint 描述更新 v0.6 演進、錯誤表同樣移除 v0.5 加的 MC / FAA 三個 code、新增 result_expired(4) 錯誤碼總覽表更新 — 新增 result_not_found404 + result_expired410兩個 code撤回 faa_unavailable / mc_token_unavailable / download_token_failed(5) v0.4 → v0.5 → v0.6 演進摘要表加 v0.6 column(6) i18n 字典補 conversion.error.result_expired 新 key。Frontend 行為對 user 仍完全透明response shape / call pattern 不變;只是 visionA backend 內部 stream 來源從 FAA 改為 converter新增 frontend 需處理410 result_expired 顯示「請重新轉檔」CTAi18n key conversion.error.result_expired)。