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

389 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# API — Conversion轉檔功能Phase 0.8 / Phase 0.8b v0.6
> **base URL**`https://stage-9527.innovedus.com:9527/`stage / `http://localhost:3721`dev
> **Authuser → 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 | 走 cookiefrontend 用 `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_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
```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` | 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
```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_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 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 tagbrowser 自動處理 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完全不適用 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_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` 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
```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` 回 404i18n 與 `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` 回 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 key**`conversion.error.result_expired` —「轉檔結果已過期,請重新轉檔」(給 410 用、與其他 502 文字明確區分)
- `result_not_found``not_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}/download`HTTP 302 redirect仿 FAA TestSite `DownloadFileDirect` patterntoken 不過 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 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](../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 → converterAPI 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` 顯示「請重新轉檔」CTAi18n key `conversion.error.result_expired`)。 |