|
|
fc7e1e0bc1
|
fix(local-agent): 修 vendor wheels 多版本累積導致 SDK 沒裝進 bundled runtime
根因:Makefile vendor-wheels 用 pip download --dest 只加不清空,跨多次
vendor-sync 累積出同套件多版本(certifi 三版/numpy 二版等共 17 wheel)→
agent bootstrap 的 ensureBundledPython 把目錄所有 .whl 當獨立參數丟 pip
install → 版本衝突 ResolutionImpossible → pip 失敗 → venv 建了但空 →
無 Kneron PLUS SDK → bridge scan 走 pyusb fallback 回假序號 0x00000000 →
裝置「尚未回報序號」+ 載入模型 device not connected。
修法(3 個 vendor-wheels target):
- 開頭 rm -f vendor/wheels/<plat>/*.whl 清空,確保每次 sync 單版本
- 結尾 dedup 驗證(sort|uniq -d 偵測多版本 → exit 1 fail-fast)
- linux 補「無專屬 KneronPLUS wheel 時退回複製 macos py3-none-any」
- .gitignore 補 local-agent/vendor|payload|dist 對稱規則(保留 ffmpeg
binary + .gitkeep 例外),防 vendor-sync 後 git add -A 撈進 build 產物
決定性 evidence(agent 實際 bundled runtime):清空 runtime → 起剛 build
的 .app → agent 自己 bootstrap 裝 9 個乾淨單版 wheel(修前 17 含重複必失敗)
→ venv 有 kp → GET /api/devices 回真序號 0xB906162C(非 0x00000000)。
Reviewer 通過(0C/0M/4Mi/3Sug)。win/linux 同邏輯已補、未在對應 runner
驗證(已知限制)。follow-up:ensureBundledPython 列舉 wheel 脆弱設計建議
改 find-links 自解析(backend 另案)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 14:21:08 +08:00 |
|
|
|
51dfd0f2d8
|
fix(frontend): normalizeDevice serialNumber 加 trim(WP-C Minor #1)
純空白字串(" ")原判 truthy → 會以空白 serial 組出必失敗的請求路由。
改為先 trim 再判定:serial !== "" ? serial : null。順帶消除字面 "0"
被 falsy 誤判為 null 的隱性行為。補純空白 → null 邊界測試。
Reviewer 通過(0C/0M/0Mi)。tsc/eslint 0、18 test 綠。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 11:40:35 +08:00 |
|
|
|
26b433eb10
|
fix(local-agent): review follow-up 小批(TLS Mi-3/4 + WP-0 S-3/4)
- Mi-3: insecure Transport 改用 DefaultTransport.Clone() 只覆寫 TLSConfig
(保留 proxy/timeout,消除「開 skip 順便改掉 proxy 行為」副作用)
- Mi-4: exchange 200 分支檢查 Success 欄位(避免 200+success:false 落到
誤導性的 missing session_token;既有回歸測試改用直接斷言防護不減反增)
- S-3: collectLocalDevices 全空 serial 濾掉不送 devices 陣列(payload 對稱)
- S-4: 假序號比對統一用 EqualFold(防未來 bridge 輸出 casing 變化)
Reviewer 通過(0C/0M/1Mi/3Sug)。兩 module build/vet/test + -race 綠、
gitleaks 0、TLS 行為級測試全 PASS。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 11:40:35 +08:00 |
|
|
|
59c57fa481
|
feat(device): WP-B repository 接 agents 模型 + exchange 重塑(A' 走向第二階段 Go 層)
- Device struct 加 4 欄(agent_id/agent_local_device_id/registered_at/
is_representative)+ deviceColumns 13→17 + scanDevice/SaveTx 讀寫新欄
- 新增 internal/agent package(domain + interface + in-memory + PG repo):
GetOrCreateAgentTx/GetByOwnerTx,advisory lock 序列化同 owner get-or-create
- exchange 重塑:建/復用 agent → representative device(綁 session_tokens、
serial=NULL)→ loop 建 N 顆真 USB device(R1 完整 N 顆非只第一顆)
- List filter is_representative=false + DeviceListItem 回傳 agent_id/registered_at
- 併入 WP-0/0005 follow-up Minor:Mi#2 lost-update 收斂(tx 內查詢+局部更新)
/ Mi#3 過時註解 / Mi#4 空 serial 回 ErrNotFound / Mi#5 serial 白名單
^0x[0-9A-Fa-f]{8}$ + 去重 / S-1 firmware forward-compat / S-2 device Name 衍生
守 ADR-018 A'(一 owner N agents、session_tokens FK 物理不動、不加 owner
unique 為多機器留路)。Reviewer 通過(0C/0M)。5 套件 dbtest 130 全綠
(db 19/device 37/agent 13/api 172/cmd 60)、build/vet/test 綠。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 11:40:17 +08:00 |
|
|
|
8369cab85c
|
feat(db): migration 0005 agents 模型(個人設備管理 A' 走向第二階段地基)
- 新增 agents 表 + devices 加 4 欄(agent_id/agent_local_device_id/
registered_at/is_representative)+ 2 index + 純 SQL data migration
- data migration 採 R-A:agent.id := device.id 決定性推導、冪等、
soft-deleted device 排除(WHERE deleted_at IS NULL)
- 守住 ADR-018 走向 A':不碰 session_tokens FK、不改現有 partial
unique index uq_devices_owner_serial_active、對 0001-0003 零破壞
- migrate_0005_db_test.go 6 個 dbtest case(apply/data migration/
rollback 對稱/re-apply 冪等/session_tokens 零影響/既有 device 讀寫回歸)
- 修 TestMigrate_UpDownUp 編號 gap 假設(0001/2/3/5 無 0004):
assert.Equal(topVer-1) → assert.Less(downVer, topVer)
- .gitignore 加 .logs/ + **/.logs/(本機執行 log per-branch 不進 git)
Reviewer 通過(0 Critical/0 Major/3 Minor/4 Sug)。全 db package 41
dbtest 130 綠、build/vet/test 綠、gitleaks 0。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 04:05:53 +08:00 |
|
|
|
72544d00ba
|
docs(adr): ADR-018 §8 addendum — C5 實作偏差回填(路由段維持 UUID)
- §5.5 加補註指向、原文一字未動(ADR 不可變原則)
- §8.1 記錄 C5 偏差證據鏈(fetchDevice 純 DB / API 無 GetBySerial /
帶 serial deep-link 必 404)與最終實作(路由段 UUID、serial 頁內派生、
入口 disable 全落實);決策本體 FE-A 與 C1–C4/C6–C8 不變
- 裁定來源:wp-c-frontend-serial-routing-review.md(reviewer 獨立驗證)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-11 08:39:32 +08:00 |
|
|
|
12f5bf6c47
|
feat(frontend): WP-C serial 路由落地(ADR-018 FE-A 混合模型)
- DeviceSummary 加 serialNumber(serial_number/serialNumber 雙 key 容錯、
缺欄/空字串 → null)
- 五個 proxy 操作識別值換 serial:connect/disconnect、flash POST+進度 WS
(單一派生點保 room key 一致)、inference WS、camera start/stop+WS、
media 三 upload
- serial 為空全面 disable + 提示(workspace banner/media 占位/flash-dialog/
device-card tooltip/device-detail 兩鈕+序號 InfoRow/選擇頁);i18n 兩語系
- UUID 組維持:fetchDevices/fetchDevice/unpair/詳情頁路由
- C5 偏差(路由段維持 UUID):與 FE-A 定案一致、reviewer 獨立驗證成立
(API 層無 GetBySerial、帶 serial deep-link 必 404);文件回填另派
- evidence:tsc 0 / eslint 0 / next build 15 route / 觸及 11 測試檔 99 passed
- review:通過 0C/0M/3Mi/2Sug(.autoflow/05-implementation/review/
wp-c-frontend-serial-routing-review.md)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-10 09:58:52 +08:00 |
|
|
|
12b1fe3bad
|
fix(local-agent): Rescan 序號身分比對替換 stale session + 鎖外 Disconnect
- serialIdentity() 三態比對(空/假序號 0x00000000 正規化為無身分、對齊
ADR-018 R2):Rescan 對既存合成 ID 比對身分——同一顆保留 live session、
換位/拔插以新 info 替換 session,serial 索引重建即正確
- stale drivers 改釋放鎖後 Disconnect(不再阻塞 GetDevice 路由)+
Disconnect 錯誤改 WARNING log
- 測試 seam:detectFn/newDriverFn 注入 + newSessionDriverLocked 統一建構
(Start 行為零變動);新 4 支行為級測試(拔除位移/換位 rebind/
同序號保 session/無身分回歸)
- evidence:build/vet/test 全綠 + -race ok;review 通過 0C/0M/0Mi/4Sug
(wp0-serial-routing-review.md「Minor #1 修復審查」章節)
- WP-C 阻擋項解除;觀察項:多裝置實測留意 changed identity log
(偵測順序抖動時解法在 detector 端排序)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-10 09:44:47 +08:00 |
|
|
|
b9ee184586
|
feat(device): WP-0 序號串通 + serial 路由(ADR-018 走向 A' 第一階段)
- local-agent/server:detector 保留 kn_number(parseScanDevices 可測化、
合成 ID 語意不動)+ DeviceInfo.SerialNumber + Manager serialToLocalID
反查表 + GetDevice 雙查(sessions 先、serial 後,純加法)
- visiona-agent:pairing exchange 帶本地裝置清單(DeviceLister 失敗不中斷
配對、timeout 2s、omitempty 舊版相容)
- visionA-backend:exchange 收 devices —— R1 取第一顆可用序號、R2 假序號
0x00000000 寫 NULL、R4 同 owner 同序號復用既有 device_id(防 23505)、
pg+mem 兩實作對齊
- 五個 proxy 操作(flash/inference/camera/connect/disconnect)收斂於
GetDevice 單一入口,serial 路由一處涵蓋
- docs:api-spec.md §2 增補 POST /api/pairing/exchange(schema + R1/R2/R4)
- 測試:行為級四環節鏈 + dbtest 130 實跑 6/6 + DBOn 回歸 4/4;
三 module build/vet/test 全綠
- review:通過 0C/0M/6Mi/5Sug(.autoflow/05-implementation/review/
wp0-serial-routing-review.md);Minor #1 Rescan stale session 掛 WP-C 前置
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-10 09:31:51 +08:00 |
|
|
|
3d30fdc580
|
feat(local-agent): TLS skip opt-in for self-signed stage + exchange envelope 對齊
- 新增 VISIONA_INSECURE_SKIP_TLS_VERIFY(DEV/TEST ONLY、須明確 opt-in "true"):
pairing exchange HTTP client、tunnel WSS dialer、設定頁 TestConnection 三路徑
共用 TLSConfigForDial(含 ALPN 釘 http/1.1);NewApp 唯一 env 讀取點注入欄位
- exchangeResponse 對齊雲端 /api/pairing/exchange success envelope
(account/relay_url 選填 fallback 保留、舊頂層格式回歸防護)
- .gitignore:.env.stage* + !.env.stage.example + *.pptx(堵 secrets 誤入)
- start-agent.sh(新增):public 模式 export skip env + 預檢 curl https 帶 -k
- 測試:自簽 TLS server 行為級(預設拒絕驗 x509 / 開啟通過)+ opt-in 規則
11+5 案例 + TestConnection 3 測試;go build/vet/test 3 packages 全綠
- review:2 輪通過(.autoflow/05-implementation/review/tls-skip-uncommitted-batch-review.md)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-10 09:01:10 +08:00 |
|
|
|
276910854e
|
docs(adr): ADR-018 一 agent 多 device 模型 + serial 路由(走向 A')
個人設備管理資料模型決策,使用者裁決 Accepted。
- 走向 A':新增 agents 表 + devices 改實體 USB 掛 agent_id +
session_tokens.device_id 保留物理 schema 改綁代表 device(避開破壞性
FK migration)+ 路由統一用 serial(kn_number) + local agent serial 反查
雙查(純加法、保護剛做完的推論)
- 前端 FE-A 混合:DB 操作用 UUID、路由到 local agent 操作用 serial、
serial 空的 device 路由操作 disable
- 關鍵論證:推論路由不經 session_tokens.device_id(proxy pickActiveSessionToken
按 userID),單一 FK 非硬傷;真缺口是 UUID↔local key 未對齊
- migration 0004(序號、無 DDL 可先交付)+ 0005(agents 模型)
替代方案(mapping 版 A/B、FE-B)與否決理由、R-Route 查證結論已併入 ADR。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 08:39:27 +08:00 |
|
|
|
4f50ad7350
|
feat(ws): flash-progress WS tunnel proxy(後端,載入模型進度回顯)
flash「載入模型到裝置」後端缺口:flash-progress WS 從 501 stub 換真
tunnel proxy,複用 inference WS 的 path-agnostic handler。
- camera.go registerWebSocketRoutes:加 /ws/devices/:id/flash-progress
(複用 newWebSocketProxyHandler、掛 wsAuthGroup same-origin cookie)
- stubs.go:刪 flash-progress 501 stub(成對防 radix panic)+ 更新註解
- api.go:更新 wsAuthGroup 註解(現含 inference + flash-progress 兩條裝置級 WS)
- camera_ws_test.go:+3 test(NoForwarder/TunnelDisconnected/CoexistsWithInference)
- all_endpoints_require_auth_test.go:收窄白名單,flash-progress 從 skipped→
covered 納入「無 cookie 應 401」回歸檢查(守得住證明:移除 auth 即 FAIL)
契約:WS /ws/devices/:id/flash-progress、payload raw {percent,stage,message?,error?}
透明轉發。Reviewer 0C/0M/1Mi 通過(Mi 註解已修)。build/vet/全回歸綠。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 06:30:36 +08:00 |
|
|
|
051994ed54
|
feat(workspace): 圖片/影片/批次推論 tab + 載入模型到裝置 FlashDialog(前端)
推論工作區前端塔3 + flash 前端(i18n/device-detail 交纏、一批 commit)。
塔3(圖片/影片/批次 tab、複用塊1/2 顯示管線 0 重造):
- media.ts 上傳 helper(XHR image/video/batch)+ media-uploader + media-tab
- workspace-client 3 tab 打開 + activeTab 互斥 camera/media WS
- video seek 用後端 durationSeconds 換算(脫離 fps 耦合、Reviewer M-1 修正)
- 補 tab 切換 WS reset 測試(S-1)
- Reviewer 2 輪通過
flash 前端(載入模型到裝置、補「進工作區前置」缺口,照 POC 移植):
- flash-dialog + flash-progress(選 model + 相容性檢查 + WS 進度)
- flash-store(throw-based ApiError)
- use-flash-progress(onOpen 才 POST 防 race + hasStartedRef 防重連重複)
- hardware-compat(targetChip 單值、寧鬆勿嚴、UX 警示非安全閘)
- device-detail-client 掛 FlashDialog(gate 未改、flash 完 fetchDevice 解鎖)
- Reviewer 0C/0M
契約:same-origin cookie、WS/stream/upload URL 相對路徑無 token、i18n 雙語對稱、
設計 token 不裸色值。全前端 tsc/eslint clean、塔3 30 test + flash 45 test 綠。
(全套 11 failed 全屬 conversion-store 既有 flaky、未觸碰)
follow-up:flash WS 逾時提示(Mi)、--success token(design)、conversion-store flaky 釐清。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 06:27:14 +08:00 |
|
|
|
81a32a5bf8
|
feat(stage): nginx 加 /ws/ location(推論 inference WS + 其餘 /ws/* stub)
兩個 server block(公網 stage-9527 + 內網 IP 直連 192.168.0.130)各加
一條 location /ws/ → proxy_pass api-server(:3721)。缺這條時 /ws/* 會落到
catch-all → Next.js → 404,擋住前端推論頁 WS 握手。
- WS upgrade 三要素(http/1.1 + Upgrade + Connection $connection_upgrade)
- 長 timeout 86400s + proxy_buffering off(比照 /tunnel/connect)
- 不影響既有 /tunnel/connect(不同 path 前綴、互不 shadow)
- 兩 block header 對齊(含 X-Forwarded-Host)
驗證:/ws/devices/test-id/inference 不帶 cookie → 401(過 backend auth)
不再 404;/tunnel/connect 回歸 401;preset 下載 200/206;demo 設定保留。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 06:00:59 +08:00 |
|
|
|
0010cc35c3
|
feat(workspace): canvas overlay 疊 bbox + WS 即時推論面板(前端塊2)
推論工作區前端塊2:canvas overlay 疊在 MJPEG <img> 上、接 WS
inference:<deviceId> 推的 raw InferenceResult、即時繪 bbox+label+信心度;
右側 Inference panel 從 Phase 1 佔位換成 WS 即時結果清單 + FPS/延遲。
照 edge-ai-platform POC 移植(唯讀參考)。
- camera-overlay.tsx:normalized bbox→canvas 像素換算、label 框
- inference-panel.tsx:指標 + 清單 + aria-live 無障礙
- inference-store.ts:fps/avgLatency、MAX_RESULTS=100 上限
- use-inference-stream.ts:用既有 useWebSocket(same-origin cookie、無 token URL)
- workspace-client:overlay 塞 CameraFeed slot、訂閱 WS(僅 isRunning && isOnline)
canvas 顏色讀設計 token(--chart-*/--background,getComputedStyle 快取、
主題切換才重讀)、跟隨深淺主題、不裸色值。
Reviewer 2 輪通過(0C/0M)。修正:
- M1 overlay 座標錯位:feedSize 初值 null、未拿真實顯示尺寸不繪(MJPEG 非
4:3 不錯位)
- M2 效能 jank:palette 快取 useState + MutationObserver 主題切換才重讀
(不再每 frame getComputedStyle)
- m2 label fallback 色改實際色(canvas 不認 currentColor)
- m3 isValidBBox guard:壞 payload 略過不 crash
+6 新測試(M1 640×360→height=360、M2 呼叫數不增、m3 NaN/undefined 略過)。
tsc/eslint clean、塊2 範圍 37 test 綠。(全套 11 failed 全屬 conversion-store
既有 time-based flaky、未觸碰)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 05:03:35 +08:00 |
|
|
|
dc6ca211ae
|
feat(ws): tunnel WS forward — 推論結果經 tunnel 推回雲端(後端塊2)
推論工作區後端塊2:實作 WS forward,讓 local agent 的推論結果 WS
(inference:<deviceId>)經 tunnel 推回雲端瀏覽器 canvas overlay。照
edge-ai-platform POC relay/server.go proxyWebSocket 移植(唯讀參考)。
- forwarder.go ForwardWebSocket:OpenStream→寫 upgrade→讀 101→WebSocketConn
(新增 WebSocketConn/wsUpgradeError/AsWSUpgradeError)
- proxy.go newWebSocketProxyHandler:hijack browser→回寫 101→雙向 io.Copy
cross-close(無 goroutine leak)+ copyWebSocketUpgradeHeaders(保留
Sec-WebSocket-*、剝 Authorization/Origin)
- camera.go registerWebSocketRoutes:GET /ws/devices/:id/inference
- api.go wsAuthGroup(/ws + AuthMiddleware):same-origin cookie 認證,
無 token-in-URL(security 定案)
- stubs.go:移除 WS inference 501 stub、更新過時 doc comment(Mi-1/2)
架構差異:POC 單 binary,visionA api-server(auth)+remote-proxy 雙 binary,
remote-proxy raw byte pipe 透明穿過 WS upgrade bytes。local agent 端未動。
認證/授權(security 定案 + S1/S2):same-origin cookie、剝 Auth/Origin、
WS 不套 300s timeout、走 pickActiveSessionToken(帶別人 deviceId 也只打到
自己 tunnel;多租戶嚴格綁定屬 Phase 1 M2 debt)。
Reviewer 0C/1M/3Mi 通過(修後)。Major-1 已修:收窄 all_endpoints_require_auth_test
的 /ws/ 白名單,讓 authed inference WS 納入「無 cookie 應 401」回歸檢查
(附守得住證明:移除 auth 測試即 FAIL)。+forwarder 5 test + camera_ws
端到端四層真連線雙向 pipe。build/vet/全回歸綠。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 04:56:54 +08:00 |
|
|
|
ba8fd454ea
|
feat(workspace): Camera tab 接真 MJPEG stream + start/stop 狀態機(前端塊1)
推論工作區前端塊1:Camera tab 從 placeholder 換成真正接 MJPEG stream
的即時畫面。照 edge-ai-platform POC camera-feed.tsx 移植(唯讀參考、
重寫為 visionA 慣例)。
- camera-feed.tsx:MJPEG <img> + ResizeObserver + overlay slot(塊2 用)+
角落標籤
- camera.ts buildStreamUrl:cache-bust 走 _t= query(encode)、token 絕不
放 URL(same-origin cookie 認證、對齊 use-websocket §10)
- workspace-client Camera tab:接 CameraFeed + 串流狀態機、掉線清串流採
render 期衍生(非 effect setState)
- use-websocket.ts:修 same-origin WS URL bug——getWsBaseUrl 回 "" 時
new WebSocket() 需絕對 URL,補 window.location wss/ws(塊2 WS 用)
- types/camera.ts + types/inference.ts(後者鏡射 local-tool driver.InferenceResult)
- start/stop 契約改打 /api/camera/start|stop
- i18n 9 key 雙語齊全
Reviewer 0C/0M 通過。13 test(camera-feed 8 + camera 5)PASS、tsc/eslint clean。
(全套 11 failed 全在 conversion-store.test.ts 既有 flaky、本次未觸碰、經 grep 證實無關)
follow-up(交塊2/3):掉線→回線 stream 卡舊 URL,建議掉線一併 clearStream。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 04:42:41 +08:00 |
|
|
|
adab000987
|
feat(camera): camera/media 推論改走 tunnel proxy(塊1,取代 501 stub)
推論工作區後端塊1:把 camera/media 9 條路徑從 501 stub 換成走
newProxyHandler 的 tunnel proxy 宣告(比照 devices.go 慣例,新增
camera.go 而非塞 stubs.go)。
- GET /camera/stream → streaming:true(MJPEG multipart 長連線逐 chunk flush)
- 其餘 8 條(camera list/start/stop、media upload×3、batch-images/:index、
seek)→ request-response(upload 大檔的 request body 由 proxy 直接
streaming 送出,streaming flag 只控 response)
- 掛在已套 AuthMiddleware 的 apiGroup 下,沿用剝 Origin 修正
WS inference:<deviceId> 結果推播為塊2(ForwardWebSocket);MJPEG <img>
的 stream 認證分支(R-C4)待 security 定案短期 stream-token 方案,本塊未做。
Reviewer 0C/0M/1Mi/3Sug 通過。+camera_test.go 路由黑箱測試(路徑不再
501、無 session 回 502 TUNNEL_DISCONNECTED),全套 api 回歸綠。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 04:36:06 +08:00 |
|
|
|
c9f7eab682
|
fix(proxy): tunnel 轉發剝除 Origin header 修「開始推論」403
雲端瀏覽器點「開始推論」→ POST /api/devices/{id}/inference/start,
瀏覽器自動帶的 Origin(stage 網域)經 nginx→api-server proxy→tunnel
透傳到 local agent,被 local agent CORSMiddleware(只允許 loopback
Origin)擋回 403。local agent 的 CORS 是為「本機模式 UI 直連」設計,
不該套用在經 tunnel 中繼的 server-to-server 請求。
修法:api-server 的 copyProxyRequestHeaders 剝除 Origin header(比照
既有 Authorization 過濾、EqualFold case-insensitive),local agent 收到
無 Origin → 走 same-origin 放行路徑。
安全邊界:copyProxyRequestHeaders 全 codebase 只用於 tunnel 轉發
(proxy.go newProxyHandler),不影響 api-server 面向瀏覽器的
CORSMiddleware(另一套 gin-contrib/cors)。Reviewer 0C/0M 通過、
Security APPROVE(剝 Origin 不開 CSRF 面,真正閘門在 api-server
AuthMiddleware + SameSite=Lax cookie)。
+2 test(Origin/Authorization 剝除 + case-insensitive)6/6 PASS。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 04:27:49 +08:00 |
|
|
|
c0e2ac0a59
|
fix(devices): 前端讀 tunnel_online 修雲端裝置恆離線
normalizeDevice 原本只讀 remote_status(DB 靜態值、exchange 建 device
寫死 offline 永不更新),沒讀後端即時算出的 tunnel_online → tunnel 已通
但雲端裝置列表恆顯示離線。
改為:tunnel_online === true 時 remoteStatus 覆蓋為 online,否則 fallback
回原本 remote_status 判定,兩者皆缺維持 unknown。嚴格布林比對避免 truthy
誤判。多裝置誤判限制(後端 resolveTunnelStatus 寬鬆比對、正解 R3/Phase 1)
已在 code 註解標明,不在本次前端修法範圍。
新增 4 test(true→online / false→沿用 / 缺欄位→沿用 / 皆缺→unknown),
共 16 test 全綠、tsc/eslint 乾淨。Reviewer 0C/0M。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-04 06:46:44 +08:00 |
|
|
|
dbe5d6a78e
|
docs(architecture): WS tunnel 網路/防火牆需求文件
整理 WS tunnel 要通需要的網路設定(正式環境交接用):
- 核心:邊界反代開 WebSocket upgrade 轉發(或 L4 TCP passthrough),
現在剝 WS upgrade header → tunnel 400
- 配套:idle timeout≥90s、wss TLS、DNS、上線前移除 demo 繞法
- Port 清單:唯一對公網開 :9527,:3800/:3801 絕不對外
- 含 HAProxy/ALB/Cloudflare/F5 等效設定 + 驗證方法 + 回傳碼對照
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-03 03:27:45 +08:00 |
|
|
|
58313169c2
|
feat(stage): nginx 加內網 IP 直連 server block(demo 用)
agent 本機 demo 需直連內網 192.168.0.130:9527 建 tunnel(繞過公網邊界
proxy — 那台剝 WS upgrade header、碰不到)。但直連 IP 時 Host=192.168.0.130
不命中 stage-9527 白名單被 444 擋。
加一個 server_name 192.168.0.130 的 server block(複製 stage-9527 block、
XFP 改 http),8 個 location 全對齊含 /tunnel/connect WS upgrade。現有兩
block(default 白名單 + stage-9527)一字未動。
安全:公網經 proxy 進來 Host 固定 stage-9527、偽造 Host 打不進;IP block
只內網可達;default 444 白名單未削弱。⚠️ demo 專用、正式上線前應移除或
加 allow/deny(註解已標)。
搭配 .env.stage VISIONA_RELAY_PUBLIC_URL 改內網 ws(部署時設、缺一不可)。
Reviewer 通過 0C/0M/0Mi。stage 純 demo 無正式使用者。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-01 20:59:13 +08:00 |
|
|
|
ab50691f8a
|
fix(conversion): 轉檔完成頁「已加入」按鈕不再重複 promote
SuccessView「加到模型庫」按鈕文字依 importedModelId 切換(已加入/加到模型庫),
但 onClick 永遠是 handleImportClick(開 PromoteDialog)→ 已 promote 後再點又
開 dialog 又 promote。
- 按鈕 onClick 改 handleImportButtonClick 分流:importedModelId 有值→
router.push(/models/{id}) 導航(不再 promote);空→開 PromoteDialog(現狀)
- aria-label 同步切換(import.aria.done)對齊實際行為
- 後端 PromoteToModels 本有冪等(同 jobID 不重複建),前端 onClick 為防禦層
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-01 00:30:18 +08:00 |
|
|
|
7d8ad4857e
|
fix(dashboard): 修首頁白屏(React #185 無限 re-render)
connected-devices-list 的 selector 內 .filter() 每次回新陣列,zustand v5
移除淺比較後 Object.is 永 false → 無限 re-render → #185 整頁白屏(配對後
有 device 資料才觸發)。
- selector 改回只取 s.devices(穩定 ref),filter 移到 component body
- activity-timeline 未知 type 加 fallback(?? Circle / muted)防 F7/F8 WS 事件 crash
- 測試 render not.toThrow + act flush 驗不再迴圈
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-01 00:17:38 +08:00 |
|
|
|
6de7c1b4a3
|
feat(devices): 裝置詳細頁加移除(unpair)功能
- device-store 加 unpairDevice action(接 POST /api/devices/:id/unpair)+ unpairingId
- 詳細頁加移除按鈕 + AlertDialog 二次確認(說明解除配對需重新配對、不可逆)
- 成功 toast + 導回 /devices;403/404/unknown 分流 toast、失敗不導頁
- i18n devices.remove.* 雙語
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-01 00:17:37 +08:00 |
|
|
|
0901ffafda
|
feat(models): 模型庫頁按來源分三區(B2)+ 來源標籤 chart token 上色
- /models 改三區渲染:預設模型/我轉檔的/我上傳的(groupModelsBySource)
- 移除 source 篩選器(三區已分來源)、保留 targetChip 篩選跨三區
- 空區顯示標題+空狀態;新 model-section 分區元件
- 來源標籤三色:preset=chart-1/converted=chart-2/uploaded=chart-3
(設計系統 token、badge + 區標題色點呼應、dark mode 自動跟隨)
- i18n models.section.* + .empty.* 雙語
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-01 00:17:19 +08:00 |
|
|
|
838d10b084
|
fix(stage): nginx 加 /preset-models/ 反代到 api-server(B8 stage 修正)
preset .nef 公用下載端點 GET /preset-models/{id}.nef 由 api-server :3721
串流。nginx.stage.conf 原本無此 location → 落 catch-all 反代到 frontend
:3000 → 404。
加 /preset-models/ location(在 /storage 後、catch-all 前),比照 /storage
pattern:proxy_buffering off 串流大檔、X-Forwarded-Proto https、安全 header
re-add、cache public max-age=86400(preset 公用內容固定)。nginx -t 通過。
配套(同一交付):Dockerfile.stage preset COPY(1d4977a)+ .env.stage 設
VISIONA_PRESET_BASE_URL(git-ignored 不進)。stage e2e 驗證:
GET /preset-models/kl520-yolov5-detection.nef → 200 + 7506224 bytes(與原檔一致)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-28 07:52:11 +08:00 |
|
|
|
1d4977a974
|
fix(stage): Dockerfile.stage 補 preset 模型 COPY(B8 stage 修正)
B8 的 preset COPY 原本只加到 dev 的 Dockerfile.api-server,漏了 stage
實際用的 docker/Dockerfile.stage(multi-stage nginx runtime)→ stage image
無 preset 檔、/preset-models/* 下載 404。
runtime stage 補:
- COPY --from=backend-builder /src/assets/preset-models → /app/assets/preset-models
- ENV VISIONA_PRESET_MODELS_DIR=/app/assets/preset-models(baked 資產、隨 image 走)
docker build --check 通過。Reviewer 複審通過。
注意:VISIONA_PRESET_BASE_URL + nginx /preset-models/ location 由 devops 在
stage 部署時補(Reviewer 指出的兩個 DevOps 配套缺口)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-28 07:46:43 +08:00 |
|
|
|
9ab1a11ea4
|
feat(models): 補 7 個預設模型(B8)+ 詳細頁下載按鈕(B5)
# B8 預設模型(簡單版:寫死常數 + 打包 + 不簽 token)
模型庫「預設模型」原本實際是空的(舊 seed 是 demo 假資料、production 不啟用)。
補 7 個公用預設模型(kl520×4 + kl720×3,來源 local-tool models.json):
- metadata 寫死成 Go 常數(presets.go),不進 DB → 公用、不可刪、無 seed 重複
- 7 個 .nef 打包進 image(assets/preset-models/,~61MB;Dockerfile COPY)
- download 走 visionA 自己(簡單版不簽 token,preset 公用本不需授權):
download handler 三分支 preset→visionA URL / converted→FAA / uploaded→501
- 新 GET /preset-models/*filepath 靜態 serve(無 auth、Content-Disposition
attachment 用 mime.FormatMediaType、path-traversal 防禦)
- list/get 含 preset(對所有人可見)
# B5 詳細頁下載按鈕 + preset 可下載
- model-detail-client.tsx 補下載按鈕(行為對齊列表卡片)
- isModelDownloadable 加 preset(source==="preset" → true)
- normalizeModelSummary 對 target_chip toLowerCase(修 preset 大寫 KL520
在小寫晶片篩選下被隱藏的 bug;收斂所有來源大小寫)
# 測試
backend:presets/preset 下載三分支/靜態 serve/path-traversal/條件對稱 全綠
frontend:詳細頁下載鈕 + preset 可下載 + target_chip 大小寫篩選回歸 38 PASS
Reviewer 兩輪通過(Major-1 + Minor-1/2 修畢、複審 0 問題)
# backlog(未做的優化)
preset 下載改 HMAC presigned / preset .nef 改放 FAA 減 image / metadata 後台可管理
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-28 07:38:25 +08:00 |
|
|
|
3e45532f55
|
fix(model-download): 改 redirect/query-string token 下載,根除 CORS preflight 405
模型庫下載原本前端用 fetch + Authorization: Bearer 跨 origin 直連 FAA,
觸發 CORS preflight(OPTIONS);FAA 未設 CORS、OPTIONS 回 405 → 下載失敗。
FAA 設計本就支援「token 放 query string(access_token) + redirect 導航下載」,
故不需 FAA 設 CORS。改採 ADR-017 §11 v1.3 乙案:
- backend: download_url 組成含 ?access_token={url.QueryEscape(token)} 完整 FAA URL;
ModelDownloadResponse.Token 標 deprecated 保留(向下相容);token 不進 log
- frontend: 移除 downloadModelFile/deriveDownloadFilename(fetch+blob),
改 triggerNavDownload 用 <a download href> 導航;ModelDownloadGrant 移除 token 欄
- 整條鏈無 CORS:前端→backend 同 origin、前端→FAA 導航無 preflight
docs: ADR-017 增補 §11(v1.3) + TDD.md §9.5
tests: backend 8 PASS(含 token escape 邊界)、frontend 35 PASS;Reviewer 通過(0C/0M)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-27 06:18:05 +08:00 |
|
|
|
c2f0b1549e
|
feat: OIDC 登入修復(email fallback / prompt=login / logout 連動)+ 真轉檔鏈路 e2e
接 DB 後真人 OIDC 登入暴露 MC OIDC provider 實作不完整,visionA 端逐項繞過,
讓登入/換帳號可用;另補真轉檔服務的整合 e2e。
OIDC 登入修復(MC 端根因另有交接檔,visionA 先繞過):
- email fallback:MC id_token 不發 email claim(ASP.NET Identity 預設 factory 只發
sub/name)→ A7 email 必填擋住登入。callback email 空時用 <sub>@noemail.visiona.local
placeholder,不污染 schema,MC 修好發真 email 後 ON CONFLICT 自動覆寫
- prompt=login:authorize 帶 prompt=login(config VISIONA_OIDC_PROMPT_LOGIN,預設關)
- logout 連動 MC:logout 回 idp_logout(MC Web :7880 /account/logout,GET),前端用
隱藏 iframe 觸發清 MC session(Web/Api 共享 DataProtection)→ 能換帳號。
config VISIONA_OIDC_LOGOUT_URL、向下相容(未設則只清本地)
真轉檔鏈路 e2e(//go:build realconv,按需對 stage 跑、不污染主測試集):
- real_converter_e2e:give 真轉檔服務 contract(init→poll→completed/promote/result)
- real_chain_e2e:真轉檔→PromoteToModels→model 進 PG→冪等 全鏈路(對 stage 跑 PASS)
交接檔(給對應團隊根治):
- mc-email-claim-handoff:MC 加 email claim(自訂 UserClaimsPrincipalFactory)
- converter-promote-oauth-handoff:轉檔服務 OAuth 用 form body 非 Basic Auth
全程 Reviewer 審查 + 對 stage 真環境驗證。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-27 03:18:59 +08:00 |
|
|
|
dc1c0dbee4
|
feat(visionA-frontend): B4 model 詳細頁顯示 metadata(input_shape/classes/framework)
對應 backend B4:model 詳細頁顯示轉檔帶來的 metadata。
- Model 型別加 inputShape/classes/framework(optional);normalize 雙吃 snake/camel
- input_shape 顯示成 1 × 3 × 224 × 224;classes 前 8 個 + N 折疊;framework 直接顯示
- 有值才顯示(沒值不渲染空白);中英 i18n
- tsc / lint / next build 綠
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-21 06:37:36 +08:00 |
|
|
|
cabbdde495
|
feat(visionA-backend): DB 接入後續 — OIDC/pairing FK 收尾 + B4 metadata + nginx healthz + 補測試
DB 接入塊 0-5 上主幹後的收尾工作,讓 DB-on 模式可真人使用 + 補齊功能與測試。
OIDC / pairing FK 修復(接 DB 上線必要):
- 新建 internal/user package(User + Store + InMemory + Postgres);OIDC callback
驗證 id_token 成功後 fail-closed upsert users(sub 直接當 users.id,MC sub 為 UUID)
- pairing exchange 雲端自建 device(不動 local-tool)+ 同 tx 綁 session token;
自建 device 空 serial 寫 NULL(避免撞 partial unique)
- device.SaveTx / session.CreateTx 新增 tx-aware 版本
B4 model metadata:
- 轉檔 result 的 analysis_info(input_shape/classes/framework)串進 model:
converter_client → flow → adapter → model.Model → PG → ModelResponse DTO
- input_shape 優先用陣列、後備四維組 NCHW、缺一不亂組;全 optional 防禦性
- 前端詳細頁顯示(另 repo);轉檔端串接交接檔 b4-converter-handoff.md
nginx healthz(部署層):
- 新增 /healthz/deep 轉發 backend(ping PG+Redis、down 回 503)給 LB
- 修掉 default_server return 444 短路 bug(docker healthcheck 長期 unhealthy 真因)
storage error 統一映射(不洩漏 storage 後端細節)。
測試:補 internal/api(storage/errors handler)、cmd/api-server(seed/adapter)、
internal/db(redis)、relay/session 弱處,含 testcontainers integration。
DB 接入相關 package 真環境覆蓋達 88-94%。全程 Reviewer 審查 + 130 真 PG/Redis dbtest 綠。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-21 06:36:35 +08:00 |
|
|
|
4d0b870480
|
feat(visionA-backend): DB 接入 — 6 store 接 PostgreSQL/Redis 持久化(塊 0-5)
把 visionA-backend 6 個 in-memory store 接到資料庫持久化,範圍=完整
(PG 全接 + session 接 Redis + 交易韌性)。interface / handler 不動,
只加 DB 實作 + 換 wiring,config 未設 DB 時保留 in-memory fallback。
- 塊 0 基礎建設:pgx/v5 連線池 + DatabaseConfig/RedisConfig + golang-migrate
runner(embed)+ cmd/migrate + testcontainers 測試基礎建設
- 塊 1 model → Postgres:array 映射、upsert 保留 CreatedAt、faa_object_key、
三維 filter(owner/chip/source)、soft-delete partial index
- 塊 2 device → Postgres:partial unique(已刪 serial 可重註冊)、雙狀態欄位
- 塊 3 token → Postgres:pairing_tokens + session_tokens 分表、token_hash 當 PK
- 塊 4 userSession → Redis:idle + absolute 雙 TTL 取代 cleanup goroutine
(tunnel session 維持 in-memory,yamux handle 不可序列化)
- 塊 5 交易/韌性:WithTx helper + 刪 device cascade 撤銷 token(同 tx 原子)
+ /healthz ping PG/Redis(fail-fast 503)+ pgx error 統一映射(不洩漏 raw error)
降級策略(fail-fast):PG 掉 → 持久資料 API 回 503;Redis 掉 → session 失敗
不自動 fallback in-memory(避免多機 session 不同步)。
DB:PostgreSQL 14.23(gen_random_uuid 內建、無 citext → email 用 lower() unique
index)。每塊經 Reviewer 審查 + 真 PG/Redis testcontainers 全量 dbtest 綠燈,
in-memory fallback 未受影響。
docs: 同步更新 database.md(schema/config/migration 清單)+ api-spec.md
(409/503 錯誤碼、/healthz 新行為、device unpair cascade)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-20 18:28:04 +08:00 |
|
|
|
22f329cdf3
|
docs(architecture): DB 規劃淡化測試占比強調
測試是工時的自然組成,不再特別強調占比 / 與業界對照:
- 移除前言「測試占比」獨立小節,併入估算假設(中性敘述)
- §5 標題去掉「測試占比分析」,§5.3 精簡為各塊測試 hrs 明細
- 移除「約 45% vs 業界 25-35%」對照句
- 保留實用建議(要砍先砍邊界、別砍 integration 與回歸)
工時數字全未動。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-16 20:20:11 +08:00 |
|
|
|
46958200eb
|
docs(architecture): DB 接入規劃補 MySQL 版本估算 + 文件修訂
- 新增 §3.5 MySQL 版本估算(並列補充、不改 PG 版數字):
MySQL 三範圍 9.2–15.7 / 18.3–31.4 / 24.6–42.5 人天,比 PG 約 +13%(中位)
delta 主來源:model array 欄位→JSON 序列化、driver/migration/testcontainers 換、
UUID + partial index 替代、交易隔離級別差異驗證;Redis delta=0
含「該不該換」中立建議(由維運能力與組織標準主導)
- 移除 130 上其他專案 DB container 的具體名稱(保留「不該共用、用獨立 DB」結論)
- 測試占比敘述改為「接 DB 的客觀測試需要」(integration/邊界/回歸),數字未動
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-16 20:15:01 +08:00 |
|
|
|
d41a57097f
|
docs(architecture): DB 接入工時規劃 — man-hours/man-day 估算文件
visionA backend 從 in-memory 接資料庫的規劃與工時估算(規劃,未實作)。
範圍與工時(三種):
- 最小可行(只 model): 7.8–13.5 人天
- 持久資料(model+device+pairing/token): 16–27.4 人天
- 完整(+ session→Redis + 韌性): 22–37.7 人天
關鍵結論:
- DB 由他人在 stage docker host(192.168.0.130)開好並提供連線,visionA 端不 provision
- ~80% Go 端工作(repository/migration/測試 via testcontainers)拿連線前就能開工,
等 DB 只卡最後 1.5–4 天 stage 收尾
- 測試占比 ~45%(依需求刻意拉高、業界常態 25–35%)
DB 選型: Postgres(model/device/pairing/session_token)+ Redis(userSession;
tunnel session 因 yamux Handle 不可序列化維持 in-memory)。
含 Executive Summary(主管)/ 子任務 man-hours 明細(工程師)/ man-day 表(PM)三層視角。
過程草案保留於 .autoflow/04-architecture/(個人層)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-16 20:01:21 +08:00 |
|
|
|
53e8ab4ae1
|
feat(visionA-frontend): Phase 0.9 模型庫下載 — 前端對接 FAA delegated download
對齊 ADR-017 v1.2 決策 2:model-card 下載按鈕 → 打 visionA endpoint 拿 url+token
→ fetch 跨 origin 直連 FAA + Authorization Bearer → blob 觸發下載(不經 visionA、不經 AWS)。
- src/lib/api/model-download.ts: getModelDownload(打 GET /api/models/:id/download)+
downloadModelFile(fetch + Bearer header + blob,credentials:"omit" 不帶 visionA cookie)+
triggerBlobDownload(延遲 revoke + finally 釋放)+ deriveDownloadFilename + ModelDownloadError
- model-store: downloadingId 互斥(同時只一個下載、finally 必清)+ downloadModel action +
isModelDownloadable(source==converted && status==ready)
- model-card: 下載按鈕(converted+ready 才顯示、上傳類隱藏;preventDefault+stopPropagation
防觸發外層 Link 導航;loading;toast 錯誤)
- i18n zh/en: models.action.download.* / models.download.*(各 error code 對應訊息)
關鍵差異:模型庫下載跨 origin + 需 Bearer header,不能用 <a href> navigation(無法帶 header),
必須 fetch+blob。與既有 conversion download(同 origin navigation)分流。
測試: 43 unit + 互動 test(mock fetch / 按鈕互動 / 顯示條件 / error 分流);tsc/lint/build 全綠。
Reviewer: 0 Critical / 0 Major / 3 Minor / 4 Suggestion,通過(ADR 決策 2 規格 7/7 符合)。
待 stage 實測: FAA 端須設 CORS(允許 visionA origin + preflight Allow-Headers: Authorization),
撞到會落 network_error。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-07 04:50:17 +08:00 |
|
|
|
c63886a194
|
feat(visionA-backend): Phase 0.9 模型庫存取 — FAA delegated download token(B1+B2)
對齊 ADR-017 v1.2:模型庫下載走 visionA 簽 MC delegated token → Client 直連 FAA。
B2 — MC download token client(internal/fileaccess):
- DownloadTokenIssuer: GetServiceToken(打 MC /oauth/token,client_credentials +
scope files:download.delegate,含 token cache)+ IssueDownloadToken(打 MC Issue 簽 fdt_)
- secret / service token / fdt token 三層全程用 hashShort 遮罩不 log
- FileAccessConfig + VISIONA_FILE_ACCESS_* env + main.go wire(Enabled() 才接)
B1 — object_key 斷層:
- model.Model 加 FAAObjectKey(json:"-" 不揭露前端)
- PromoteToModels 寫入(用 promote response TargetObjectKey = models/{userID}/{jobID}.nef)
- 三方對映天然一致(visionA Issue / FAA path / MC validate)
- 第一階段框死只 Source=converted 類 model,上傳類 download 回 501
download endpoint:
- GET /api/models/:id/download(owner-only)→ {download_url, token, expires_at}
- 前端帶 Authorization: Bearer 直連 FAA(不經 visionA、不經 AWS)
- 401/403/404/501/502 分明,502 對外 mask 不洩漏 MC 內部狀態
測試: 13 + 8 unit test(mock MC + fake issuer,httptest 驗真 HTTP);go build/vet/test 全綠。
Reviewer: 0 Critical / 0 Major / 3 Minor / 4 Suggestion,通過。
技術債(正式上線前): 第一階段 PoC 共用 FAA service client,MC 規範禁止 client 混用
usage、secret 不共用,須 MC 配發 visionA 專屬 usage=file_api client。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-07 04:06:09 +08:00 |
|
|
|
88a8ddbd82
|
docs(architecture): ADR-017 模型庫存取架構(FAA delegated token)— v1.2 stage e2e 實證
決策:模型庫下載走 (a) MC delegated download token + Client 直連 FAA(照 pptx)
- visionA 向 MC POST /file-access/download-tokens 簽 fdt_ token
- Client 帶 Authorization: Bearer 直接打 FAA GET /files/{key}(不經 visionA、不經 AWS)
- FAA 用 MemberCenterDelegatedDownloadTokenValidator 打 MC validate
v1.0→v1.1→v1.2 演進(保留歷史):
- v1.0 推 (c) visionA 自簽(誤判 MC endpoint fictional,只看了 master)
- v1.1 改推 (a)(發現 MC develop 已實作 Issue/Validate、FAA validator 配套)
- v1.2 stage 真環境 + 真 secret + 真 user e2e 實測打通(§10 證據):
MC Issue HTTP 200 簽出 fdt token → FAA 僅因假 object_key 回 404 file_not_found(認證鏈全綠)
剩餘 visionA 端 blocking(backend 接手):
- B1 object_key 斷層:model.Model 加 FAAObjectKey,第一階段只支援轉檔→promote 類 model
- B2 加打 MC Issue 簽 fdt 的 client code + .env
技術債(正式上線前處理):短期共用 FAA service client 4242ba63 做 PoC,
正式上線須 MC 配發 visionA 專屬 usage=file_api client(MC 規範禁止 client 混用 usage)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-07 03:42:46 +08:00 |
|
|
|
8c27da7cca
|
test(local-tool): M9-5 — three-platform validation plan + e2e scripts + MJ3 fix
A 階段最後 milestone、出測試計畫 + 自動化腳本 + 三平台人工 checklist、使用者下週手動跑實機驗證。
Testing artifacts (8 檔、2630 行):
- .autoflow/06-testing/m9-5-validation-plan.md: 656 行(4 情境 × 3 平台 × 2 chip = 24 combo)
- 4 e2e specs (vitest + RTL + mock WS / mock fetch):
- firmware-upgrade-happy-path.spec.ts (357 / 4 cases)
- firmware-upgrade-error-recovery.spec.ts (356 / 4 cases + 8 reason it.each)
- firmware-r-fw-11-modal-not-closable.spec.ts (303 / 6 cases)
- wails-onbeforeclose-firmware-active.spec.ts (217 / 9 cases、含 5 todo 占位 M9-12)
- 3 manual checklists: macOS 264 / Windows 234 / Linux 243 行
設計取捨:
- 不引入 Playwright/Cypress (visionA-local frontend 沒裝、屬 architect 決策)、走 vitest + mock
- E2E 腳本放 06-testing/scripts/ 作 spec doc + 可選實作參考
- 實機驗證走人工 checklist (dongle 插拔 / kill process / SIGTERM 等需要實體互動)
MJ3 修復 (M9-4 reviewer round 1 留的 follow-up):
- server/internal/api/ws/firmware_ws_test.go: +16/-8
- "type": "firmware:progress" → "firmware_progress" (對齊 firmwareProgressMessage.Type)
- "phase" → "stage" (對齊 TDD §4.2 + FirmwareProgress.Stage)
- 不動 production code、只 test schema 對齊
執行建議 (給你下週):
- Day 1 P0: macOS+Win+Linux × KL520+KL720 happy path (~3h)
- Day 2 P1: R-FW-11 + disconnect_during_op + upgrade_mid_failed + 失敗注入 (4h)
- Day 3 P2: SIGTERM 延遲關閉 + Wails OnBeforeClose force-quit modal (2-3h)
測試:
- go test ./... -race 全綠 (server / wails / frontend 60 tests)
- MJ3 修復不破壞既有測試
A 階段開發 6/7 完成 (M9 文件 + M9-1 ~ M9-4.5)、剩 M9-5 實機驗證 (你下週跑)、跑完依結果決定 A 階段交付或派 sub-agent 修。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-25 15:34:17 +08:00 |
|
|
|
ff9bbc81ed
|
feat(local-tool): M9-4.5 — server SIGTERM + Wails OnBeforeClose firmware-aware shutdown
A 階段尾端 milestone、雙層防護避免使用者在 firmware 升級進行中關閉 app 造成 dongle brick。
Server 端 (3 改):
- main.go: SIGTERM/SIGINT goroutine 加 firmware-aware preamble
- server/internal/firmware/shutdown.go: 新 211 行(AwaitActiveTasksOrTimeout + 3 interfaces + shutdownBroadcastTask minimal struct + toBroadcastTasks helper)
- server/internal/firmware/shutdown_test.go: 新 384 行、8 tests
Wails 端 (3 新 + 2 改):
- visiona-local/main.go: OnBeforeClose 從 inline → app.OnBeforeClose
- visiona-local/app.go: App struct 加 firmwareCloseGuard
- visiona-local/firmware_close_guard.go: 新 244 行(CloseGuard + OnBeforeClose + ConfirmForceClose)
- visiona-local/firmware_close_guard_test.go: 新 280 行、8 tests
- visiona-local/query_firmware_active_tasks.go: 新 111 行(HTTP helper、fail-open、1s timeout)
- visiona-local/query_firmware_active_tasks_test.go: 新 250 行、7 tests
行為:
- Server SIGTERM 有 active task → broadcast `server:shutdown-pending` to "system" room → RequestShutdown + WaitForActiveTasks(220s) → 走原本 shutdownFn
- Wails OnBeforeClose 有 active task → emit Wails event `app:firmware-in-progress` + return true 擋住關閉
- ConfirmForceClose binding 給 frontend 第二層 FORCE 確認用、走 graceful 7+1s shutdown(不是 SIGKILL bypass、雙層防護)
Reviewer 兩輪審查:
- Round 1: 0 Critical / 1 Major / 3 Minor / 4 Suggestion
- 第 2 輪修法(3 sub-agent 平行):
- Architect: TDD §8.6 改 event 名 `firmware:shutdown-rejected` → `server:shutdown-pending`、標題「拒絕」→「延遲」、補 payload schema 註明 tasks 不含 startTs
- Design: control-panel.md §6a 改「SIGKILL bypass」→「graceful 7+1s 雙層防護」、補「為何不採 SIGKILL」5 點設計理由、§6a.11 IPC 規格對齊
- Backend: MaxShutdownWait 180s → 220s(KL720 200s upgrade + 20s buffer)+ broadcast 過濾 startTs(shutdownBroadcastTask minimal struct + toBroadcastTasks helper)
測試:
- server: go test ./... -race 全綠(firmware 2.7s + api/ws/handlers)
- wails: go test ./... -race 全綠(visiona-local 11.2s、21 tests)
- 合計新增 23 unit tests race-clean、0 regression
下一步: M9-5 三平台實機驗證 + 順手修 MJ3(backend smoke test schema phase→stage / firmware:progress→firmware_progress)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-25 15:07:29 +08:00 |
|
|
|
06ff2fe987
|
feat(local-tool): M9-4 — Frontend FW badge + 升級 modal + WS hot-fix
A 階段第四個 milestone、完整 Frontend FW UI(badge / modal / 8 種 reason 復原)+ backend WS hot-fix(補對稱於 flash 的 firmware WS endpoint)。
Frontend(13 修改 / 7 新檔):
- 新 firmware/ component group (badge / upgrade-button / upgrade-dialog 4-phase / progress-view / error-view 8-reason / index)
- Zustand store (firmware-store.ts) + WS hook (use-firmware-progress.ts) 對齊既有 useFlashProgress pattern
- DeviceCard 整合 FirmwareBadge + FirmwareUpgradeButton
- i18n: settings.firmware.* namespace (對齊 Design Spec §9 SoT) + devices.card.fwBadge.* (zh-TW + en, 57 leaf keys × 2 lang = 114 strings)
- toast.ts ToastOptions interface (duration param)
- types/device.ts: FW 衍生欄位 + FirmwareStage/Reason/ProgressEvent/ActiveTask types
Backend WS hot-fix (3 檔):
- ws/firmware_ws.go (50 行、純對稱 flash_ws.go)
- ws/firmware_ws_test.go (165 行、2 smoke tests: broadcast + room isolation)
- router.go: GET /ws/devices/:id/firmware-progress
關鍵設計:
- R-FW-11 緩解: upgrading phase modal 不可關 (onInteractOutside/onEscapeKeyDown preventDefault + 隱藏 X)
- 多裝置隔離 defense in depth: store handleEvent activeDeviceId mismatch 直接 return
- 8 種 reason → 4 種 UX (recoverable/destructive/brick 警告/contactSupport)
- ContactSupport mailto handler (RFC 6068 + encodeURIComponent)
Reviewer 兩輪審查:
- Round 1: 0 Critical / 3 Major / 8 Minor / 5 Suggestion
- Round 2: 0 Critical / 0 Major / 0 Minor / 2 Suggestion(接受方案 A、不需 frontend 第 3 輪)
- MJ1 i18n namespace 採方案 A (settings.firmware.*)、Design SoT 優先、Reviewer 同意
測試:
- pnpm test --run: 60 tests pass (32 firmware: 22 store + 10 badge + 新 9 error-view + 19 既有)
- npx tsc --noEmit: 0 error
- pnpm build: production build 成功
- go test ./internal/api/ws/... -race: 1.964s 全綠
- pnpm lint firmware/: 0 hit (17 既有 lint 問題不屬 M9-4、follow-up)
未做(範圍外):
- Settings 韌體面板 (M9-12 B 階段)
- 手動降版 UI (M9-12)
- 版本切換 dropdown (B 階段)
- Wails 控制台 force-quit modal (M9-4.5)
A 階段 MVP 後端 + 前端開發全部完成、剩 M9-4.5 (SIGTERM + Wails OnBeforeClose) + M9-5 (三平台實機驗證)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-25 12:57:21 +08:00 |
|
|
|
5e281ed449
|
feat(local-tool): M9-3 — firmware API handlers + WebSocket progress room
A 階段第三個 milestone、暴露 firmware service 給 Frontend / Wails control panel。
New / modified:
- server/internal/api/handlers/firmware_handler.go: 新檔 465 行(upgrade + active-tasks endpoint + WS broadcast goroutine)
- server/internal/api/handlers/firmware_handler_test.go: 新檔 938 行、26+ subtests
- server/internal/api/handlers/device_handler.go: +47 行(3 個 firmware 衍生欄位)
- server/internal/api/router.go: +23 行
- server/main.go: +10 行(wire firmware service + handler)
4 endpoints 全到位(對齊 TDD §3.1):
- GET /api/devices: 加 firmwareIsLegacy / firmwareCanUpgrade / bundledFirmwareVersion(firmwareVersion 沿用既有 DeviceInfo 鍵)
- POST /api/devices/scan: 同步走 enrichDevices
- POST /api/devices/:id/firmware/upgrade: 202 + {taskId}
- GET /api/firmware/active-tasks: HasActiveTask + GetActiveTaskInfo
- WebSocket room firmware:<deviceID> broadcast 對齊 §4.2
關鍵設計:
- 3 層 interface(firmwareBroadcaster / firmwareService / deviceLookupSource)+ DeviceManagerAdapter 解 import cycle
- bundledVersion cache(只 cache success、避免 thundering herd / poison)
- isLegacyFirmware 對齊 bridge.py 規則(legacy_exact set + KDP1.x prefix + KDP2-9 forward-compat)+ parity 真值表測試
- 5 個錯誤碼齊全(DEVICE_NOT_FOUND / FW_UNSUPPORTED_CHIP / FW_DEVICE_BUSY / FW_UPGRADE_FAILED / FW_UPGRADE_BRICK_RISK)
Reviewer 兩輪審查:
- Round 1: 0 Critical / 1 Major / 3 Minor / 5 Suggestion
- Round 2: 0 Critical / 0 Major / 0 Minor / 3 極小 Suggestion(全部 backend 不需處理、純評估)
- Major 1(JSON 雙鍵衝突 firmwareVer vs firmwareVersion)方案 A 完全到位、3 個 test 鎖定 regression
TDD 同步:firmware-management.md §3.1 line 131 firmwareVer → firmwareVersion 對齊實作。
測試:go test ./... -race -count=1 全綠(handlers 2.489s / api 3.522s / ws 4.623s / device 1.931s / firmware 2.695s / driver/kneron 5.583s / model 5.022s)
SIGTERM main.go 整合留 M9-4.5(與 Wails OnBeforeClose 一起做)。
下一步:M9-4 Frontend Devices 頁 FW badge + 升級 modal + i18n(1.5 人天)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-25 12:05:42 +08:00 |
|
|
|
c03eb6fd0e
|
feat(local-tool): M9-2 — Go driver UpgradeFirmware + firmware service module
A 階段第二個 milestone、銜接 M9-1 bridge.py、暴露 service layer 給 M9-3 API/WebSocket。
New module `server/internal/firmware/`:
- types.go: 123 行(FirmwareVersion / FirmwareProgress / ActiveTaskInfo / UpgradeDriver interface / 8 reason const)
- progress.go: 147 行(仿 flash pattern 的 Tracker、Task.cancel 預留 SIGTERM force-cancel godoc)
- service.go: 373 行(核心 service:UpgradeFirmware / HasActiveTask / GetActiveTaskInfo / RequestShutdown / WaitForActiveTasks / ListBundledVersions / GetCurrentVersion)
- service_test.go: 676 行、13 個 test 含 MultiDeviceParallel
Driver layer:
- kl720_driver.go: 697 → 1054 行(+357、新 UpgradeFirmware method + tryRouteFirmwareEvent + sendCommandForUpgrade snapshot pattern)
- kl720_driver_test.go: 360 行、11 個 test(含 InfoNotBlockedDuringUpgrade / CtxCancelReleasesBridge / StderrEventAfterCtxCancel 100 round stress)
關鍵設計:
- flash 與 firmware 模組分離(不 import flash)
- UpgradeDriver interface 隔離 driver 細節、DeviceLookup interface 隔離 device manager
- 中介 channel pattern(service ↔ driver)方便 service 補欄位(DeviceID / Direction / BeforeVersion)
- timeout 雙保險:chip timeout + 30s margin
- 8 reason enum 對齊 bridge.py、stage 採 Design 命名
Concurrency race 修復(M9-2 Reviewer round 1 → round 2):
- Major 1(mutex deadlock):新 fwUpgradeMu 獨立鎖 + sendCommandForUpgrade snapshot stdin/stdout pattern、避開 d.mu field-level race + 升級期間 Info/Disconnect 不被卡 + timeout 路徑無死鎖
- Major 2(close-channel race):tryRouteFirmwareEvent 持 fwMu 整段、配合 defer setFirmwareProgressCh(nil) 提供 happen-before、絕無 send on closed channel panic
Reviewer 兩輪審查:
- Round 1: 0 Critical / 2 Major / 5 Minor / 5 Suggestion
- Round 2: 0 Critical / 0 Major / 2 Minor / 2 Suggestion(11/12 issue 修到位、Suggestion 4 留 follow-up)
M9-1 follow-up 順手清:
- m5(test 死碼 _firmware_upgrade_start_ts 殘留兩行)已清
- s5(test 註解 idempotent shape 說明)已加
測試:
- go test ./... -race -count=1: 全綠(28s、無 regression)
- Python: 36 tests + 22 subtests 全綠(0.31s)
- go vet / build: 0 output
下一步:M9-3 API handler + WebSocket progress(CI 建議 `go test -race -count=3` 提升 race 偵測強度)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-25 11:27:36 +08:00 |
|
|
|
d7b5a2398a
|
feat(local-tool): M9-1 — bridge.py firmware_upgrade handler(KL520+KL720 KDP1→KDP2)
A 階段第一個 milestone、純 bridge.py 層 + ctypes 直接呼叫 KneronPLUS C symbol。
Source:
- server/scripts/kneron_bridge.py: 1207 → 2058 行(+851)
- server/scripts/test_kneron_bridge_firmware.py: 新檔 840 行、36 unit tests 全綠 0.076s
Firmware bundled:
- server/scripts/firmware/KL520/fw_loader.bin(90112 bytes、MD5 aef7cca17bc023abbd6152c46c18e774、與 warrenchen 一致)
- server/scripts/firmware/{KL520,KL720}/VERSION(v2.2.0)
實作對齊 TDD §6.1 規格(98% 對齊度):
- handler input/output schema 100%
- stage enum: preparing/loading/flashing/verifying/done/error(採 Design 命名)
- reason enum 7/8(disconnect_during_op 留 M9-5 實機測試)
- ctypes binding 1:1 對齊 warrenchen legacy_plus121_runner.py
- 4 個情境 stage 序列驗證通過(KL520 KDP1+loader / KL520 KDP1 缺 loader / KL720 legacy / 已 KDP2)
- timeout 60s/200s、USB stable 5-8s wait、SIGTERM 拒絕邏輯
- progress event schema 完整(percent/stage/message/elapsed_ms/eta_ms/extra)
Reviewer 兩輪審查:
- 第 1 輪:0 Critical / 3 Major / 4 Minor / 4 Suggestion
- 第 2 輪:通過 with 1 Minor + 1 Suggestion(m5 test 死碼 / s5 test 註解、留 M9-2 順手清)
- M3 firmware 字串覆蓋從 substring → 顯式 enumeration + KDP3+ forward-compat(防未來 brick 風險)
- M2 控制流重構(needs_loader/should_run_loader_stage/loader_required_but_missing 三個顯式 bool)
- m3 single-owner disconnect 原則完整落地
既有 6 個 handler(scan/connect/disconnect/reset/load_model/inference)零改動、無 spillover risk。
下一步:M9-2 Go driver UpgradeFirmware + firmware/service.go
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-25 08:10:46 +08:00 |
|
|
|
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 |
|
|
|
2d629f3ba2
|
fix(visionA-backend): DownloadStream 移除 dead ensurePromoted call (Bug #11)
ADR-016 v0.6 後 visionA download 直接從 converter MinIO 拿 NEF、不需先 promote 推上 FAA;
原 Phase 0.8 / v0.4-v0.5 設計的 ensurePromoted 是 dead call。
stage e2e 證實:
- visionA DownloadStream → f.ensurePromoted → converter.Promote
- converter Promote → faa.putFile (OAuthClientError 401 — converter↔FAA OAuth 鏈獨立 bug)
- converter Promote 回 500、visionA DownloadStream 因 ensurePromoted 失敗回 502
- user 看到下載失敗
修法:flow.go DownloadStream 移除 ensurePromoted call、直接 GetResult(converter MinIO
已在 worker `_upload_output` 寫進 NEF、不需 promote 把 NEF 推 FAA)。
PromoteToModels(line 531)流程仍會呼叫 Promote、這是「加到模型庫」的合理步驟、不動。
驗證:
- 17 packages race -count=3 全綠
- 新 test TestDownloadStream_DoesNotCallPromote 取代舊 TestDownloadStream_PromoteError_Propagation
(故意把 promoteFunc 設成 t.Fatalf、確保 download path 完全不打 promote)
關聯 follow-up(未在本 commit 修):
- converter↔FAA OAuth 401 仍是 promote-to-models 流程的 bug、需 converter / MC scope debug
- 但 download 解耦後 user 至少能下載
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-18 16:33:23 +08:00 |
|
|
|
78c1343e9a
|
fix(visionA-frontend): conversion init multipart 對齊 converter 規格 — ref_images + model_id
Phase 0.8b stage e2e 連環 2 個跨層 schema mismatch(5/2 寫 frontend 時對 converter spec 沒驗、5/16 stage e2e 第一次跑通才暴露)。
Bug #5 ref_images field name PHP-style 不對齊
- 現象:converter multer LIMIT_UNEXPECTED_FILE、visionA 透傳 → 400 validation_failed
- 原因:frontend `form.append('ref_images[]', img)`、converter `uploader.fields([{name:'ref_images',...}])`(Express/multer 標準、無 [])
- 修法:frontend `form.append('ref_images', img)`(FormData 多筆同 key、multer 自動收成陣列)
Bug #6 model_id 用 taskName 當值、converter 要 integer
- 現象(修完 Bug #5 後暴露):converter validator → 400 validation_error 「model_id 必須為非負整數」
- 原因:frontend `args.taskName ?? args.file.name` → 字串 "input" / "yolov5s_test";
converter validator (`createJob.js:153-164`) 規定 integer 1-65535(KTC tool 內部 model 編號)
- 修法:新增 `generateConverterModelId()` helper(Math.random 1-65535)、每次 init 自動生成;
taskName 留給 visionA UX(promote 後 model store 的 name),與 converter model_id 解耦
驗證:
- pnpm test src/lib/api/conversion.test 22/22 pass
- pnpm build 通過
- stage redeploy 兩次(commit ce6a657 後跑出 Bug #5、fix Bug #5 deploy 後跑出 Bug #6、fix Bug #6 已 deploy)
剩餘 e2e 待 user browser 真實上傳 ONNX file 驗證。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-18 11:21:23 +08:00 |
|
|
|
9ebf46112b
|
feat(visionA-frontend): AuthGuard — 未登入自動導 /login + 記住原本 path
需求:
使用者未登入訪 protected route(如 /conversion、/devices)→ 卡在空 UI、無 redirect。
使用者反映「希望未登入直接跳登入頁、登入完跳回原本要去的頁」。
實作:
- 新增 src/components/auth-guard.tsx:
* public routes allowlist:/login, /register
* protected route + user=null + hydrate 完成 → router.replace('/login?next=<path>')
* hydrate 進行中 → render null(避免閃過 protected UI)
* buildNextParam helper:排除 public routes 和純 / 路徑
- 改 src/app/layout.tsx:
* 用 <Suspense fallback={null}> 包 AuthGuard(next/navigation useSearchParams 規範)
* AuthGuard 包 AppShell
- 改 src/app/login/page.tsx:
* buildLoginUrl 接受 returnTo param、組進 backend `/api/auth/login?return_to=<path>`
* sanitizeNext helper:對齊 backend oidc_auth.go:382 sanitizeReturnTo("/" 開頭、無 "//"、無 "://")
* 已登入 redirect:從 query ?next= 跳該 path(不再固定 /)
- 改 src/app/login/login.test.tsx:
* mock 補 useSearchParams(next/navigation mock 既有只 mock useRouter)
- backend oidc_auth.go:88 sanitizeReturnTo 已支援 return_to query param、無需改 backend code
驗證:
- tsc --noEmit 0 errors
- pnpm lint 0 errors
- pnpm build 通過(13 pages prerendered)
- login.test.tsx 12/12 pass
- stage deploy verify:SSR HTML 含 AuthGuard component bundle
不在本 commit 範圍:
- .env.stage CORS fix(VISIONA_CORS_ALLOWED_ORIGINS、stage host .env 直接改、不進 git)
- 5/16 deploy script typo fix(已在 commit fad17dd)
- converter multipart field mismatch(converter scheduler 跨 repo 處理)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-05-18 10:38:16 +08:00 |
|