Skip to content

fix(integration): SystemController resolved=None 時 resume heartbeat#141

Open
csp0924 wants to merge 1 commit into
mainfrom
fix/system-controller-heartbeat-stuck-paused
Open

fix(integration): SystemController resolved=None 時 resume heartbeat#141
csp0924 wants to merge 1 commit into
mainfrom
fix/system-controller-heartbeat-stuck-paused

Conversation

@csp0924

@csp0924 csp0924 commented May 13, 2026

Copy link
Copy Markdown
Owner

問題

Closed-loop probe 發現:SystemController._on_strategy_change(system_controller.py:1295)把 heartbeat pause + resume 兩個動作都包在 if resolved is not None: 下。從 suppress_heartbeat=True strategy(BypassStrategy / CascadingStrategy)轉到 resolved=None(如 remove_base_mode 把最後一個 base mode 移除、無 override)時,heartbeat 永遠卡 paused。field device 進 safety mode timeout,現場停機。

Sandbox 證據(sandbox/system_controller_lifecycle_timing_demo.py H2):BYPASS → set_strategy(None) → pause_calls=1, resume_calls=0, is_paused=True

改動

resolved is not None 從外層 guard 移到內層 pause 判斷,resolved=None 時 fall-through 到 resume()(idempotent)。4 行改動。

驗證

  • 新增 tests/integration/test_system_controller.py::TestHeartbeatPauseResumeOnStrategyChange 2 案(核心案 + false-positive guard)。Pre-fix FAIL → post-fix PASS。
  • Sandbox rerun: state_after_None_paused: False
  • 完整 regression: integration 層 946 passed

… 避免卡 paused

`_on_strategy_change` 原本在 `resolved is not None` 條件下才呼叫 pause/resume,
當前一個策略 suppress_heartbeat=True(如 BypassStrategy / CascadingStrategy)切換
到 resolved=None(remove_base_mode / deactivate_schedule_mode 後無 override),
heartbeat 會永久卡在 paused,設備端會因超時進入安全模式。

修復:resolved is None 時走 resume 分支(idempotent,無策略 assert suppress 時
安全預設為持續送心跳)。

加上兩個 regression test:
- BypassStrategy → remove_base_mode 後 heartbeat.is_paused 必須為 False
- 非 suppress 策略 → resolved=None 不應誤觸 pause
Copilot AI review requested due to automatic review settings May 13, 2026 00:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a control-lifecycle bug in SystemController where heartbeat could remain permanently paused when transitioning from a suppress_heartbeat=True strategy (e.g., BypassStrategy) to resolved=None (no effective strategy). This aligns heartbeat state with the actual effective strategy after mode removal, preventing field devices from timing out into safety mode.

Changes:

  • Adjust _on_strategy_change so heartbeat resume() is executed when resolved is None (no strategy asserting suppression).
  • Add integration regression tests covering the suppress → none transition and a non-suppress guard case.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
csp_lib/integration/system_controller.py Moves the resolved is not None guard to only apply to the pause path, ensuring resolved=None triggers heartbeat resume.
tests/integration/test_system_controller.py Adds regression tests validating heartbeat pause/resume behavior across strategy changes, including the resolved=None case.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants