本卡屬 FR-166(母卡 CM-3109 延伸),決策者 2026-10-28 要求。Repo:evidence-agent。主控台按鈕是另一張 FE 卡(下一批),依賴本卡的端點。
白話
Agent 的弱點資料本來就是自動同步:每次心跳(預設 60 秒)主系統會帶回現行資料包的日期與雜湊,比較新就背景下載、驗章、切換。但有兩種情況要等很久:下載或驗章失敗後同一包要等 30 分鐘才重試(避免每次心跳重下 0.5GB);或使用者剛在主系統上傳完、想馬上看到 Agent 跟上,不想等下一次心跳。現在唯一的手動方式是重啟 Agent。
要加一個主控台按鈕「立即檢查更新」:清掉失敗冷卻,喚醒心跳迴圈立刻跳一次。弱點資料的判斷與下載照走既有那一條路,不另寫第二套拉取邏輯。
問題在哪
- core/vulndb_sync.py(268 行):_observe() 裡
sha256 == _failed_sha and time.monotonic() - _failed_at < _RETRY_AFTER_SEC 就 return,這就是 30 分鐘冷卻;_failed_sha/_failed_at 是模組層全域,沒有對外清除的函式。_sync_lock 被持有時(正在下載)也直接 return。
- core/enroll.py(419 行):_heartbeat_loop() 最後
if stop.wait(interval): break,等待期間沒有任何辦法提前叫醒;start_heartbeat() 建的 stop Event 只用來停止。
- core/console/ops_routes.py(201 行):維運端點都在這支,守門用 routes.require_session(scope);照慣例新路由只加在這支。
怎麼做
- 【new 喚醒機制,放新小模組 core/heartbeat_wake.py】一個模組層 threading.Event
_wake+三支函式:request()(set)、wait(stop, interval)(等 interval 秒,期間被 stop 或 _wake 叫醒都返回;返回時 clear _wake;回傳是否該停止)、reset_for_tests()。不往 enroll.py 塞全域狀態。
- 【extend enroll._heartbeat_loop】把
if stop.wait(interval): break 換成 if heartbeat_wake.wait(stop, interval): break,其餘不動。保住既有語意:失敗也照樣等、網路不通不會變忙迴圈。
- 【new vulndb_sync.request_check() -> dict】清 _failed_sha/_failed_at(冷卻歸零),呼叫 heartbeat_wake.request();回 {"accepted": bool, "syncing": bool, "vulndb_date": …}。正在下載(_sync_lock.locked())時不清、不喚醒,回 accepted=false、syncing=true,讓畫面顯示「同步中」,不要重複觸發。
- 【new 端點】ops_routes.py 加 POST /console/api/ops/vulndb/check,@require_session("configure"),回 request_check() 的結果。成功 202。稽核:沿用既有 console 稽核寫一筆 vulndb_check_requested(看 core/console/audit.py 既有事件命名,主系統事件碼白名單若要動就不要新增事件、只寫 log)。
- 【速率限制】同一台 10 秒內重複按只算一次(模組層記上次時間,期間內直接回上一次結果),避免連點把心跳打成忙迴圈。
- 【不動】主系統(BE)不用改;弱點資料下載、驗章、切換流程不動;_RETRY_AFTER_SEC 不改。
改哪些檔
- 新:core/heartbeat_wake.py
- 改:core/enroll.py(一行)、core/vulndb_sync.py、core/console/ops_routes.py、(視需要)core/console/audit.py
- 測試:test/ 下新增或擴 vulndb_sync 與 ops 的測試檔(先 ls test 找既有檔名)
驗證