mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
v2 single-rail 5.7: migrate Analyzer trio + AgentAnalyzerManager to Signal-Post (B24-B26)
Full migration — the 3 analyzer classes and their manager all move to
InformationalEventPost, no Action<EnrollmentEvent>-bypass remains in the
analyzer surface (no dual-use concerns; no standalone CLI mode touches
these types).
Analyzers (1 Emit-site each):
* LocalAdminAnalyzer (B24): local_admin_analysis.
* SoftwareInventoryAnalyzer (B25): software_inventory_analysis
(startup baseline + shutdown delta; WG-part discriminator unchanged).
* IntegrityBypassAnalyzer (B26): integrity_bypass_analysis.
Manager:
* AgentAnalyzerManager stores InformationalEventPost _post and passes
it to all 3 analyzer constructors in Initialize().
Program.cs wiring:
* AgentAnalyzerManager construction deferred into orchestrator.Start's
onIngressReady hook so lifecyclePost is non-null at construction time.
The outer-scope `AgentAnalyzerManager analyzerManager = null;`
declaration is captured by both the later RunStartup() call (after
Start returns) and the terminationHandler constructor (inside the
same hook).
Tests:
* AgentAnalyzerManagerTests helper NewPost() returns a throwaway
InformationalEventPost(new FakeSignalIngressSink(), VirtualClock)
for the 7 ctor sites including the null-arg test.
* 538 V2.Core + 108 DecisionCore = 646 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-9
@@ -5,8 +5,10 @@ using System.Threading;
|
||||
using AutopilotMonitor.Agent.V2.Core.Configuration;
|
||||
using AutopilotMonitor.Agent.V2.Core.Logging;
|
||||
using AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers;
|
||||
using AutopilotMonitor.Agent.V2.Core.Orchestration;
|
||||
using AutopilotMonitor.Agent.V2.Core.Runtime;
|
||||
using AutopilotMonitor.Agent.V2.Core.Tests.Harness;
|
||||
using AutopilotMonitor.Agent.V2.Core.Tests.Orchestration;
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
using Xunit;
|
||||
|
||||
@@ -24,12 +26,18 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Runtime
|
||||
|
||||
private static AgentLogger NewLogger(string dir) => new AgentLogger(dir, AgentLogLevel.Info);
|
||||
|
||||
// Throwaway InformationalEventPost for tests that exercise manager lifecycle, not
|
||||
// emission surface. Tracker events flow into a discarded FakeSignalIngressSink so
|
||||
// tests that inject a FakeAnalyzer see no cross-talk from real analyzer emissions.
|
||||
private static InformationalEventPost NewPost() =>
|
||||
new InformationalEventPost(new FakeSignalIngressSink(), new VirtualClock(new DateTime(2026, 4, 23, 10, 0, 0, DateTimeKind.Utc)));
|
||||
|
||||
[Fact]
|
||||
public void Initialize_respects_toggle_all_off_registers_no_analyzers()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var sut = new AgentAnalyzerManager(
|
||||
Config(), NewLogger(tmp.Path), _ => { },
|
||||
Config(), NewLogger(tmp.Path), NewPost(),
|
||||
new AnalyzerConfiguration
|
||||
{
|
||||
EnableLocalAdminAnalyzer = false,
|
||||
@@ -46,7 +54,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Runtime
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var sut = new AgentAnalyzerManager(
|
||||
Config(), NewLogger(tmp.Path), _ => { },
|
||||
Config(), NewLogger(tmp.Path), NewPost(),
|
||||
new AnalyzerConfiguration
|
||||
{
|
||||
EnableLocalAdminAnalyzer = true,
|
||||
@@ -66,7 +74,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Runtime
|
||||
public void Initialize_tolerates_null_analyzer_config()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var sut = new AgentAnalyzerManager(Config(), NewLogger(tmp.Path), _ => { }, analyzerConfig: null);
|
||||
var sut = new AgentAnalyzerManager(Config(), NewLogger(tmp.Path), NewPost(), analyzerConfig: null);
|
||||
|
||||
sut.Initialize();
|
||||
// Default AnalyzerConfiguration enables LocalAdmin + IntegrityBypass, disables SoftwareInventory.
|
||||
@@ -81,7 +89,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Runtime
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var sut = new AgentAnalyzerManager(
|
||||
Config(), NewLogger(tmp.Path), _ => { },
|
||||
Config(), NewLogger(tmp.Path), NewPost(),
|
||||
new AnalyzerConfiguration { EnableLocalAdminAnalyzer = true });
|
||||
|
||||
sut.Initialize();
|
||||
@@ -96,9 +104,9 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Runtime
|
||||
using var tmp = new TempDirectory();
|
||||
var logger = NewLogger(tmp.Path);
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new AgentAnalyzerManager(null!, logger, _ => { }, null));
|
||||
new AgentAnalyzerManager(null!, logger, NewPost(), null));
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new AgentAnalyzerManager(Config(), null!, _ => { }, null));
|
||||
new AgentAnalyzerManager(Config(), null!, NewPost(), null));
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new AgentAnalyzerManager(Config(), logger, null!, null));
|
||||
}
|
||||
@@ -149,7 +157,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Runtime
|
||||
public void RunShutdown_invokes_AnalyzeAtShutdown_on_every_analyzer()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var sut = new AgentAnalyzerManager(Config(), NewLogger(tmp.Path), _ => { }, new AnalyzerConfiguration());
|
||||
var sut = new AgentAnalyzerManager(Config(), NewLogger(tmp.Path), NewPost(), new AnalyzerConfiguration());
|
||||
var fake = new FakeAnalyzer();
|
||||
InjectAnalyzer(sut, fake);
|
||||
|
||||
@@ -161,7 +169,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Runtime
|
||||
public void RunShutdown_swallows_analyzer_exceptions_and_continues()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var sut = new AgentAnalyzerManager(Config(), NewLogger(tmp.Path), _ => { }, new AnalyzerConfiguration());
|
||||
var sut = new AgentAnalyzerManager(Config(), NewLogger(tmp.Path), NewPost(), new AnalyzerConfiguration());
|
||||
var fake = new FakeAnalyzer { ThrowOnShutdown = new InvalidOperationException("boom") };
|
||||
InjectAnalyzer(sut, fake);
|
||||
|
||||
@@ -175,7 +183,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Runtime
|
||||
public void RunShutdown_is_safe_on_empty_analyzer_list()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var sut = new AgentAnalyzerManager(Config(), NewLogger(tmp.Path), _ => { },
|
||||
var sut = new AgentAnalyzerManager(Config(), NewLogger(tmp.Path), NewPost(),
|
||||
new AnalyzerConfiguration
|
||||
{
|
||||
EnableLocalAdminAnalyzer = false,
|
||||
|
||||
+5
-4
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using AutopilotMonitor.Agent.V2.Core.Logging;
|
||||
using AutopilotMonitor.Agent.V2.Core.Orchestration;
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
using Microsoft.Win32;
|
||||
|
||||
@@ -34,7 +35,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
|
||||
{
|
||||
private readonly string _sessionId;
|
||||
private readonly string _tenantId;
|
||||
private readonly Action<EnrollmentEvent> _emitEvent;
|
||||
private readonly InformationalEventPost _post;
|
||||
private readonly AgentLogger _logger;
|
||||
|
||||
public string Name => "IntegrityBypassAnalyzer";
|
||||
@@ -63,12 +64,12 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
|
||||
public IntegrityBypassAnalyzer(
|
||||
string sessionId,
|
||||
string tenantId,
|
||||
Action<EnrollmentEvent> emitEvent,
|
||||
InformationalEventPost post,
|
||||
AgentLogger logger)
|
||||
{
|
||||
_sessionId = sessionId ?? throw new ArgumentNullException(nameof(sessionId));
|
||||
_tenantId = tenantId ?? throw new ArgumentNullException(nameof(tenantId));
|
||||
_emitEvent = emitEvent ?? throw new ArgumentNullException(nameof(emitEvent));
|
||||
_post = post ?? throw new ArgumentNullException(nameof(post));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -220,7 +221,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
|
||||
}
|
||||
};
|
||||
|
||||
_emitEvent(new EnrollmentEvent
|
||||
_post.Emit(new EnrollmentEvent
|
||||
{
|
||||
SessionId = _sessionId,
|
||||
TenantId = _tenantId,
|
||||
|
||||
+5
-4
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using AutopilotMonitor.Agent.V2.Core.Logging;
|
||||
using AutopilotMonitor.Agent.V2.Core.Orchestration;
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
using Microsoft.Win32;
|
||||
|
||||
@@ -30,7 +31,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
|
||||
{
|
||||
private readonly string _sessionId;
|
||||
private readonly string _tenantId;
|
||||
private readonly Action<EnrollmentEvent> _emitEvent;
|
||||
private readonly InformationalEventPost _post;
|
||||
private readonly AgentLogger _logger;
|
||||
private readonly List<string> _allowedAccounts;
|
||||
|
||||
@@ -57,13 +58,13 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
|
||||
public LocalAdminAnalyzer(
|
||||
string sessionId,
|
||||
string tenantId,
|
||||
Action<EnrollmentEvent> emitEvent,
|
||||
InformationalEventPost post,
|
||||
AgentLogger logger,
|
||||
List<string> allowedAccounts = null)
|
||||
{
|
||||
_sessionId = sessionId ?? throw new ArgumentNullException(nameof(sessionId));
|
||||
_tenantId = tenantId ?? throw new ArgumentNullException(nameof(tenantId));
|
||||
_emitEvent = emitEvent ?? throw new ArgumentNullException(nameof(emitEvent));
|
||||
_post = post ?? throw new ArgumentNullException(nameof(post));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
// Tenant-supplied accounts are additive (union with built-ins, not replacement)
|
||||
@@ -203,7 +204,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
|
||||
? $"{Name}: No unexpected local admins detected"
|
||||
: $"{Name}: Unexpected admin activity detected (confidence={confidenceScore})";
|
||||
|
||||
_emitEvent(new EnrollmentEvent
|
||||
_post.Emit(new EnrollmentEvent
|
||||
{
|
||||
SessionId = _sessionId,
|
||||
TenantId = _tenantId,
|
||||
|
||||
+5
-4
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using AutopilotMonitor.Agent.V2.Core.Logging;
|
||||
using AutopilotMonitor.Agent.V2.Core.Orchestration;
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
using Microsoft.Win32;
|
||||
|
||||
@@ -22,7 +23,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
|
||||
{
|
||||
private readonly string _sessionId;
|
||||
private readonly string _tenantId;
|
||||
private readonly Action<EnrollmentEvent> _emitEvent;
|
||||
private readonly InformationalEventPost _post;
|
||||
private readonly AgentLogger _logger;
|
||||
|
||||
// Captured at startup for delta detection at shutdown
|
||||
@@ -349,12 +350,12 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
|
||||
public SoftwareInventoryAnalyzer(
|
||||
string sessionId,
|
||||
string tenantId,
|
||||
Action<EnrollmentEvent> emitEvent,
|
||||
InformationalEventPost post,
|
||||
AgentLogger logger)
|
||||
{
|
||||
_sessionId = sessionId ?? throw new ArgumentNullException(nameof(sessionId));
|
||||
_tenantId = tenantId ?? throw new ArgumentNullException(nameof(tenantId));
|
||||
_emitEvent = emitEvent ?? throw new ArgumentNullException(nameof(emitEvent));
|
||||
_post = post ?? throw new ArgumentNullException(nameof(post));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -874,7 +875,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
|
||||
: $"{Name}: Shutdown inventory ({inventory.Count} items, {newInstalls?.Count ?? 0} new during enrollment)")
|
||||
: $"{Name}: Inventory chunk {i + 1}/{totalChunks}";
|
||||
|
||||
_emitEvent(new EnrollmentEvent
|
||||
_post.Emit(new EnrollmentEvent
|
||||
{
|
||||
SessionId = _sessionId,
|
||||
TenantId = _tenantId,
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
||||
using AutopilotMonitor.Agent.V2.Core.Configuration;
|
||||
using AutopilotMonitor.Agent.V2.Core.Logging;
|
||||
using AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers;
|
||||
using AutopilotMonitor.Agent.V2.Core.Orchestration;
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
|
||||
namespace AutopilotMonitor.Agent.V2.Core.Runtime
|
||||
@@ -24,7 +25,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Runtime
|
||||
{
|
||||
private readonly AgentConfiguration _configuration;
|
||||
private readonly AgentLogger _logger;
|
||||
private readonly Action<EnrollmentEvent> _emitEvent;
|
||||
private readonly InformationalEventPost _post;
|
||||
private readonly AnalyzerConfiguration _analyzerConfig;
|
||||
|
||||
private readonly List<IAgentAnalyzer> _analyzers = new List<IAgentAnalyzer>();
|
||||
@@ -33,12 +34,12 @@ namespace AutopilotMonitor.Agent.V2.Core.Runtime
|
||||
public AgentAnalyzerManager(
|
||||
AgentConfiguration configuration,
|
||||
AgentLogger logger,
|
||||
Action<EnrollmentEvent> emitEvent,
|
||||
InformationalEventPost post,
|
||||
AnalyzerConfiguration? analyzerConfig)
|
||||
{
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_emitEvent = emitEvent ?? throw new ArgumentNullException(nameof(emitEvent));
|
||||
_post = post ?? throw new ArgumentNullException(nameof(post));
|
||||
_analyzerConfig = analyzerConfig ?? new AnalyzerConfiguration();
|
||||
}
|
||||
|
||||
@@ -56,7 +57,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Runtime
|
||||
_analyzers.Add(new LocalAdminAnalyzer(
|
||||
_configuration.SessionId,
|
||||
_configuration.TenantId,
|
||||
_emitEvent,
|
||||
_post,
|
||||
_logger,
|
||||
_analyzerConfig.LocalAdminAllowedAccounts));
|
||||
_logger.Info("LocalAdminAnalyzer registered");
|
||||
@@ -71,7 +72,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Runtime
|
||||
_analyzers.Add(new SoftwareInventoryAnalyzer(
|
||||
_configuration.SessionId,
|
||||
_configuration.TenantId,
|
||||
_emitEvent,
|
||||
_post,
|
||||
_logger));
|
||||
_logger.Info("SoftwareInventoryAnalyzer registered");
|
||||
}
|
||||
@@ -85,7 +86,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Runtime
|
||||
_analyzers.Add(new IntegrityBypassAnalyzer(
|
||||
_configuration.SessionId,
|
||||
_configuration.TenantId,
|
||||
_emitEvent,
|
||||
_post,
|
||||
_logger));
|
||||
_logger.Info("IntegrityBypassAnalyzer registered");
|
||||
}
|
||||
|
||||
@@ -503,14 +503,14 @@ namespace AutopilotMonitor.Agent.V2
|
||||
using (var shutdown = new ManualResetEventSlim(false))
|
||||
using (var shutdownComplete = new ManualResetEventSlim(false))
|
||||
{
|
||||
// M4.6.δ — Analyzer manager. Emits through the live orchestrator's event
|
||||
// emitter. RunStartup fires after orchestrator.Start; RunShutdown is wired
|
||||
// into the termination handler so it runs before diagnostics upload.
|
||||
var analyzerManager = new AgentAnalyzerManager(
|
||||
configuration: agentConfig,
|
||||
logger: logger,
|
||||
emitEvent: evt => { orchestrator.EventEmitter.Emit(evt); },
|
||||
analyzerConfig: remoteConfig.Analyzers);
|
||||
// M4.6.δ — Analyzer manager. Single-rail refactor (plan §5.7): the manager
|
||||
// now takes an InformationalEventPost so LocalAdmin / SoftwareInventory /
|
||||
// IntegrityBypass analyzer events flow through the same ingress pipe as
|
||||
// every other telemetry source. Construction is deferred into
|
||||
// orchestrator.Start's onIngressReady hook so lifecyclePost is non-null at
|
||||
// construction time. RunStartup fires after orchestrator.Start; RunShutdown
|
||||
// is wired into the termination handler so it runs before diagnostics upload.
|
||||
AgentAnalyzerManager analyzerManager = null;
|
||||
|
||||
// M4.6.β — the peripheral termination sequence (FinalStatus + SummaryDialog
|
||||
// + diagnostics upload + enrollment-complete.marker + CleanupService) lives
|
||||
@@ -675,6 +675,17 @@ namespace AutopilotMonitor.Agent.V2
|
||||
EmitVersionCheckEventIfAny(lifecyclePost, agentConfig, logger);
|
||||
EmitUnrestrictedModeAuditIfChanged(lifecyclePost, agentConfig, configMergeResult, logger);
|
||||
|
||||
// Single-rail refactor (plan §5.7) — AgentAnalyzerManager emits through
|
||||
// the same InformationalEventPost. Constructed inside this hook so
|
||||
// lifecyclePost is guaranteed non-null by the time RunStartup (below,
|
||||
// after Start returns) and RunShutdown (via terminationHandler) call
|
||||
// into the three analyzers.
|
||||
analyzerManager = new AgentAnalyzerManager(
|
||||
configuration: agentConfig,
|
||||
logger: logger,
|
||||
post: lifecyclePost,
|
||||
analyzerConfig: remoteConfig.Analyzers);
|
||||
|
||||
// Single-rail refactor (plan §5.3) — EnrollmentTerminationHandler emits
|
||||
// through the same InformationalEventPost. Constructed inside this hook
|
||||
// so lifecyclePost is guaranteed non-null; the terminated-dispatch wrapper
|
||||
|
||||
Reference in New Issue
Block a user