From e12f44e84d21adf9f4822068add7e9990f8045f9 Mon Sep 17 00:00:00 2001 From: richblack Date: Sat, 8 Aug 2026 11:44:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(daemon):=20=E5=A4=B1=E6=95=97=E7=9C=9F?= =?UTF-8?q?=E5=9B=A0=E6=AF=8F=E8=BC=AA=E8=A2=AB=E8=87=AA=E5=B7=B1=E6=8A=B9?= =?UTF-8?q?=E6=8E=89=E2=80=94=E2=80=94=E5=B1=95=E9=96=8B=E5=8F=AA=E5=89=A9?= =?UTF-8?q?=E3=80=8C=E7=95=B6=E6=99=82=E6=B2=92=E6=9C=89=E8=A8=98=E4=B8=8B?= =?UTF-8?q?=E5=8E=9F=E5=9B=A0=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Evan 封測(08-08):首頁「有 20 份沒有送進知識庫」,逐份展開看到的都是 「已經自動試過 N 次都沒成功……這個檔是在舊版失敗的,當時沒有記下原因」。 原因當時真的記下來了(MarkFailed 有寫 LastError),是 scan.go 每輪重建 ManifestEntry 時漏 carry 這一欄,5 秒就把真因抹掉一次 ⇒ 下一輪組不出 「|原因:」⇒ 畫面退回 humanizeFailure 的最後退路。 這是 t195「加了欄位卻寫進去就不見」的第二次實例:LastError 正是同一批 新增的欄位,卻漏在 carry 名單裡(wiki mistakes.md:92 已立過警告)。 對照實驗(非空靶):拿掉這一行 → 兩支測試都紅(真因為空字串)。 Co-Authored-By: Claude Opus 5 --- scan.go | 6 +++ scan_carry_lasterror_test.go | 75 ++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 scan_carry_lasterror_test.go diff --git a/scan.go b/scan.go index 65904c9..0cec4ae 100644 --- a/scan.go +++ b/scan.go @@ -473,6 +473,12 @@ func Scan(root string, m *Manifest, opts ScanOptions) (*TriggerPayload, error) { ne.FailCount = carry.FailCount ne.LastFailAt = carry.LastFailAt ne.NextRetry = carry.NextRetry + // 🔴 2026-08-08(Evan 封測回報「20 份沒送進知識庫」但展開看不到真因): + // LastError 是 t195 同一批加的欄位,卻**漏在這個 carry 裡** ⇒ + // 每輪掃描(預設 5 秒)就把失敗真因抹掉一次,下一輪退避訊息組不出「|原因:」 + // ⇒ 畫面只剩 humanizeFailure 的最後退路「當時沒有記下原因」。 + // 這正是上面那句警告的第二次實例——真因是我們自己刪掉的,不是沒記。 + ne.LastError = carry.LastError } newEntries[p] = ne } diff --git a/scan_carry_lasterror_test.go b/scan_carry_lasterror_test.go new file mode 100644 index 0000000..0985962 --- /dev/null +++ b/scan_carry_lasterror_test.go @@ -0,0 +1,75 @@ +// scan_carry_lasterror_test.go — 2026-08-08,Evan 封測回報的第二層病。 +// +// 症狀(用戶端):首頁「有 20 份沒有送進知識庫」,展開每一份看到的都是 +// 「已經自動試過 N 次都沒成功⋯⋯這個檔是在舊版失敗的,當時沒有記下原因」。 +// 但原因**當時真的記下來了**(MarkFailed 有寫 LastError),是 Scan() 每輪重建 +// ManifestEntry 時沒 carry,5 秒就被自己抹掉一次。 +// +// 這是 t195「加了欄位卻寫進去就不見」的**第二次實例**(wiki mistakes.md 已立警告, +// 而 LastError 正是同一批新增的欄位卻漏在 carry 名單裡)。 +// 這支測試守的是那條規則本身:跨輪欄位必須活過 Scan()。 +package collector + +import ( + "os" + "path/filepath" + "testing" +) + +// 失敗真因必須活過下一輪掃描——否則畫面只能說「當時沒有記下原因」。 +func TestScan_CarriesLastErrorAcrossRounds(t *testing.T) { + root := t.TempDir() + writeFile(t, root, "a.md", "內容", baseTime) + + m := newTestManifest() + mustScan(t, root, m) // 第 1 輪:認識這個檔 + + const reason = "今天的免費 AI 額度用完了(4006 neurons)" + m.MarkFailed("a.md", 1000, reason) + + // 檔案內容沒變,只是時間到了下一輪掃描。 + mustScan(t, root, m) + + e := m.Entries["a.md"] + if e == nil { + t.Fatal("掃描後 entry 不該消失") + } + if e.LastError != reason { + t.Fatalf("失敗真因被下一輪掃描抹掉了:want %q, got %q\n"+ + "(這會讓畫面退回「當時沒有記下原因」——Evan 2026-08-08 實撞)", reason, e.LastError) + } + // 同批的其他跨輪欄位一併守住,避免下次又漏一個。 + if e.FailCount != 1 || e.NextRetry == 0 { + t.Fatalf("退避狀態也該活過掃描:FailCount=%d NextRetry=%d", e.FailCount, e.NextRetry) + } +} + +// 改名後真因同樣要跟著搬(renamed 走的是另一條 carry 來源)。 +func TestScan_CarriesLastErrorAcrossRename(t *testing.T) { + root := t.TempDir() + writeFile(t, root, "old.md", "同一份內容", baseTime) + + m := newTestManifest() + mustScan(t, root, m) + + const reason = "這份 PDF 看起來是掃描的圖片,沒有文字可以讀" + m.MarkFailed("old.md", 1000, reason) + + // 內容一字不動,只換檔名 ⇒ Scan 會判為 renamed。 + if err := renameForTest(root, "old.md", "new.md"); err != nil { + t.Fatal(err) + } + mustScan(t, root, m) + + e := m.Entries["new.md"] + if e == nil { + t.Fatal("改名後應該在新路徑有 entry") + } + if e.LastError != reason { + t.Fatalf("改名後失敗真因遺失:want %q, got %q", reason, e.LastError) + } +} + +func renameForTest(root, from, to string) error { + return os.Rename(filepath.Join(root, from), filepath.Join(root, to)) +}