致命發現(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>
389 lines
22 KiB
Markdown
389 lines
22 KiB
Markdown
# API — Conversion(轉檔功能,Phase 0.8 / Phase 0.8b v0.6)
|
||
|
||
> **base URL**:`https://stage-9527.innovedus.com:9527/`(stage) / `http://localhost:3721`(dev)
|
||
> **Auth(user → visionA)**:OIDC cookie session(`visiona_session`),參見 `oidc-tdd.md` — 與 Phase 0.8 完全一致,未變
|
||
> **服務間認證(visionA → converter)**:**Phase 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](../adr/adr-015-server-to-server-api-key.md)
|
||
> **服務間認證(visionA → FAA / MC)**:**Phase 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](../adr/adr-016-download-via-converter.md) + `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.md`(v0.6 上位)
|
||
> **角色**:給 visionA-frontend 實作時的 API 契約
|
||
|
||
---
|
||
|
||
## 通用約定
|
||
|
||
| 項目 | 值 |
|
||
|------|-----|
|
||
| 通用回應格式 | `{ "success": true, "data": {...} }` / `{ "success": false, "error": {code, message, details?} }` |
|
||
| Auth | 走 cookie;frontend 用 `credentials: "include"` |
|
||
| Request ID | header `X-Request-Id`(visionA-backend 沒收到會自動產生) |
|
||
| Content-Type | 除 `init` 用 `multipart/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_id,backend 會從 cookie 灌**):
|
||
|
||
| Field | Type | 必填 | 說明 |
|
||
|-------|------|-----|------|
|
||
| `model` | file | ✓ | `.onnx` / `.tflite`,≤ 500MB |
|
||
| `ref_images[]` | file × N | — | 可 0–100 張,每張 ≤ 10MB |
|
||
| `model_id` | text | ✓ | 1–65535,使用者自訂編號(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
|
||
|
||
```json
|
||
{
|
||
"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 + 7d`(converter 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` | visionA(Phase 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
|
||
|
||
```json
|
||
{
|
||
"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` enum:`created` / `running` / `completed` / `failed`
|
||
`stage` enum:`onnx` / `bie` / `nef`
|
||
|
||
| 欄位 | 用途 |
|
||
|------|------|
|
||
| `expires_at` | `created_at + 7d`,frontend 顯示倒數 |
|
||
| `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
|
||
|
||
```json
|
||
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 對齊(議題 #4)**:Phase 0.8 wireframe §7.1 的 import Dialog **只有名稱欄位**(不含描述);backend Phase 0.8 也只用 `name`,`description` 雖在 schema 內但不顯示給使用者填寫。Phase 1 Design 開放描述欄位時 backend 已 ready,無需改 API。
|
||
|
||
### Response 201
|
||
|
||
```json
|
||
{
|
||
"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_chip:visionA 既有 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` 三個 code(v0.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 → FAA(promote 時 converter 內部 PUT)是 converter 自己的事、與 visionA 無關(converter 端失敗會在 promote response 中體現為 5xx)。
|
||
|
||
---
|
||
|
||
## 4. `GET /api/conversion/{job_id}/download`
|
||
|
||
「下載」 — **Phase 0.8b v0.6:visionA-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 NEF),token 用 visionA API key
|
||
- **Phase 0.8b v0.5 (ADR-015 v2.0)**:保留 server-side stream proxy;token 來源改回 MC delegated download token(**對 MC source 驗證後確認設計 fictional、未實際 e2e 跑通**)
|
||
- **Phase 0.8b v0.6 (ADR-016)**:保留 server-side stream proxy;**stream 來源從 FAA 改 converter `GET /api/v1/jobs/{id}/result`**;visionA 端不再經 MC / FAA
|
||
|
||
詳見 [ADR-016](../adr/adr-016-download-via-converter.md)、`conversion.md` §3 / §4.1。
|
||
|
||
對 frontend 而言**呼叫方式完全一致**(`<a href>` / `window.location.href`),response 仍是「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 完全一致)
|
||
|
||
```html
|
||
<!-- 推薦:anchor tag,browser 自動處理 attachment download -->
|
||
<a href={`/api/conversion/${jobId}/download`} download>下載</a>
|
||
```
|
||
|
||
或:
|
||
|
||
```ts
|
||
// 程式化觸發
|
||
window.location.href = `/api/conversion/${jobId}/download`;
|
||
```
|
||
|
||
Frontend **不需處理 token、不需處理 redirect**;`Content-Disposition: attachment` 觸發 browser 原生 download 行為。
|
||
|
||
**Phase 0.8b 不需要 FAA CORS**:visionA backend → FAA 是 server-side 同進程 outbound HTTP call,完全不適用 CORS(CORS 只管 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 被 GC;frontend 顯示「轉檔結果已過期,請重新轉檔」並提供重新轉檔 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.8b(v0.4 / v0.5 / v0.6)server-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_expired`(410)是新增的明確 case,給 frontend 「過期」精確提示。
|
||
|
||
---
|
||
|
||
## 5. `GET /api/conversion/active`
|
||
|
||
查當前 user 是否有 active job — 給 frontend 在跳出「上傳」UI 前 pre-check。
|
||
|
||
### Response 200(有 active)
|
||
|
||
```json
|
||
{
|
||
"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` shape;wireframe §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` 並重建 ownership(lazy rebuild)。對 frontend 完全透明(同樣 endpoint、同樣 response shape)。詳見 `conversion.md` §2.6.1。
|
||
|
||
### Response 200(無 active)
|
||
|
||
```json
|
||
{
|
||
"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_found`(404)與 `result_expired`(410)對應 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` 回 404;i18n 與 `not_found` 共用文字、SRE 從 log 區分)|
|
||
| `active_job_exists` | 409 | `conversion.error.active_job` | 你目前已有進行中的轉檔任務 |
|
||
| `job_not_completed` | 409 | `conversion.error.not_completed` | 任務尚未完成(`promote-to-models` 與 `download` 共用) |
|
||
| `result_expired` | 410 | `conversion.error.result_expired` | 轉檔結果已過期,請重新轉檔(v0.6 新增:converter `GET result` 回 410;frontend 顯示重新轉檔 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 端不再直接打 FAA;converter `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 key**:`conversion.error.result_expired` —「轉檔結果已過期,請重新轉檔」(給 410 用、與其他 502 文字明確區分)
|
||
- `result_not_found` 與 `not_found` 共用 `conversion.error.not_found` i18n key(user 角度兩者等價:任務不存在)
|
||
|
||
---
|
||
|
||
## 版本記錄
|
||
|
||
| 日期 | 版本 | 變更 |
|
||
|------|------|------|
|
||
| 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}/download`(HTTP 302 redirect),仿 FAA TestSite `DownloadFileDirect` pattern;token 不過 frontend JS、不需 FAA CORS;`job_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 單欄位(議題 #4,`description` 留 Phase 1) |
|
||
| 2026-05-11 | 0.4 | **Phase 0.8b** 對應 [ADR-015](../adr/adr-015-server-to-server-api-key.md):(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 相關 code(`idp_misconfigured` / `idp_unavailable` / `download_token_failed` / `mc_token_unavailable`),新增 2 個 `*_auth_failed`(`converter_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 token);visionA → 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](../adr/adr-016-download-via-converter.md)**:撤回 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 → converter(API key)。主要變更:(1) Header metadata 區段改寫「服務間認證(visionA → FAA / MC)」段落,整段撤回;(2) §3 promote-to-models 錯誤表移除 `mc_token_unavailable` / `download_token_failed` / `faa_unavailable`,新增 `result_expired`(410);(3) §4 download endpoint 描述更新 v0.6 演進、錯誤表同樣移除 v0.5 加的 MC / FAA 三個 code、新增 `result_expired`;(4) 錯誤碼總覽表更新 — 新增 `result_not_found`(404) + `result_expired`(410)兩個 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` 顯示「請重新轉檔」CTA(i18n key `conversion.error.result_expired`)。 |
|