# Phase 0.8 三方交叉審閱 — Architect 視角 > **作者**:Architect Agent > **日期**:2026-04-30 > **審閱對象**:PM PRD(`feature-converter-integration.md`)、Design wireframe(`wireframe-conversion.md`)、Design flow(`flow-conversion.md`) > **對照基準**:Architect ADR-014 v1.1、`conversion.md` v0.3、`api/api-conversion.md` v0.3 --- ## 摘要 | 對象 | 嚴重 | 中 | 小 / nit | |------|-----|----|---------| | PM PRD | 1 | 4 | 2 | | Design wireframe | 1 | 2 | 2 | | Design flow | 1 | 1 | 1 | > **嚴重 = 直接導致實作有歧義或 frontend / backend 對不上**;中 = 文件不精確但可推測;小 = 命名 / 措辭。 **Architect 自己的 TDD 修訂**:4 個章節(議題 #2 / #5 / #6 / #7)已寫進 `conversion.md` v0.3 + `api/api-conversion.md` v0.3。詳見 §3。 **需要使用者裁決的事項**:1 件(API endpoint 路徑命名 — `/api/conversion/*` vs `/api/converter/jobs/*`,目前三方文件不一致)。 --- ## 1. 對 PM PRD 的審閱(`02-prd/features/feature-converter-integration.md`) ### 1.1 ✅ 同意的部分 - **§1 概要 / §2 User Stories**:MVP 範圍清楚,半自動設計理由明確 - **§3 F1–F5 的功能定義**:與我 TDD 的 endpoint 行為對齊(status enum、5–10s polling、500MB cap、ref images 上限、translated error messages) - **§4 非功能需求**:500MB / 10MB / 100 張上限、polling 間隔、active job 限制 — 全部已在 TDD §4 / §9 / §10 cover - **§5 Non-Goals**:完整且符合 MVP 邊界(不做歷史 / 取消 UI / SSE / 多 chip) - **§6 整合決策 D1–D6**:與 ADR-014 完全一致(streaming proxy / FAA 直連 / 半自動 / polling / converter API 不動 / sidebar 獨立 tab) - **§7 Dependency 一覽**:清楚標出 converter / FAA / MC / visionA-backend / visionA-frontend 各自要做什麼 - **§9 驗收條件**:可測、與 TDD 行為對齊 - **§11 給 Architect 的注意事項**:streaming proxy 用 `io.Pipe` 不 buffer 全 RAM、user-scoped 授權、idempotent promote — 全部已落地 ### 1.2 ⚠️ 嚴重 — API endpoint 路徑命名與 TDD 不一致 **位置**:PRD §F6 / §F7 ``` PRD §F6:POST /api/converter/jobs/{id}/import-to-models PRD §F7:POST /api/converter/jobs/{id}/download-token Wireframe §3.3 / Flow §3:GET /api/converter/jobs/active Wireframe §6.3:GET /api/converter/jobs/{id}(polling) TDD(Architect): POST /api/conversion/init GET /api/conversion/{job_id} POST /api/conversion/{job_id}/promote-to-models GET /api/conversion/{job_id}/download ← 注意:是 GET、不是 POST `/download-token` GET /api/conversion/active ``` **三個衝突點**: 1. **path prefix**:`/api/converter/jobs/*`(PM/Design)vs `/api/conversion/*`(Architect) 2. **action 命名**:`import-to-models`(PM)vs `promote-to-models`(Architect) 3. **download endpoint method + 形式**:PRD/Design 假設 `POST /download-token` 回 `{url, expires_at}` JSON;TDD 已改為 `GET /download` server-side 302 redirect(ADR-014 v1.1 議題 #9 已決議的安全升級) **建議改法**: 採 Architect 的版本(`/api/conversion/*` + `promote-to-models` + `GET /download` 302)— 理由: - 路徑前綴 `/conversion/` 對應 visionA 內部模組名(`internal/conversion/`),命名一致 - `/converter/jobs/*` 容易讓人誤以為是「轉發給 converter 的 raw API」(其實 visionA backend 有自己的 ownership / 半自動邏輯,不是純 proxy) - `promote-to-models` 對齊 converter 的 `/promote` 動詞、語意精確 - `GET /download` + 302 已在 ADR-014 v1.1 完成決策(安全考量壓倒 UX 微差) **請 PM 修**(新增到 §1.3 PM 修訂建議清單): - §F6 endpoint 改 `POST /api/conversion/{job_id}/promote-to-models` - §F7 endpoint 改 `GET /api/conversion/{job_id}/download`(server 302 redirect)+ 移除「visionA backend 跟 Member Center 換 delegated download token」的描述(這仍正確但藏在 server-side),把「回傳 `{url, expires_at}`」改為「browser 自動 follow 302 直連 FAA」 **請 Design 修**(同節 §2.2): - wireframe §3.3、wireframe §6.3、flow §3 / §5.5 / §8.2 全部 `/api/converter/jobs/*` → `/api/conversion/*` - flow §3 sequence diagram 的 download 那段 `POST /api/converter/jobs/{id}/download-token` + `200 { url, expires_at }` → `GET /api/conversion/{id}/download` + `302 Location: faa-url` - wireframe §7.2「下載」流程描述:移除「拿到 `{ url, expires_at }`」,改為「按鈕觸發 anchor click 或 `window.location.href = '/api/conversion/{id}/download'`,browser 自動處理 302」 ### 1.3 ⚠️ 中 — Active job 端點未列入 PRD **位置**:PRD §F 段(功能需求) PRD §F1–F7 沒有列出「Active job pre-check」這個功能,但 §3 F3 隱含提到「同 user 同時只能跑一個 active job」。Design wireframe §3.3 與 flow §5.1 明確使用 `GET /api/converter/jobs/active`(建議改名 `/api/conversion/active`)做 idle / 重新整理 / 多分頁的恢復邏輯。 **建議**:PRD §F 段補一條 F0: ```markdown ### F0:進入「轉檔」頁面時的 active job 偵測 - 進入 /conversion 時前端打 GET /api/conversion/active - 後端回應 `{ has_active: bool, job? }` - has_active=true → 直接顯示「進行中」畫面(同 F3)+ banner「您離開前的轉檔仍在進行中」 - has_active=false → 顯示空狀態 + CTA ``` 理由:這個端點是 wireframe 的核心(§3.3、§6.4 多分頁同步、§6.6 重啟頁面恢復),在 PRD 沒列就成了「設計依賴的隱形 contract」,三方對齊時容易遺漏。 ### 1.4 ⚠️ 中 — Streaming upload 進度語意未明示 **位置**:PRD §F2「上傳行為」、§F3「轉檔執行與進度」 PRD §F2 寫「使用者看到的是『上傳到 visionA』的單一進度條(XHR upload event)」— 這裡有歧義: - **語意 A**:XHR `upload.onprogress` 顯示 browser → backend 的進度。當 browser send 完 100% 後,backend 還在 forward 給 converter 的階段,前端顯示什麼? - **語意 B**:「上傳到 visionA」= browser → backend 的單一進度,使用者看到 100% 就視為完成 實作上若採 B,則 backend 需要 background goroutine + 額外 ownership 狀態(`upload_in_progress`)才能 early-return;採 A 則 backend 等 converter 201 才回 200,前端進度顯示 100% 後到實際切 processing 之間有 1-3 秒「即將完成…」/「伺服器處理中…」。 **TDD 採用語意 A**(見 `conversion.md` §4.3.1 v0.3 新增)— 理由: - 100% 接近端到端真實狀態,使用者不會被欺騙 - 實作簡單,失敗處理路徑清楚 - 1-3 秒延遲對 500MB 上傳體感影響極小 **建議 PM 補在 §F2 / §F3**: ```markdown > **進度條語意說明**:上傳進度條顯示 browser → visionA-backend 的進度。當顯示 100% 但 > backend 還在轉發給 converter 時,文案會切為「即將完成…」/「伺服器處理中…」(1–3 秒), > 然後才切到「轉檔進行中」畫面。對使用者誠實 — 不謊報已完成。 ``` ### 1.5 ⚠️ 中 — Promote-to-models request body 對齊 Design **位置**:PRD §F6「加到模型庫」流程 PRD §F6 沒有提到「需要使用者輸入名稱」,但 Design wireframe §7.1 設計了 import Dialog 含名稱輸入欄。我這邊 API spec(v0.3)已對齊 Design 的「單欄位 name only」決議。 **建議 PM 補在 §F6**: ```markdown - 點「加到模型庫」開確認 Dialog(單欄位): - 模型名稱(預設 `{source_filename_stem}_{target_chip.lower()}`,可改) - 描述欄位 Phase 1 才開放 - 確認後呼叫 POST /api/conversion/{id}/promote-to-models ``` 或在 §F6 末尾加註:「Dialog UI 與欄位定義詳見設計規格 wireframe §7.1。」 ### 1.6 ⚠️ 中 — visionA-backend 重啟後的 UX **位置**:PRD §11 給 Architect 的注意事項 / 應該補在 §6 整合決策或 §F0 PRD 沒有討論 visionA-backend 重啟(部署、crash recovery)後使用者 UX 該怎樣。我 TDD §2.6.1 v0.3 補了 lazy rebuild 機制(A4 方案 — `/active` 端點 fallback 對 converter 查 `?user_id=&status=in_progress`),讓使用者重啟後仍能看到自己的 active job。 **這需要 converter Phase 1 提供 `GET /api/v1/jobs?user_id=&status=in_progress` endpoint**(converter Phase 1 已實作,但 PRD §7 dependency 表未列入「visionA 會用此 endpoint」)。 **建議 PM 補在 §7 Dependency 表**: ```markdown | kneron_model_converter | ❌ 完全不用動 | 沿用既有 endpoint:`POST /api/v1/jobs`、`GET /api/v1/jobs/{id}`、`POST /promote`、`GET /api/v1/jobs?user_id=&status=in_progress`(visionA-backend 重啟後 lazy rebuild ownership 用)、`POST /api/v1/jobs/{id}/cancel`(visionA-backend 偵測 client disconnect 時內部 cleanup 用) | ``` **也建議在 §6 整合決策表加一條 D7**: ```markdown | D7 | visionA-backend in-memory ownership 重啟遺失 | Phase 0.8 採 lazy rebuild(`/active` fallback 對 converter 查);Phase 0.9+ 評估 DB persist | converter 7 天 expires_at 兜底;UX 上使用者重進頁面仍看得到 active job | ``` ### 1.7 小 — KPI「上傳到拿 NEF P95 < 10 分鐘」量測點 **位置**:PRD §8 KPI PRD §8 寫「visionA backend 在 import / download 觸發點 log timestamp」— 但 import/download 是「使用者拿到 NEF 後的下一步」,不是「拿到 NEF 的當下」。建議改: - 量測點 = converter status 從 `running` → `succeeded` 的當下時間 - `init` 收到 request 的時間 - 這是 backend 視角的端到端時間,不依賴使用者按按鈕 非阻擋議題,建議調整。 ### 1.8 小 — 4 個 chip 數量 PRD §F2 寫「目標 chip:KL520 / KL630 / KL720 / KL730」(4 個),但 TDD `api-conversion.md` §1 與 converter `platform` 欄位的 enum 寫的是 `520 / 720`(2 個)。需確認: - converter Phase 1 實際支援哪幾個 platform? - 如果只支援 520 / 720,PRD 應改為 2 個(避免使用者選了 630/730 然後被 converter 拒絕) - 如果 converter Phase 1 已擴充到 4 個,TDD `platform` enum 應改為 `520 / 630 / 720 / 730` **建議 PM**:請與 converter 團隊確認 Phase 1 實際支援的 chip list,PRD 與 TDD 同步更新。 --- ## 2. 對 Design 的審閱 ### 2.1 對 Wireframe(`03-design/wireframes/wireframe-conversion.md`) #### 2.1.1 ✅ 同意的部分 - **§0 設計對齊備註**:複用既有元件、不新增 Design Tokens、走 i18n — 全部對的方向 - **§1 Sidebar 進入點**:放模型庫之後,心智模型清楚;Wand2 icon 選擇合理 - **§2 頁面狀態總覽**:state 機切 4 種畫面,無 URL query state — 簡潔正確 - **§3.3 邊界 — 已有 active job**:用 `GET /jobs/active` 做進入恢復是正確的設計(解決多分頁 / 重新整理 / 重啟) - **§4 Upload Dialog**:階段切換(select / uploading)合理;§4.4 上傳失敗的 4 種 case 完整 - **§5 主畫面 vs Dialog 進度**:分開顯示避免使用者困惑、tab title 動態更新 - **§6 Processing 畫面**:3-stage indicator 設計、indeterminate progress、polling 間隔(5s 前 60 秒、之後 10s)— 都與 TDD 對齊 - **§6.4 邊界情境**:4 種情境(關分頁、多分頁、長排隊、長執行)有 cover - **§7 Success 結果**:兩按鈕互不互斥 / 過期倒數 / 「開始新轉檔」放外面 — 與 PRD §F4 對齊 - **§8 Failed 狀態 + suggestions**:依 error code 切換建議很棒 - **§8.2 Job 已過期**:對 TDD `expires_at` 機制依賴正確 - **§9 響應式 / §11 i18n / §12 無障礙**:全部到位 #### 2.1.2 ⚠️ 嚴重 — endpoint 路徑與 TDD 不一致 同 §1.2。請改 `/api/converter/jobs/*` → `/api/conversion/*`、`POST /download-token` 形式 → `GET /download` + 302 redirect。 具體要改的位置: - §3.3 邊界表「`GET /api/converter/jobs/active`」 → `GET /api/conversion/active` - §6.3 polling 表「端點 `GET /api/converter/jobs/{id}`」 → `GET /api/conversion/{id}` - §7.1 「呼叫 POST /api/converter/jobs/{id}/import-to-models」 → `POST /api/conversion/{id}/promote-to-models` - §7.2 整段流程:去掉 `POST /download-token` + 拿 `{ url, expires_at }` → 改為 anchor tag 觸發 `GET /api/conversion/{id}/download`,browser 自動 follow 302 §7.2 流程修改範例: ```diff - 1. 點擊 → 按鈕進 loading 狀態(spinner + 「準備下載…」) - 2. 呼叫 POST /api/converter/jobs/{id}/download-token - 3. 200 OK: - - 拿到 { url, expires_at } - - 立即 window.location.href = url(瀏覽器內建下載管理器接手) - - 按鈕回到原狀態(不變灰,使用者可重複下載) + 1. 點擊 → 觸發 anchor 行為 / window.location.href = '/api/conversion/{id}/download' + 2. visionA-backend server-side 302 → browser 自動 follow → 直連 FAA + 3. 按鈕不需 loading 狀態(navigation 由 browser 接管) + 若需要錯誤處理(例 job_not_completed),可改用 fetch + 檢 302 status ``` > **附註**:這個改動實際上「簡化」了 Design 的 §7.2 描述(不用處理 token、不用 fetch then redirect)— 是 ADR-014 v1.1 帶來的好處。 #### 2.1.3 ⚠️ 中 — Stage indicator 對應實際狀態 **位置**:wireframe §6.1 「Stage 對應」表 Design 把 converter 的 `running` 狀態切成兩個視覺 stage(「解析模型」、「編譯 NEF」),純粹是 UI 上的分段,不對應 converter 內部真實階段。 **問題**:converter Phase 1 的 `GET /api/v1/jobs/{id}` response 其實有 `stage` 欄位(`onnx` / `bie` / `nef`,見 api-conversion.md §2 Job response),這比 wireframe 假設的「單一 running 狀態」更精細。 **建議**: - **選項 A**:wireframe 的 3-stage 對應到 converter `stage` 欄位(`onnx` → 解析、`bie` → 量化、`nef` → 編譯),這樣 stage indicator 是真實狀態而不是視覺 fake - **選項 B**:維持現狀(fake stage)— 簡單但不夠誠實 **Architect 建議選項 A**。理由:converter `stage` 已經提供 — 不利用反而浪費。具體 mapping: | converter stage | UI stage 名稱 | 觸發條件 | |----------------|------------|---------| | `onnx` | 解析模型 | converter status=running, stage=onnx | | `bie` | 量化模型 | converter status=running, stage=bie | | `nef` | 編譯 NEF | converter status=running, stage=nef | stage 名稱建議改為 3 個:「解析」/「量化」/「編譯」(更貼近 converter 實際做的事)。 **請 Design 對齊 + 改 §6.1**:把 stage 名稱與 converter `stage` 欄位 mapping,並更新 wireframe §11.6 i18n key。 #### 2.1.4 ⚠️ 中 — Upload XHR 進度 100% 後的文案 **位置**:wireframe §4.2 階段 B「上傳中」 wireframe §4.2 設計「進度條 + 預估剩餘」,但沒明確規定 XHR `loaded === total` 後但 backend 還沒回 200 的這 1-3 秒要顯示什麼。flow §5.3 有提到「即將完成…」/「伺服器處理中…」— 這需要在 wireframe 對齊: **建議**:wireframe §4.2 加註: ```markdown **進度 100% 後但伺服器還沒回**: | XHR 狀態 | 文案 | |---------|-----| | progress < 100% | 已上傳 X / Y · 預估剩餘 N | | progress = 100%, 等待 < 5 秒 | 即將完成… | | progress = 100%, 等待 ≥ 5 秒 | 伺服器處理中… | ``` 對應 i18n key(§11.5)已有 `conversion.uploading.almostDone`,建議再加 `conversion.uploading.serverProcessing`。 #### 2.1.5 小 — 「加到模型庫」按鈕 vs Dialog 確認 wireframe §7.1 設計了確認 Dialog(複用 import flow)— 在 §14 給 PM 的補充第 1 條已正確標記為「待 PM 決定」。 **Architect 立場**:保留 Dialog 是對的(model record 是永久資源、使用者控制名稱比較自然)。已在 TDD api-conversion.md v0.3 的 promote-to-models request body 對齊「單欄位 name only」。 #### 2.1.6 小 — Wand2 icon 替代 §10 給的替代方案合理(FileCog / Replace),不是 Architect 領地,由 Design / 使用者決定。 ### 2.2 對 Flow(`03-design/flows/flow-conversion.md`) #### 2.2.1 ✅ 同意的部分 - **§3 全景圖(Mermaid)**:完整且與 ADR-014 對齊,包含所有 5 個 stage(active check / select / uploading / processing / completed) - **§4 State Machine + 5 狀態保存策略**:「不持久化 jobId 全部由 backend `/active` 提供」是核心原則,正確 - **§5.4 Polling 策略**:visibilitychange 暫停、退避、終止條件 — 全部與 TDD §9 對齊 - **§6 邊界情境**:6.1 active job、6.2 上傳失敗、6.3 過期、6.4 多分頁、6.5/6.6 重新整理 — 全部 cover - **§7 UX Writing 要點**:誠實呈現狀態、避免技術語 — 對齊 design-spec - **§8.2 給 Architect 的補充**:5 條建議,第 1/2/3/4/5 條我都已採納並補進 TDD(謝謝) #### 2.2.2 ⚠️ 嚴重 — endpoint 路徑與 TDD 不一致 同 §1.2 / §2.1.2。具體位置: - §3 全景圖 sequence diagram 中所有 `POST /api/converter/jobs` / `GET /api/converter/jobs/{id}` / `POST /api/converter/jobs/{id}/promote-to-models` / `POST /api/converter/jobs/{id}/download-token` → 全部改 `/api/conversion/*` 前綴 - §3 stage 3b sequence diagram「下載」那段 `POST /api/converter/jobs/{id}/download-token` + 「`{ url, expires_at }`」→ `GET /api/conversion/{id}/download` + `HTTP 302 Found, Location: faa-url` - §5.5 「下載」分支步驟 1-4 改成 anchor 觸發、browser 自動 302 follow - §8.2 給 Architect 第 1 條建議端點名稱 修法同 §2.1.2。 #### 2.2.3 ⚠️ 中 — Cancel 後 backend 對 converter 的 cleanup **位置**:flow §5.3「取消」末尾、§8.2 給 Architect 第 4 條 flow §5.3 寫「visionA backend 收到取消信號後**也要對 converter 發 cancel**(避免孤立 job)」— 我在 TDD `conversion.md` §4.3.2 v0.3 補了完整的 cleanup 鏈分析(C1–C4 情境表 + best-effort cancel via `POST /api/v1/jobs/{id}/cancel`)。 Design 的這段描述沒問題,但**沒講清楚** converter 是否提供 cancel endpoint。事實上 converter Phase 1 已實作 `POST /jobs/{id}/cancel`(PRD §5 N2 提到),但 PM Phase 0.8 Non-Goals 把「使用者主動取消」列在 N2。 **澄清**:「使用者主動取消」(PM 不做)≠ 「backend 內部 best-effort cancel 做 cleanup」(TDD 做)。前者是 UI feature、後者是 reliability infra。兩者用同一個 converter endpoint 但語義不同。 **建議 Design**:在 flow §5.3「取消」末尾加註: ```markdown > **澄清**:使用者按「取消上傳」是 frontend 端的 `xhr.abort()`,TCP RST 觸發 backend > cleanup 鏈,backend 內部會 best-effort 對 converter 發 cancel。這個 cancel **是內部 > 韌性處理**,不暴露 UI;PRD Phase 0.8 Non-Goals N2 講的是「進行中 job 的取消 UI」, > 兩者不衝突。 ``` #### 2.2.4 小 — `expires_at` 來源 + frontend 顯示 flow §5.5 success 階段「過期提醒」描述: > 計算 `expires_at - now()` → 顯示「6 天 21 小時後自動清除」 正確,且 TDD api-conversion.md v0.3 已確保 `expires_at` 在 Job response 必出現(無論 converter 給或 backend 推算)。✅ 對齊。 --- ## 3. Architect 自己的 TDD 修訂(這次補上的) ### 3.1 議題 #2 — visionA backend 重啟後 ownership 全失 **修訂**:`conversion.md` §2.6.1(新增) 採方案 **A4:lazy rebuild**:`/active` endpoint 在 in-memory miss 時,fallback 對 converter 查 `GET /api/v1/jobs?user_id=&status=in_progress` 並重建 ownership。對 frontend 完全透明。 **為什麼選 A4 不選 A2(啟動時批次掃)**:A2 對 converter 是 hammer;A4 是 lazy(user 行為觸發),cost 對應實際需求。 **新增依賴**:converter Phase 1 的 `GET /api/v1/jobs?user_id=&status=in_progress` endpoint(converter Phase 1 已實作)。已要求 PM 補進 §7 dependency 表(§1.6)。 ### 3.2 議題 #3 — `GET /api/conversion/active` 端點 **修訂**:原本 TDD 已有 `GET /api/conversion/active`,這次只是把它的 response shape 補上 `expires_at` / `source_filename` / `target_chip`,並文件化「lazy rebuild」行為(議題 #2)。 **修訂位置**:`api/api-conversion.md` §5 **已對齊 Design 需求**:wireframe §3.3 / flow §5.1 進入 `/conversion` 打 `/active` 直接落 processing 的設計,能拿到所有需要顯示的欄位(檔名、目標 chip、過期時間)。 ### 3.3 議題 #5 — Cancel 清理鏈 **修訂**:`conversion.md` §4.3.2(新增完整章節) 新增內容: 1. 4 種 cancel 觸發情境(C1 使用者取消 / C2 重新整理 / C3 網路斷 / C4 converter 拒絕) 2. cleanup 鏈:`xhr.abort()` → TCP RST → `gin Context.Done()` → goroutine `pw.Close()` → io.Pipe EOF → converter multer abort 3. Best-effort `POST /api/v1/jobs/{id}/cancel`:當 backend 已拿到 job_id 但 streaming 失敗時,主動對 converter 發 cancel 避免孤立 job 4. §9 retry 表新增 `cancel` row(best-effort、不重試) **重要區分**:「使用者主動取消 UI」(PM Non-Goals N2,Phase 1+ 才做)vs 「backend 內部 best-effort cancel cleanup」(TDD 此次補)— 兩者不衝突,後者是韌性 infra。 ### 3.4 議題 #6 — Upload XHR 進度語意 **修訂**:`conversion.md` §4.3.1(新增完整章節) 設計選擇表(選項 A vs B),明確採選項 A:**backend 等 converter 201 才回 200,不 early-return**。理由: - 進度 100% 接近端到端真實狀態 - 實作簡單(同步等) - 失敗處理路徑清楚(同步錯誤直接回) - 1-3 秒 io.Pipe drain 延遲對 500MB 上傳可接受 Frontend UX 補償:100% 後到 backend 回 200 之間,文案切「即將完成…」/「伺服器處理中…」(已在 flow-conversion.md §5.3 提到)。已要求 Design 在 wireframe §4.2 對齊文案表(§2.1.4)。 ### 3.5 議題 #7 — `expires_at` 來源 **修訂**:`conversion.md` §2.6.2(新增)+ `api/api-conversion.md` §1 / §2 / §5(response shape 補欄位) 決策: - 優先從 converter response 直接讀(converter Phase 1 是否提供 — 給 Backend Agent 確認) - 若 converter 沒給,backend 自行 `created_at + 7d` 推算 - **Frontend 永遠拿到 `expires_at`**,無論來源 **Job response shape 統一補欄位**(v0.3): - `expires_at`:必有 - `source_filename`:必有(給 success card 顯示「yolov5s.onnx → yolov5s_kl720.nef」) - `target_chip`:必有(給 wireframe §6 / §7 顯示「→ KL720」) ### 3.6 同步影響的小修訂 - `conversion.md` §3 endpoint 表加註「不對外暴露但內部使用的 converter endpoint」(cancel + lazy rebuild) - `conversion.md` §9 retry 表新增 cancel / lazy rebuild 兩 row - `api/api-conversion.md` §3 promote-to-models request body 對齊 Design 單欄位(議題 #4) --- ## 4. 給 Orchestrator 的決策清單 > 三方修訂大部分各自進行,以下是需要 Orchestrator 協調或使用者裁決的事項。 ### 4.1 [使用者裁決] API endpoint 路徑命名統一 **問題**:PM PRD 用 `/api/converter/jobs/*`、Design wireframe / flow 用 `/api/converter/jobs/*`、Architect TDD 用 `/api/conversion/*`,三方不一致。 **選項**: - **選項 A**(Architect 建議):統一用 `/api/conversion/*`、`promote-to-models`、`GET /download` 302 - 優點:對齊 visionA 內部模組名(`internal/conversion/`);ADR-014 v1.1 download 安全升級已採;命名語意清楚 - 改動:PM PRD §F6 / F7、Design wireframe / flow(多處) - **選項 B**:統一用 `/api/converter/jobs/*` - 優點:與 converter 真實 path(`/api/v1/jobs/*`)名字相近、有「轉發」直覺 - 缺點:誤導使用者以為是 raw proxy(其實 backend 有 ownership / 半自動邏輯);download flow 已決議用 GET 302,不能維持 `POST /download-token` **Architect 強烈建議選 A**。 **決策後**: - 選 A → PM 改 PRD §F6/F7、Design 改 wireframe / flow(標記位置已在 §1.2 / §2.1.2 / §2.2.2 列出);TDD 不動 - 選 B → Architect 改 TDD 全部(`internal/conversion/` package 名也要改)— 不建議 ### 4.2 [PM 待補] 修訂建議清單(嚴重度) | # | 嚴重度 | 修訂位置 | 內容 | |---|--------|---------|------| | P1 | 嚴重 | §F6 / §F7 | 對齊 endpoint 命名(見 §4.1)| | P2 | 中 | §F 段(新增 F0)| 補「Active job pre-check」功能 | | P3 | 中 | §F2 / §F3 | 補上傳進度 100% 後的文案語意說明 | | P4 | 中 | §F6 | 補「加到模型庫」Dialog 含名稱欄位(對齊 Design)| | P5 | 中 | §6 + §7 | 補 D7(重啟 lazy rebuild)+ converter dependency 補 2 個內部用 endpoint | | P6 | 小 | §8 KPI | 量測點改為 backend log job 完成時間 | | P7 | 小 | §F2 | 確認 chip 數量(4 個 vs 2 個),與 converter 對齊 | ### 4.3 [Design 待補] 修訂建議清單(嚴重度) | # | 嚴重度 | 修訂位置 | 內容 | |---|--------|---------|------| | D1 | 嚴重 | wireframe §3.3 / §6.3 / §7.1 / §7.2、flow §3 / §5.5 / §8.2 | 對齊 endpoint 命名(見 §4.1)| | D2 | 中 | wireframe §6.1 | stage indicator 改用 converter `stage` 欄位(onnx/bie/nef → 解析/量化/編譯)| | D3 | 中 | wireframe §4.2、flow §5.3 | 補 100% 後等待文案表 + i18n key | | D4 | 中 | flow §5.3 / §8.2 | 補「使用者取消」vs「backend 內部 cancel cleanup」的區分 | | D5 | 小 | wireframe §10 | Wand2 icon 採用(不需改)| ### 4.4 [需與其他團隊確認] - **Backend Agent**:確認 converter Phase 1 的 `GET /api/v1/jobs/{id}` response 是否含 `expires_at`,決定 backend 是直接透傳還是自行推算 - **DevOps**:確認 visionA stage → 192.168.0.130 converter 網路可達性(ADR-014 合規清單仍未勾)+ MC service client 4 scope 已授權 - **Converter 團隊**:確認 Phase 1 `platform` 欄位實際支援哪幾個 chip enum(`520 / 720` 或 `520 / 630 / 720 / 730`) --- ## 5. 結論 **已落地(Architect 端)**:4 個議題(#2 / #5 / #6 / #7)已寫進 `conversion.md` v0.3 + `api/api-conversion.md` v0.3。 **待 Orchestrator 協調**: 1. 1 個使用者裁決:API endpoint 命名統一(§4.1) 2. 7 個 PM 修訂:1 嚴重 + 4 中 + 2 小 3. 5 個 Design 修訂:1 嚴重 + 3 中 + 1 小 4. 3 個跨團隊確認 **建議下一步**: 1. Orchestrator 把 §4.1 提給使用者裁決(這是阻擋三方對齊的單一最大議題) 2. 使用者決定後,PM / Design 同步修訂(兩邊都在改 endpoint,可平行進行) 3. PM / Design 第二輪產出後,Architect 再做最終 review(確認沒新衝突) 4. 完成後進 Phase 0.8 實作階段 --- ## 版本記錄 | 日期 | 版本 | 變更 | |------|------|------| | 2026-04-30 | 1.0 | 初版 — Phase 0.8 三方交叉審閱(Architect 視角)|