SPEC-45b — anomaly-detector 24h staleness check & dynamic minSamples 審查報告

Agent: reviewer (reviewer)
Confidence: 65%
Cost: $0.4218
Duration: 1m 34s
Task ID: a7fcef9a-9def-409c-857f-99673f1ed81a


我已完成審查。以下是完整報告:


SPEC-45b — anomaly-detector 24h staleness check & dynamic minSamples 審查報告

概述

改動符合 SPEC-45b 規格,邏輯簡潔正確。兩個功能(24h staleness check、dynamic minSamples 6→2)都正確實作。有一個低風險邊界情況和一個測試覆蓋不足的觀察。

分析詳情

改動 1 — BASELINE_MAX_AGE_MS 常數(line 47-48)

1
const BASELINE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
  • 模組層級常數,計算結果 86400000ms = 24h
  • 命名清楚,JSDoc 註釋到位
  • 與專案中其他 staleness pattern 一致(checkpoint.ts:186reflection.ts:226 也用 24h + Date.now() 比較)

結論: 正確,無問題。

改動 2 — Staleness Check(line 255-262)

1
2
3
4
5
6
7
if (data.savedAt) {
const age = Date.now() - new Date(data.savedAt).getTime();
if (age > BASELINE_MAX_AGE_MS) {
logger.info('AnomalyDetector', 'Baseline older than 24h, starting fresh');
return 0;
}
}

正向:

  • 先檢查 data.savedAt 存在再計算,避免 new Date(undefined) 問題
  • 使用 return 0 而非 throw,符合專案 Result pattern 精神
  • log level 用 info 適當

邊界情況 — savedAt 缺失時直接 pass through:

  • data.savedAt 為 falsy(例如舊版 baseline 檔沒有 savedAt 欄位),staleness check 被跳過
  • 但查看 saveBaselines() (line 242),savedAt 是硬編碼寫入的:{ savedAt: new Date().toISOString(), baselines }
  • 因此 savedAt 缺失只會在手動篡改檔案或極舊檔案的情況下發生
  • 風險: 🟢 低 — 防禦性 if (data.savedAt) 合理,不需要改

改動 3 — Dynamic minSamples(line 265-266)

1
2
if (restored > 0) {
this.config.minSamples = 2;

分析:

  • 直接修改 instance 的 this.config.minSamples,因為 config 在 constructor 中已用 spread 拷貝(line 115: { ...DEFAULT_CONFIG, ...config }),不會影響 DEFAULT_CONFIG 或其他 instance
  • check() (line 135) 讀取 this.config.minSamples,所以此修改立即生效
  • 只在 restored > 0 時降低,若 baseline 為空(所有 metric 的 values 都是空陣列)不會降低
  • 全域 singleton anomalyDetector (line 278) 只有一個 instance,不會有 race condition

行為路徑整理:

場景 staleness restored minSamples
無 baseline 檔案 N/A 0 6 (預設)
Baseline >24h stale 0 6 (預設)
Baseline ≤24h,有資料 fresh >0 2
Baseline ≤24h,空資料 fresh 0 6 (預設)

結論: 邏輯完全正確,所有路徑符合 SPEC 意圖。

呼叫端影響

  • heartbeat.ts:394loadBaselines()startHeartbeat() 中 async 呼叫,non-blocking .catch() 處理
  • heartbeat.ts:323saveBaselines() 未修改,照常寫入 savedAt
  • 無其他檔案引用 loadBaselines/saveBaselines,blast radius = 0

測試覆蓋

現有測試 (tests/unit/anomaly-detector.test.ts) 未覆蓋新功能

  • 沒有 loadBaselines() / saveBaselines() 的測試(涉及 fs I/O,需 mock)
  • 沒有 staleness check 的測試
  • 沒有 dynamic minSamples 的測試

風險評估: 🟡 中 — 新功能無單元測試。但考慮到:

  1. 邏輯非常簡單(一個 if + 一行賦值)
  2. 改動已有 TypeScript 型別保護
  3. 失敗影響低(最差情況是 cold start,不影響系統穩定性)
  4. SPEC-45b 未明確要求新增測試

結論: 不因此退回,但建議未來加測試。

風險評估

  • 🟢 低風險:savedAt 缺失的防禦性處理已足夠
  • 🟡 中風險:新邏輯無單元測試覆蓋(建議性,不阻擋)

驗收結論

驗收項目 結果
Baseline >24h → 不使用,minSamples=6
Baseline ≤24h → 使用,minSamples=2
saveBaselines 包含 savedAt ✅ (已存在)
不改動 heartbeat.ts
TypeScript 編譯通過
Commit 在正確 branch ✅ (607e5e0 on agent/task-db203c3d)
程式碼風格一致
無安全風險

結論: ✅ 驗收通過。

—HANDOFF—
TO: secretary
INTENT: handoff
ARTIFACT_TYPE: review
SUMMARY: SPEC-45b 驗收通過 — anomaly-detector 24h staleness check + dynamic minSamples,commit 607e5e0 on agent/task-db203c3d,請 merge 回 main