diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/EventTypeConstantGuardTests.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/EventTypeConstantGuardTests.cs
new file mode 100644
index 00000000..1a85951b
--- /dev/null
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/EventTypeConstantGuardTests.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using AutopilotMonitor.Shared;
+using Xunit;
+
+namespace AutopilotMonitor.Agent.V2.Core.Tests
+{
+ ///
+ /// Drift guard for the single-source event-type catalog (consolidation 2026-05-29).
+ /// Every event type emitted by the V2 agent MUST be defined as a const in
+ /// — that class is the ONE canonical source the MCP
+ /// catalog/search and the backend derive from. This test scans the V2 source for the known
+ /// emit shapes and fails if any uses a raw string literal not present in Constants.EventTypes.
+ ///
+ /// Scope: V2 only (Agent.V2.Core, Agent.V2, DecisionCore). V1 is decommissioning and is not
+ /// guarded. Known boundary: gather-rule OutputEventType values are data-driven (rule
+ /// JSON), not code literals, so they are out of scope by design.
+ ///
+ ///
+ public class EventTypeConstantGuardTests
+ {
+ // Emit shapes that carry an event-type string. Capture group 1 = the literal.
+ private static readonly Regex[] EmitPatterns =
+ {
+ new Regex("\\bEventType\\s*=\\s*\"([a-z][a-z0-9_]*)\"", RegexOptions.Compiled),
+ new Regex("\\beventType\\s*=\\s*\"([a-z][a-z0-9_]*)\"", RegexOptions.Compiled),
+ new Regex("\\beventType\\s*==\\s*\"([a-z][a-z0-9_]*)\"", RegexOptions.Compiled),
+ new Regex("\\.Emit\\(\\s*\"([a-z][a-z0-9_]*)\"", RegexOptions.Compiled),
+ new Regex("EmitDeviceInfoEvent\\(\\s*\"([a-z][a-z0-9_]*)\"", RegexOptions.Compiled),
+ new Regex("\\[\"eventType\"\\]\\s*=\\s*\"([a-z][a-z0-9_]*)\"", RegexOptions.Compiled),
+ };
+
+ // The reducer/param key name itself ("eventType") and the like are not event types.
+ private static readonly HashSet NotEventTypes = new(StringComparer.Ordinal)
+ {
+ "eventType",
+ };
+
+ private static readonly string[] V2SourceDirs =
+ {
+ Path.Combine("src", "Agent", "AutopilotMonitor.Agent.V2.Core"),
+ Path.Combine("src", "Agent", "AutopilotMonitor.Agent.V2"),
+ Path.Combine("src", "Shared", "AutopilotMonitor.DecisionCore"),
+ };
+
+ [Fact]
+ public void All_V2_emitted_event_types_are_defined_in_Constants()
+ {
+ var known = KnownEventTypes();
+ var offenders = new List();
+
+ foreach (var dir in V2SourceDirs)
+ {
+ var full = Path.Combine(RepoRoot(), dir);
+ Assert.True(Directory.Exists(full), $"V2 source dir not found: {full}");
+
+ foreach (var file in EnumerateSourceFiles(full))
+ {
+ var lines = File.ReadAllLines(file);
+ for (int i = 0; i < lines.Length; i++)
+ {
+ var line = lines[i];
+ var trimmed = line.TrimStart();
+
+ // Skip comments and const DEFINITIONS (Constants/DeadlineNames key names etc.).
+ if (trimmed.StartsWith("//") || trimmed.StartsWith("*") || trimmed.StartsWith("///"))
+ continue;
+ if (line.Contains("const ")) continue;
+
+ foreach (var pattern in EmitPatterns)
+ {
+ foreach (Match m in pattern.Matches(line))
+ {
+ var literal = m.Groups[1].Value;
+ if (NotEventTypes.Contains(literal)) continue;
+ if (!known.Contains(literal))
+ {
+ offenders.Add(
+ $"{Path.GetFileName(file)}:{i + 1} \"{literal}\" -> {line.Trim()}");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Assert.True(
+ offenders.Count == 0,
+ "Found V2 event-type string literals NOT defined in Constants.EventTypes. " +
+ "Add a const (exact value) and reference it at the emit site:\n " +
+ string.Join("\n ", offenders));
+ }
+
+ /// All string-const values declared on .
+ private static HashSet KnownEventTypes()
+ {
+ var values = typeof(Constants.EventTypes)
+ .GetFields(BindingFlags.Public | BindingFlags.Static)
+ .Where(f => f.IsLiteral && !f.IsInitOnly && f.FieldType == typeof(string))
+ .Select(f => (string)f.GetRawConstantValue()!)
+ .ToHashSet(StringComparer.Ordinal);
+
+ Assert.True(values.Count > 50, $"Expected many event-type consts, found {values.Count}.");
+ return values;
+ }
+
+ private static IEnumerable EnumerateSourceFiles(string root) =>
+ Directory.EnumerateFiles(root, "*.cs", SearchOption.AllDirectories)
+ .Where(p => !p.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}")
+ && !p.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}"));
+
+ private static string RepoRoot()
+ {
+ var dir = AppContext.BaseDirectory;
+ for (int i = 0; i < 12; i++)
+ {
+ if (File.Exists(Path.Combine(dir, "AutopilotMonitor.sln"))) return dir;
+ var parent = Directory.GetParent(dir)?.FullName;
+ if (parent == null || parent == dir) break;
+ dir = parent;
+ }
+ throw new DirectoryNotFoundException(
+ "Could not locate repo root (AutopilotMonitor.sln) from " + AppContext.BaseDirectory);
+ }
+ }
+}
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Diagnostics/PendingCrashReporter.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Diagnostics/PendingCrashReporter.cs
index 8d64e20d..e89c46ea 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Diagnostics/PendingCrashReporter.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Diagnostics/PendingCrashReporter.cs
@@ -5,6 +5,7 @@ using System.IO;
using AutopilotMonitor.Agent.V2.Core.Configuration;
using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using Newtonsoft.Json;
@@ -89,7 +90,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Diagnostics
{
SessionId = configuration.SessionId,
TenantId = configuration.TenantId,
- EventType = "previous_crash_detected",
+ EventType = Constants.EventTypes.PreviousCrashDetected,
Severity = EventSeverity.Warning,
Source = "PendingCrashReporter",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime/ImeProcessWatcher.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime/ImeProcessWatcher.cs
index 223335eb..3d0a07f8 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime/ImeProcessWatcher.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime/ImeProcessWatcher.cs
@@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Threading;
using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.Ime
@@ -110,7 +111,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.Ime
SessionId = _sessionId,
TenantId = _tenantId,
Timestamp = DateTime.UtcNow,
- EventType = "ime_process_exited",
+ EventType = Constants.EventTypes.ImeProcessExited,
Severity = EventSeverity.Warning,
Source = "ImeProcessWatcher",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/HelloTracker.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/HelloTracker.cs
index eeb1571e..5dfa8670 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/HelloTracker.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/HelloTracker.cs
@@ -4,6 +4,7 @@ using System.Diagnostics.Eventing.Reader;
using System.Text.RegularExpressions;
using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using Microsoft.Win32;
@@ -325,7 +326,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "hello_policy_detected",
+ EventType = Constants.EventTypes.HelloPolicyDetected,
Severity = EventSeverity.Info,
Source = "EspAndHelloTracker",
Phase = EnrollmentPhase.Unknown,
@@ -520,7 +521,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
return;
case EventId_NgcKeyRegistered: // 300
- eventType = "hello_provisioning_completed";
+ eventType = Constants.EventTypes.HelloProvisioningCompleted;
severity = EventSeverity.Info;
message = "Windows Hello for Business provisioned successfully - NGC key registered";
shouldTriggerHelloCompleted = MarkHelloCompleted();
@@ -528,7 +529,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
break;
case EventId_NgcKeyRegistrationFailed: // 301
- eventType = "hello_provisioning_failed";
+ eventType = Constants.EventTypes.HelloProvisioningFailed;
severity = EventSeverity.Error;
message = "Windows Hello for Business provisioning failed - NGC key registration error";
shouldTriggerHelloCompleted = MarkHelloCompleted();
@@ -542,7 +543,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
return;
case EventId_ProvisioningBlocked: // 362
- eventType = "hello_provisioning_blocked";
+ eventType = Constants.EventTypes.HelloProvisioningBlocked;
severity = EventSeverity.Warning;
message = "Windows Hello for Business provisioning blocked";
shouldTriggerHelloCompleted = MarkHelloCompleted();
@@ -550,7 +551,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
break;
case EventId_PinStatus: // 376
- eventType = "hello_pin_status";
+ eventType = Constants.EventTypes.HelloPinStatus;
severity = EventSeverity.Info;
message = "Windows Hello PIN status update";
break;
@@ -591,7 +592,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
// (willlaunch, willnotlaunch, pin_status) are not decision-relevant and can
// flip state multiple times — keep batched.
ImmediateUpload = shouldTriggerHelloCompleted
- || eventType == "hello_provisioning_failed"
+ || eventType == Constants.EventTypes.HelloProvisioningFailed
});
_logger.Info($"Hello event detected: {eventType} (EventID {eventId}{(isBackfill ? ", backfill" : "")})");
@@ -632,7 +633,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
SessionId = _sessionId,
TenantId = _tenantId,
Timestamp = timestamp,
- EventType = "hello_policy_detection_mismatch",
+ EventType = Constants.EventTypes.HelloPolicyDetectionMismatch,
Severity = EventSeverity.Warning,
Source = source,
Phase = EnrollmentPhase.Unknown,
@@ -780,7 +781,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
switch (eventId)
{
case EventId_HelloForBusiness_ProcessingStarted: // 3024
- eventType = "hello_processing_started";
+ eventType = Constants.EventTypes.HelloProcessingStarted;
severity = EventSeverity.Info;
message = "Windows Hello for Business processing started";
_logger.Info("Hello for Business processing started (event 3024)");
@@ -791,7 +792,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
if (string.Equals(hresult, HResult_UserSkippedHello, StringComparison.OrdinalIgnoreCase))
{
- eventType = "hello_skipped";
+ eventType = Constants.EventTypes.HelloSkipped;
severity = EventSeverity.Warning;
message = $"Windows Hello for Business skipped by user ({HResult_UserSkippedHello})";
shouldTriggerHelloCompleted = MarkHelloSkipped();
@@ -799,7 +800,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
}
else
{
- eventType = "hello_processing_stopped";
+ eventType = Constants.EventTypes.HelloProcessingStopped;
severity = EventSeverity.Info;
message = $"Windows Hello for Business processing stopped (HRESULT: {hresult ?? "unknown"})";
_logger.Info($"Hello for Business processing stopped (event 6045, HRESULT {hresult ?? "unknown"}) - not treated as terminal");
@@ -961,7 +962,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "hello_wait_timeout",
+ EventType = Constants.EventTypes.HelloWaitTimeout,
Severity = EventSeverity.Info,
Source = "EspAndHelloTracker",
Phase = EnrollmentPhase.Unknown,
@@ -992,7 +993,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "hello_wait_timeout",
+ EventType = Constants.EventTypes.HelloWaitTimeout,
Severity = EventSeverity.Info,
Source = "EspAndHelloTracker",
Phase = EnrollmentPhase.Unknown,
@@ -1051,7 +1052,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "hello_completion_timeout",
+ EventType = Constants.EventTypes.HelloCompletionTimeout,
Severity = EventSeverity.Warning,
Source = "EspAndHelloTracker",
Phase = EnrollmentPhase.Unknown,
@@ -1085,7 +1086,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "hello_policy_detected",
+ EventType = Constants.EventTypes.HelloPolicyDetected,
Severity = EventSeverity.Info,
Source = "EspAndHelloTracker",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ModernDeploymentTracker.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ModernDeploymentTracker.cs
index 334db999..8a830214 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ModernDeploymentTracker.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ModernDeploymentTracker.cs
@@ -330,7 +330,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "whiteglove_started",
+ EventType = Constants.EventTypes.WhiteGloveStarted,
Severity = EventSeverity.Info,
Source = "ModernDeploymentWatcher",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ProvisioningStatusTracker.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ProvisioningStatusTracker.cs
index 168463d9..37e9c9e8 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ProvisioningStatusTracker.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ProvisioningStatusTracker.cs
@@ -1092,7 +1092,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "esp_failure_settle_started",
+ EventType = Constants.EventTypes.EspFailureSettleStarted,
Severity = EventSeverity.Info,
Source = "EspAndHelloTracker",
Phase = EnrollmentPhase.Unknown,
@@ -1258,7 +1258,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "esp_provisioning_raw",
+ EventType = Constants.EventTypes.EspProvisioningRaw,
Severity = EventSeverity.Trace,
Source = "EspAndHelloTracker",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ShellCoreTracker.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ShellCoreTracker.cs
index 01093a02..ddaf24e7 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ShellCoreTracker.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/SystemSignals/ShellCoreTracker.cs
@@ -4,6 +4,7 @@ using System.Diagnostics.Eventing.Reader;
using System.Text.RegularExpressions;
using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
@@ -168,7 +169,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
case EventId_ShellCore_WebAppStarted: // 62404
if (description.Contains("AADHello") || description.Contains("'NGC'"))
{
- eventType = "hello_wizard_started";
+ eventType = Constants.EventTypes.HelloWizardStarted;
message = "Windows Hello wizard started (CloudExperienceHost)";
triggerFinalizingSetup = true;
finalizingSetupReason = "hello_wizard_started";
@@ -193,7 +194,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
_whiteGloveDetected = true;
}
- eventType = "whiteglove_complete";
+ eventType = Constants.EventTypes.WhiteGloveComplete;
message = "WhiteGlove (Pre-Provisioning) completed successfully";
// No FinalizingSetup transition — WhiteGlove terminates pre-provisioning entirely
@@ -202,14 +203,14 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
else if (HasEspFailurePattern(description))
{
detectedFailureType = ExtractEspFailureType(description);
- eventType = "esp_failure";
+ eventType = Constants.EventTypes.EspFailure;
severity = EventSeverity.Error;
message = $"ESP (Enrollment Status Page) reported a failure: {detectedFailureType}";
_logger.Warning($"ESP failure detected via Shell-Core event 62407: {detectedFailureType}");
}
else if (EspExitingPattern.IsMatch(description))
{
- eventType = "esp_exiting";
+ eventType = Constants.EventTypes.EspExiting;
message = "ESP (Enrollment Status Page) phase exiting";
triggerFinalizingSetup = true;
finalizingSetupReason = "esp_exiting";
@@ -244,7 +245,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
{ "eventTime", timestamp.ToString("o") }
};
- if (eventType == "esp_failure" && detectedFailureType != null)
+ if (eventType == Constants.EventTypes.EspFailure && detectedFailureType != null)
{
eventData["failureType"] = detectedFailureType;
}
@@ -279,7 +280,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
// Fire WhiteGloveCompleted AFTER event emission so the whiteglove_complete event
// is in the spool before the agent exits.
- if (eventType == "whiteglove_complete")
+ if (eventType == Constants.EventTypes.WhiteGloveComplete)
{
try { WhiteGloveCompleted?.Invoke(this, EventArgs.Empty); }
catch (Exception ex) { _logger.Error("WhiteGloveCompleted handler failed", ex); }
@@ -287,7 +288,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
// Fire EspFailureDetected AFTER event emission so the esp_failure event is in the
// spool before the agent potentially shuts down.
- if (eventType == "esp_failure" && detectedFailureType != null)
+ if (eventType == Constants.EventTypes.EspFailure && detectedFailureType != null)
{
try { EspFailureDetected?.Invoke(this, detectedFailureType); }
catch (Exception ex) { _logger.Error($"EspFailureDetected handler failed for '{detectedFailureType}'", ex); }
@@ -297,7 +298,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
// a DecisionSignalKind.EspExiting so the engine can arm HelloSafety on the genuine
// post-AccountSetup exit. Engine-side guard (ShouldTransitionToAwaitingHello)
// distinguishes intermediate exits from the real one.
- if (eventType == "esp_exiting")
+ if (eventType == Constants.EventTypes.EspExiting)
{
try { EspExited?.Invoke(this, new EspExitedEventArgs(timestamp)); }
catch (Exception ex) { _logger.Error("EspExited handler failed", ex); }
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Runtime/ServerActionDispatcher.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Runtime/ServerActionDispatcher.cs
index c5f11b4a..25c2ecef 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Runtime/ServerActionDispatcher.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Runtime/ServerActionDispatcher.cs
@@ -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.Orchestration;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime
@@ -179,7 +180,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime
private void EmitReceived(ServerAction action)
{
PostEvent(
- eventType: "server_action_received",
+ eventType: Constants.EventTypes.ServerActionReceived,
severity: EventSeverity.Info,
message: $"Received server action '{action.Type}'",
data: BuildTelemetryData(action));
@@ -195,7 +196,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime
}
PostEvent(
- eventType: "server_action_executed",
+ eventType: Constants.EventTypes.ServerActionExecuted,
severity: EventSeverity.Info,
message: $"Executed server action '{action.Type}'",
data: data);
@@ -207,7 +208,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime
data["failureReason"] = reason ?? string.Empty;
PostEvent(
- eventType: "server_action_failed",
+ eventType: Constants.EventTypes.ServerActionFailed,
severity: EventSeverity.Warning,
message: $"Failed to execute server action '{action.Type}': {reason}",
data: data);
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/IntegrityBypassAnalyzer.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/IntegrityBypassAnalyzer.cs
index 09e7abce..0be36a26 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/IntegrityBypassAnalyzer.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/IntegrityBypassAnalyzer.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Management;
using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using Microsoft.Win32;
@@ -225,7 +226,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "integrity_bypass_analysis",
+ EventType = Constants.EventTypes.IntegrityBypassAnalysis,
Severity = severity,
Source = Name,
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/LocalAdminAnalyzer.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/LocalAdminAnalyzer.cs
index c58f87c3..6a55bfae 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/LocalAdminAnalyzer.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/LocalAdminAnalyzer.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Management;
using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using Microsoft.Win32;
@@ -208,7 +209,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "local_admin_analysis",
+ EventType = Constants.EventTypes.LocalAdminAnalysis,
Severity = severity,
Source = Name,
Phase = phase,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/SoftwareInventoryAnalyzer.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/SoftwareInventoryAnalyzer.cs
index c7d4da26..c5ad6bef 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/SoftwareInventoryAnalyzer.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/SoftwareInventoryAnalyzer.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading;
using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using Microsoft.Win32;
using static AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers.SoftwareInventoryNormalization;
@@ -641,7 +642,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Analyzers
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "software_inventory_analysis",
+ EventType = Constants.EventTypes.SoftwareInventoryAnalysis,
Severity = EventSeverity.Info,
Source = Name,
Phase = phase,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.HardwareSpec.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.HardwareSpec.cs
index 81bc841d..093d1aed 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.HardwareSpec.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.HardwareSpec.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
+using AutopilotMonitor.Shared;
namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
{
@@ -27,7 +28,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
CollectGpuInfo(data);
var message = BuildHardwareSpecMessage(data);
- EmitDeviceInfoEvent("hardware_spec", message, data);
+ EmitDeviceInfoEvent(Constants.EventTypes.HardwareSpec, message, data);
}
catch (Exception ex)
{
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.NetworkAndSecurity.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.NetworkAndSecurity.cs
index 38f47fdb..2f279c38 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.NetworkAndSecurity.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.NetworkAndSecurity.cs
@@ -12,6 +12,7 @@ using AutopilotMonitor.Agent.V2.Core.Orchestration;
using AutopilotMonitor.Agent.V2.Core.Security;
using AutopilotMonitor.DecisionCore.Engine;
using AutopilotMonitor.DecisionCore.Signals;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using Microsoft.Win32;
@@ -88,14 +89,14 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
}
}
- EmitDeviceInfoEvent("network_adapters", "Network adapters configuration",
+ EmitDeviceInfoEvent(Constants.EventTypes.NetworkAdapters, "Network adapters configuration",
new Dictionary
{
{ "adapterCount", adapters.Count },
{ "adapters", adapters }
});
- EmitDeviceInfoEvent("dns_configuration", "DNS server configuration",
+ EmitDeviceInfoEvent(Constants.EventTypes.DnsConfiguration, "DNS server configuration",
new Dictionary
{
{ "dnsEntries", dnsServers }
@@ -138,7 +139,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
data["proxyType"] = proxyType;
- EmitDeviceInfoEvent("proxy_configuration", $"Proxy configuration: {proxyType}", data);
+ EmitDeviceInfoEvent(Constants.EventTypes.ProxyConfiguration, $"Proxy configuration: {proxyType}", data);
}
catch (Exception ex)
{
@@ -283,14 +284,14 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
// Include enrollment type in autopilot_profile event data
data["enrollmentType"] = detectedType;
- EmitDeviceInfoEvent("autopilot_profile", "Autopilot profile configuration", data);
+ EmitDeviceInfoEvent(Constants.EventTypes.AutopilotProfile, "Autopilot profile configuration", data);
// Emit dedicated enrollment_type_detected event for easy filtering
_post.Emit(new EnrollmentEvent
{
SessionId = _sessionId,
TenantId = _tenantId,
- EventType = "enrollment_type_detected",
+ EventType = Constants.EventTypes.EnrollmentTypeDetected,
Severity = EventSeverity.Info,
Source = "EnrollmentTracker",
Phase = EnrollmentPhase.Unknown,
@@ -360,7 +361,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
}
}
- EmitDeviceInfoEvent("secureboot_status", $"SecureBoot: {data["uefiSecureBootEnabled"]}, CA2023: {data["uefiCA2023Status"]}", data);
+ EmitDeviceInfoEvent(Constants.EventTypes.SecureBootStatus, $"SecureBoot: {data["uefiSecureBootEnabled"]}, CA2023: {data["uefiCA2023Status"]}", data);
}
catch (Exception ex)
{
@@ -395,7 +396,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
data["systemDriveProtected"] = volumes.Any(v =>
v["driveLetter"]?.ToString() == "C:" && v["protectionStatus"]?.ToString() == "1");
- EmitDeviceInfoEvent("bitlocker_status", "BitLocker encryption status", data);
+ EmitDeviceInfoEvent(Constants.EventTypes.BitLockerStatus, "BitLocker encryption status", data);
}
catch (Exception ex)
{
@@ -433,12 +434,12 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
if (data.Count > 0)
{
- EmitDeviceInfoEvent("tpm_status", $"TPM: {data["manufacturerName"]} v{data["manufacturerVersion"]}", data);
+ EmitDeviceInfoEvent(Constants.EventTypes.TpmStatus, $"TPM: {data["manufacturerName"]} v{data["manufacturerVersion"]}", data);
}
else
{
data["available"] = false;
- EmitDeviceInfoEvent("tpm_status", "TPM: not available", data);
+ EmitDeviceInfoEvent(Constants.EventTypes.TpmStatus, "TPM: not available", data);
}
}
catch (Exception ex)
@@ -497,7 +498,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
object joinTypeValue;
var joinType = data.TryGetValue("joinType", out joinTypeValue) ? joinTypeValue?.ToString() ?? "Unknown" : "Unknown";
- EmitDeviceInfoEvent("aad_join_status", $"AAD join: {joinType}", data);
+ EmitDeviceInfoEvent(Constants.EventTypes.AadJoinStatus, $"AAD join: {joinType}", data);
}
catch (Exception ex)
{
@@ -548,7 +549,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
var summary = BuildSummary(snapshot);
_logger.Info($"EnrollmentTracker: ESP configuration detected — {summary}");
- EmitDeviceInfoEvent("esp_config_detected", $"ESP configuration: {summary}", data);
+ EmitDeviceInfoEvent(Constants.EventTypes.EspConfigDetected, $"ESP configuration: {summary}", data);
PostEspConfigDetectedSignal(snapshot);
}
catch (Exception ex)
@@ -595,7 +596,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
if (activeNic == null)
{
- EmitDeviceInfoEvent("network_interface_info", "No active network interface found",
+ EmitDeviceInfoEvent(Constants.EventTypes.NetworkInterfaceInfo, "No active network interface found",
new Dictionary { { "status", "no_active_interface" } });
return;
}
@@ -623,7 +624,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
}
var message = $"Active interface: {activeNic.Description} ({data["connectionType"]}, {data["linkSpeedMbps"]} Mbps)";
- EmitDeviceInfoEvent("network_interface_info", message, data);
+ EmitDeviceInfoEvent(Constants.EventTypes.NetworkInterfaceInfo, message, data);
// Fire-and-forget WiFi signal collection — separate event, never blocks
if (isWifi)
@@ -739,7 +740,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
if (data.ContainsKey("wifiRadioType"))
message += $" ({data["wifiRadioType"]})";
- EmitDeviceInfoEvent("wifi_signal_info", message, data);
+ EmitDeviceInfoEvent(Constants.EventTypes.WifiSignalInfo, message, data);
}
}
catch (Exception ex)
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.cs
index 97be2f82..9aadb3e1 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/DeviceInfo/DeviceInfoCollector.cs
@@ -10,6 +10,7 @@ using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
using AutopilotMonitor.DecisionCore.Engine;
using AutopilotMonitor.DecisionCore.Signals;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using Microsoft.Win32;
using AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment;
@@ -186,7 +187,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
: $" (Build {currentBuild}.{buildRevision})";
}
- EmitDeviceInfoEvent("os_info", message, data);
+ EmitDeviceInfoEvent(Constants.EventTypes.OsInfo, message, data);
}
catch (Exception ex)
{
@@ -201,7 +202,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
bootData["note"] = "Boot time could not be determined";
}
- EmitDeviceInfoEvent("boot_time",
+ EmitDeviceInfoEvent(Constants.EventTypes.BootTime,
bootData.ContainsKey("bootTime")
? $"Last boot: {bootData["bootTime"]}"
: "Boot time unavailable",
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/Collectors/CommandCollector.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/Collectors/CommandCollector.cs
index 01bf9bdc..fa3a4500 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/Collectors/CommandCollector.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/Collectors/CommandCollector.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using AutopilotMonitor.Agent.V2.Core.Security;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Gather.Collectors
@@ -32,7 +33,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Gather.Collectors
SessionId = context.SessionId,
TenantId = context.TenantId,
Timestamp = DateTime.UtcNow,
- EventType = "security_warning",
+ EventType = Constants.EventTypes.SecurityWarning,
Severity = EventSeverity.Warning,
Source = "GatherRuleExecutor",
Message = $"Blocked command not on allowlist: {command} (Rule: {rule.RuleId})",
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/GatherRuleContext.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/GatherRuleContext.cs
index 6411c094..2d131d55 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/GatherRuleContext.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/GatherRuleContext.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using AutopilotMonitor.Agent.V2.Core.Logging;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment;
using AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.Ime;
@@ -58,7 +59,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Gather
SessionId = SessionId,
TenantId = TenantId,
Timestamp = DateTime.UtcNow,
- EventType = "security_warning",
+ EventType = Constants.EventTypes.SecurityWarning,
Severity = EventSeverity.Warning,
Source = "GatherRuleExecutor",
Message = $"Blocked {collectorType} target not on allowlist: {target} (Rule: {rule.RuleId})",
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/GatherRuleExecutor.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/GatherRuleExecutor.cs
index b744b66c..0ff8c842 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/GatherRuleExecutor.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Gather/GatherRuleExecutor.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading;
using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Gather.Collectors;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment;
using AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.Ime;
@@ -194,7 +195,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Gather
result["ruleId"] = rule.RuleId;
result["ruleTitle"] = rule.Title;
- var eventType = !string.IsNullOrEmpty(rule.OutputEventType) ? rule.OutputEventType : "gather_result";
+ var eventType = !string.IsNullOrEmpty(rule.OutputEventType) ? rule.OutputEventType : Constants.EventTypes.GatherResult;
// Allow collectors to override severity via _severityOverride in result
object severityOverride;
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/AgentSelfMetricsCollector.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/AgentSelfMetricsCollector.cs
index e581b085..35069214 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/AgentSelfMetricsCollector.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/AgentSelfMetricsCollector.cs
@@ -7,6 +7,7 @@ using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Monitoring.Transport;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
using AutopilotMonitor.Agent.V2.Core.Transport.Telemetry;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Periodic
@@ -176,7 +177,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Periodic
SessionId = SessionId,
TenantId = TenantId,
Timestamp = now,
- EventType = "agent_metrics_snapshot",
+ EventType = Constants.EventTypes.AgentMetricsSnapshot,
Severity = EventSeverity.Debug,
Source = "AgentSelfMetricsCollector",
Phase = EnrollmentPhase.Unknown,
@@ -213,7 +214,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Periodic
SessionId = SessionId,
TenantId = TenantId,
Timestamp = now,
- EventType = "spool_pressure_detected",
+ EventType = Constants.EventTypes.SpoolPressureDetected,
Severity = EventSeverity.Warning,
Source = "AgentSelfMetricsCollector",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/DeliveryOptimizationCollector.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/DeliveryOptimizationCollector.cs
index 4705ec8d..4419fd73 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/DeliveryOptimizationCollector.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/DeliveryOptimizationCollector.cs
@@ -8,6 +8,7 @@ using System.Text;
using System.Threading;
using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -342,7 +343,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Periodic
{
SessionId = SessionId,
TenantId = TenantId,
- EventType = "download_progress",
+ EventType = Constants.EventTypes.DownloadProgress,
Severity = EventSeverity.Debug,
Source = "DeliveryOptimizationCollector",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/PerformanceCollector.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/PerformanceCollector.cs
index 1584883e..c80d78b0 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/PerformanceCollector.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/PerformanceCollector.cs
@@ -7,6 +7,7 @@ using System.Threading;
using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Monitoring.Interop;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Periodic
@@ -245,7 +246,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Periodic
SessionId = SessionId,
TenantId = TenantId,
Timestamp = DateTime.UtcNow,
- EventType = "performance_snapshot",
+ EventType = Constants.EventTypes.PerformanceSnapshot,
Severity = EventSeverity.Debug,
Source = "PerformanceCollector",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/EnrollmentOrchestrator.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/EnrollmentOrchestrator.cs
index a32fee11..133a1376 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/EnrollmentOrchestrator.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/EnrollmentOrchestrator.cs
@@ -14,6 +14,7 @@ using AutopilotMonitor.DecisionCore.Classifiers;
using AutopilotMonitor.DecisionCore.Engine;
using AutopilotMonitor.DecisionCore.Signals;
using AutopilotMonitor.DecisionCore.State;
+using AutopilotMonitor.Shared;
namespace AutopilotMonitor.Agent.V2.Core.Orchestration
{
@@ -728,7 +729,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Orchestration
var lifecyclePost = new InformationalEventPost(_ingress, _clock);
var resumedAtUtc = _clock.UtcNow;
lifecyclePost.Emit(
- eventType: "whiteglove_resumed",
+ eventType: Constants.EventTypes.WhiteGloveResumed,
source: "Agent",
message: "WhiteGlove Part 2 resumed after reseal-reboot; Part-1 state archived.",
severity: Shared.Models.EventSeverity.Info,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Runtime/StartupEnvironmentProbes.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Runtime/StartupEnvironmentProbes.cs
index 70cda681..d2e13ee9 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Runtime/StartupEnvironmentProbes.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Runtime/StartupEnvironmentProbes.cs
@@ -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.Transport;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
namespace AutopilotMonitor.Agent.V2.Core.Runtime
@@ -124,7 +125,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Runtime
{
SessionId = configuration.SessionId,
TenantId = configuration.TenantId,
- EventType = "device_location",
+ EventType = Constants.EventTypes.DeviceLocation,
Severity = EventSeverity.Info,
Source = "StartupEnvironmentProbes",
Phase = EnrollmentPhase.Unknown,
@@ -139,7 +140,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Runtime
{
SessionId = configuration.SessionId,
TenantId = configuration.TenantId,
- EventType = "agent_trace",
+ EventType = Constants.EventTypes.AgentTrace,
Severity = EventSeverity.Warning,
Source = "StartupEnvironmentProbes",
Phase = EnrollmentPhase.Unknown,
@@ -162,7 +163,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Runtime
{
SessionId = configuration.SessionId,
TenantId = configuration.TenantId,
- EventType = "timezone_auto_set",
+ EventType = Constants.EventTypes.TimezoneAutoSet,
Severity = tz.Success ? EventSeverity.Info : EventSeverity.Warning,
Source = "StartupEnvironmentProbes",
Phase = EnrollmentPhase.Unknown,
@@ -189,7 +190,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Runtime
{
SessionId = configuration.SessionId,
TenantId = configuration.TenantId,
- EventType = "ntp_time_check",
+ EventType = Constants.EventTypes.NtpTimeCheck,
Severity = severity,
Source = "StartupEnvironmentProbes",
Phase = EnrollmentPhase.Unknown,
@@ -209,7 +210,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Runtime
{
SessionId = configuration.SessionId,
TenantId = configuration.TenantId,
- EventType = "ntp_time_check",
+ EventType = Constants.EventTypes.NtpTimeCheck,
Severity = EventSeverity.Warning,
Source = "StartupEnvironmentProbes",
Phase = EnrollmentPhase.Unknown,
@@ -288,7 +289,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Runtime
{
SessionId = configuration.SessionId,
TenantId = configuration.TenantId,
- EventType = "power_state_check",
+ EventType = Constants.EventTypes.PowerStateCheck,
Severity = severity,
Source = "StartupEnvironmentProbes",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/SignalAdapters/ImeLogTrackerAdapter.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/SignalAdapters/ImeLogTrackerAdapter.cs
index 198116df..d40f8cf9 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/SignalAdapters/ImeLogTrackerAdapter.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/SignalAdapters/ImeLogTrackerAdapter.cs
@@ -863,7 +863,7 @@ namespace AutopilotMonitor.Agent.V2.Core.SignalAdapters
if (!string.IsNullOrEmpty(script.PolicyId)) data["policyId"] = script.PolicyId!;
_post.Emit(
- eventType: "agent_trace",
+ eventType: SharedEventTypes.AgentTrace,
source: SourceLabel,
message: $"Autopilot-Monitor bootstrap v{version} detected via Platform Script stdout",
severity: EventSeverity.Info,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Telemetry/Events/BackPressureEventObserver.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Telemetry/Events/BackPressureEventObserver.cs
index 3feb94f1..a46b6bd2 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Telemetry/Events/BackPressureEventObserver.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Telemetry/Events/BackPressureEventObserver.cs
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
using AutopilotMonitor.DecisionCore.Engine;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
namespace AutopilotMonitor.Agent.V2.Core.Telemetry.Events
@@ -18,7 +19,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Telemetry.Events
///
internal sealed class BackPressureEventObserver : IBackPressureObserver
{
- internal const string EventType = "ingress_backpressure";
+ internal const string EventType = Constants.EventTypes.IngressBackpressure;
internal const string SourceId = "signal_ingress";
private readonly TelemetryEventEmitter _emitter;
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Termination/EnrollmentTerminationHandler.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Termination/EnrollmentTerminationHandler.cs
index 2e406c40..f7863f88 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Termination/EnrollmentTerminationHandler.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Termination/EnrollmentTerminationHandler.cs
@@ -291,7 +291,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Termination
{
SessionId = _configuration.SessionId,
TenantId = _configuration.TenantId,
- EventType = "whiteglove_part1_complete",
+ EventType = Constants.EventTypes.WhiteGlovePart1Complete,
Severity = EventSeverity.Info,
Source = "EnrollmentTerminationHandler",
Phase = EnrollmentPhase.Unknown,
@@ -424,7 +424,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Termination
{
SessionId = _configuration.SessionId,
TenantId = _configuration.TenantId,
- EventType = "enrollment_summary_shown",
+ EventType = Constants.EventTypes.EnrollmentSummaryShown,
Severity = EventSeverity.Info,
Source = "EnrollmentTerminationHandler",
Phase = EnrollmentPhase.Unknown,
@@ -617,7 +617,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Termination
{
SessionId = _configuration.SessionId,
TenantId = _configuration.TenantId,
- EventType = "diagnostics_collecting",
+ EventType = Constants.EventTypes.DiagnosticsCollecting,
Severity = EventSeverity.Info,
Source = "EnrollmentTerminationHandler",
Phase = EnrollmentPhase.Unknown,
@@ -648,7 +648,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Termination
{
SessionId = _configuration.SessionId,
TenantId = _configuration.TenantId,
- EventType = "diagnostics_uploaded",
+ EventType = Constants.EventTypes.DiagnosticsUploaded,
Severity = EventSeverity.Info,
Source = "EnrollmentTerminationHandler",
Phase = EnrollmentPhase.Unknown,
@@ -674,7 +674,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Termination
{
SessionId = _configuration.SessionId,
TenantId = _configuration.TenantId,
- EventType = "diagnostics_upload_failed",
+ EventType = Constants.EventTypes.DiagnosticsUploadFailed,
Severity = EventSeverity.Warning,
Source = "EnrollmentTerminationHandler",
Phase = EnrollmentPhase.Unknown,
@@ -730,7 +730,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Termination
{
SessionId = _configuration.SessionId,
TenantId = _configuration.TenantId,
- EventType = "reboot_triggered",
+ EventType = Constants.EventTypes.RebootTriggered,
Severity = EventSeverity.Info,
Source = "EnrollmentTerminationHandler",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2/Program.GatherRulesMode.cs b/src/Agent/AutopilotMonitor.Agent.V2/Program.GatherRulesMode.cs
index cf5a6cef..14b4a082 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2/Program.GatherRulesMode.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2/Program.GatherRulesMode.cs
@@ -209,7 +209,7 @@ namespace AutopilotMonitor.Agent.V2
SessionId = sessionId,
TenantId = tenantId,
Timestamp = DateTime.UtcNow,
- EventType = "gather_rules_collection_started",
+ EventType = Constants.EventTypes.GatherRulesCollectionStarted,
Severity = EventSeverity.Info,
Source = "Agent",
Phase = EnrollmentPhase.Start,
@@ -250,7 +250,7 @@ namespace AutopilotMonitor.Agent.V2
SessionId = sessionId,
TenantId = tenantId,
Timestamp = DateTime.UtcNow,
- EventType = "gather_rules_collection_completed",
+ EventType = Constants.EventTypes.GatherRulesCollectionCompleted,
Severity = EventSeverity.Info,
Source = "Agent",
Phase = EnrollmentPhase.Start,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2/Runtime/LifecycleEmitters.cs b/src/Agent/AutopilotMonitor.Agent.V2/Runtime/LifecycleEmitters.cs
index 80181bac..65581195 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2/Runtime/LifecycleEmitters.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2/Runtime/LifecycleEmitters.cs
@@ -134,7 +134,7 @@ namespace AutopilotMonitor.Agent.V2.Runtime
{
SessionId = agentConfig.SessionId,
TenantId = agentConfig.TenantId,
- EventType = "remote_config_fetch_failed",
+ EventType = SharedConstants.EventTypes.RemoteConfigFetchFailed,
Severity = EventSeverity.Warning,
Source = "Agent",
Phase = EnrollmentPhase.Unknown,
@@ -205,7 +205,7 @@ namespace AutopilotMonitor.Agent.V2.Runtime
{
SessionId = agentConfig.SessionId,
TenantId = agentConfig.TenantId,
- EventType = "agent_unrestricted_mode_changed",
+ EventType = SharedConstants.EventTypes.AgentUnrestrictedModeChanged,
Severity = newValue ? EventSeverity.Warning : EventSeverity.Info,
Source = "RemoteConfigMerger",
Phase = EnrollmentPhase.Unknown,
@@ -623,7 +623,7 @@ namespace AutopilotMonitor.Agent.V2.Runtime
{
SessionId = agentConfig.SessionId,
TenantId = agentConfig.TenantId,
- EventType = "enrollment_failed",
+ EventType = SharedConstants.EventTypes.EnrollmentFailed,
Severity = EventSeverity.Error,
Source = "EnrollmentOrchestrator",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/Agent/AutopilotMonitor.Agent.V2/Runtime/ServerControlPlane.cs b/src/Agent/AutopilotMonitor.Agent.V2/Runtime/ServerControlPlane.cs
index 960a670c..0cc51858 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2/Runtime/ServerControlPlane.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2/Runtime/ServerControlPlane.cs
@@ -7,6 +7,7 @@ using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
using AutopilotMonitor.Agent.V2.Core.Termination;
+using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
namespace AutopilotMonitor.Agent.V2.Runtime
@@ -177,7 +178,7 @@ namespace AutopilotMonitor.Agent.V2.Runtime
{
SessionId = agentConfig.SessionId,
TenantId = agentConfig.TenantId,
- EventType = "admin_marked_session",
+ EventType = Constants.EventTypes.AdminMarkedSession,
Severity = outcome == EnrollmentTerminationOutcome.Succeeded ? EventSeverity.Info : EventSeverity.Warning,
Source = "WireTelemetryServerResponse",
Phase = EnrollmentPhase.Unknown,
diff --git a/src/McpServer/autopilot-monitor-mcp/src/__tests__/event-types-drift.test.ts b/src/McpServer/autopilot-monitor-mcp/src/__tests__/event-types-drift.test.ts
new file mode 100644
index 00000000..ceb5c70b
--- /dev/null
+++ b/src/McpServer/autopilot-monitor-mcp/src/__tests__/event-types-drift.test.ts
@@ -0,0 +1,79 @@
+/**
+ * Drift guard: the MCP event-type catalog (resource-catalog.ts) MUST stay in sync
+ * with the canonical C# source `Constants.EventTypes`. The agent-side guard test
+ * (EventTypeConstantGuardTests) keeps Constants complete; this test keeps the MCP
+ * catalog equal to it, so the catalog never advertises a phantom type nor omits a
+ * real one (which would silently degrade event-TYPE-driven cross-session search).
+ *
+ * It reads Constants.cs directly — no codegen, no runtime coupling. Same-repo only;
+ * skipped gracefully if the C# source can't be located (e.g. an isolated package).
+ */
+import { describe, it, expect } from 'vitest';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+import { existsSync, readFileSync } from 'node:fs';
+import { EVENT_TYPES_CATALOG, INTERNAL_EVENT_TYPES, ALL_EVENT_TYPES } from '../resource-catalog.js';
+
+/** Walk up from this test file to the repo root (marked by AutopilotMonitor.sln). */
+function findConstantsCs(): string | null {
+ let dir = dirname(fileURLToPath(import.meta.url));
+ for (let i = 0; i < 12; i++) {
+ if (existsSync(join(dir, 'AutopilotMonitor.sln'))) {
+ const p = join(dir, 'src', 'Shared', 'AutopilotMonitor.Shared', 'Constants.cs');
+ return existsSync(p) ? p : null;
+ }
+ const parent = dirname(dir);
+ if (parent === dir) break;
+ dir = parent;
+ }
+ return null;
+}
+
+/** Extract every event-type string value declared inside `class EventTypes`. */
+function canonicalEventTypes(source: string): Set {
+ const start = source.indexOf('class EventTypes');
+ expect(start, 'class EventTypes not found in Constants.cs').toBeGreaterThan(-1);
+ // The EventTypes body ends at the next `class ` declaration (EventSources).
+ const after = source.indexOf('class ', start + 'class EventTypes'.length);
+ const body = source.slice(start, after === -1 ? undefined : after);
+ const values = new Set();
+ for (const m of body.matchAll(/=\s*"([a-z][a-z0-9_]+)"/g)) values.add(m[1]);
+ return values;
+}
+
+describe('event-type catalog drift vs C# Constants.EventTypes', () => {
+ const constantsPath = findConstantsCs();
+ const itOrSkip = constantsPath ? it : it.skip;
+
+ itOrSkip('catalog + internal exactly equals the canonical C# event types', () => {
+ const canonical = canonicalEventTypes(readFileSync(constantsPath!, 'utf8'));
+ const ours = new Set(ALL_EVENT_TYPES);
+
+ const missingInTs = [...canonical].filter((t) => !ours.has(t)).sort();
+ const phantomInTs = [...ours].filter((t) => !canonical.has(t)).sort();
+
+ expect(
+ missingInTs,
+ 'Event types defined in Constants.EventTypes but ABSENT from the MCP catalog ' +
+ '(add them to a group in resource-catalog.ts — search/catalog recall is degraded without them):',
+ ).toEqual([]);
+ expect(
+ phantomInTs,
+ 'Event types in the MCP catalog with NO matching Constants.EventTypes const ' +
+ '(remove them or add the const — the model is told these are valid when they are not):',
+ ).toEqual([]);
+ });
+
+ it('public catalog excludes internal/TEMP types', () => {
+ const flat = Object.values(EVENT_TYPES_CATALOG).flat();
+ for (const internal of INTERNAL_EVENT_TYPES) {
+ expect(flat, `${internal} is internal — keep it out of the public catalog`).not.toContain(internal);
+ }
+ });
+
+ it('no event type appears in more than one catalog group', () => {
+ const flat = Object.values(EVENT_TYPES_CATALOG).flat();
+ const dupes = flat.filter((t, i) => flat.indexOf(t) !== i);
+ expect(dupes, 'duplicate event types across catalog groups').toEqual([]);
+ });
+});
diff --git a/src/McpServer/autopilot-monitor-mcp/src/resource-catalog.ts b/src/McpServer/autopilot-monitor-mcp/src/resource-catalog.ts
index 9b33bd94..f5b4a946 100644
--- a/src/McpServer/autopilot-monitor-mcp/src/resource-catalog.ts
+++ b/src/McpServer/autopilot-monitor-mcp/src/resource-catalog.ts
@@ -5,14 +5,53 @@
* whose resource discovery is broken).
*/
+// Single source of truth for the model-facing event-type catalog. MUST stay in
+// sync with C# `Constants.EventTypes` (Shared) — enforced by the vitest drift test
+// in __tests__/event-types-drift.test.ts, which reads Constants.cs and asserts
+// (all catalog values ∪ INTERNAL_EVENT_TYPES) === the C# const values. When the
+// agent adds a new event type to Constants.EventTypes, add it to a group here too.
export const EVENT_TYPES_CATALOG = {
phase_events: [
'phase_transition',
'esp_state_change',
+ 'esp_phase_changed',
+ 'esp_provisioning_status',
+ 'esp_ui_state',
+ 'esp_provisioning_raw',
+ 'esp_failure',
+ 'esp_failure_advisory',
+ 'esp_failure_settle_started',
+ 'esp_exiting',
+ 'esp_config_detected',
'completion_check',
'enrollment_complete',
'enrollment_failed',
'desktop_arrived',
+ 'desktop_detector_started',
+ 'desktop_detector_first_poll',
+ 'desktop_detector_no_candidate',
+ ],
+ whiteglove_events: [
+ 'whiteglove_complete',
+ 'whiteglove_started',
+ 'whiteglove_resumed',
+ 'whiteglove_part1_complete',
+ 'whiteglove_classification',
+ ],
+ hello_events: [
+ 'hello_policy_detected',
+ 'hello_policy_detection_mismatch',
+ 'hello_wait_timeout',
+ 'hello_completion_timeout',
+ 'hello_wizard_started',
+ 'hello_processing_started',
+ 'hello_processing_stopped',
+ 'hello_provisioning_completed',
+ 'hello_provisioning_failed',
+ 'hello_provisioning_blocked',
+ 'hello_pin_status',
+ 'hello_skipped',
+ 'waiting_for_hello',
],
app_events: [
'app_install_started',
@@ -20,33 +59,118 @@ export const EVENT_TYPES_CATALOG = {
'app_install_failed',
'app_download_started',
'app_install_skipped',
+ 'download_progress',
+ 'do_telemetry',
+ 'all_apps_completed',
+ 'app_tracking_summary',
],
- network_events: ['network_state_change', 'network_connectivity_check'],
- device_info_events: [
- 'os_info',
- 'hardware_spec',
- 'tpm_status',
- 'autopilot_profile',
- 'secureboot_status',
- 'bitlocker_status',
+ script_events: ['script_started', 'script_completed', 'script_failed'],
+ network_events: [
+ 'network_state_change',
+ 'network_connectivity_check',
'network_adapters',
'network_interface_info',
- 'aad_join_status',
+ 'dns_configuration',
+ 'proxy_configuration',
+ 'wifi_signal_info',
+ ],
+ device_info_events: [
+ 'os_info',
+ 'boot_time',
+ 'hardware_spec',
+ 'tpm_status',
+ 'secureboot_status',
+ 'bitlocker_status',
+ 'autopilot_profile',
'enrollment_type_detected',
+ 'aad_join_status',
+ 'cert_validation',
+ 'device_location',
+ 'timezone_auto_set',
+ 'ntp_time_check',
+ 'power_state_check',
],
- error_events: ['error_detected'],
- vulnerability_events: ['software_inventory_analysis', 'vulnerability_report'],
- other: [
- 'performance_snapshot',
- 'log_entry',
- 'gather_result',
- 'script_started',
- 'script_completed',
- 'script_failed',
+ identity_events: [
+ 'aad_placeholder_user_detected',
+ 'aad_user_joined_observed',
+ 'hybrid_login_pending',
+ ],
+ realmjoin_events: [
+ 'realmjoin_detected',
+ 'realmjoin_phase_changed',
+ 'realmjoin_resolved',
+ 'realmjoin_timeout',
+ 'realmjoin_package_started',
+ 'realmjoin_package_completed',
+ ],
+ deployment_events: [
+ 'modern_deployment_log',
+ 'modern_deployment_warning',
+ 'modern_deployment_error',
+ 'configmgr_client_detected',
+ ],
+ lifecycle_events: [
+ 'agent_started',
+ 'agent_shutting_down',
+ 'agent_shutdown',
+ 'agent_version_check',
'ime_agent_version',
+ 'ime_user_session_completed',
+ 'ime_session_change',
+ 'ime_process_exited',
+ 'system_reboot_detected',
+ 'prior_run_died_with_state',
+ 'previous_crash_detected',
+ 'performance_collector_stopped',
+ 'agent_metrics_collector_stopped',
+ 'agent_unrestricted_mode_changed',
+ 'remote_config_fetch_failed',
+ 'agent_trace',
],
+ health_events: [
+ 'stall_probe_check',
+ 'stall_probe_result',
+ 'session_stalled',
+ 'spool_pressure_detected',
+ ],
+ metrics_events: ['performance_snapshot', 'agent_metrics_snapshot', 'ingress_backpressure'],
+ security_events: [
+ 'integrity_bypass_analysis',
+ 'local_admin_analysis',
+ 'security_warning',
+ 'security_audit',
+ ],
+ vulnerability_events: ['software_inventory_analysis', 'vulnerability_report'],
+ diagnostics_events: ['diagnostics_collecting', 'diagnostics_uploaded', 'diagnostics_upload_failed'],
+ server_action_events: [
+ 'server_action_received',
+ 'server_action_executed',
+ 'server_action_failed',
+ 'admin_marked_session',
+ ],
+ gather_events: ['gather_result', 'gather_rules_collection_started', 'gather_rules_collection_completed'],
+ termination_events: ['enrollment_summary_shown', 'reboot_triggered'],
+ classification_events: ['enrollment_type_mismatch', 'decision_process_completion'],
+ other: ['error_detected', 'log_entry', 'esp_resumed', 'esp_provisioning_settle_started'],
} as const;
+/**
+ * Event types that exist on the wire but are intentionally NOT advertised in the
+ * public catalog (TEMP / internal). They ARE searchable (kept in ALL_EVENT_TYPES)
+ * so historical recall works, but the model shouldn't treat them as a normal type.
+ */
+export const INTERNAL_EVENT_TYPES = ['shadow_discrepancy'] as const;
+
+/**
+ * Flat list of every known event type (public catalog + internal). Used by the
+ * search tools for keyword→event-type candidate matching, where complete recall
+ * matters more than hiding TEMP types.
+ */
+export const ALL_EVENT_TYPES: readonly string[] = [
+ ...Object.values(EVENT_TYPES_CATALOG).flat(),
+ ...INTERNAL_EVENT_TYPES,
+];
+
export const DEVICE_PROPERTIES_CATALOG = {
_usage: {
note: 'Use these keys in the deviceProperties parameter of search_sessions.',
diff --git a/src/McpServer/autopilot-monitor-mcp/src/tools/search.ts b/src/McpServer/autopilot-monitor-mcp/src/tools/search.ts
index 62aeba69..3bc3782e 100644
--- a/src/McpServer/autopilot-monitor-mcp/src/tools/search.ts
+++ b/src/McpServer/autopilot-monitor-mcp/src/tools/search.ts
@@ -5,6 +5,7 @@ import { withToolTelemetry } from '../telemetry.js';
import type { SearchProvider } from '../search-provider.js';
import { READ_ONLY, MAX_RESULT_SIZE_CHARS, toolResultText, SessionIdSchema } from './shared.js';
import { toolError } from './error-handler.js';
+import { ALL_EVENT_TYPES } from '../resource-catalog.js';
// ── Helpers ─────────────────────────────────────────────────────────────
@@ -15,22 +16,12 @@ type EventEntry = {
sessionId?: string;
};
-/** All known event type strings — used for index-based pre-filtering. */
-const KNOWN_EVENT_TYPES = [
- 'phase_transition', 'esp_state_change', 'completion_check',
- 'enrollment_complete', 'enrollment_failed', 'desktop_arrived',
- 'app_install_started', 'app_install_completed', 'app_install_failed',
- 'app_download_started', 'app_install_skipped',
- 'network_state_change', 'network_connectivity_check',
- 'os_info', 'hardware_spec', 'tpm_status', 'autopilot_profile',
- 'secureboot_status', 'bitlocker_status', 'network_adapters',
- 'network_interface_info', 'aad_join_status', 'enrollment_type_detected',
- 'error_detected', 'software_inventory_analysis', 'vulnerability_report',
- 'performance_snapshot', 'log_entry', 'gather_result',
- 'script_started', 'script_completed', 'script_failed', 'ime_agent_version',
- 'do_telemetry', 'download_progress', 'agent_started',
- 'esp_phase_changed', 'shadow_discrepancy',
-];
+/**
+ * All known event-type strings — used for index-based pre-filtering. Derived from
+ * the single-source catalog (resource-catalog.ts), which is drift-tested against the
+ * canonical C# `Constants.EventTypes`. Includes internal/TEMP types for full recall.
+ */
+const KNOWN_EVENT_TYPES = ALL_EVENT_TYPES;
/** Match query keywords against known event types using prefix-aware matching. */
function extractEventTypeCandidates(keywords: string[]): string[] {
@@ -392,20 +383,16 @@ export function registerSearchTools(server: McpServer, knowledgeBase: SearchProv
title: 'Search Events (Semantic)',
description:
'TIER 1 — FAST EVENT SEARCH (try this first). ' +
- 'Searches enrollment events by matching keywords against event type, message, source, severity, and data fields. ' +
- 'Uses prefix-aware matching (e.g. "install" matches "installation", "installed", "app_install_failed") ' +
- 'and weighted field scoring (matches in eventType rank higher than in data). ' +
- 'When the query implies a problem (error/fail/stuck/timeout…), ranking lifts failure/Warning events and damps ' +
- 'benign Info/Trace events. `matchedSessionIds` lists the distinct sessions behind the ranked hits (drill into ' +
- 'these next); `sessionsSearchedCount` is how many sessions were scanned. ' +
- 'IMPORTANT — cross-session recall is event-TYPE-driven: with no sessionId, the scan first maps your keywords to ' +
- 'known event types (see the event_types catalog) and only fetches events of those types. A concept that has no ' +
- 'event type of its own (e.g. "certificate") will NOT surface cross-session even when it lives inside another ' +
- 'event\'s data/message — so if a keyword maps to no event type, that info is likely buried elsewhere: pass a ' +
- 'sessionId (which scans EVERY field of EVERY event in that session) or search by a related event type instead. ' +
- 'Provide sessionId to search within one session, or omit for a fast (single-page-per-type) cross-session scan. ' +
- 'This is a bounded scan: check the returned `truncated` flag — if true, recall is incomplete, so escalate to ' +
- 'deep_search_events for a broader multi-page scan or narrow the query as `recallNote` suggests.',
+ 'Matches keywords against event type, message, source, severity, and data, prefix-aware (e.g. "install" ' +
+ 'matches "app_install_failed") with weighted scoring (eventType > data). Problem queries ' +
+ '(error/fail/stuck/timeout…) lift failure/Warning events and damp benign Info/Trace. ' +
+ '`matchedSessionIds` are the sessions behind the ranked hits (drill in next); `sessionsSearchedCount` is how ' +
+ 'many were scanned. ' +
+ 'IMPORTANT — cross-session recall is event-TYPE-driven: without a sessionId the scan maps keywords to known ' +
+ 'event types (see event_types catalog) and fetches only those. A term with no event type of its own (e.g. ' +
+ '"certificate") never surfaces cross-session even when it sits in another event\'s data — if `keywordsUsed` ' +
+ 'shows it mapped to none, pass a sessionId (scans EVERY field of EVERY event) or search by a related type. ' +
+ 'Bounded scan: if `truncated` is true, recall is incomplete — escalate to deep_search_events or narrow per `recallNote`.',
inputSchema: {
query: z.string().describe('Natural language description of what to find (e.g. "app download stuck", "certificate error", "disk space low")'),
sessionId: SessionIdSchema.optional().describe('Search within a specific session. If omitted, searches across recent failed sessions.'),
@@ -546,24 +533,17 @@ export function registerSearchTools(server: McpServer, knowledgeBase: SearchProv
title: 'Deep Search Events',
description:
'TIER 3 — DEEP SEARCH (thorough, use when accuracy is critical). ' +
- 'Uses the same weighted keyword scoring as search_events_semantic but with lower thresholds and a broader, ' +
- 'paginated scan: for each event type matched from the query it walks the cross-session index, following pages ' +
- 'up to a generous budget, then ranks the matches. Scores across ALL event fields including full DataJson content. ' +
- 'Returns the top `topK` ranked matches (NOT every matching event) — compare `resultCount` vs `eventsMatched`, ' +
- 'and use get_session_events / search_sessions_by_event for full per-event recall. ' +
- 'When the query implies a problem (error/fail/stuck/timeout…), ranking lifts failure/Warning events and damps ' +
- 'benign Info/Trace events so a successful enrollment can\'t outrank a real error. ' +
- '`matchedSessionIds` lists the distinct sessions behind the ranked hits (drill into these next); ' +
- '`sessionsSearchedCount` is how many sessions were scanned. ' +
- 'IMPORTANT — cross-session recall is event-TYPE-driven: with no sessionId the scan maps your keywords to known ' +
- 'event types (see the event_types catalog) and only fetches events of those types; it then scores ALL their ' +
- 'fields including full DataJson, but a concept with no event type of its own (e.g. "certificate") is never ' +
- 'reached cross-session even when it lives inside another event\'s data. If `keywordsUsed` shows your key term ' +
- 'mapped to no event type, that info is likely buried elsewhere — pass a sessionId (scans EVERY field of EVERY ' +
- 'event in the session, complete and untruncated) or search by a related event type. ' +
- 'Check the `truncated` flag: if true, the scan hit its page/time budget (or fell back to recent failed ' +
- 'sessions) and recall is incomplete — narrow the query as `recallNote` suggests. ' +
- 'Provide sessionId to search within one session (complete, no truncation), or omit to search across sessions.' +
+ 'Same weighted scoring as search_events_semantic but lower thresholds and a broader multi-page scan per event ' +
+ 'type, ranking across ALL fields incl. full DataJson. Problem queries (error/fail/stuck/timeout…) lift ' +
+ 'failure/Warning events and damp benign Info/Trace. Returns the top `topK` (NOT every match) — compare ' +
+ '`resultCount` vs `eventsMatched`; use get_session_events / search_sessions_by_event for full per-event recall. ' +
+ '`matchedSessionIds` are the sessions behind the ranked hits (drill in next); `sessionsSearchedCount` is how ' +
+ 'many were scanned. ' +
+ 'IMPORTANT — cross-session recall is event-TYPE-driven: without a sessionId the scan maps keywords to known ' +
+ 'event types (see event_types catalog) and fetches only those. A term with no event type of its own (e.g. ' +
+ '"certificate") never surfaces cross-session even when it sits in another event\'s data — if `keywordsUsed` ' +
+ 'shows it mapped to none, pass a sessionId (scans EVERY field of EVERY event, complete) or search by a related type. ' +
+ 'If `truncated` is true, recall is incomplete — narrow per `recallNote`.' +
(ga ? ' Omit tenantId for cross-tenant search (Global Admin), or specify tenantId for single-tenant.' : ''),
inputSchema: {
query: z.string().describe('Natural language description of what to find (e.g. "app download stuck", "certificate error", "disk space low")'),
diff --git a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Classic.cs b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Classic.cs
index f86f1cfe..2cb57b52 100644
--- a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Classic.cs
+++ b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Classic.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using AutopilotMonitor.DecisionCore.Signals;
using AutopilotMonitor.DecisionCore.State;
using AutopilotMonitor.Shared.Models;
+using SharedConstants = AutopilotMonitor.Shared.Constants;
namespace AutopilotMonitor.DecisionCore.Engine
{
@@ -664,7 +665,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
kind: DecisionEffectKind.EmitEventTimelineEntry,
parameters: new Dictionary
{
- ["eventType"] = "phase_transition",
+ ["eventType"] = SharedConstants.EventTypes.PhaseTransition,
["phase"] = phase.ToString(),
["source"] = "DecisionEngine",
["message"] = $"Phase: {phase}",
@@ -694,7 +695,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
kind: DecisionEffectKind.EmitEventTimelineEntry,
parameters: new Dictionary
{
- ["eventType"] = "enrollment_complete",
+ ["eventType"] = SharedConstants.EventTypes.EnrollmentComplete,
},
typedPayload: DecisionAuditTrailBuilder.Build(
postState: state,
diff --git a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Edge.cs b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Edge.cs
index 8b48a38c..806b3499 100644
--- a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Edge.cs
+++ b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Edge.cs
@@ -315,7 +315,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
{
["eventType"] = advisoryPath
? SharedConstants.EventTypes.EspFailureAdvisory
- : "enrollment_failed",
+ : SharedConstants.EventTypes.EnrollmentFailed,
["reason"] = reason,
};
diff --git a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Shared.cs b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Shared.cs
index 793d551d..79c609c4 100644
--- a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Shared.cs
+++ b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.Shared.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using AutopilotMonitor.DecisionCore.Signals;
using AutopilotMonitor.DecisionCore.State;
using AutopilotMonitor.Shared.Models;
+using SharedConstants = AutopilotMonitor.Shared.Constants;
namespace AutopilotMonitor.DecisionCore.Engine
{
@@ -122,7 +123,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
DecisionEffectKind.EmitEventTimelineEntry,
parameters: new Dictionary(StringComparer.Ordinal)
{
- ["eventType"] = "enrollment_failed",
+ ["eventType"] = SharedConstants.EventTypes.EnrollmentFailed,
["reason"] = reason,
},
typedPayload: DecisionAuditTrailBuilder.Build(
@@ -264,7 +265,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
var succeeded = string.Equals(adminOutcome, "Succeeded", StringComparison.OrdinalIgnoreCase);
var toStage = succeeded ? SessionStage.Completed : SessionStage.Failed;
- var eventType = succeeded ? "enrollment_complete" : "enrollment_failed";
+ var eventType = succeeded ? SharedConstants.EventTypes.EnrollmentComplete : SharedConstants.EventTypes.EnrollmentFailed;
var nextStep = state.StepIndex + 1;
var newState = state.ToBuilder()
diff --git a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.WhiteGlove.cs b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.WhiteGlove.cs
index 02a13b2a..e694a69b 100644
--- a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.WhiteGlove.cs
+++ b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionEngine.WhiteGlove.cs
@@ -4,6 +4,7 @@ using AutopilotMonitor.DecisionCore.Classifiers;
using AutopilotMonitor.DecisionCore.Signals;
using AutopilotMonitor.DecisionCore.State;
using AutopilotMonitor.Shared.Models;
+using SharedConstants = AutopilotMonitor.Shared.Constants;
namespace AutopilotMonitor.DecisionCore.Engine
{
@@ -114,7 +115,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
{
new DecisionEffect(
DecisionEffectKind.EmitEventTimelineEntry,
- parameters: new Dictionary { ["eventType"] = "whiteglove_complete" },
+ parameters: new Dictionary { ["eventType"] = SharedConstants.EventTypes.WhiteGloveComplete },
typedPayload: DecisionAuditTrailBuilder.Build(
postState: sealedState,
decidedStage: SessionStage.WhiteGloveSealed,
@@ -231,7 +232,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
{
new DecisionEffect(
DecisionEffectKind.EmitEventTimelineEntry,
- parameters: new Dictionary { ["eventType"] = "whiteglove_complete" },
+ parameters: new Dictionary { ["eventType"] = SharedConstants.EventTypes.WhiteGloveComplete },
typedPayload: DecisionAuditTrailBuilder.Build(
postState: sealedState,
decidedStage: SessionStage.WhiteGloveSealed,
diff --git a/src/Shared/AutopilotMonitor.Shared/Constants.cs b/src/Shared/AutopilotMonitor.Shared/Constants.cs
index b115a451..62f7173d 100644
--- a/src/Shared/AutopilotMonitor.Shared/Constants.cs
+++ b/src/Shared/AutopilotMonitor.Shared/Constants.cs
@@ -324,6 +324,110 @@ namespace AutopilotMonitor.Shared
public const string RealmJoinTimeout = "realmjoin_timeout";
public const string RealmJoinPackageStarted = "realmjoin_package_started";
public const string RealmJoinPackageCompleted = "realmjoin_package_completed";
+
+ // =====================================================================
+ // Single-source consolidation (2026-05-29): event-type string literals
+ // that were previously emitted inline across V2 collectors/trackers are
+ // now defined here so Constants.EventTypes is the ONE canonical catalog.
+ // Values are byte-identical to the former literals (persisted strings —
+ // rules/UI/MCP match on them). A drift guard test enforces that no V2
+ // emit site uses a raw literal not defined here.
+ // =====================================================================
+
+ // Device info / environment (DeviceInfoCollector + StartupEnvironmentProbes)
+ public const string OsInfo = "os_info";
+ public const string BootTime = "boot_time";
+ public const string HardwareSpec = "hardware_spec";
+ public const string NetworkAdapters = "network_adapters";
+ public const string DnsConfiguration = "dns_configuration";
+ public const string ProxyConfiguration = "proxy_configuration";
+ public const string AutopilotProfile = "autopilot_profile";
+ public const string EnrollmentTypeDetected = "enrollment_type_detected";
+ public const string SecureBootStatus = "secureboot_status";
+ public const string BitLockerStatus = "bitlocker_status";
+ public const string TpmStatus = "tpm_status";
+ public const string AadJoinStatus = "aad_join_status";
+ public const string EspConfigDetected = "esp_config_detected";
+ public const string NetworkInterfaceInfo = "network_interface_info";
+ public const string WifiSignalInfo = "wifi_signal_info";
+ public const string DeviceLocation = "device_location";
+ public const string TimezoneAutoSet = "timezone_auto_set";
+ public const string NtpTimeCheck = "ntp_time_check";
+ public const string PowerStateCheck = "power_state_check";
+ public const string AgentTrace = "agent_trace";
+
+ // Crash / process lifecycle
+ public const string PreviousCrashDetected = "previous_crash_detected";
+ public const string ImeProcessExited = "ime_process_exited";
+
+ // White Glove / pre-provisioning
+ public const string WhiteGloveStarted = "whiteglove_started";
+ public const string WhiteGloveResumed = "whiteglove_resumed";
+ public const string WhiteGlovePart1Complete = "whiteglove_part1_complete";
+
+ // Windows Hello for Business provisioning
+ public const string HelloPolicyDetected = "hello_policy_detected";
+ public const string HelloPolicyDetectionMismatch = "hello_policy_detection_mismatch";
+ public const string HelloWaitTimeout = "hello_wait_timeout";
+ public const string HelloCompletionTimeout = "hello_completion_timeout";
+ public const string HelloWizardStarted = "hello_wizard_started";
+ public const string HelloProcessingStarted = "hello_processing_started";
+ public const string HelloProcessingStopped = "hello_processing_stopped";
+ public const string HelloProvisioningCompleted = "hello_provisioning_completed";
+ public const string HelloProvisioningFailed = "hello_provisioning_failed";
+ public const string HelloProvisioningBlocked = "hello_provisioning_blocked";
+ public const string HelloPinStatus = "hello_pin_status";
+ public const string HelloSkipped = "hello_skipped";
+
+ // ESP provisioning detail
+ public const string EspFailureSettleStarted = "esp_failure_settle_started";
+ public const string EspProvisioningRaw = "esp_provisioning_raw";
+ public const string EspFailure = "esp_failure";
+ public const string EspExiting = "esp_exiting";
+
+ // Security / inventory analyzers
+ public const string IntegrityBypassAnalysis = "integrity_bypass_analysis";
+ public const string LocalAdminAnalysis = "local_admin_analysis";
+ public const string SecurityWarning = "security_warning";
+
+ // Termination / diagnostics / server actions
+ public const string EnrollmentSummaryShown = "enrollment_summary_shown";
+ public const string DiagnosticsCollecting = "diagnostics_collecting";
+ public const string DiagnosticsUploaded = "diagnostics_uploaded";
+ public const string DiagnosticsUploadFailed = "diagnostics_upload_failed";
+ public const string RebootTriggered = "reboot_triggered";
+ public const string ServerActionReceived = "server_action_received";
+ public const string ServerActionExecuted = "server_action_executed";
+ public const string ServerActionFailed = "server_action_failed";
+ public const string AdminMarkedSession = "admin_marked_session";
+ public const string RemoteConfigFetchFailed = "remote_config_fetch_failed";
+ public const string AgentUnrestrictedModeChanged = "agent_unrestricted_mode_changed";
+
+ // Gather-rules collection mode
+ public const string GatherRulesCollectionStarted = "gather_rules_collection_started";
+ public const string GatherRulesCollectionCompleted = "gather_rules_collection_completed";
+
+ // Agent self-metrics / back-pressure
+ public const string SpoolPressureDetected = "spool_pressure_detected";
+ public const string AgentMetricsSnapshot = "agent_metrics_snapshot";
+ public const string IngressBackpressure = "ingress_backpressure";
+
+ // ---------------------------------------------------------------------
+ // V1 (legacy) event types — emitted ONLY by the decommissioning V1 agent
+ // (Agent.Core / Agent). Defined here so the canonical catalog and the MCP
+ // search/catalog stay complete for historical V1 session analysis. V1 emit
+ // sites are intentionally NOT refactored to use these (V1 is going away).
+ // ---------------------------------------------------------------------
+ public const string ConfigMgrClientDetected = "configmgr_client_detected";
+ public const string EnrollmentTypeMismatch = "enrollment_type_mismatch";
+ public const string DecisionProcessCompletion = "decision_process_completion";
+ public const string WhiteGloveClassification = "whiteglove_classification";
+ public const string EspResumed = "esp_resumed";
+ public const string WaitingForHello = "waiting_for_hello";
+ public const string EspProvisioningSettleStarted = "esp_provisioning_settle_started";
+ public const string ImeSessionChange = "ime_session_change";
+ public const string SecurityAudit = "security_audit";
+ public const string AgentShutdown = "agent_shutdown"; // distinct from V2 AgentShuttingDown
}
// -----------------------------------------------------------------------