refactor(events): single-source event-type catalog in Constants.EventTypes + drift guards

Event types were defined in 3 hand-maintained, drifted copies: Constants.EventTypes
(C#, incomplete — most types were raw string literals at ~70 V2 emit sites),
KNOWN_EVENT_TYPES (MCP search), and EVENT_TYPES_CATALOG (MCP resource). The MCP catalog
advertised 6 phantom types that are never emitted and omitted ~80 real ones, silently
degrading the now event-TYPE-driven cross-session search recall.

Make Constants.EventTypes the ONE canonical source:
- Add 60 consts (49 V2/both + 11 legacy V1 types kept so historical V1 sessions stay
  analyzable). Refactor ~70 V2 emit sites from literals to consts (values byte-identical;
  persisted strings matched by rules/UI/MCP). V1 emit sites left untouched (decommissioning).
- Guard test (V2): scan V2 source for the 6 emit shapes, assert every literal is defined in
  Constants.EventTypes — prevents re-drift. A deeper sweep this test validated also caught 11
  emitted types the initial audit missed (local-var emit shape): whiteglove_part1_complete,
  hello_*, hello_wizard_started, esp_failure, esp_exiting.
- MCP: resource-catalog.ts becomes the single TS source (130 types, 21 groups);
  KNOWN_EVENT_TYPES derives from it; shadow_discrepancy marked internal (searchable, not
  advertised). Drift test reads Constants.cs and asserts catalog ∪ internal === C# values.

Pure refactor — no event-type string value changed. Tests: DecisionCore 357/357,
V2.Core 1186/1186 (incl. guard), MCP 109/109, all tsc/build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-05-29 18:24:15 +02:00
parent 6728dc474d
commit 469c692866
36 changed files with 592 additions and 151 deletions
@@ -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
{
/// <summary>
/// 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
/// <see cref="Constants.EventTypes"/> — 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.
/// <para>
/// Scope: V2 only (Agent.V2.Core, Agent.V2, DecisionCore). V1 is decommissioning and is not
/// guarded. Known boundary: gather-rule <c>OutputEventType</c> values are data-driven (rule
/// JSON), not code literals, so they are out of scope by design.
/// </para>
/// </summary>
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<string> 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<string>();
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));
}
/// <summary>All string-const values declared on <see cref="Constants.EventTypes"/>.</summary>
private static HashSet<string> 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<string> 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);
}
}
}
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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); }
@@ -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);
@@ -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,
@@ -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,
@@ -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,
@@ -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)
{
@@ -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<string, object>
{
{ "adapterCount", adapters.Count },
{ "adapters", adapters }
});
EmitDeviceInfoEvent("dns_configuration", "DNS server configuration",
EmitDeviceInfoEvent(Constants.EventTypes.DnsConfiguration, "DNS server configuration",
new Dictionary<string, object>
{
{ "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<string, object> { { "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)
@@ -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",
@@ -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})",
@@ -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})",
@@ -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;
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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
/// </summary>
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;
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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<string> {
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<string>();
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([]);
});
});
@@ -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.',
@@ -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")'),
@@ -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<string, string>
{
["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<string, string>
{
["eventType"] = "enrollment_complete",
["eventType"] = SharedConstants.EventTypes.EnrollmentComplete,
},
typedPayload: DecisionAuditTrailBuilder.Build(
postState: state,
@@ -315,7 +315,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
{
["eventType"] = advisoryPath
? SharedConstants.EventTypes.EspFailureAdvisory
: "enrollment_failed",
: SharedConstants.EventTypes.EnrollmentFailed,
["reason"] = reason,
};
@@ -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<string, string>(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()
@@ -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<string, string> { ["eventType"] = "whiteglove_complete" },
parameters: new Dictionary<string, string> { ["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<string, string> { ["eventType"] = "whiteglove_complete" },
parameters: new Dictionary<string, string> { ["eventType"] = SharedConstants.EventTypes.WhiteGloveComplete },
typedPayload: DecisionAuditTrailBuilder.Build(
postState: sealedState,
decidedStage: SessionStage.WhiteGloveSealed,
@@ -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
}
// -----------------------------------------------------------------------