jim800121chen b9c228df4f docs(autoflow): Phase 0.8b ADR-015 + TDD 修訂 — server-to-server 改 API key
新增 ADR-015:visionA → converter / FAA 從 OAuth client_credentials 改 pre-shared API key
- §1-§9 決策、4 個替代方案、後果分析、合規性
- §3.5 reference middleware snippet(Go converter + C# FAA 兩種寫法)+ 部署檢查清單
- 部分 supersede ADR-014 §5/§6/§7(service token / scope / MC retry rows)
- 觸發背景:Phase 0.8 stage e2e 撞 4 個 blocker,1:1 internal trust 用 OAuth client_credentials 過度設計

3 份 TDD 配合修訂:
- conversion.md:重寫 §3 服務間認證、§4.1 download 退回 server-side stream proxy、刪 §2.4 mc_token_client、§5.3 補 cancel 鏈、§10.3 改 pre-shared key 保護
- api-conversion.md:error code idp_unavailable → converter_auth_failed/faa_auth_failed;download response 從 302 redirect 改 200 + Content-Disposition: attachment + NEF stream
- oidc-tdd.md:標廢棄 service client env 兩 row、新增 API key env 兩 row、§13.1.3 user login 與 server-to-server 脫鉤說明、v0.2 changelog

未動:source code(步驟 2 由 backend agent 處理;範圍含 mc_token_client 刪除、TenantID 移除、API key 改造,含 test files)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 06:39:45 +08:00

15 KiB
Raw Blame History

API — Conversion轉檔功能Phase 0.8 / Phase 0.8b

base URLhttps://stage-9527.innovedus.com:9527/stage / http://localhost:3721dev Authuser → visionAOIDC cookie sessionvisiona_session),參見 oidc-tdd.md — 與 Phase 0.8 完全一致,未變 服務間認證visionA → converter / FAAPhase 0.8b 已改為 pre-shared API key(取代 OAuth client_credentials— 對 frontend 透明,不影響本 API 契約;詳見 conversion.md §3、adr/adr-015-server-to-server-api-key.md 同層api/api-spec.md(總覽)、conversion.md(內部設計)、adr/adr-014-conversion-integration.md(仍有效部分)、adr/adr-015-server-to-server-api-key.mdPhase 0.8b 認證機制) 角色:給 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 新增) 同上文字 — frontend 看不出差別SRE 從 log 排查 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 不存在
409 job_not_completed job 還沒 completed不能 promote
502 converter_unavailable promote 失敗,可重試
502 converter_auth_failed converter API key 不同步(運維事件)
502 faa_unavailable FAA pull 失敗,可重試
502 faa_auth_failed FAA API key 不同步(運維事件)

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


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

「下載」 — Phase 0.8bvisionA-backend server-side stream proxy(從 FAA pull NEF stream 後中轉回 browser。Phase 0.8 原本的「302 redirect to FAA delegated URL」設計因服務間認證改 API key 而退回 proxy 模式,詳見 ADR-015 §7、conversion.md §4.1。

對 frontend 而言呼叫方式完全一致<a href> / window.location.href),但 response 從「302 Location」變成「200 + NEF binary stream + Content-Disposition: attachment」。

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 不存在 / 已過期
409 job_not_completed job 還沒 completed不能下載
502 converter_unavailable promote 失敗(首次下載且尚未 promote 過時可能發生)
502 converter_auth_failed converter API key 不同步運維事件frontend 不需區分)
502 faa_unavailable FAA pull 失敗
502 faa_auth_failed FAA API key 不同步運維事件frontend 不需區分)

錯誤回應格式:依 Accept header

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

注意

  • 每次「下載」按鈕都直接打 /download endpoint不要前端 cache 任何中間狀態
  • Phase 0.8b 退回 server-side proxy 後visionA backend 變 streaming bottleneck — Phase 1 量大評估升級ADR-015 §7 選項 B
  • 不會與 promote-to-models 衝突;兩者內部都會 ensurePromoted冪等兩條路徑都拿同一個 target_object_key

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 變更:移除 4 個 MC 相關錯誤碼(download_token_failed / mc_token_unavailable / idp_unavailable / idp_misconfigured)— 服務間認證取消 MC 依賴。新增 2 個 *_auth_failed 錯誤碼對應 API key 不同步的運維事件frontend 不需區分UX 文字仍是「服務暫時無法使用」)。

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 任務不存在
active_job_exists 409 conversion.error.active_job 你目前已有進行中的轉檔任務
job_not_completed 409 conversion.error.not_completed 任務尚未完成(promote-to-modelsdownload 共用)
payload_too_large 413 conversion.error.too_large 檔案超過大小限制
converter_unavailable 502 conversion.error.converter_down 轉檔服務暫時無法使用
converter_auth_failed 502 conversion.error.converter_down 轉檔服務暫時無法使用Phase 0.8b 新增 — frontend 文字同 converter_unavailable
faa_unavailable 502 conversion.error.faa_down 檔案存取服務暫時無法使用
faa_auth_failed 502 conversion.error.faa_down 檔案存取服務暫時無法使用Phase 0.8b 新增)
service_busy 503 conversion.error.busy 系統繁忙,請稍後再試

Phase 0.8b 移除(不會再出現的舊 code

已移除 原 HTTP 原語意
idp_misconfigured 500 MC token endpoint 4xx
idp_unavailable 503 MC token endpoint 5xx
download_token_failed 502 MC delegated token 4xx
mc_token_unavailable 502 MC 持續失敗

frontend i18n 字典可保留 conversion.error.idp_down / conversion.error.token_failed 兩個 key 暫不刪除(防舊版 client 拿到舊 error code 時還能翻譯),但新版 backend 已不再回這 4 個 code。


版本記錄

日期 版本 變更
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 不同步的運維事件