L 級新功能、PRD/Design/TDD/ADR 三方協作 + 互審 + M9-6 SDK 雙驗證、總計 ~9000 行文件。
範圍:
- A 階段(MVP、5 人天):KL520 + KL720 自動升級 KDP1 → KDP2
- B 階段(10.5 人天):手動降版面向一般使用者 + KL630 / KL730 擴展
- 合計 15.5 人天、安裝包 +7MB(保守 bundle 策略)
關鍵決策:
- 翻案 R5-Q9(progress.md 第二輪使用者決策「韌體燒錄 flash → B 砍掉」)
- 跨平台用 KneronPLUS Python C API、不用 DFUT.exe
- 多版本目錄結構選 C metadata(firmware/<chip>/{version}/ + CURRENT_VERSION)
- Kneron firmware redistribution 授權與 R5-B4 預置模型同性質、發佈前評估
文件產出:
- PRD v2.2(PRD-v2.md 495 行 + features/feature-firmware-management.md 599 行)
- Design v2.2(firmware-management.md 948 行 + control-panel.md §6a graceful shutdown)
- TDD v2.2(v2/firmware-management.md 823 行 + ADR-001 218 行)
- 8 份 research(含 M9-6 弱驗證 + 強驗證、~3200 行)
- 3 份三方互審報告(PM/Design/Architect cross-review)
M9-6 強驗證重大發現(影響 B 階段):
- KL730 product_id 實際是 0x732(不是 0x0730)
- KL630/KL730 firmware 是 embedded Linux rootfs(不是 .bin、不同代設計)
- KneronPLUS Python 沒 update_kdp_firmware_from_files 公開 API、warrenchen 走 ctypes
- 不影響 A 階段、B 階段 M9-8 需 spike
下一步:派 backend M9-1 起跑(bridge.py handle_firmware_upgrade)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
36 KiB
Architect 互審報告 — PM PRD + Design Spec(v2.2 Firmware 管理)
作者:Architect Agent 日期:2026-05-24 任務性質:三方互審 — Architect 視角審 PM PRD + Design Spec 對照基準:
- 我的 TDD:
.autoflow/04-architecture/v2/firmware-management.md- 我的 ADR:
.autoflow/04-architecture/adr/ADR-001-firmware-management.md- 7 份 research:
.autoflow/04-architecture/research-kl520-fw-management/
TL;DR
整體判斷:PM PRD 與 Design Spec 技術上都可實現、與我的 TDD 高度一致、沒有架構衝突。 但有幾個對齊問題需要解決,主要落在三處:
- 狀態機名稱不對齊(Design
preparing/loading/flashing/verifyingvs TDDconnecting/loading_loader/loading_firmware/verifying)—— 必須我修 TDD(採 Design 的命名、對使用者語意較自然) - 失敗類型 6 vs 8 不一致(TDD 列 6 stage + 6 錯誤碼、Design 列 8 種失敗類型)—— Design 多列的
timeout+disconnect是橫切性失敗、本來就在我的錯誤碼裡(FW_UPGRADE_FAILED/FW_UPGRADE_BRICK_RISK)—— 補一張對應表即可 - 降版進行中 graceful shutdown 拒絕(Design §14.4 第 6 點自提懸念)—— 我 TDD 沒設計、必須補進 TDD §8
其餘對齊度高、issue 多為「補一句說明」級。M9-6 不卡 M9-1~5、A 階段可以照計畫啟動。
一、對 PM PRD 的審閱
1.1 AC-FW-* 技術可行性逐條檢視
| AC ID | PM 描述 | 技術可行性 | 對應我 TDD 位置 | Issue |
|---|---|---|---|---|
| AC-FW-1.1 | scan 後 FW badge 綠/黃/紅三色 | ✅ 可做 | TDD §3.1 firmwareIsLegacy / firmwareCanUpgrade / bundledFirmwareVersion |
無 |
| AC-FW-1.2 | progress modal 顯示 6 階段 | ⚠️ 名稱不對齊 | TDD §4.3 列 6 stage | 必須修:見後文 §1.6 |
| AC-FW-1.3 | 升級成功後 5 秒自動關閉 modal + rescan | ✅ 可做 | TDD §5.1 完成 → driver needsReset=true → 下次 connect 走完整 reset |
無 |
| AC-FW-1.4 | 失敗顯示明確錯誤類型 + 「複製錯誤訊息」 + 「重新插拔後重試」 | ✅ 可做 | TDD §5.3 列 4 種失敗類型、與 Design §7.1 8 種對應見後文 §2.3 | 無 |
| AC-FW-1.5 | 升級期間 device 進入 StatusUpgrading 鎖住其他操作 |
✅ 可做 | TDD §2.2 新增 status enum + §3.3 FW_DEVICE_BUSY 409 |
無 |
| AC-FW-1.6 | 升級成功後等 5-8 秒讓 USB stable | ⚠️ 數字略有出入 | TDD §5.1 / §7.2 用 sleep 2s + sleep 3s 兩段(合計 5s) |
PM 寫 5-8s、TDD 寫 5s。PM 區間較保守、TDD 是實測值。建議 PM 改為「5-8 秒(實測 5s 已穩、保留上界容忍)」對齊 |
| AC-FW-1.7 | KL520 預估 30s、KL720 預估 180s | ✅ 可做 | TDD §3.3 FW_UPGRADE_FAILED timeout > 60s、FW_UPGRADE_BRICK_RISK timeout > 180s |
無 |
| AC-FW-1.8 | 升級失敗屬 device 層、不觸發 watchServer Error | ✅ 可做 | TDD §8.1 明確 watchServer 不被觸發 | 無 |
| AC-FW-2.1 | 降版入口在 Settings →「韌體管理」 | ✅ 可做、與 Design §2 一致(Settings 第 3 分頁) | TDD §5.2 流程進入點 | 無 |
| AC-FW-2.2~2.4 | 卡片 + dropdown + 紅色按鈕 | ✅ 純前端 | — | 無 |
| AC-FW-2.5~2.7 | 二次確認 modal + 警告語 + 不可外部關閉 | ✅ 純前端 | — | 無 |
| AC-FW-2.6 | 強制輸入「DOWNGRADE」字串 | ✅ 可做 | TDD §3.1 / §3.3 FW_NO_CONFIRM_TOKEN 400 |
無 |
| AC-FW-2.8 | 進行中不顯示取消按鈕 + persistent banner | ✅ 純前端 | — | 無 |
| AC-FW-2.9 | API 強制 confirmToken: "DOWNGRADE" |
✅ 可做 | TDD §3.1 / §3.3 明確規格 | 無 |
| AC-FW-2.10 | Driver 4 項 safety guards | ✅ 可做 | TDD §2.1 firmware/guards.go |
無 |
| AC-FW-3.1~3.4 | KL630/KL730 偵測 + 升降版 + ~60s | ✅ 可做(M9-7~M9-13 落地) | TDD §6.2 chip 判斷擴展 + §9 M9-7~M9-10 | 無 |
| AC-FW-3.5 | 若 SDK 不支援、降級為 FW 偵測 only | ⚠️ PM 與 TDD 對齊但缺一條技術判定 trigger | M9-6 結論後決定 | 詳見 §1.3 |
1.2 成功指標(PRD §7)的 measurement source 對應
| PM 指標 | 我 TDD 是否埋點 | Issue |
|---|---|---|
| 5 分鐘首次推論達成率 +5pp | ❌ 我 TDD 沒設計埋點 | 不是 FW feature 該埋的、應是既有 /api/system/metrics 或前端 analytics 範圍。建議 PM 在 §7.1 註明「指標 source 待定、跨多個 feature 統計」、不要當 FW feature 的 AC |
| FW 升級成功率 ≥ 95% | ⚠️ 無自動上報 | TDD §5.1 流程結束會有 {status:"upgraded", duration_ms} log、但不會自動上傳。現階段靠技術支援統計 log、不是自動指標。建議 PM 在 §7.2 註明「靠 log 統計、非自動上報、待 telemetry feature」 |
| 升降版平均時長 | ⚠️ 同上 | 同上 |
| 一般使用者誤觸降版發生率 ≤ 1% | ❌ 完全無法自動量測 | 純客服回報統計、非自動。PM §7.2 已標「客服回報」、OK |
| Brick 事件 ≤ 0.1% | ❌ 完全無法自動量測 | 純客服回報、PM §7.2 已標、OK |
結論:PM §7 列的指標沒有違反技術可行性、但很多依賴客服回報 / 手動統計。建議 PM 在 §7 開頭加一句「v2.2 階段大部分指標靠客服回報 + log 統計、非自動上報;自動 telemetry 是未來 feature」、避免後續 Reviewer 誤判「指標沒實作」。
1.3 AC-FW-3.5 生效條件需要明確
PM 寫「若 KneronPLUS Python wheel 版本不支援 KL630/KL730 的 update API、降級為 FW 偵測 only」、但沒寫「不支援」的判定條件是什麼。我 TDD §6.3 / §7.3 描述了 M9-6 SDK 驗證的範圍、但沒明文說「如果 M9-6 跑出什麼結果 → 觸發 AC-FW-3.5」。
建議補充(PM 或我都可以補):
AC-FW-3.5 生效條件(M9-6 結論其一):
- 條件 a:KneronPLUS Python wheel 沒有 `kp.core.update_kdp_firmware_from_files` 對 product_id 0x0630/0x0730 的 dispatch
- 條件 b:SDK 對 .tar firmware 既不支援直接餵、也無法 build-time 解壓出可用 .bin(策略 Z 與 Y 都失敗)
- 條件 c:升級驗證階段 firmware 字串無法穩定回讀
任一條件成立 → AC-FW-3.5 觸發 → B 階段對 KL630/KL730 範圍縮限為「scan + connect + inference + FW 偵測」、移除 upgrade/downgrade UI。
1.4 工時對齊(M9 表)
| 我 TDD §9 | PM §10 | 對齊度 |
|---|---|---|
| M9-0 0.5 文件先行 | 沒列 | PM 沒把文件先行算工時、可接受(屬 Orchestrator 範圍) |
| M9-1 1.0 bridge.py upgrade | M9-1 1.0 | ✅ |
| M9-2 1.0 driver + service | M9-2 1.0 | ✅ |
| M9-3 0.5 API + WS | M9-3 0.5 | ✅ |
| M9-4 1.5 Frontend | M9-4 1.5 | ✅ |
| M9-5 1.0 三平台驗證 | M9-5 1.0 | ✅ |
| M9-6 1.0 SDK 驗證 | M9-6 1.0 | ✅(與 A 平行) |
| M9-7 0.5 chip 判斷 | M9-7 1.5 | ⚠️ PM 寫 1.5、我寫 0.5。0.5 是只改 detector + bridge dispatch;1.5 是含 detector + chip-aware connect 全套(含 KL630/KL730 firmware 載入分支)。PM 比較保守、可採 PM 數字(多估留 buffer) |
| M9-8 1.5 .tar handling | M9-8 1.0 | ⚠️ PM 偏緊、我偏鬆。建議採我 1.5(含策略 Y 解壓 fallback 風險) |
| M9-9 2.0 KL630/KL730 connect+inference | M9-9 1.0 / M9-10 1.5 | ⚠️ PM 拆成兩個 milestone(多版本目錄重整 + KL630/KL730 升降版)= 2.5。我合併成 M9-9 2.0(純 connect+inference)+ M9-10 1.0(升版擴展)= 3.0。兩邊算法不同、需要對齊。詳見 §1.5 |
| M9-11 1.5 多版本降版 | M9-11 1.5 | ✅ |
| M9-12 2.0 降版 UI | M9-12 2.0 | ✅ |
| M9-13 1.0 B 階段驗證 | M9-13 1.0 | ✅ |
| A 合計 | 5.0 | 5.0 ✅ |
| B 合計 | 10.5 | 10.5 ✅ |
| 總計 | 15.5 | 15.5 ✅ |
結論:A/B/總工時對齊、但中間 M9-7~M9-10 拆法不同。建議 PM 與我用同一張 M9 表(採 PM 拆法、語意較清楚)、我下輪修 TDD §9 對齊。
1.5 M9-7~M9-10 拆法差異
| Milestone | PM 拆法 | TDD 拆法 | 建議統一 |
|---|---|---|---|
| M9-7 | Driver 擴 chip + chip-aware connect 分流(1.5 人天) | B0 認 chip(detector + bridge.py + driver)0.5 人天 | 採 PM 的 1.5(含 chip-aware connect、語意清楚) |
| M9-8 | .tar handling(1.0) | B1.1 .tar handling(1.5) | 採 TDD 的 1.5(M9-6 結論若需策略 Y、解壓 build script 工程更大) |
| M9-9 | 多版本目錄重整(1.0) | B1.2 KL630/KL730 connect + inference(2.0) | 不同 milestone、PM 與 TDD 對齊不到——詳見後文 |
| M9-10 | KL630/KL730 升降版(1.5) | B1.3 FW 升版擴 KL630/KL730(1.0) | 同上 |
根因:PM 把「多版本目錄重整」獨立成 M9-9、TDD 把它合進 M9-11;PM 把「KL630/KL730 inference」併進 M9-7、TDD 拆出 M9-9。兩邊都對、只是拆法不同。
建議統一(採 PM 拆法、語意對外部審閱者更清楚):
- M9-7:B0 認 chip(driver + bridge.py + detector)1.5 人天
- M9-8:B1.1 .tar firmware handling 1.5 人天(採 TDD 數字)
- M9-9:多版本目錄重整 +
_resolve_firmware_paths_versioned()1.0 人天 - M9-10:KL630/KL730 升降版 driver method 1.5 人天
- M9-11:多版本降版後端 1.5 人天
- 總和 7.0、+ M9-12 2.0 + M9-13 1.0 = B 階段 10.0、與 PM 10.5 差 0.5(M9-9 KL630/KL730 inference 需要併進去?)
結論:M9 表需要 PM 與 Architect 對一下、最終一張表。我下輪修 TDD 時對齊 PM 的 §10。
1.6 R-FW 風險清單對齊
| 我 TDD §10 | PM §8 | 對齊度 |
|---|---|---|
| R-FW-1 升級中拔除(KL720 寫 flash brick) | R-FW-1 KL520 reset bug 再現 | ⚠️ 語意不同!PM 的 R-FW-1 是「升級後 device re-enumerate 不穩定」、TDD 的 R-FW-1 是「升級中拔 USB brick」 |
| R-FW-2 升級後 USB race | R-FW-2 KneronPLUS Python wheel update API 支援度 | ⚠️ 同上、不對齊 |
| R-FW-3 KL520 已是 KDP2 誤觸發 | R-FW-3 bridge.py update_kdp_firmware_from_files macOS x86_64 未驗證 |
⚠️ 同上 |
| R-FW-4 跨晶片誤匹配 | R-FW-4 timeout 設定不合理 | ⚠️ 同上 |
| R-FW-5 Kneron 授權 | R-FW-5 打包 Kneron 官方 firmware 是否合法 | ✅ 同概念、PM 標 P0 release gate(與我 ADR §Consequences 對齊) |
| R-FW-6 HTTP timeout | R-FW-6 既有 flash/ 模組命名混淆 |
⚠️ 同上 |
| R-FW-7 Windows admin(WinUSB) | R-FW-7 升級失敗 device unknown state | ⚠️ 同上 |
| R-FW-8 SDK 對 KL630/KL730 API | R-FW-8 同 | ✅ |
| R-FW-9 .tar 安裝包衝擊 | R-FW-9 同 | ✅ |
| R-FW-10 KL630/KL730 沒 Loader mode | R-FW-10 同 | ✅ |
| R-FW-11 一般使用者誤觸降版 | R-FW-11 同 | ✅ |
| R-FW-12 多版本管理 UX 複雜度 | R-FW-12 同 | ✅ |
| R-TAR-1~4(我 TDD 自己加的 4 條) | — | ❌ PM 沒列、應補進 PRD §8 |
結論:
- R-FW-1~7 的編號 PM 與 TDD 完全不對齊——可能是 PM 引用了 research 30 的早期編號、我 TDD 內部重新編了。這是重大問題、必須對齊。
- 建議統一:採 PM 的 R-FW-1~7 編號(因為 PRD 已是「對外的」風險清單、跨多個下游 agent 引用)、我下輪修 TDD §10 重新對應。
- R-TAR-1~4 PM 沒列:M9-8 .tar handling 的 4 條技術風險(策略 Z 退回 Y / build script 漏跑 / notarization / 跨平台 tarfile)—— PM 應補進 PRD §8 或同意「這 4 條留 TDD §10 即可、不進 PRD」。建議後者:PRD 是給 PM 視角、TAR 風險偏技術細節。
1.7 Kneron 授權 P0 懸念對齊
PM PRD §8.1 R-FW-5 標 P0 release gate、§9 Q-FW-1 寫「使用者明示先不管、發佈前再評估」。
我 ADR-001 §Consequences 寫「法律 / 簽章授權待釐清、與 R5-B4 同性質、發佈前統一處理、不阻塞開發但 PRD P0 懸念」。
我 ADR-001 §Compliance 標 [ ] 與 Kneron 取得 firmware redistribution 授權(發佈前必須完成、不阻塞開發)。
對齊度:✅ 完全一致。R5-B4 合併處理也與我 ADR §Related 對齊。
1.8 scope 邊界對齊
| 範圍 | PM PRD §2.4 / §3 | 我 TDD §1.2 | 對齊度 |
|---|---|---|---|
| 升級到 Kneron 官方 KDP2 標準版本 | ✅ 做 | ✅ 做 | ✅ |
| 使用者燒任意 model 到 device flash | ❌ 不做(Q9 維持) | ❌ 不做 | ✅ |
| 使用者選任意 binary 寫 flash | ❌ 不做 | ❌ 不做 | ✅ |
| 偵測 dongle 當前 FW 版本 | ✅ 做 | ✅ 做 | ✅ |
| 切換到內建其他 bundle 版本(含降版) | ✅ 做 | ✅ 做 | ✅ |
| 線上 OTA 更新通道 | ❌ 不做 | ❌ 不做 | ✅ |
| KL530 / KL830 支援 | 未提及 | ❌ 不做(本期) | ⚠️ PM 沒列「不做 KL530/KL830」、建議 PM §3 補一條「本期不做 KL530 / KL830(warrenchen 雲端版雖列出、本期排除)」 |
| DFUT.exe 救磚工具打包 | 未提及 | ❌ 不做 | ⚠️ 同上、PM 應補 |
結論:scope 大方向一致、但 PM 漏列兩個「不做」(KL530/KL830 + DFUT.exe)。建議 PM 補進 §3 範圍切割表。
1.9 B2 一般使用者降版 — driver/bridge guard 對齊
PM AC-FW-2.10 列了 4 項 driver 層 safety guards:
- 拒絕跨晶片誤匹配
- 拒絕升版偽裝
- 拒絕 no-op
- 拒絕 status busy
我 TDD §2.1 / §3.3 對應如下:
firmware/guards.go拒絕跨晶片 ✅firmware/guards.go拒絕升版偽裝(FW_INVALID_DIRECTION400)✅firmware/guards.go拒絕 no-op(同上錯誤碼)✅FW_DEVICE_BUSY409 ✅
對齊度:✅ 完全一致、bridge.py _validate_downgrade_request 也對應上。
二、對 Design Spec 的審閱
2.1 §11.2 token 對比比率:誰負責新增 design tokens
Design §11.2 列了 6 個新 component-level tokens(color.fw-badge.*)+ 推導表(Light/Dark 各 6 個值)+ 對比比率(4.7:1 / 5.2:1 / 4.8:1)。
問題:這些 token 需要寫進 frontend/src/app/globals.css 的 :root 與 [data-theme='dark'] block、不是寫進設計文件就會自動生效。
架構層判斷:
- Design 文件已明確規格、Frontend 實作時對著抄即可
- 不需要新增到
spec/07-design-tokens.md(既有檔不動、避免架構文件膨脹) - 責任歸 Frontend Agent:M9-4(A 階段、Devices 頁 FW badge)實作時順手加 6 個 CSS variable
Dark 模式 legacy.fg 用黑底紅對比的驗證:Design 推算 4.8:1、實作時 Frontend 用 contrast checker 驗證(如 https://webaim.org/resources/contrastchecker/)。我不需要 verify 推算數字、信任 Design 的 oklch 推算。
結論:Design §11.2 設計合理、無架構層 issue。Frontend Agent 在 M9-4 落地時負責 CSS variable 寫入 + 驗證。
2.2 §6.1 「DOWNGRADE」嚴格比對 — 與 TDD §3.1/§3.3 對齊
Design §6.1 規格:
- input
type="text" autocomplete="off" spellcheck="false" - 比對方式:
event.target.value === 'DOWNGRADE'(嚴格相等、case-sensitive、不接受小寫/全形/半形/空白) - 「確認切換」按鈕 disabled 條件:input 值不等於字面
DOWNGRADE
我 TDD §3.1 / §3.3:
- API request body
{version:"v2.1.0", confirmToken:"DOWNGRADE"} - API 強制
confirmToken === "DOWNGRADE"(字面字串) - 沒帶 / 帶錯 → 400 +
FW_NO_CONFIRM_TOKEN
對齊度:✅ 前後端規格完全一致。
Design §14.4 第 4 點懸念(中文 i18n 化):Design 建議「短期維持英文字串、跨語言一致」。
我的判斷:✅ 同意。後端 confirmToken 是 API contract、不應 i18n 化(會引入 server 端多語對照表、增加攻擊面)。保留英文字面 "DOWNGRADE"、UI 層用 i18n key 顯示提示文字(「請輸入 DOWNGRADE 確認」中文 + 「Type DOWNGRADE to confirm」英文)但 input 比對的字串一致。
2.3 §7.1 8 種失敗 stage vs TDD §3.3 6 種錯誤碼 + §5.3 4 種失敗類型對應
Design §7.1 列的 8 種(後端錯誤 stage → 友善訊息):
scan找不到裝置connect失敗loader寫入失敗upgrade寫入失敗verify失敗Timeout (>60s)Disconnect during op- (無第 8 項、Design 表格只有 7 列、但 §14.3 表寫「8 種失敗類型對應」、可能是 Design 自己估算誤差)
我 TDD §3.3 列的 6 個錯誤碼(HTTP 層):
FW_DEVICE_BUSY409FW_VERSION_NOT_FOUND404FW_INVALID_DIRECTION400FW_NO_CONFIRM_TOKEN400FW_UPGRADE_FAILED500FW_UPGRADE_BRICK_RISK500
我 TDD §5.3 列的 4 種失敗類型(流程失敗、不是 HTTP 錯誤碼):
- device disconnect during upgrade
- firmware load 失敗(pre-flash)
- firmware load 失敗(中段、timeout > 180s)
- 升級成功但 verify 失敗
對應表(補進 TDD 與 Design 都需要):
| Design §7.1 後端 stage | 我 TDD §3.3 錯誤碼 | 我 TDD §5.3 失敗類型 | 對齊 |
|---|---|---|---|
scan 找不到裝置 |
FW_UPGRADE_FAILED |
(1) disconnect | ✅ |
connect 失敗 |
FW_UPGRADE_FAILED |
(1) disconnect | ✅ |
loader 寫入失敗 |
FW_UPGRADE_FAILED |
(2) pre-flash | ✅ |
upgrade 寫入失敗 |
FW_UPGRADE_FAILED 或 FW_UPGRADE_BRICK_RISK |
(3) 中段 / (2) pre-flash | ✅ |
verify 失敗 |
FW_UPGRADE_FAILED |
(4) verify | ✅ |
| Timeout (>60s) | FW_UPGRADE_FAILED 或 FW_UPGRADE_BRICK_RISK |
(3) 中段 | ✅ |
| Disconnect during op | FW_UPGRADE_BRICK_RISK |
(1) disconnect | ✅ |
結論:Design 8 種(含 1 種空項)= TDD 6 錯誤碼 + 4 失敗類型的不同切面。沒有矛盾、只是 granularity 不同:
- Design 是「使用者面友善訊息」的分類(給 i18n 用)
- TDD §3.3 是「HTTP API 錯誤碼」(給 frontend 處理)
- TDD §5.3 是「服務內部失敗分類」(給 bridge.py 處理)
Issue:我 TDD 必須補一張「stage → 錯誤碼 → 失敗類型」對應表、讓 Frontend / Testing 不會疑惑「Design 列的 8 種對應後端哪個 code」。下輪修 TDD §3.3 後追加 §3.4「stage 對應 friendly message + 錯誤碼」。
2.4 §8 狀態機名稱 vs TDD §4.3 stage 列舉 — 必須對齊
| Design §8 狀態 | 我 TDD §4.3 stage | 對齊度 |
|---|---|---|
idle |
— | OK(Design 用、TDD 是 device status) |
confirming |
— | OK(純前端狀態) |
preparing |
connecting |
❌ 不對齊(preparing vs connecting) |
loading |
loading_loader |
❌ 不對齊 |
flashing |
loading_firmware |
❌ 不對齊 |
verifying |
verifying |
✅ |
success_toast |
done |
⚠️ 不同切面(toast vs stage 完成) |
error_modal |
error |
⚠️ 同上 |
根因:Design 命名是 UI 視角(preparing 比 connecting 對使用者更友善)、TDD 命名是技術視角(connecting 對應實際的 USB connect 動作)。
判斷:Design 的命名對使用者較自然、TDD 應該採 Design 的命名作為 WebSocket event 的 stage 值。理由:
- progress event 的
stage值最終會被 Frontend 用 i18n key lookup(如settings.firmware.progress.stage.preparing)顯示給使用者 - 如果 TDD 用
connecting但 Design 的 i18n key 是preparing、Frontend 需要做 mapping、增加複雜度 - 一致命名讓 Backend / Frontend / Design / Testing 都用同一組詞
動作:我下輪必須修 TDD §4.3、stage 列舉改為:
connecting→preparing(5%)loading_loader→loading(20%、僅 KDP1→KDP2 路徑)loading_firmware→flashing(50%)verifying→verifying(90%、不變)done→done(100%、不變)error→error(-1、不變)
同時要修 TDD §5.1 流程圖中的 stage 名稱、§6.1 bridge.py handler 回傳的 stage 欄位 enum 值。Design §9 i18n keys(settings.firmware.progress.stage.scan/connect/loader/upgrade/verify)也需要 Design 對應修為 preparing/loading/flashing/verifying——或者 Design 維持原 key 名、我內部用 Design 的命名——這需要 Design + Architect 對一下。
建議統一(給 Orchestrator):
- Option A:TDD 採 Design 的
preparing/loading/flashing/verifying、Design 的 i18n key 也改成progress.stage.preparing等 - Option B:兩邊維持各自命名、TDD 在 §4.3 加一張對應表「TDD stage → Design i18n key」
我建議 Option A(更乾淨、減少 mapping 心智負擔)。Design 互審我提一下這點、看 Design 是否同意改 i18n key。
2.5 §14.4 第 6 點:降版進行中 graceful shutdown 拒絕 — TDD 必須補設計
Design §14.4 第 6 點自提懸念:
「降版進行中關閉 Wails 控制台視窗會發生什麼:依 R5-2 控制台關閉 = 結束 server。降版進行中關控制台 = 中斷降版 = 可能 brick。Frontend / Backend 是否需要在控制台側也偵測『降版進行中』並擋下關閉動作?建議 Architect 補充。」
我 TDD 目前沒設計這個。檢視 TDD §8:
- §8.1 watchServer Error state 不被觸發 ✅
- §8.2 KL520 reset bug 對齊 ✅
- §8.5 既有
restartBridge()不重用 ✅ - ❌ 沒提「降版中 graceful shutdown」這條
Design 提的風險是真實的:
- R5-2 規定「關閉 Wails 視窗 = 結束 server」
- 結束 server = SIGTERM Python sidecar → bridge.py handler 被中斷
- 如果
handle_firmware_downgrade正在update_kdp_firmware_from_files中段、SIGTERM 中斷 = device flash 寫到一半就停 = brick 風險
設計方向(補進 TDD §8.6):
### 8.6 降版進行中 graceful shutdown 拒絕
當 server 收到 SIGTERM(使用者關閉 Wails 視窗、或 systemd / Wails close handler 觸發)時:
1. server 進 `shutting_down` state、停止 accept 新 HTTP 連線
2. **檢查 `firmware.Service` 是否有 active task**(`StatusUpgrading` / `StatusDowngrading` device)
3. 如果有 active task:
a. server **拒絕 graceful shutdown**、回 Wails close handler 一個 `firmwareInProgress: true` 訊號
b. Wails 控制台收到後**不關閉視窗**、顯示 modal:「韌體切換進行中(device X)、為避免裝置損毀、無法關閉應用程式。請等待約 {遠端推算 ETA}」
c. modal 不可關閉、不可 dismiss、只能等 firmware task 完成
d. firmware task 完成後、Wails close handler 重試 graceful shutdown
4. 如果沒 active task:正常 graceful shutdown(既有 7+1s pattern)
實作 hook 點:
- `server/internal/firmware/service.go`:新增 `HasActiveTask() bool` method
- Wails close handler(`app.go` 或同等):close 前先 query server `/api/firmware/status`、如果有 active task 顯示 modal 並阻擋 close
- bridge.py:firmware handler 內加 signal handler 拒絕 SIGTERM(如果有 active task)
風險:
- 使用者強制殺 process(`kill -9` / 強制關機)仍可能 brick、無法完全防範
- modal 阻擋使用者關 app 可能造成體驗困擾、但 brick 風險高於體驗困擾
結論:我下輪必須補 TDD §8.6 + 在 §9 工時表反映(M9-11 多版本降版後端 + 0.5 人天「graceful shutdown 拒絕」或併入 M9-11)。Design 也應在 control-panel.md 補對應 modal 規格。
2.6 progress event schema 技術可行性
Design §6.3 進行中 UI 需要的欄位:
stage(階段名稱)percentage(進度百分比)elapsed(已耗時)ETA(預估剩餘時間)
我 TDD §4.2 FirmwareProgress:
Percent int✅Stage string✅Direction stringMessage stringError string
缺:elapsed 和 ETA。
技術可行性:
elapsed:✅ 簡單、service goroutine 記錄 task 啟動 timestamp、每次 push event 算time.Since(startTime)ETA:⚠️ 複雜。KneronPLUS C API 沒提供進度回傳(update_kdp_firmware_from_files是 blocking call)、percent 是 bridge.py 自己估算(依 stage hardcode)。如果 stage 內部進度不可知、ETA 只能根據 stage 對應的「預估時長」算(如flashing階段預計 30s 內完成)、不準確。
結論:
elapsed可以加進 TDD §4.2 ✅ETA也可以加、但值是估算非精確、Design 應該在 UI 明示「預估剩餘X 秒」(Design §9.6 已有{seconds}s remaining」、OK)settings.firmware.progress.estimatedRemainingkey、寫「
動作:我下輪修 TDD §4.2、加 Elapsed int64 + ETA int64 欄位。
2.7 52 個 i18n keys 與 TDD stage 列舉 / 錯誤碼一致性
| Design §9 i18n key 類別 | 對應我 TDD | 一致性 |
|---|---|---|
| §9.1 頁面結構(7 個) | 純前端、與 TDD 無關 | ✅ |
| §9.2 裝置卡片(11 個) | 與 TDD §3.1 DeviceInfo 衍生欄位對齊 |
✅ |
| §9.3 版本切換 accordion(11 個) | 純前端、TDD §5.2 流程進入點 | ✅ |
| §9.4 升級確認 modal(6 個) | 純前端 | ✅ |
| §9.5 降版二次確認 modal(13 個) | 與 TDD §3.1/§3.3 confirmToken 對齊 |
✅ |
| §9.6 進度 modal(10 個) | ⚠️ stage 名稱不對齊(見 §2.4) | 修 §2.4 stage 命名同時對齊 |
| §9.7 成功 toast(5 個) | 純前端 | ✅ |
| §9.8 失敗訊息(13 個) | 與 TDD §5.3 失敗類型對應、但對應表缺(見 §2.3) | TDD 補對應表後一致 |
| §9.9 Devices 頁 FW badge tooltip(4 個) | 與 TDD §3.1 firmwareIsLegacy 對齊 |
✅ |
結論:52 keys 大部分與 TDD 一致。只有 §9.6 進度 stage 命名需要在 §2.4 對齊後一併修。
2.8 分頁順序「韌體」在硬體與模型之間
Design §2 把 Settings 從 4 變 5 分頁、新分頁「韌體」插在第 3 位(一般 / 硬體 / 韌體 / 模型 / 進階)。
架構層影響評估:
- IPC 路由 / API 路徑:✅ 無影響。我 TDD §3.1 API 路徑用
/api/devices/:id/firmware/*、與 Settings UI 分頁順序無關 - i18n key namespace:✅ 無影響。Design 用
settings.firmware.*、與分頁順序無關 - 既有
settings-update.md的 4 分頁結構:⚠️ 需要更新——Design §14.1 提到「settings-update.md 應在下次補丁加註『分頁順序見 firmware-management.md §2』」、但沒有改 settings-update.md 本身。建議 Design 在 v2.2 同步小修 settings-update.md(一行註解即可)
結論:架構層無反對。分頁順序純 UI 決策、Design 已論證合理(硬體 → 韌體 → 模型 由低階到高階)。
2.9 失敗復原 UX 的 backend 配合
Design §7.1 列的 retry / re-plug / 取得協助按鈕:
| Design 按鈕 | Backend 配合 | 是否需新 API |
|---|---|---|
| 「重新插拔後重試」 | ✅ 既有 rescan + 重 POST upgrade | ❌ 不需新 API |
| 「重試」 | ✅ 既有 POST upgrade(同一 device) | ❌ 不需新 API |
| 「拔插後重試」 | ✅ 同上 | ❌ 不需新 API |
| 「拔插後重新掃描」 | ✅ 既有 POST /api/devices/scan | ❌ 不需新 API |
| 「複製錯誤訊息」 | ⚠️ 需 backend 回傳完整錯誤上下文 | 需新增 |
| 「取得協助」 | ⚠️ 純前端連結(i18n key) | ❌ 不需新 API |
「複製錯誤訊息」的 backend 對齊:
- Design §7.2 失敗 modal 內展開「技術資訊」、顯示
stage / device / before / raw_error / duration_ms - 我 TDD §4.2
FirmwareProgress.Error string、§6.1 bridge.py 失敗回傳{error:str, stage:"..."} - 缺:
before(升級前的 firmware 字串)、duration_ms、device_id在 error event 中
動作:我下輪修 TDD §4.2、FirmwareProgress 在 error event 中應包含完整失敗 context:
type FirmwareProgress struct {
Percent int `json:"percent"`
Stage string `json:"stage"`
Direction string `json:"direction"`
Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
Elapsed int64 `json:"elapsed_ms,omitempty"`
ETA int64 `json:"eta_ms,omitempty"`
// 失敗時補
DeviceID string `json:"device_id,omitempty"`
BeforeVer string `json:"before_version,omitempty"`
RawError string `json:"raw_error,omitempty"` // bridge.py 拋的原始 exception text
ErrorCode string `json:"error_code,omitempty"` // 如 "fw_upgrade_stage_loader_E102"
}
2.10 R-FW-11.5 / R-FW-11.6 自提風險 — 是否併入 TDD §10
Design §12.2 / §12.3 自提兩個風險:
- R-FW-11.5:使用者輸入「downgrade」小寫繞過 — Frontend 用嚴格
===比對、不用.toUpperCase() - R-FW-11.6:deep-link 跳轉自動展開 accordion 暗示降版 — 改為只 highlight 卡片邊框、不展開
架構層判斷:
- R-FW-11.5:這是 Frontend 實作細節、不是架構層風險。我 TDD §3.1 已要求 backend
confirmToken === "DOWNGRADE"嚴格比對(雙重保險)。不需要進 TDD §10 風險清單 - R-FW-11.6:純 UX 設計、與架構無關。不需要進 TDD §10
結論:兩個風險都 Design 內部處理、不併入 TDD §10。但建議 PM PRD §8 補一條 R-FW-11.5(Frontend 嚴格比對)、明文要求 Reviewer 在 M9-12 檢查 Frontend 是否用了 === 而非 .toUpperCase()。
三、我自己 TDD 自查
審完 PM/Design 後、回頭看我的 TDD、發現以下需要下輪修改:
3.1 必修項(影響跨 agent 對齊)
| # | TDD 章節 | 修什麼 | 來源 |
|---|---|---|---|
| F1 | §4.3 Stage 列舉 | 改名對齊 Design:connecting → preparing / loading_loader → loading / loading_firmware → flashing |
§2.4 |
| F2 | §3.4(新增) | 加「stage → 錯誤碼 → 失敗類型」對應表(給 Frontend / Testing 用) | §2.3 |
| F3 | §4.2 FirmwareProgress |
補 Elapsed int64 + ETA int64 + 失敗時補 DeviceID / BeforeVer / RawError / ErrorCode |
§2.6 / §2.9 |
| F4 | §8.6(新增) | 補「降版進行中 graceful shutdown 拒絕」設計(HasActiveTask + Wails close handler 攔截) | §2.5 |
| F5 | §9 工時表 | 對齊 PM 的 M9-7~M9-10 拆法(採 PM 拆法) | §1.5 |
| F6 | §10 R-FW 風險編號 | 對齊 PM 的 R-FW-1~7 編號(採 PM 編號) | §1.6 |
| F7 | §5.1 / §6.1 | stage 名稱對齊 F1(包含 bridge.py handler 回傳的 stage enum 值) | §2.4 連動 |
3.2 建議修但非必要
| # | TDD 章節 | 修什麼 | 來源 |
|---|---|---|---|
| O1 | §1.2 範圍邊界 | 補一條「不做 KL530 / KL830」(雖然 1.2 寫了「KL530 / KL830 不做」、但 PM PRD §3 沒列、可以兩邊對齊一下) | §1.8 |
| O2 | §10 R-TAR-1~4 | PM 沒列、保留 TDD §10 即可(PM 同意的話) | §1.6 |
| O3 | §11.2 整合測試表 | 補「升級期間關 Wails 視窗」測試案例(驗證 §8.6 graceful shutdown 拒絕) | §2.5 連動 |
3.3 不修項(自查後仍然成立)
| # | 之前提的 5 個 PM 互審注意 | 是否仍成立 |
|---|---|---|
| 1 | §1.2 範圍邊界 R5-Q9 翻案 | ✅ 仍成立、PM PRD §2 完全對齊 |
| 2 | §8.4 R5-B4 授權對齊 | ✅ 仍成立、PM PRD §8.1 R-FW-5 標 P0 |
| 3 | §9 工時 15.5 對齊 | ✅ 總工時對齊、只是 M9-7~M9-10 拆法不同(F5 修) |
| 4 | §11.4 回歸測試 | ✅ PM PRD 沒寫測試細節、TDD 自帶 OK |
| — | — | — |
| # | 之前提的 5 個 Design 互審注意 | 是否仍成立 |
|---|---|---|
| 1 | §5.2 降版「DOWNGRADE」字面輸入 | ✅ Design §6.1 已落地 |
| 2 | §3.3 錯誤碼 + §5.3 失敗復原文案 | ✅ Design §7.1 / §9.8 已落地、但需 F2 補對應表 |
| 3 | §4.3 Stage 列舉 i18n keys | ⚠️ 需 F1 修 stage 命名後對齊 |
| 4 | §10 R-FW-11/12 UI 落地 | ✅ Design §12.1 已落地、多層 safety net 完整 |
| 5 | §4.4 多版本目錄 A → B2 migration | ✅ Design 沒涉及、TDD §4.5 自帶 OK |
四、結論
4.1 PM PRD:通過 — 需小修
| 嚴重度 | Issue | 建議 |
|---|---|---|
| Major | M9 工時表 M9-7~M9-10 拆法與 TDD 不對齊 | 對齊一張表(採 PM 拆法) |
| Major | R-FW-1~7 編號與 TDD 不對齊 | 對齊(採 PM 編號) |
| Minor | AC-FW-3.5 生效條件不明 | 補 a/b/c 條件(見 §1.3) |
| Minor | §3 scope 邊界漏列「不做 KL530/KL830 + DFUT.exe」 | PM 補 1 條 |
| Minor | §7 成功指標 measurement source 模糊 | PM 在 §7 開頭補一句「靠客服回報 + log、非自動 telemetry」 |
| Minor | AC-FW-1.6 USB stable 等待時間 5-8s vs TDD 5s | PM 改為「5-8 秒(實測 5s 已穩、保留上界容忍)」 |
4.2 Design Spec:通過 — 需小修
| 嚴重度 | Issue | 建議 |
|---|---|---|
| Major | §8 狀態機名稱與 TDD §4.3 stage 不對齊 | 共識採 Design 命名(preparing / loading / flashing / verifying)、TDD 修 stage、Design i18n key 也對應修 |
| Major | §14.4 第 6 點 graceful shutdown 拒絕 | Design 提出問題、TDD 補 §8.6 落地 |
| Minor | §11.2 token 對比比率 Dark legacy.fg | 信任 Design 推算、Frontend M9-4 實作時 verify |
| Minor | §7.1 8 種失敗 stage 對應不清晰 | Design + TDD 共同補對應表(給 Frontend / Testing 用) |
| Minor | progress event 缺 elapsed / ETA |
TDD §4.2 補欄位 |
| Minor | settings-update.md 沒更新分頁順序 | Design 在 v2.2 同步小修一行 |
4.3 我自己 TDD:需修 7 項必修 + 3 項建議
詳見 §3.1 / §3.2。我下輪修 TDD、目標:
- F1-F7 全修(必修)
- O1-O3 視情況修(建議)
- ADR-001 不需修(仍然 Accepted、決策邏輯沒變)
4.4 整體判斷
- 架構層:✅ 通過、PM/Design/Architect 三方在 R5-Q9 翻案、A+B 範圍、+7MB 安裝包、CURRENT_VERSION 多版本目錄、二次確認 DOWNGRADE 字串、watchServer 解耦等核心架構決策完全對齊
- 介面層:⚠️ 需修 F1(stage 命名)+ F2/F3(progress event schema 補欄位)+ F4(graceful shutdown 拒絕)
- 工時 / 風險:⚠️ 需修 F5(M9 表)+ F6(R-FW 編號)
- M9-6 SDK 驗證:不卡 A 階段、M9-1~5 可以照計畫啟動
五、給 Orchestrator 的建議
5.1 互審後的處理順序(建議)
1. Orchestrator 收齊三方互審報告(PM 審 Design+Architect、Design 審 PM+Architect、本報告 = Architect 審 PM+Design)
2. 跨報告比對、識別「共識點」「分歧點」
3. 對齊以下幾個關鍵點:
a. M9 工時表(採 PM 拆法、總和 15.5 不變)
b. R-FW 編號(採 PM 編號)
c. stage 命名(採 Design 命名:preparing / loading / flashing / verifying)
d. graceful shutdown 拒絕(補 TDD §8.6 + Design control-panel.md 補 modal)
4. 確認分歧後、派 Architect 修 TDD(F1-F7 + 視情況 O1-O3)
5. 派 PM 微修 PRD(§1.3 + §3 scope 補 + §7 measurement 註解 + §10 表 M9-7~M9-10 拆法 + R-FW-1~7 對齊)
6. 派 Design 微修(§7 失敗對應表 + settings-update.md 一行 + 同意 stage 命名)
7. 第二輪互審(小規模、只審修改處)
8. 通過 → 進 M9-1 / M9-6 開發
5.2 不需要 Orchestrator 額外裁決的事
| Item | 為何不需裁決 |
|---|---|
| token 對比比率 | Frontend M9-4 實作時驗證、Design 推算先信任 |
| R-FW-11.5 / R-FW-11.6 自提風險 | Design 內部處理、不進 TDD §10 |
| KL530/KL830 不做 | 三方都已隱性同意、PM 補一行即可 |
| Confirm token 「DOWNGRADE」未來 i18n 化 | 短期維持英文、Architect + Design 已同意(不分語系) |
5.3 需要 Orchestrator 裁決的事
| Item | 選項 |
|---|---|
| stage 命名最終採哪一邊 | A. 採 Design(preparing / loading / flashing / verifying)✅ 建議;B. 採 TDD(connecting / loading_loader / loading_firmware / verifying) |
| M9 表最終採哪一邊拆法 | A. 採 PM 拆法 ✅ 建議;B. 採 TDD 拆法;C. 折衷 |
| graceful shutdown 拒絕的 modal | Design 補 control-panel.md / Architect 補 TDD §8.6 / 工時影響(建議併入 M9-11) |
變更紀錄
| 日期 | 版本 | 變更 | 作者 |
|---|---|---|---|
| 2026-05-24 | v1.0 | 初稿、Architect 視角審 PM PRD + Design Spec、識別 F1-F7 必修項 + 6 個 PM Minor + 6 個 Design Minor | Architect Agent |