mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
feat(agent): OOBE-state observability via WinRT SystemSetupInfo
Sample Windows.System.Profile.SystemSetupInfo.OutOfBoxExperienceState
(build >= 17763, SYSTEM-callable, CLR-native WinRT projection — no NuGet)
as a purely observational signal, mirroring the bootstrap guard marker:
- oobeStateAtAgentStart on agent_started and agent_late_start payloads:
makes late starts provable instead of heuristic ("completed" on a
first_run = OOBE was already over when the agent arrived) and validates
the bootstrap OOBE guard fleet-wide.
- One-shot oobe_state_completed trace event on the in_progress->completed
flip, piggybacked on the DesktopArrivalDetector 30s poll: an
owner-independent desktop corroboration for DAD-starved sessions
(4d5a0b78 class — WTS+WMI owner resolution dead, desktop_arrived never
fires). Often absent in healthy sessions since the DAD timer is
disposed at desktop arrival, which is when the flip lands.
OobeStateReader is fail-soft (never throws, "unavailable" on missing
contract); the flip check has its own try/catch so it cannot pollute the
DAD wmiErrorCount liveness diagnostics, "unavailable" never overwrites
the baseline, and the one-shot flag survives ResetForRealUserSwitch.
No decision-engine involvement, no config/backend changes. Event type
registered in Constants.EventTypes, MCP resource-catalog (phase_events)
and web gather-rule autocomplete.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+139
@@ -405,5 +405,144 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring
|
||||
var started = trace.Events.Find(e => e.EventType == SharedEventTypes.DesktopDetectorStarted);
|
||||
Assert.Equal(0, started.Data["noCandidateThresholdMinutes"]);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OOBE-state flip (oobe_state_completed) — observational one-shot on the poll
|
||||
// ============================================================================
|
||||
// The WinRT OutOfBoxExperienceState flip in_progress->completed is an owner-
|
||||
// independent desktop corroboration for DAD-starved sessions (4d5a0b78 class).
|
||||
// Tests drive polls synchronously via PollOnceForTest after Stop() so the real
|
||||
// timer cannot race, and pin the owner seams to an excluded user so a real
|
||||
// explorer.exe on the test machine can never set _desktopArrived (which would
|
||||
// short-circuit PollForDesktop before the flip check).
|
||||
|
||||
private static DesktopArrivalDetector BuildFlipDetector(TempDirectory tmp, TraceCapture trace, System.Func<string> oobeProvider)
|
||||
{
|
||||
var detector = new DesktopArrivalDetector(new AgentLogger(tmp.Path, AgentLogLevel.Info), noCandidateTimeoutMinutes: 0)
|
||||
{
|
||||
OnTraceEvent = trace.Sink,
|
||||
OobeStateProvider = oobeProvider,
|
||||
SessionOwnerResolver = _ => "SYSTEM",
|
||||
ProcessOwnerResolver = _ => "SYSTEM",
|
||||
};
|
||||
return detector;
|
||||
}
|
||||
|
||||
/// <summary>Returns the queued states in order, then repeats the last one forever.</summary>
|
||||
private static System.Func<string> StateQueue(params string[] states)
|
||||
{
|
||||
var queue = new Queue<string>(states);
|
||||
var last = states[states.Length - 1];
|
||||
return () => queue.Count > 0 ? queue.Dequeue() : last;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OobeFlip_emits_exactly_once_with_payload()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var trace = new TraceCapture();
|
||||
// Baseline sample happens synchronously inside Start()/ArmTimer.
|
||||
using var detector = BuildFlipDetector(tmp, trace, StateQueue("in_progress", "completed"));
|
||||
|
||||
detector.Start();
|
||||
detector.Stop();
|
||||
detector.PollOnceForTest(); // reads "completed" -> flip
|
||||
detector.PollOnceForTest(); // still "completed" -> no second emit
|
||||
|
||||
Assert.Equal(1, trace.CountOf(SharedEventTypes.OobeStateCompleted));
|
||||
var flip = trace.Events.Find(e => e.EventType == SharedEventTypes.OobeStateCompleted);
|
||||
Assert.Equal("in_progress", flip.Data["previousState"]);
|
||||
Assert.True(flip.Data.ContainsKey("pollCount"));
|
||||
Assert.True(flip.Data.ContainsKey("minutesSinceStart"));
|
||||
Assert.False((bool)flip.Data["desktopArrived"]);
|
||||
Assert.True(flip.Data.ContainsKey("wmiErrorCount"));
|
||||
Assert.True(flip.Data.ContainsKey("wtsErrorCount"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OobeFlip_does_not_emit_when_state_is_already_completed()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var trace = new TraceCapture();
|
||||
using var detector = BuildFlipDetector(tmp, trace, StateQueue("completed"));
|
||||
|
||||
detector.Start();
|
||||
detector.Stop();
|
||||
detector.PollOnceForTest();
|
||||
detector.PollOnceForTest();
|
||||
|
||||
// Productive machines read "completed" from the very first sample — the flip
|
||||
// event must never fire there (the agent_started field already records the state).
|
||||
Assert.Equal(0, trace.CountOf(SharedEventTypes.OobeStateCompleted));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OobeFlip_does_not_emit_when_in_progress_was_never_seen()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var trace = new TraceCapture();
|
||||
using var detector = BuildFlipDetector(tmp, trace, StateQueue("unavailable", "completed"));
|
||||
|
||||
detector.Start();
|
||||
detector.Stop();
|
||||
detector.PollOnceForTest();
|
||||
|
||||
// Old builds without the WinRT contract report "unavailable"; a later
|
||||
// "completed" without a positively observed "in_progress" is not a flip.
|
||||
Assert.Equal(0, trace.CountOf(SharedEventTypes.OobeStateCompleted));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OobeFlip_survives_transient_unavailable_between_polls()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var trace = new TraceCapture();
|
||||
using var detector = BuildFlipDetector(tmp, trace, StateQueue("in_progress", "unavailable", "completed"));
|
||||
|
||||
detector.Start();
|
||||
detector.Stop();
|
||||
detector.PollOnceForTest(); // transient reflection failure — must NOT clear the baseline
|
||||
detector.PollOnceForTest(); // "completed" -> flip still detected
|
||||
|
||||
Assert.Equal(1, trace.CountOf(SharedEventTypes.OobeStateCompleted));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OobeFlip_provider_throwing_never_emits_and_never_pollutes_wmi_error_count()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var trace = new TraceCapture();
|
||||
using var detector = BuildFlipDetector(tmp, trace, () => throw new System.InvalidOperationException("boom"));
|
||||
|
||||
detector.Start(); // baseline sample throws -> swallowed
|
||||
detector.Stop();
|
||||
detector.PollOnceForTest();
|
||||
detector.PollOnceForTest();
|
||||
|
||||
Assert.Equal(0, trace.CountOf(SharedEventTypes.OobeStateCompleted));
|
||||
// The flip check has its own try/catch — a throw must not fall into
|
||||
// PollForDesktop's outer catch, which increments the WMI error counter the
|
||||
// session-4d5a0b78 liveness diagnostics rely on. first_poll carries that counter.
|
||||
var firstPoll = trace.Events.Find(e => e.EventType == SharedEventTypes.DesktopDetectorFirstPoll);
|
||||
Assert.Equal(0, firstPoll.Data["wmiErrorCount"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OobeFlip_one_shot_flag_survives_ResetForRealUserSwitch()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var trace = new TraceCapture();
|
||||
using var detector = BuildFlipDetector(tmp, trace,
|
||||
StateQueue("in_progress", "completed", "in_progress", "completed"));
|
||||
|
||||
detector.Start();
|
||||
detector.Stop();
|
||||
detector.PollOnceForTest(); // flip #1
|
||||
detector.ResetForRealUserSwitch(); // re-arms + re-samples baseline ("in_progress")
|
||||
detector.Stop();
|
||||
detector.PollOnceForTest(); // "completed" again — must NOT re-emit
|
||||
|
||||
Assert.Equal(1, trace.CountOf(SharedEventTypes.OobeStateCompleted));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#nullable enable
|
||||
using AutopilotMonitor.Agent.V2.Core.Monitoring.Interop;
|
||||
using Xunit;
|
||||
|
||||
namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Smoke tests for the WinRT <c>SystemSetupInfo.OutOfBoxExperienceState</c> reader.
|
||||
/// The actual state on the test machine is unknown (CI runners are post-OOBE; Server
|
||||
/// SKUs may lack the UniversalApiContract entirely), so only the fail-soft contract
|
||||
/// is pinned: never throw, always return a member of the known value set.
|
||||
/// </summary>
|
||||
public sealed class OobeStateReaderTests
|
||||
{
|
||||
[Fact]
|
||||
public void Read_never_throws_and_returns_known_value()
|
||||
{
|
||||
var value = OobeStateReader.Read();
|
||||
|
||||
Assert.False(string.IsNullOrEmpty(value));
|
||||
Assert.True(
|
||||
value == "not_started"
|
||||
|| value == "in_progress"
|
||||
|| value == "completed"
|
||||
|| value == OobeStateReader.Unavailable
|
||||
|| value.StartsWith("unknown_"),
|
||||
$"unexpected reader value '{value}'");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Read_is_stable_across_repeated_calls()
|
||||
{
|
||||
// Exercises the cached-PropertyInfo path (second call skips resolution).
|
||||
var first = OobeStateReader.Read();
|
||||
var second = OobeStateReader.Read();
|
||||
|
||||
Assert.Equal(first, second);
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -61,6 +61,15 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
|
||||
internal Func<int, string> SessionOwnerResolver { get; set; }
|
||||
internal Func<int, string> ProcessOwnerResolver { get; set; }
|
||||
|
||||
// Test seam for the WinRT OOBE-state sample (null = OobeStateReader.Read).
|
||||
internal Func<string> OobeStateProvider { get; set; }
|
||||
|
||||
// OOBE-state flip tracking (observational only — see OobeStateReader contract).
|
||||
// _oobeCompletedEmitted is one-shot per agent lifetime and deliberately NOT reset
|
||||
// by ResetForRealUserSwitch: the OOBE flip is a global OS transition, not per-user.
|
||||
private string _lastOobeState;
|
||||
private bool _oobeCompletedEmitted;
|
||||
|
||||
/// <summary>
|
||||
/// System/service account names that should NOT be considered real users.
|
||||
/// </summary>
|
||||
@@ -161,6 +170,10 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
|
||||
{
|
||||
_startedAtUtc = DateTime.UtcNow;
|
||||
|
||||
// Baseline OOBE-state sample so the first poll can already detect a flip
|
||||
// (self-contained try/catch inside, like the Emit* helpers).
|
||||
CheckOobeStateFlip();
|
||||
|
||||
_pollingTimer?.Dispose();
|
||||
_pollingTimer = new Timer(
|
||||
PollForDesktop,
|
||||
@@ -186,6 +199,8 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
|
||||
|
||||
_pollCount++;
|
||||
|
||||
CheckOobeStateFlip();
|
||||
|
||||
try
|
||||
{
|
||||
var explorerProcesses = Process.GetProcessesByName("explorer");
|
||||
@@ -381,6 +396,66 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
|
||||
catch (Exception ex) { _logger.Verbose($"DesktopArrivalDetector: detector-started emit failed: {ex.Message}"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samples the WinRT OOBE state (<see cref="OobeStateReader"/>) and emits the one-shot
|
||||
/// <c>oobe_state_completed</c> trace event on the in_progress->completed flip.
|
||||
/// Observational only — an owner-independent desktop corroboration for sessions where
|
||||
/// WTS+WMI owner resolution starves and <c>desktop_arrived</c> never fires. Often absent
|
||||
/// in healthy sessions: the polling timer is disposed at desktop arrival, which is when
|
||||
/// the flip lands (empirical probe, session 9c404ae9).
|
||||
/// <para>
|
||||
/// Self-contained try/catch: a throw here must NOT reach <see cref="PollForDesktop"/>'s
|
||||
/// outer catch, which would inflate <see cref="_wmiErrorCountSinceStart"/> and skew the
|
||||
/// session-4d5a0b78 liveness diagnostics. "unavailable" readings never overwrite the last
|
||||
/// known state, so a transient reflection failure between two polls cannot mask the flip.
|
||||
/// One-shot flag survives <see cref="ResetForRealUserSwitch"/> — the flip is a global OS
|
||||
/// transition, not per-user.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private void CheckOobeStateFlip()
|
||||
{
|
||||
try
|
||||
{
|
||||
var current = (OobeStateProvider ?? OobeStateReader.Read)();
|
||||
if (string.IsNullOrEmpty(current) || current == OobeStateReader.Unavailable)
|
||||
return;
|
||||
|
||||
if (!_oobeCompletedEmitted
|
||||
&& _lastOobeState == "in_progress"
|
||||
&& current == "completed")
|
||||
{
|
||||
_oobeCompletedEmitted = true;
|
||||
var minutesSinceStart = Math.Round((DateTime.UtcNow - _startedAtUtc).TotalMinutes, 1);
|
||||
_logger.Info("DesktopArrivalDetector: OOBE state flipped in_progress->completed (WinRT SystemSetupInfo)");
|
||||
OnTraceEvent?.Invoke(
|
||||
SharedEventTypes.OobeStateCompleted,
|
||||
$"OOBE state flipped in_progress->completed (WinRT SystemSetupInfo, poll {_pollCount}) — owner-independent desktop corroboration.",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "previousState", "in_progress" },
|
||||
{ "pollCount", _pollCount },
|
||||
{ "minutesSinceStart", minutesSinceStart },
|
||||
{ "desktopArrived", _desktopArrived },
|
||||
{ "wmiErrorCount", _wmiErrorCountSinceStart },
|
||||
{ "wtsErrorCount", _wtsErrorCountSinceStart },
|
||||
});
|
||||
}
|
||||
|
||||
_lastOobeState = current;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Verbose($"DesktopArrivalDetector: OOBE state check failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drives a single synchronous poll for unit tests — the production poll is private and
|
||||
/// timer-driven with a fixed 30 s interval (precedent: <see cref="ResolveOwner"/> is
|
||||
/// likewise internal for direct testing).
|
||||
/// </summary>
|
||||
internal void PollOnceForTest() => PollForDesktop(null);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the owning user of an explorer.exe candidate. Session 4d5a0b78 fix
|
||||
/// (2026-06-11): the WMI <c>Win32_Process.GetOwner</c> call failed on every poll on
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Interop
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads WinRT <c>Windows.System.Profile.SystemSetupInfo.OutOfBoxExperienceState</c>
|
||||
/// (NotStarted=0 / InProgress=1 / Completed=2) via the CLR's built-in WinRT projection —
|
||||
/// no NuGet package, no winmd reference; the type is resolved at runtime with
|
||||
/// <c>ContentType=WindowsRuntime</c>, the same mechanism the bootstrap script uses from
|
||||
/// PowerShell 5.1.
|
||||
/// <para>
|
||||
/// <b>Contract:</b> observational only — never feed this into decision-engine logic.
|
||||
/// Requires Windows 10 1809+ (build 17763, UniversalApiContract 7.0) and is callable
|
||||
/// from the SYSTEM service context (empirically validated, probe session 9c404ae9).
|
||||
/// On older builds or SKUs without the contract the type resolves to null and
|
||||
/// <see cref="Read"/> returns <c>"unavailable"</c>. <see cref="Read"/> never throws.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Empirical timing (Win11 24H2 probe): the state flips InProgress→Completed at desktop
|
||||
/// arrival — it is a corroboration signal, not an early one. WDP reseal and placeholder
|
||||
/// (fooUser) flip semantics are unverified; another reason this must stay observational.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal static class OobeStateReader
|
||||
{
|
||||
public const string Unavailable = "unavailable";
|
||||
|
||||
// Resolved once per process. Benign race: concurrent first calls resolve the same
|
||||
// PropertyInfo and the assignment is idempotent — no lock needed.
|
||||
private static PropertyInfo _stateProperty;
|
||||
private static bool _initTried;
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>"not_started"</c> / <c>"in_progress"</c> / <c>"completed"</c>,
|
||||
/// <c>"unknown_<n>"</c> for unexpected enum values, or <c>"unavailable"</c>
|
||||
/// when the WinRT contract is absent or any part of the read fails. Never throws.
|
||||
/// </summary>
|
||||
public static string Read()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_initTried)
|
||||
{
|
||||
var type = Type.GetType(
|
||||
"Windows.System.Profile.SystemSetupInfo, Windows.System.Profile, ContentType=WindowsRuntime",
|
||||
throwOnError: false);
|
||||
_stateProperty = type?.GetProperty(
|
||||
"OutOfBoxExperienceState",
|
||||
BindingFlags.Public | BindingFlags.Static);
|
||||
_initTried = true;
|
||||
}
|
||||
|
||||
if (_stateProperty == null)
|
||||
return Unavailable;
|
||||
|
||||
var value = Convert.ToInt32(_stateProperty.GetValue(null));
|
||||
switch (value)
|
||||
{
|
||||
case 0: return "not_started";
|
||||
case 1: return "in_progress";
|
||||
case 2: return "completed";
|
||||
default: return "unknown_" + value;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Unavailable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||
using AutopilotMonitor.Agent.V2.Core.Configuration;
|
||||
using AutopilotMonitor.Agent.V2.Core.Logging;
|
||||
using AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.Ime;
|
||||
using AutopilotMonitor.Agent.V2.Core.Monitoring.Interop;
|
||||
using AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime;
|
||||
using AutopilotMonitor.Agent.V2.Core.Orchestration;
|
||||
using AutopilotMonitor.Agent.V2.Core.Runtime;
|
||||
@@ -93,6 +94,9 @@ namespace AutopilotMonitor.Agent.V2.Core.Termination
|
||||
// correction between agent start and termination no longer skews bootToStartSeconds.
|
||||
private readonly ulong _bootAnchorTickMs;
|
||||
private readonly DateTime _bootAnchorUtc;
|
||||
// WinRT OOBE state at construction (handler is built at agent start) — corroborates
|
||||
// the late-start heuristic: "completed" here means OOBE was already over on arrival.
|
||||
private readonly string _oobeStateAtAgentStart;
|
||||
private int _handled;
|
||||
|
||||
public EnrollmentTerminationHandler(
|
||||
@@ -129,6 +133,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Termination
|
||||
|
||||
_bootAnchorTickMs = ObservationCoverage.CurrentTickMs();
|
||||
_bootAnchorUtc = DateTime.UtcNow;
|
||||
_oobeStateAtAgentStart = OobeStateReader.Read();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -658,6 +663,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Termination
|
||||
{ "agentUptimeSeconds", Math.Round(uptimeSeconds, 1) },
|
||||
{ "deviceBootUtc", deviceBootUtc.ToString("o") },
|
||||
{ "agentStartUtc", _agentStartTimeUtc.ToString("o") },
|
||||
{ "oobeStateAtAgentStart", _oobeStateAtAgentStart },
|
||||
{ "outcome", args.Outcome.ToString() },
|
||||
{ "note", "The agent's bootstrap (an Intune platform script) ran only near the end of the enrollment, so the agent observed the already-decided end-state rather than the failure window. Treat the diagnosis as a post-mortem; check for a platform/remediation script that hung ahead of the bootstrap (see script_timeout_suspected)." },
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using AutopilotMonitor.Agent.V2.Core.Configuration;
|
||||
using AutopilotMonitor.Agent.V2.Core.Logging;
|
||||
using AutopilotMonitor.Agent.V2.Core.Monitoring.Interop;
|
||||
using AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime;
|
||||
using AutopilotMonitor.Agent.V2.Core.Orchestration;
|
||||
using AutopilotMonitor.Agent.V2.Core.Security;
|
||||
@@ -68,6 +69,10 @@ namespace AutopilotMonitor.Agent.V2.Runtime
|
||||
{ "configVersion", configVersion },
|
||||
{ "remoteConfigFetched", outcome == RemoteConfigFetchOutcome.Succeeded },
|
||||
{ "remoteConfigOutcome", outcome.ToString() },
|
||||
// WinRT OOBE state sampled at this start. Together with previousExitType
|
||||
// this makes late starts provable: "completed" on a first_run means OOBE
|
||||
// was already over when the agent arrived (e.g. IME queue blocked).
|
||||
{ "oobeStateAtAgentStart", OobeStateReader.Read() },
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(previousExit?.CrashExceptionType))
|
||||
|
||||
@@ -34,6 +34,7 @@ export const EVENT_TYPES_CATALOG = {
|
||||
'desktop_detector_started',
|
||||
'desktop_detector_first_poll',
|
||||
'desktop_detector_no_candidate',
|
||||
'oobe_state_completed',
|
||||
],
|
||||
whiteglove_events: [
|
||||
'whiteglove_complete',
|
||||
|
||||
@@ -317,6 +317,12 @@ namespace AutopilotMonitor.Shared
|
||||
public const string DesktopDetectorStarted = "desktop_detector_started"; // 1x on Start() / ResetForRealUserSwitch()
|
||||
public const string DesktopDetectorFirstPoll = "desktop_detector_first_poll"; // 1x after first PollForDesktop() completes
|
||||
public const string DesktopDetectorNoCandidate = "desktop_detector_no_candidate"; // 1x after threshold polls without resolution (configurable)
|
||||
// 1x when WinRT SystemSetupInfo.OutOfBoxExperienceState flips InProgress->Completed,
|
||||
// sampled on the DAD 30s poll. Observational only (no decision-engine involvement) —
|
||||
// an owner-independent desktop corroboration for sessions where WTS+WMI owner
|
||||
// resolution starves and desktop_arrived never fires. Often absent in healthy
|
||||
// sessions: the DAD timer is disposed at desktop arrival, which is when the flip lands.
|
||||
public const string OobeStateCompleted = "oobe_state_completed";
|
||||
public const string CompletionCheck = "completion_check";
|
||||
// Liveness plan PR2 — emitted (state-change-only, deduped via the
|
||||
// CompletionWaitingFingerprint state fact) whenever the DecisionEngine blocks or
|
||||
|
||||
@@ -32,6 +32,8 @@ export const KNOWN_EVENT_TYPES: EventTypeEntry[] = [
|
||||
description: "Pre-Provisioning Part 2 resumed after the user powered on the device." },
|
||||
{ value: "desktop_arrived", label: "desktop_arrived", category: "enrollment",
|
||||
description: "User desktop detected (explorer.exe under real user)." },
|
||||
{ value: "oobe_state_completed", label: "oobe_state_completed", category: "enrollment",
|
||||
description: "Windows OOBE state flipped InProgress→Completed (WinRT SystemSetupInfo, sampled on the desktop-arrival poll). One-shot and observational only; valuable as an owner-independent desktop corroboration in sessions where desktop_arrived never fires." },
|
||||
{ value: "completion_check", label: "completion_check", category: "enrollment",
|
||||
description: "Throttled state-machine snapshot during completion evaluation." },
|
||||
{ value: "completion_waiting", label: "completion_waiting", category: "enrollment",
|
||||
|
||||
Reference in New Issue
Block a user