jim800121chen c54f16fca0 Initial commit: visionA monorepo with local-tool subproject
local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
            Wails IPC raise endpoint, stale process cleanup

.autoflow/: full PRD / Design Spec / Architecture / Testing docs
            (4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:10:38 +08:00

231 lines
12 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.

# M1 End-to-End Verification Report
## 測試環境
- macOS: 14.7.6 (BuildVersion 23H626)
- 架構: x86_64
- 檔案系統: APFScase-insensitive預設
- 日期: 2026-04-11
- 測試方式: 複製 `.app``/tmp/` 模擬全新機器,清空 `~/Library/Application Support/visiona-local`
## 結論TL;DR
**M1 未通過。** `.dmg` 本身可正常 mount、`.app` bundle 結構正確、codesign 驗證通過,但**應用程式一啟動就失敗**。發現 **2 個 P0 阻斷 bug**
1. **資料目錄遷移邏輯在 case-insensitive APFS 上壞掉** — 預設 Mac 根本啟動不了。
2. **Wails 端呼叫 server binary 時用了不存在的 flag `--python`** — 就算繞過 bug 1server 子行程也會立刻 crash。
Mock 模式無法在全新 Mac 上運作,**瀏覽器無法看到主 UI**M1 的核心承諾全部跳票。
---
## 前置清理
啟動前發現系統上 legacy `edge-ai-server` 持續被 launchd 重啟(`~/Library/LaunchAgents/com.innovedus.edge-ai-server.plist`),已執行:
```
launchctl unload ~/Library/LaunchAgents/com.innovedus.edge-ai-server.plist
pkill -9 -f edge-ai-server
```
之後 port 3721 淨空。**注意:這是測試環境前置清理,不是 M1 驗收的一部分。但這提醒:使用者第一次安裝若曾裝過 edge-ai-platform升級 visiona-local 可能 port 衝突。**
---
## 驗證步驟結果
### Step 1: .dmg 檔案 ✅
- 路徑:`/Users/jimchen/visionA/local-tool/dist/visiona-local.dmg`
- 大小70 MB
- 檔案類型:`zlib compressed data`
### Step 2: Mount ✅
`hdiutil attach` 成功,檢查碼全部通過:
- 掛載點:`/Volumes/visionA-local`
- 可見 `visiona-local.app`
### Step 3: .app 結構 ✅
- `Contents/Info.plist`
- `Contents/MacOS/visiona-local`9.8 MBMach-O 64-bit x86_64
- `Contents/Resources/bin/visiona-local-server`30 MB
- `Contents/Resources/data/models.json``data/nef/`
- `Contents/Resources/iconfile.icns``scripts/`
- `codesign --verify --verbose`**valid on disk, satisfies Designated Requirement** ✓
### Step 4: 全新機器模擬 ✅
`.app` 複製到 `/tmp/visiona-local.app`DMG 已 detach。
### Step 5: 資料目錄清理 ✅
確認 `~/Library/Application Support/` 下無任何 `vision*` 殘留。
### Step 6 + 7: 啟動 → Server 起來 ❌❌
#### 第一次啟動(走正常流程)
```
$ /tmp/visiona-local.app/Contents/MacOS/visiona-local
[visiona-local] 遷移 /Users/jimchen/Library/Application Support/visionA-local
→ /Users/jimchen/Library/Application Support/visiona-local 失敗:
rename ...: no such file or directory
visiona-local already running
```
**Wails app 直接 exit(0)。** 沒有 server、沒有 UI、什麼都沒有。
**根因分析P0 Bug #1** `visiona-local/app.go:572 migrateOldDataDirs` 的邏輯在 case-insensitive APFS 上會自我毀滅。
實際流程:
1. `startup()``MkdirAll(newDir)` 建立 `visiona-local/`
2. `migrateOldDataDirs` 迭代舊候選路徑,其中包含 `visionA-local`(大寫 A
3. 在 case-insensitive APFS 上,`os.Stat(visionA-local)` **會 hit 剛建立的 `visiona-local`**(同 inode所以 Stat 成功。
4. 程式碼接著檢查 newDir 是否存在(成功)、是否為空(剛建出來的,當然是空)→ 執行 `os.Remove(newDir)`
5. `os.Remove(newDir)` 把那個目錄刪了,但在 case-insensitive FS 上這等於同時刪掉 `visionA-local`(同 inode
6. `os.Rename(visionA-local → newDir)` → 來源不存在 → `ENOENT`
7. 更致命newDir 已經被刪掉了,後續 `acquireSingleInstance` 嘗試在裡面建 lock 檔,`os.OpenFile``ENOENT`(父目錄不存在)。
8. `ENOENT` 不是 `IsExist`,函式直接回錯。外層的 handler 把「任何錯誤」都當成「already running」→ `os.Exit(0)`
我用 Python 實測確認:
```python
os.makedirs('~/Library/Application Support/visiona-local')
os.path.exists('~/Library/Application Support/visionA-local') # → True
```
**這個 bug 在任何預設設定的 Mac 上都會觸發APFS 預設 case-insensitive等於 M1 在大部分 Mac 上根本跑不起來。**
#### 第二次啟動workaround預先建立非空 dataDir
為了繼續測後面的步驟,我先 `mkdir visiona-local && touch .keep`。這樣 `migrateOldDataDirs` 的「newDir 已有內容 → 拒絕遷移」分支會 kick in繞過 bug 1。
結果Wails app 進程活著PID 77828lock file 建好了,寫了 `visiona-local.ipc-port = 3721`
**port 3721 並沒有人 listen**
```
$ lsof -i :3721
(空)
$ curl http://127.0.0.1:3721/api/system/health
(連線失敗)
```
檢查 `~/Library/Application Support/visiona-local/logs/server.stderr.log`
```
flag provided but not defined: -python
Usage of .../visiona-local-server:
-python-mode string ...
(列出所有合法 flag其中沒有 -python
```
**P0 Bug #2** `visiona-local/app.go:234` 組 server 參數時:
```go
args := []string{
"--host", "127.0.0.1",
"--port", strconv.Itoa(port),
"--data-dir", a.dataDir,
"--python-mode", string(pyMode),
}
if pyBin != "" {
args = append(args, "--python", pyBin) // ← server 沒有這個 flag
}
```
Server binary 只認 `--python-mode`**不認 `--python`**。當系統有 python3我這台 `/usr/local/bin/python3`)時,`ensurePythonRuntime` 會回傳 pyBin於是 `--python /usr/local/bin/python3` 被加到參數列。Server 一啟動就 `flag provided but not defined: -python``os.Exit(2)`
Wails app 沒有偵測到 server 掛了(沒有 health check wait或者 check 失敗但沒有 abort繼續掛在前景。
**組合效應:** 就算使用者手動繞過 bug 1只要這台 Mac 上有安裝過 python3絕大多數開發者 / macOS 自帶),就會撞到 bug 2。Mock 模式理論上不該碰 python但目前的 code path 還是會把 pyBin 傳下去。
### Step 8: 主 UI 可訪問 ❌
因為 server 沒起來,`curl http://127.0.0.1:3721/` 得到 `HTTP 000`(連線拒絕)。**瀏覽器看不到任何東西。**
### Step 9: 資料目錄 ⚠️
在 workaround 下資料目錄內容:
```
.keep我手動建的
logs/
server.stderr.log ← 記錄 server 啟動失敗
server.stdout.log ← 空
visiona-local.ipc-port ← 內容 "3721"(但 port 根本沒在 listen
visiona-local.lock ← 有 PID
```
`.ipc-port` 檔寫的是**預期 port**,不是**實際 port**,這本身也是潛在問題(但相比前兩個 bug 是次要的)。
### Step 10: 乾淨退出 ✅
`pkill -9 -f visiona-local``pgrep -fl visiona-local` 為空,`.app` 複本、資料目錄都清除乾淨。
### Step 11: make clean + make dmg 從頭跑 ⏭️ 略過
前面已經是阻斷級問題,重新 build 也不會修掉這兩個 bug。略過本步驟等修好再驗。
---
## M1 驗收結論
| 核心承諾 | 結果 |
|---------|------|
| 1. 雙擊 .dmg → mount → .app 存在 | ✅ |
| 2. 啟動 → Mock 模式跑起來 | ❌ Wails app 秒退bug 1或 server 立刻 crashbug 2 |
| 3. API (`/api/system/health``/api/system/info`) 可訪問 | ❌ port 沒有人 listen |
| 4. 主 UI 可在瀏覽器看到 | ❌ 同上 |
| 5. 乾淨退出 | ✅(但因為根本沒真的「運作」過,退出也沒什麼好清的) |
**核心承諾 2 / 3 / 4 全部沒達成。M1 未通過。**
---
## 發現的問題
### 🔴 P0 阻斷
#### P0-1: `migrateOldDataDirs` 在 case-insensitive APFS 上自我毀滅
- **位置**`visiona-local/app.go:572-598` + `oldDataDirCandidates` 清單中的 `visionA-local`
- **影響**:任何預設 APFS 的 Mac絕大多數使用者**第一次啟動必失敗**。
- **修法建議**
- (A) 用 `os.Stat` 比對 inode如果舊路徑和新路徑指向同一個 inode代表 case-insensitive FS 且實際上是同一個目錄,直接 `continue`,不要嘗試遷移。
- (B) 或者,在 candidates 裡**移除** `visionA-local` — 反正新版是 `visiona-local`,如果之前用的也是 `visiona-local`case-insensitive FS 下等效),根本不需要遷移。只有在真正 case-sensitive FS少數使用者自選才需要這個 candidate那就用 FS 類型偵測決定要不要加。
- (C) 額外修正:`startup()` 裡對 `acquireSingleInstance` 的錯誤處理要分辨「真的有別的 instance 在跑」vs「其他錯誤例如資料目錄不見了」。現在任何錯誤都印 "already running" 會誤導 debug。
#### P0-2: Wails 傳給 server 的 `--python` flag 不存在
- **位置**`visiona-local/app.go:234`
- **影響**:只要系統上有 pythonmacOS 幾乎都有server 子行程啟動即死。Mock 模式也一樣會死。
- **修法建議**
- 改成 `--python-bin`(並在 server 端加對應 flag
- 把 pyBin 放在環境變數 `VISIONA_PYTHON_BIN` 傳,或
- 在 Mock 模式下完全不傳 pyBin 相關參數(根本用不到)。
- **額外**Wails 端應該在 `startServer()` spawn 後做一次 health check例如 500ms 內 poll `/api/system/health` 幾次),失敗就 `reportFatal`,不要讓 app 繼續假裝有在運作。
### 🟡 需修(非阻斷)
#### M-1: `.ipc-port` 寫的是預期 port 不是實際 port
Server 啟動前就寫好 port file但如果 server 實際沒起來bug 2 的情況),前端或 IPC 呼叫者會連到一個不存在的 port。應該在 server 真的 `LISTEN` 成功後才寫 ipc-port 檔(或由 server 自己寫)。
#### M-2: 系統級 launchd agent 殘留
`~/Library/LaunchAgents/com.innovedus.edge-ai-server.plist` 會自動重啟 legacy daemon搶 3721 port。M1 uninstall / migration 文件應提醒使用者:若曾安裝 edge-ai-platform需要手動 `launchctl unload` + 刪 plist。或在首次啟動時偵測到同 port 衝突時給出提示。
### 🟢 建議
- **測試環境 matrix**:目前所有開發測試都在同一台有 python、有舊 edge-ai-platform、有 legacy launchd 的 Mac 上跑。這兩個 bug尤其 bug 1在「乾淨 Mac」上才暴露。建議納入「新 macOS VM / 沒有 python 的 Mac / case-sensitive APFS 的 Mac」三種情境做 CI-level 驗證。
- `codesign` 目前是 ad-hoc/self-signed上線前需要 Apple Developer ID 簽章 + notarize否則 Gatekeeper 會擋。本次測試沒觸發這個議題(因為 `.app` 是本機 build 直接 run但那是因為本機沒有 quarantine attr。從 `.dmg` 真的雙擊打開時會有不同行為,建議補 Gatekeeper 測試。
- Legacy LaunchAgent 議題(上方 M-2也要在交付文件 (`07-delivery/launch-checklist.md`) 列成「升級安裝注意事項」。
---
## 結論:**M1 未通過,需修**
兩個 P0 bug 都必須在 ship 前修掉。建議流程:
1. **Architect Agent** 決定 bug 1 的修法inode 比對 vs. 移除候選 vs. FS 偵測),以及 bug 2 的 flag 契約(改名 / 環境變數 / mock 模式不傳)。
2. **Backend Agent** 實作修復(`app.go` 兩處改動 + 可能的 server flag 新增)。
3. **Reviewer** 審查。
4. 重新 `make clean && make dmg`**我再跑一次完整 E2E**。
5. 本次的前置清理步驟unload launchd也應寫進 launch-checklist提醒升級使用者。
---
## 附錄:本次使用的清理指令
```bash
# 卸下 legacy launchd agent若有
launchctl unload ~/Library/LaunchAgents/com.innovedus.edge-ai-server.plist
pkill -9 -f edge-ai-server
pkill -9 -f visiona-local
# 清資料目錄(模擬全新使用者)
rm -rf "$HOME/Library/Application Support/visiona-local"
rm -rf "$HOME/Library/Application Support/visionA-local" # 注意APFS 上這會和上一行等效
# 卸載 .dmg若還掛著
hdiutil detach /Volumes/visionA-local
```