From a40c14b3a390f787f494a3bb30cef4e763007037 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Thu, 21 May 2026 19:16:36 +0200 Subject: [PATCH] refactor(agent/v2): RealmJoinWatcher arms package watchers only after the Services gate fires MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous staging armed HKLM\SOFTWARE\RealmJoin in parallel with the Services\realmjoin parent-watch, and ArmHku (called on Desktop-Arrival) attached HKU\\SOFTWARE\RealmJoin regardless of whether RJ was actually installed. Both are wasted observation surface on the ~80% of devices that never get RJ — and HKU\\SOFTWARE in particular is a noisy parent with many subkey changes per session. Single entry point now: Services\realmjoin is the SOLE detection gate. - Start() only arms the parent-watch on HKLM\SYSTEM\CurrentControlSet\Services. - ArmHku(sid) records the SID but no HKU watcher is created until the gate fires. - AttachServiceRealmjoinSubtreeWatcher (= "RJ detected") now also triggers: - EnsureMachineRealmJoinStage (HKLM\SOFTWARE\RealmJoin subtree) - EnsureUserRealmJoinStage (HKU\\SOFTWARE\RealmJoin subtree) — only when a SID was already recorded; otherwise the later ArmHku(sid) sees _realmJoinServiceDetected and arms immediately. - Result on a non-RJ device: a single parent-watch on the Services hive, sleeping idle. No HKLM\SOFTWARE parent-watch, no HKU parent-watch. Tests: 10/10 RealmJoin/SID tests still green (the trigger-from-test seams target the read paths the gate ordering does not touch). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../SystemSignals/RealmJoinWatcher.cs | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/RealmJoinWatcher.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/RealmJoinWatcher.cs index 5ca30e53..a7b18a38 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/RealmJoinWatcher.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/RealmJoinWatcher.cs @@ -107,6 +107,10 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals private readonly HashSet _hkuPackagesCompleted = new HashSet(StringComparer.OrdinalIgnoreCase); private string? _hkuSid; + // Set once the Services\realmjoin gate fires. Until then, HKLM\SOFTWARE\RealmJoin and + // HKU\\SOFTWARE\RealmJoin remain unarmed — RJ presence is the prerequisite for + // looking at those keys at all. + private bool _realmJoinServiceDetected; private bool _disposed; public event EventHandler? RealmJoinDetected; @@ -124,36 +128,43 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals { if (_disposed) throw new ObjectDisposedException(nameof(RealmJoinWatcher)); - // Initial sync reads (cheap; both bail out if the target keys don't exist). - CheckParameters(); - CheckMachinePackages(); - - // Stage A: ensure the machine RJ service key. + // The Services\realmjoin gate is the SOLE entry point. HKLM\SOFTWARE\RealmJoin and + // the HKU watcher do not run until this fires — RJ presence is the prerequisite for + // touching those keys at all. On RJ-less devices the only standing observer is the + // parent-watch on HKLM\SYSTEM\CurrentControlSet\Services. EnsureRealmJoinServiceStage(); - - // Stage B: ensure HKLM\SOFTWARE\RealmJoin (independent of A — either can appear first). - EnsureMachineRealmJoinStage(); } /// - /// Attach the user-scope HKU watcher. Called by the host once + /// Record the user SID for HKU watching. Called by the host once /// resolves a real user owner and /// produces the SID. Idempotent. + /// + /// The actual HKU watcher only arms once the Services\realmjoin gate fires — + /// either it has already fired (immediate arm) or it fires later + /// (deferred arm from ). On devices + /// without RJ the SID is recorded but no HKU watcher is ever created. + /// /// public void ArmHku(string sid) { if (_disposed) return; if (string.IsNullOrEmpty(sid)) return; + bool armNow; lock (_stateLock) { if (_hkuSid != null) return; _hkuSid = sid; + armNow = _realmJoinServiceDetected; } - _logger.Info($"RealmJoinWatcher: arming HKU watcher for SID {sid}"); + _logger.Info($"RealmJoinWatcher: recorded SID {sid} for HKU watching (armNow={armNow})"); - CheckUserPackages(); - EnsureUserRealmJoinStage(); + if (armNow) + { + CheckUserPackages(); + EnsureUserRealmJoinStage(); + } } public void Stop(string reason = "watcher_stopped") @@ -303,6 +314,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals private void AttachServiceRealmjoinSubtreeWatcher() { + bool armUserToo; lock (_stateLock) { if (_serviceRealmjoinWatcher != null) return; @@ -325,12 +337,23 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals { _logger.Warning($"RealmJoinWatcher: failed to attach Services\\realmjoin watcher: {ex.Message}"); } + + _realmJoinServiceDetected = true; + armUserToo = _hkuSid != null; } // The appearance of Services\realmjoin is itself the RJ-detected signal — // surface it even if Parameters\DeploymentPhase isn't populated yet. NotifyRealmJoinPresence(phase: null); CheckParameters(); + + // RJ is now confirmed installed — arm the package watchers. + EnsureMachineRealmJoinStage(); + if (armUserToo) + { + CheckUserPackages(); + EnsureUserRealmJoinStage(); + } } private void AttachMachineRealmJoinSubtreeWatcher()