fix(t213): 診斷檔遮蔽引擎錯誤訊息裡的本機絕對路徑

engine.detail/engine.last_error 在同步引擎沒在跑時,會把 collector 開機
橫幅的完整原文帶出來(direct.go RunDirect 的「監看 %s → %s」那行),其中
含監看資料夾的絕對路徑(如 /Users/xxx/Desktop/...)。這是上一輪(第二/
三輪)已發現但標記「附帶發現、未修」的洞——首頁在同一狀態下本來就會顯示
同一句話(不動,留給另案判斷),但診斷檔現在會被匯出成檔案交給外部人看,
風險層級跟留在托盤畫面上不同,這輪把它遮掉。

新增 redactLocalPaths(),只在 buildDiagnosticsPayload() 組裝 engine 欄位時
套用:把訊息裡看起來像本機絕對路徑的片段換成「…/<路徑最後一截>」,
http(s) URL 先放行原文(避免 URL 裡的 / 被誤判成本機路徑)。只動
diagnostics_export.go 這一端,不改 describeStatus()/collectorFailure()/
direct.go 的訊息本身——那些同時是首頁托盤畫面在用的同一組憑據。

擴充 diagnostics_engine_e2e_test.go(真執行檔+真 supervisor 子行程,零
網路零帳號):監看資料夾改用真實絕對路徑,證明子行程停擺後整份匯出 JSON
找不到該路徑,同時 last_error/detail 仍讀得出「引擎沒在跑、以及為什麼」
(含監看資料夾的名字)。另加 8 個 redactLocalPaths 純函式單測。

go build/go vet/go test ./...(collector+arcrun-app+supervisor 三個
package)全綠;diagnostics_stage_manual_test.go 維持預設 SKIP,本輪未碰
網路/stage。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 16:36:20 +08:00
parent 4cf09bf8ca
commit 0c6fb43117
3 changed files with 286 additions and 5 deletions
+39 -1
View File
@@ -18,8 +18,10 @@ package main
// 不需要任何雲端憑證,任何機器上都能跑、都穩定。
import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
@@ -42,12 +44,23 @@ func TestDiagnosticsEngine_ReflectsRealSupervisorLifecycle(t *testing.T) {
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
// t213 phase 4:監看資料夾要是一個**真的絕對路徑**,才能重現「collector 開機橫幅把
// 監看路徑印進 stderr → supervisor 記成 LastError → 診斷檔把它原樣帶出去」這個洞
// (見 direct.go RunDirect 的「監看 %s → %s」那行;leo 08-08 附帶發現、本輪要修的問題)。
watchDir := filepath.Join(home, "知識庫")
if err := os.MkdirAll(watchDir, 0o755); err != nil {
t.Fatalf("建監看資料夾失敗:%v", err)
}
// 需要至少一個帳號:LoadDirectConfig 會拒絕零帳號的設定(「缺必填欄位:accounts」,
// 連迴圈都進不去),但這個帳號**不必連得上**——cypher_url 刻意指向一個解析不出來的
// 網址,驗證的是「心跳(LastSync)跟著子行程死活走」這條機制,不是雲端連線本身。
cfg := &directConfig{
Manifest: filepath.Join(home, ".arcrun-rag", "manifest.json"),
Accounts: []accountCfg{{CypherURL: "https://example.invalid.test", Namespace: "test", APIKey: "test"}},
Accounts: []accountCfg{{
CypherURL: "https://example.invalid.test", Namespace: "test", APIKey: "test",
WatchFolders: []string{watchDir},
}},
}
if err := saveCfg(cfg); err != nil {
t.Fatalf("存設定失敗:%v", err)
@@ -110,4 +123,29 @@ func TestDiagnosticsEngine_ReflectsRealSupervisorLifecycle(t *testing.T) {
t.Fatal("情境二:alive=false 時 headline 不該是空的——這是給人看的『還在不在跑』答案")
}
t.Logf("✅ Q2 證據鏈完整:pending 不變的情況下,alivelast_syncseconds_since_last_sync 三者都反映了子行程真的停了")
// ── t213 phase 4leo 08-08 附帶發現、本輪要修)──
// alive=false 這個狀態下,engine.detailengine.last_error 過去會帶出 collector 開機
// 橫幅的完整原文,其中含監看資料夾的絕對路徑(見 direct.go RunDirect 的
// 「監看 %s → %s」那行)。這裡的 watchDir 就是刻意設成真實絕對路徑,用來重現這個洞:
// 驗證整份匯出 JSON 裡找不到它,同時確認「引擎沒在跑、以及為什麼」仍然讀得懂
// (不能把資訊遮成啞巴——資料夾最後一截「知識庫」要還在)。
if strings.Contains(string(stoppedJSON), watchDir) {
t.Fatalf("情境二:整份診斷檔仍然帶出監看資料夾的絕對路徑 %q:%s", watchDir, stoppedJSON)
}
if strings.Contains(string(stoppedJSON), home) {
t.Fatalf("情境二:整份診斷檔仍然帶出本機家目錄路徑 %q:%s", home, stoppedJSON)
}
if containsAbsPath(string(stoppedJSON)) {
t.Fatalf("情境二:整份診斷檔(掃全文,不只 engine 兩個欄位)仍有本機絕對路徑殘留:%s", stoppedJSON)
}
reason := stoppedOut.Local.Engine.LastError + stoppedOut.Local.Engine.Detail
if reason == "" {
t.Fatal("情境二:alive=false 時 last_errordetail 不該兩個都是空的——『為什麼沒在跑』答不出來了")
}
if !strings.Contains(reason, "知識庫") {
t.Fatalf("情境二:遮蔽把資訊遮成啞巴了——連監看資料夾叫什麼名字都看不出來:last_error=%q detail=%q",
stoppedOut.Local.Engine.LastError, stoppedOut.Local.Engine.Detail)
}
t.Logf("✅ t213 phase 4:路徑遮掉了,但『引擎沒在跑、以及為什麼(監看的是「知識庫」資料夾)』還讀得懂")
}