From f252a0709f240f3377c952b199d8f4687ca2e010 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Tue, 22 Sep 2026 00:48:50 +0200 Subject: [PATCH] fix(agent): network link changes no longer count as enrollment activity network_state_change and network_connectivity_check join the non-activity list next to power_state_change. A flapping dock or access point kept the idle-stopped periodic collectors running for the whole episode and reset the stall clock of the enrollment it was stalling. The events are emitted unchanged; the device-info network snapshots stay activity. Co-Authored-By: Claude Fable 5.1 --- .../SignalActivityClassifierTests.cs | 18 ++++++++++++++++++ .../Orchestration/SignalActivityClassifier.cs | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Orchestration/SignalActivityClassifierTests.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Orchestration/SignalActivityClassifierTests.cs index 785d1860..825c7f2b 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Orchestration/SignalActivityClassifierTests.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Orchestration/SignalActivityClassifierTests.cs @@ -37,6 +37,12 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Orchestration [InlineData("modern_deployment_log")] // Environment observation — battery drain on an idle device must not reset idle clocks. [InlineData("power_state_change")] + // A flapping link is environment too: it must neither keep the collectors awake nor + // reset the stall clock of the enrollment it is stalling. + [InlineData("network_state_change")] + [InlineData("network_connectivity_check")] + // Bookkeeping of health-script runs that were deliberately not reported. + [InlineData("script_recurrence_summary")] // Agent health / control / transport — not device/enrollment progress (P2). [InlineData("collector_degraded")] [InlineData("telemetry_upload_poisoned")] @@ -67,6 +73,18 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Orchestration Assert.True(SignalActivityClassifier.IsRealActivity(DecisionSignalKind.InformationalEvent, payload)); } + [Theory] + // Only the link-change pair is exempt. The device-info snapshots are emitted at start + // and at the end, not per flap, and stay activity. + [InlineData("network_interface_info")] + [InlineData("wifi_signal_info")] + [InlineData("network_bandwidth_estimate")] + public void Other_network_events_remain_activity(string eventType) + { + var payload = new Dictionary { [SignalPayloadKeys.EventType] = eventType }; + Assert.True(SignalActivityClassifier.IsRealActivity(DecisionSignalKind.InformationalEvent, payload)); + } + [Theory] // Genuine ModernDeployment warnings/errors stay real activity — only the Info/Debug // `modern_deployment_log` forwarding is excluded. diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/SignalActivityClassifier.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/SignalActivityClassifier.cs index 5f355e0e..432ddd84 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/SignalActivityClassifier.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/SignalActivityClassifier.cs @@ -61,6 +61,15 @@ namespace AutopilotMonitor.Agent.V2.Core.Orchestration // detection — the exact scenario the power watcher exists to observe. SharedConstants.EventTypes.PowerStateChange, + // Same for the network link: a dock or access point that flaps for hours (field: + // 288 lost/connected pairs in 11 h, each with its connectivity check) is not + // enrollment progress. Counted as activity it kept the periodic collectors running + // for the whole episode and hid the stall it was causing, because every flap reset + // the stall clock. Progress that follows a reconnect arrives as its own download, + // app or phase event. + SharedConstants.EventTypes.NetworkStateChange, + SharedConstants.EventTypes.NetworkConnectivityCheck, + // Same reasoning for the system timeline watcher: a clock step, a wake from standby, // or a backfilled episode at agent start is environment observation and must not // reset the idle/stall clocks.