visionA/local-tool/.autoflow/04-architecture/reviews/architect-review-of-prd-and-design-v2.2-firmware.md
jim800121chen 46514d77d7 docs(local-tool): M9 — Kneron Dongle FW 偵測 + 升降版(A+B、翻案 R5-Q9)
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>
2026-05-25 07:40:56 +08:00

577 lines
36 KiB
Markdown
Raw 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.

# Architect 互審報告 — PM PRD + Design Specv2.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 高度一致、沒有架構衝突。** 但有幾個對齊問題需要解決,主要落在三處:
1. **狀態機名稱不對齊**Design `preparing/loading/flashing/verifying` vs TDD `connecting/loading_loader/loading_firmware/verifying`)—— **必須我修 TDD**(採 Design 的命名、對使用者語意較自然)
2. **失敗類型 6 vs 8 不一致**TDD 列 6 stage + 6 錯誤碼、Design 列 8 種失敗類型)—— Design 多列的 `timeout` + `disconnect` 是橫切性失敗、本來就在我的錯誤碼裡(`FW_UPGRADE_FAILED` / `FW_UPGRADE_BRICK_RISK`)—— 補一張對應表即可
3. **降版進行中 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 結論其一):
- 條件 aKneronPLUS Python wheel 沒有 `kp.core.update_kdp_firmware_from_files` 對 product_id 0x0630/0x0730 的 dispatch
- 條件 bSDK 對 .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 dispatch1.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 認 chipdetector + bridge.py + driver0.5 人天 | **採 PM 的 1.5**(含 chip-aware connect、語意清楚|
| M9-8 | .tar handling1.0| B1.1 .tar handling1.5| **採 TDD 的 1.5**M9-6 結論若需策略 Y、解壓 build script 工程更大)|
| M9-9 | 多版本目錄重整1.0| B1.2 KL630/KL730 connect + inference2.0| **不同 milestone**、PM 與 TDD 對齊不到——詳見後文 |
| M9-10 | KL630/KL730 升降版1.5| B1.3 FW 升版擴 KL630/KL7301.0| 同上 |
**根因**PM 把「多版本目錄重整」獨立成 M9-9、TDD 把它合進 M9-11PM 把「KL630/KL730 inference」併進 M9-7、TDD 拆出 M9-9。**兩邊都對、只是拆法不同**。
**建議統一**(採 PM 拆法、語意對外部審閱者更清楚):
- M9-7B0 認 chipdriver + bridge.py + detector1.5 人天
- M9-8B1.1 .tar firmware handling 1.5 人天(採 TDD 數字)
- M9-9多版本目錄重整 + `_resolve_firmware_paths_versioned()` 1.0 人天
- M9-10KL630/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.5M9-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 adminWinUSB| 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 |
**結論**
1. **R-FW-1~7 的編號 PM 與 TDD 完全不對齊**——可能是 PM 引用了 research 30 的早期編號、我 TDD 內部重新編了。**這是重大問題、必須對齊**。
2. **建議統一**:採 PM 的 R-FW-1~7 編號(因為 PRD 已是「對外的」風險清單、跨多個下游 agent 引用)、我下輪修 TDD §10 重新對應。
3. **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
1. 拒絕跨晶片誤匹配
2. 拒絕升版偽裝
3. 拒絕 no-op
4. 拒絕 status busy
我 TDD §2.1 / §3.3 對應如下:
1. `firmware/guards.go` 拒絕跨晶片 ✅
2. `firmware/guards.go` 拒絕升版偽裝(`FW_INVALID_DIRECTION` 400
3. `firmware/guards.go` 拒絕 no-op同上錯誤碼
4. `FW_DEVICE_BUSY` 409 ✅
**對齊度**:✅ 完全一致、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-4A 階段、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 → 友善訊息):
1. `scan` 找不到裝置
2. `connect` 失敗
3. `loader` 寫入失敗
4. `upgrade` 寫入失敗
5. `verify` 失敗
6. `Timeout (>60s)`
7. `Disconnect during op`
8. (無第 8 項、Design 表格只有 7 列、但 §14.3 表寫「8 種失敗類型對應」、可能是 Design 自己估算誤差)
**我 TDD §3.3 列的 6 個錯誤碼**HTTP 層):
1. `FW_DEVICE_BUSY` 409
2. `FW_VERSION_NOT_FOUND` 404
3. `FW_INVALID_DIRECTION` 400
4. `FW_NO_CONFIRM_TOKEN` 400
5. `FW_UPGRADE_FAILED` 500
6. `FW_UPGRADE_BRICK_RISK` 500
**我 TDD §5.3 列的 4 種失敗類型**(流程失敗、不是 HTTP 錯誤碼):
1. device disconnect during upgrade
2. firmware load 失敗pre-flash
3. firmware load 失敗中段、timeout > 180s
4. 升級成功但 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` | — | OKDesign 用、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.pyfirmware 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 string`
- `Message string`
- `Error 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 已有 `settings.firmware.progress.estimatedRemaining` key、寫「~{seconds}s remaining」、OK
**動作****我下輪修 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 版本切換 accordion11 個)| 純前端、TDD §5.2 流程進入點 | ✅ |
| §9.4 升級確認 modal6 個)| 純前端 | ✅ |
| §9.5 降版二次確認 modal13 個)| 與 TDD §3.1/§3.3 `confirmToken` 對齊 | ✅ |
| §9.6 進度 modal10 個)| ⚠️ **stage 名稱不對齊**(見 §2.4| **修 §2.4 stage 命名同時對齊**|
| §9.7 成功 toast5 個)| 純前端 | ✅ |
| §9.8 失敗訊息13 個)| 與 TDD §5.3 失敗類型對應、但對應表缺(見 §2.3| **TDD 補對應表後一致** |
| §9.9 Devices 頁 FW badge tooltip4 個)| 與 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.5Frontend 嚴格比對)**、明文要求 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 解耦等核心架構決策完全對齊
- **介面層**:⚠️ 需修 F1stage 命名)+ F2/F3progress event schema 補欄位)+ F4graceful shutdown 拒絕)
- **工時 / 風險**:⚠️ 需修 F5M9 表)+ F6R-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 修 TDDF1-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. 採 Designpreparing / loading / flashing / verifying✅ 建議B. 採 TDDconnecting / 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 |