# M1-3 Review — 改寫 main/config/router + 清 import + module rename - 審查者:Reviewer Agent - 日期:2026-04-10 - 審查對象:`/Users/jimchen/visionA/local-tool/server/` - 依據文件:`api-endpoints.md §4`、`removed-code.md §3~§6`、`M1-2-review.md` ## 結論:✅ 通過 M1-3 的所有目標達成:module 已改名為 `visiona-local/server`、四個要刪的檔案全數刪除、CLI flag 對齊規格、router/config/main/handlers/test 全部清乾淨,`go build ./...` 與 `go vet ./...` 一次通過。可以直接進入 M1-5。 --- ## 檢查清單 ### A. 要刪除的檔案(removed-code.md §2) | 檔案 | 狀態 | |------|------| | `internal/api/handlers/cluster_handler.go` | ✅ 已刪除 | | `internal/api/ws/flash_ws.go` | ✅ 已刪除 | | `internal/api/ws/cluster_flash_ws.go` | ✅ 已刪除 | | `internal/api/ws/cluster_inference_ws.go` | ✅ 已刪除 | 剩餘 `internal/api/ws/` 下只剩:`device_events_ws.go`、`hub.go`、`inference_ws.go`、`server_logs_ws.go`,符合 api-endpoints.md 中保留的 WebSocket 清單。 剩餘 `internal/api/handlers/` 下只剩:`camera_handler.go`、`device_handler.go`、`model_handler.go`、`model_upload_handler.go`、`system_handler.go`,乾淨。 ### B. Module 改名 | 檢查項 | 結果 | |-------|------| | `go.mod` module 行 | ✅ `module visiona-local/server` | | `grep -rn "edge-ai-platform" server/ --include="*.go"` | ⚠️ 僅命中 `driver/kneron/detector.go` L32-33 兩處**字串 literal**(`~/.edge-ai-platform/venv/...`),**非 import path** | | 全檔 `edge-ai-platform/` import | ✅ 0 處 | `detector.go` 內的路徑字串是掃描使用者家目錄找既有 Python venv 的 fallback 路徑,不是 module import,**符合 M1-3 要求**(僅 import 需更新)。列為 🟢 備註:等 installer / python runtime 重設計時再調整這兩個 legacy 路徑。 ### C. CLI flag 對齊(config.go) | Flag | 規格 | 實際 | 結果 | |------|------|------|------| | `--mock` | 必須 | ✅ bool | ✅ | | `--dev` | 必須 | ✅ bool | ✅ | | `--port` | 必須 | ✅ int,default 3721 | ✅ | | `--data-dir` | 必須 | ✅ string | ✅ | | `--python-mode` | 必須 | ✅ string(auto/bundled/system) | ✅ | | `--mock-camera` | 保留 | ✅ bool | ✅ 合理 | | `--mock-devices` | 保留 | ✅ int | ✅ 合理 | | `--log-level` | 保留 | ✅ string | ✅ 合理 | | `--model-dir` | 保留 | ✅ string | ✅ 合理 | | `--host` | 保留(強制覆寫) | ✅ string,default 127.0.0.1 | ✅ 見 D | | `--relay-url` | **必砍** | ❌ 不存在 | ✅ | | `--relay-token` | **必砍** | ❌ 不存在 | ✅ | | `--tray` | **必砍** | ❌ 不存在 | ✅ | | `--gui` | **必砍** | ❌ 不存在 | ✅ | `PythonMode` 定義為 typed string + 三個常數 + 預設 fallback,乾淨且符合 api-endpoints.md §5.2 未來要擴充的規劃。 ### D. Host 強制 127.0.0.1 `config.go` L48-49: ```go // 強制 localhost-only cfg.Host = "127.0.0.1" ``` ✅ **確實 override**。在 `flag.Parse()` 之後無條件覆寫,即使使用者傳 `--host 0.0.0.0` 也會被改回 `127.0.0.1`。防呆正確。 ### E. Router 乾淨度(api-endpoints.md §4) | 檢查項 | 結果 | |-------|------| | 簽章移除 `clusterMgr`、`flashSvc`、`relayToken` 三參數 | ✅ | | `/api/clusters/*` routes | ✅ 0 處 | | `/api/devices/:id/flash` | ✅ 已移除 | | `/api/system/update-check` | ✅ 已移除 | | `/auth/token` + OPTIONS | ✅ 已移除 | | `/ws/devices/:id/flash-progress` | ✅ 已移除 | | `/ws/clusters/*` | ✅ 0 處 | | 保留的 REST routes 與 §4 清單逐項比對 | ✅ 完全符合 | | 保留的 WS routes(devices/events, devices/:id/inference, server-logs) | ✅ 三條都在 | | grep `clusterMgr|flashSvc|/api/clusters|/auth/token|update-check|/flash-progress|CheckUpdate` | ✅ 0 matches | ### F. main.go 清理(removed-code.md §3, §4) | 檢查項 | 結果 | |-------|------| | imports 內無 `cluster/flash/tunnel/hwid` | ✅ | | 沒有 `tunnelClient`、`clusterMgr`、`flashSvc`、`relayToken` 變數 | ✅ | | 沒有 `relayWebURL`、`openBrowser` 函式 | ✅ | | `NewSystemHandler` 呼叫移除 `giteaURL` | ✅ L147 只傳 `Version, BuildTime, restartFn` | | `NewRouter` 呼叫移除 3 個參數 | ✅ L150 簽章對齊 | | 保留 graceful shutdown + self-restart exec | ✅ | | 保留 `killExistingProcess` | ✅ 合理(解決 port 被占用) | ### G. device_handler.go | 檢查項 | 結果 | |-------|------| | 移除 `internal/flash` import | ✅ | | 移除 `FlashDevice` handler | ✅(檔內已無) | | 建構子 `NewDeviceHandler` 不再注入 `flashSvc` | ✅ 只收 deviceMgr/inferenceSvc/wsHub | | 保留 Connect/Disconnect/Scan/List/Get/Start/Stop Inference | ✅ | ### H. system_handler.go | 檢查項 | 結果 | |-------|------| | 移除 `internal/update` import | ✅ | | 移除 `CheckUpdate` handler 與 `giteaURL` 欄位 | ✅ | | 保留 Health/Info/Metrics/Deps/Restart | ✅ | **備註**:api-endpoints.md §1.1 有要求 `/api/system/info` 擴充 `actual_port`、`mode`、`python_mode` 三個欄位,以及新增 `/api/system/mode`、`/api/system/python-runtime`、`/api/system/port` 三個 endpoint。目前 `Info()` 只回傳 `version/platform/uptime/goVersion`,新 endpoint 也尚未新增。**但這些是後續 milestone(M2 以後)的工作,不屬於 M1-3 的範圍**(M1-3 的任務是 *清理*,不是新增功能)。列入待辦追蹤,不影響本次通過。 ### I. api_e2e_test.go | 檢查項 | 結果 | |-------|------| | 移除 `cluster` / `flash` import | ✅ | | 移除 cluster / flash / auth 測試案例 | ✅ 檔內無殘留 | | `setupTestServer` 呼叫 `NewRouter` 簽章對齊 | ✅ L50-53 | | 保留的測試:Health、DeviceWorkflow、DeviceScan、ModelList、ConnectNonExistent、MultiDeviceIsolation | ✅ | --- ## Build 驗證 自己實際跑的結果: ```bash cd /Users/jimchen/visionA/local-tool/server go build ./... → exit 0(無任何輸出) go vet ./... → exit 0(無任何輸出) ``` ✅ **完全乾淨**,無 compile error、無 vet warning。 --- ## 針對審查重點 6、7、8 的判斷 ### 6. CORS 中的 `X-Relay-Token` header(middleware.go L19) ```go c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Relay-Token") ``` **判斷:🟢 Minor(無害但可清)**。 - `relay-url/relay-token` flag、`RelayToken` config、`/auth/token` endpoint 都已移除,前端也不會再送這個 header。保留在 CORS allow-list 屬於**無害殘留**(瀏覽器只會放行,不會主動產生請求)。 - Backend 的判斷「保留但可清」合理。建議 M1-3 收尾或 M1-5 之前順手改成 `"Content-Type, Authorization"`,避免將來審計時引起疑問,但不阻擋進入 M1-5。 ### 7. `internal/driver/` 裡的 `Flash` / `FlashProgress` 讀了 `internal/driver/interface.go`、`driver/kneron/kl720_driver.go:397` 附近、`driver/mock/mock_driver.go:68`,結論: - `DeviceDriver` 介面的 `Flash(modelPath, progressCh)` 是**把 .nef 模型載入到 Kneron 裝置**的方法,對應 Kneron SDK 的 model load API(`kl720_driver.go` 的註解明確寫「models can be freely reloaded」,並搭配 `scripts/kneron_bridge.py`)。 - 這跟已刪除的 `internal/flash` package(韌體燒錄服務)完全是兩回事 —— 後者是 firmware flashing(KL720 firmware binary → device flash memory),前者是 model loading(把已訓練好的 .nef 模型丟到裝置的 RAM/ flash 區)。 - 既然 `/api/devices/:id/inference/start` 仍然需要先把模型載上去,這個 `Flash` 方法是**必要的**,不能刪。 **Backend 的判斷正確,列為 ✅ 無問題**。唯一 cosmetic 的點:未來可考慮把 `Flash` / `FlashProgress` 改名為 `LoadModel` / `LoadProgress` 避免語意混淆,但屬於重構,不在 M1-3 範圍。 ### 8. `web/out/placeholder.txt` 解法 確認: - `web/embed.go` 用 `//go:embed all:out` 指向 `out/` 目錄 - 因為 frontend 尚未 build,原本 `out/` 不存在會導致 `go build` 失敗(M1-2 review 已預警) - M1-3 在 `out/` 下放了一個 `placeholder.txt`,讓 embed target 存在,build 即可通過 **判斷:🟢 合理的最小修補**。`all:` 前綴會一起 embed 底線或點開頭的檔案,但 placeholder.txt 不影響實際 static serving(因為等 frontend build 完就會覆蓋整個 `out/`)。簡單、正確、不擾動 embed.go。 --- ## 問題 ### 🔴 Critical 無。 ### 🟡 Major 無。 ### 🟢 Minor / Suggestion 1. **`middleware.go` L19 的 `X-Relay-Token`** — 無害殘留,建議之後收尾時清掉: ```go c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") ``` 2. **`driver/kneron/detector.go` L32-33 的 `.edge-ai-platform` 字串** — legacy 路徑字串,不是 import path,不影響 M1-3,但未來 installer/python runtime 重設計時應跟著改名(例如改為 `.visiona-local/venv/...`)。 3. **`/api/system/info` 尚未擴充 `actual_port` / `mode` / `python_mode`,以及 `/api/system/mode`、`/api/system/python-runtime`、`/api/system/port` 三個新 endpoint 尚未實作** — 屬於 M2 以後的工作(api-endpoints.md §1.1, §5),**不阻擋 M1-3 / M1-5**,但要記得追蹤。 4. **`Flash` / `FlashProgress` 命名語意模糊** — 未來重構時可考慮改為 `LoadModel` / `LoadProgress`,非必要。 ### 優點 - Module rename 徹底、Go import 0 殘留,找不到任何 `edge-ai-platform/` import path。 - Router 與 api-endpoints.md §4 的規格**逐行對齊**,連註解順序都維持原作者風格。 - `config.go` 的 Host 強制 override 防呆寫得乾淨(flag 還是允許傳入便於開發者習慣,但結尾強制覆寫,二者兼顧)。 - `PythonMode` 用 typed string + fallback 預設值,為 M2 的 runtime 切換邏輯鋪好路。 - `api_e2e_test.go` 精簡得宜,保留了關鍵 workflow 測試,build + 編譯都乾淨(雖然本次沒跑 `go test`,但 vet 通過代表 test file 的 symbol 引用都對)。 - `killExistingProcess` 保留是明智的 — 對本地 daemon 很實用,開發時切換 binary 不會被 stale process 擋住。 --- ## 可以進入 M1-5(build Go binary)嗎? ✅ **可以直接進入 M1-5**。 理由: 1. `go build ./...` 與 `go vet ./...` 在 reviewer 端重現,皆一次通過。 2. 所有刪除清單、module rename、router 簽章、CLI flag、Host 強制覆寫都對齊規格。 3. `web/out/placeholder.txt` 已解決 M1-2 預警的 embed 編譯障礙,`go build` 不再卡在 embed target 缺失。 4. 剩餘的 🟢 項目(CORS header、detector 路徑字串、system info 擴充)都不影響 binary 能不能 build,也不影響 binary 跑起來的核心功能,可延後處理。 建議 M1-5 時: - 跑一次 `go test ./...` 當作額外保險(本次 review 未跑,僅跑 build + vet)。 - 驗證 `go build -o visiona-local-server .` 產出 binary 並直接執行一次,確認 `--mock --dev --port 3721` 能成功起 server。 - 順手把 🟢#1(CORS header)清掉,避免審計疑問。