fix(manager): AlarmPersistenceManager 修 stuck-active silent monitoring blackout#135
Open
csp0924 wants to merge 2 commits into
Open
fix(manager): AlarmPersistenceManager 修 stuck-active silent monitoring blackout#135csp0924 wants to merge 2 commits into
csp0924 wants to merge 2 commits into
Conversation
…g blackout 當 resolve() 失敗 (modify_count=0,常見於 Mongo replica transient unavailable / network jitter / write concern timeout) → DB 殘留 ACTIVE 記錄 → 後續同 alarm_key 的 disconnect upsert() 返回 is_new=False → 完全 silent (無 history、無 notify、無 log)。 Production 場景:Mongo replica 短暫掉線觸發一次 resolve fail → 該 device 從此停發任何 告警 audit / alert,直到某次 resolve 巧合成功 → 維運完全察覺不到 silent blackout。 修法: - _create_alarm 在 is_new=False 時改成寫 history with event="duplicate_trigger" + WARNING log,而非 silent skip - Notification 維持 dedupe (避免 spam,保留 v0.8.x 設計合約) - _NotifyEvent 改名為 _AlarmEvent (本來就同時用於 notify + history doc event 欄位, 新增 DUPLICATE_TRIGGER value) 合約變更: - buffered_uploader 的 history collection 新增 event="duplicate_trigger" 類型 (caller 若 query event=triggered 不會誤包,需明確 query 對應 event) - test_existing_alarm_does_not_write_history → test_existing_alarm_writes_duplicate_trigger_history Closed-loop validation (sandbox 20-cycle flap × 3 scenario): - A baseline (no fail): audit_blackout=0, silent_notify=0 - B transient (30% resolve fail): audit_blackout=0 (17 triggered + 3 duplicate), silent_notify=3 (合預期 dedupe) - C permanent (100% resolve fail): audit_blackout=0 (1 triggered + 19 duplicate), silent_notify=19 (合預期 dedupe) 修法前 C 場景: 1 個 history record + 19 silent → 修法後 20 個 history record。 Test plan: - 全 4854 tests passed - 新增 TestStuckActiveSilentBlackoutRegression 三個 regression test - 既有 test_existing_alarm_does_not_write_history 更新合約
There was a problem hiding this comment.
Pull request overview
This PR fixes a silent “stuck ACTIVE” alarm scenario in AlarmPersistenceManager where upsert(is_new=False) previously caused trigger events to be silently dropped (no audit history / no notify), leading to monitoring blackout after transient resolve failures.
Changes:
- Update alarm creation flow to always write an audit history record for every trigger event, using
event="triggered"for new alarms andevent="duplicate_trigger"for duplicate triggers (while keeping notification dedupe behavior). - Add/adjust tests to validate the new history contract and provide multi-cycle regression coverage for the stuck-ACTIVE blackout chain.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
csp_lib/manager/alarm/persistence.py |
Writes history for duplicate triggers (duplicate_trigger) while keeping notifications deduped; adds warning log for duplicate trigger events. |
tests/manager/test_persistence_history.py |
Updates existing expectations and adds regression tests covering resolve-fail → subsequent disconnect still writes history and does not notify. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
286
to
289
| async def _notify(self, record: AlarmRecord, event: _AlarmEvent) -> None: | ||
| """發送告警通知(非阻塞,失敗僅記 log)""" | ||
| if self._dispatcher is None: | ||
| return |
回應 PR#135 Copilot review: _notify 簽名原本 event: _AlarmEvent 型別上允許 DUPLICATE_TRIGGER,但內部 NotificationDispatcher.from_alarm_record(NotificationEvent(event.value)) 只支援 triggered/resolved,傳 DUPLICATE_TRIGGER 進來會 runtime ValueError。 實際上 _notify 唯一呼叫端永遠傳 _AlarmEvent.TRIGGERED(DUPLICATE_TRIGGER 是 audit-only 不發 notification,resolved 流走獨立的 _notify_resolved),event 參數從一開始就是冗餘。移除參數,內部寫死 NotificationEvent.TRIGGERED,消除 'type 放寬但實作沒跟上' 的陷阱。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
_create_alarm在is_new=False時 silent skip → audit history 漏記Why(觸發路徑)
upsert(is_new=False)不代表 disconnect 沒發生,只代表 DB 已有 ACTIVE 記錄。is_new是 record state,不是 event state。將兩者混淆是 root cause。Invariant(修法後合約)
is_new=True→ INFO log + notify + historyevent=\"triggered\"is_new=False→ WARNING log + ❌ 不 notify(維持 dedupe) + historyevent=\"duplicate_trigger\"Closed-loop sandbox 驗證
修法前:C 場景只記 1 個 history record + 19 silent。修法後:20 個 record 完整覆蓋。
Test plan
TestStuckActiveSilentBlackoutRegression三個 test:test_resolve_fail_then_subsequent_disconnect_still_writes_history— 兩 cycle 重現test_continuous_stuck_blackout_quantified— N=5 cycle 量化驗證(1 triggered + 4 duplicate)test_duplicate_trigger_does_not_notify— dedupe 合約保護test_existing_alarm_does_not_write_history更新為test_existing_alarm_writes_duplicate_trigger_history(合約改動)Compatibility(合約變更)
event=\"duplicate_trigger\"文檔類型event=\"triggered\"不會誤包(明確 query)event in [\"triggered\", \"duplicate_trigger\"]_create_alarm是 private,public AlarmPersistenceManager 介面零變動bump:patch(fix:)
Background — closed-loop emergent bug
pytest 單測各 method 獨立,抓不到這條 multi-cycle stateful chain (trigger → resolve-fail → trigger-suppressed)。需用 plant model(FakeRepository with resolve_fail_rate)模擬 multi-cycle 才能 surface。第 4 個 csp_lib closed-loop emergent bug 案例(前 3:PowerCompensator、Droop chatter、Modbus queue dead branch、SetpointDriftReconciler audit spam)。