mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-26 05:29:43 +00:00
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:
@@ -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,
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+15
-14
@@ -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,
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
+9
-8
@@ -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); }
|
||||
|
||||
+4
-3
@@ -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);
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+2
-1
@@ -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)
|
||||
{
|
||||
|
||||
+15
-14
@@ -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)
|
||||
|
||||
+3
-2
@@ -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",
|
||||
|
||||
+2
-1
@@ -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})",
|
||||
|
||||
+2
-1
@@ -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})",
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+3
-2
@@ -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,
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+6
-6
@@ -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
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user