mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
refactor(decisioncore): SelfDeploying + Device-Only handlers + 2 scenarios (M3.2)
SelfDeploying-v1 vertical slice: DeviceSetupProvisioningComplete terminal
handler, DeviceOnlyEspDetection deadline wiring, and the
DeviceOnlyDeployment hypothesis progression UserPresent / DeviceOnly /
Ambiguous expressed via Hypothesis.Reason (plan §2.3).
Handlers (DecisionEngine.SelfDeploying.cs):
- DeviceSetupProvisioningComplete terminal. Cancels all deadlines,
finalizes DeviceOnlyDeployment based on
user-presence facts (AadJoinedWithUser,
HelloResolvedUtc, DesktopArrivedUtc),
raises EnrollmentType to Strong when
no AccountSetup was observed before.
- DeviceOnlyEspDetectionFired hypothesis-only update to
DeviceOnlyDeployment=Strong(DeviceOnly).
Stage unchanged; completion still
requires explicit provisioning signal.
Classic.cs extended:
- First EspPhaseChanged(DeviceSetup) arms the DeviceOnlyEspDetection
deadline (5 min window, plan §2.7).
- EspPhaseChanged(AccountSetup) cancels it — UserDriven-v1 path.
Shared DeadlineFired router routes the deadline to the SelfDeploying body.
Scenario fixtures (tests/fixtures/enrollment-sessions/):
- selfdeploying-happy-v1.jsonl — 3 signals, SkipUserStatusPage
path. Terminal Completed with
DeviceOnly hypothesis confirmed
and no user-presence facts.
- selfdeploying-esp-exit-unknown-v1.jsonl — 4 signals, deadline fires at
T+5m and sets DeviceOnly before
provisioning-complete arrives.
Tests (SelfDeployingScenarioTests, 4 new):
- SelfDeployingHappy terminal state + hypotheses
- SelfDeployingHappy deadline cleanup
- Device-Only ESP-exit-unknown deadline -> provisioning flow
- AccountSetup on the Classic path cancels DeviceOnlyEspDetection
(regression guard — UserDriven should never classify as DeviceOnly)
InternalsVisibleTo added to DecisionCore for DecisionCore.Tests so the
internal DeviceOnlyReasons tokens stay hidden from consumers while tests
assert on them directly.
Suite totals: 45 DecisionCore (was 41) / 507 Legacy / 557 Functions.
Plan: plans/REFACTOR_AGENT_V2.md §4 M3 sub-milestone M3.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
8
|
||||
9
|
||||
|
||||
@@ -1 +1 @@
|
||||
1050
|
||||
1051
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
using System.Linq;
|
||||
using AutopilotMonitor.DecisionCore.Engine;
|
||||
using AutopilotMonitor.DecisionCore.State;
|
||||
using Xunit;
|
||||
|
||||
namespace AutopilotMonitor.DecisionCore.Tests.Scenarios
|
||||
{
|
||||
/// <summary>
|
||||
/// Plan §4 M3.2 — SelfDeploying-v1 and Device-Only scenarios.
|
||||
/// </summary>
|
||||
public sealed class SelfDeployingScenarioTests : ScenarioTestBase
|
||||
{
|
||||
[Fact]
|
||||
public void SelfDeployingHappy_completes_onProvisioningComplete_noUserFacts()
|
||||
{
|
||||
var result = RunFixture(
|
||||
fixtureFilename: "selfdeploying-happy-v1.jsonl",
|
||||
sessionId: "session-anon-0004",
|
||||
tenantId: "tenant-anon-0004");
|
||||
|
||||
Assert.Equal(SessionStage.Completed, result.FinalState.Stage);
|
||||
Assert.Equal(SessionOutcome.EnrollmentComplete, result.FinalState.Outcome);
|
||||
Assert.Empty(result.FinalState.Deadlines);
|
||||
|
||||
// No user-presence facts should have been produced.
|
||||
Assert.Null(result.FinalState.HelloResolvedUtc);
|
||||
Assert.Null(result.FinalState.DesktopArrivedUtc);
|
||||
Assert.Null(result.FinalState.AadJoinedWithUser);
|
||||
Assert.Null(result.FinalState.AccountSetupEnteredUtc);
|
||||
|
||||
// DeviceOnly hypothesis confirmed as DeviceOnly (no user presence seen).
|
||||
Assert.Equal(HypothesisLevel.Confirmed, result.FinalState.DeviceOnlyDeployment.Level);
|
||||
Assert.Equal(
|
||||
DecisionEngine.DeviceOnlyReasons.DeviceOnly,
|
||||
result.FinalState.DeviceOnlyDeployment.Reason);
|
||||
|
||||
// EnrollmentType upgraded to Strong by the SelfDeploying handler.
|
||||
Assert.Equal(HypothesisLevel.Strong, result.FinalState.EnrollmentType.Level);
|
||||
Assert.Equal("selfdeploying_provisioning_complete", result.FinalState.EnrollmentType.Reason);
|
||||
|
||||
Assert.Equal(3, result.Transitions.Count);
|
||||
Assert.All(result.Transitions, t => Assert.True(t.Taken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelfDeployingHappy_schedulesAndCancelsDeviceOnlyEspDetection()
|
||||
{
|
||||
var result = RunFixture(
|
||||
fixtureFilename: "selfdeploying-happy-v1.jsonl",
|
||||
sessionId: "session-anon-0004",
|
||||
tenantId: "tenant-anon-0004");
|
||||
|
||||
// Replay intermediate check via harness is not exposed, but we can assert the
|
||||
// final state has no active deadlines (the provisioning-complete handler clears them).
|
||||
Assert.Empty(result.FinalState.Deadlines);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeviceOnlyEspExitUnknown_deadlineFires_then_provisioningCompletes()
|
||||
{
|
||||
var result = RunFixture(
|
||||
fixtureFilename: "selfdeploying-esp-exit-unknown-v1.jsonl",
|
||||
sessionId: "session-anon-0005",
|
||||
tenantId: "tenant-anon-0005");
|
||||
|
||||
Assert.Equal(SessionStage.Completed, result.FinalState.Stage);
|
||||
Assert.Equal(SessionOutcome.EnrollmentComplete, result.FinalState.Outcome);
|
||||
|
||||
// DeviceOnlyDeployment goes through Strong (by deadline) -> Confirmed (by provisioning).
|
||||
Assert.Equal(HypothesisLevel.Confirmed, result.FinalState.DeviceOnlyDeployment.Level);
|
||||
Assert.Equal(
|
||||
DecisionEngine.DeviceOnlyReasons.DeviceOnly,
|
||||
result.FinalState.DeviceOnlyDeployment.Reason);
|
||||
|
||||
// The deadline transition is present and taken (hypothesis update without stage change).
|
||||
var deadlineTransition = Assert.Single(
|
||||
result.Transitions,
|
||||
t => t.Trigger == $"DeadlineFired:{DeadlineNames.DeviceOnlyEspDetection}");
|
||||
Assert.True(deadlineTransition.Taken);
|
||||
Assert.Equal(deadlineTransition.FromStage, deadlineTransition.ToStage);
|
||||
Assert.Equal(SessionStage.EspDeviceSetup, deadlineTransition.ToStage);
|
||||
|
||||
Assert.Equal(4, result.Transitions.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Classic_AccountSetupPhase_CancelsDeviceOnlyEspDetection()
|
||||
{
|
||||
// Re-use the UserDriven-Happy fixture (already in M3.1): it goes
|
||||
// DeviceSetup -> AccountSetup, so the DeviceOnlyEspDetection deadline is
|
||||
// scheduled and then cancelled before it can fire. The final state must
|
||||
// carry no DeviceOnlyEspDetection deadline, and the DeviceOnly hypothesis
|
||||
// stays Unknown — the UserDriven path does not classify itself as DeviceOnly.
|
||||
var result = RunFixture(
|
||||
fixtureFilename: "userdriven-happy-v1.jsonl",
|
||||
sessionId: "s", tenantId: "t");
|
||||
|
||||
Assert.DoesNotContain(
|
||||
result.FinalState.Deadlines,
|
||||
d => d.Name == DeadlineNames.DeviceOnlyEspDetection);
|
||||
Assert.Equal(HypothesisLevel.Unknown, result.FinalState.DeviceOnlyDeployment.Level);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,14 @@
|
||||
<ProjectReference Include="..\AutopilotMonitor.Shared\AutopilotMonitor.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Internal helpers (DeviceOnlyReasons etc.) are visible to the test assembly for
|
||||
precise assertions; never exposed to consumers. -->
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>AutopilotMonitor.DecisionCore.Tests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Plan §2.8 — Newtonsoft.Json on both sides (Agent net48 + Backend net8.0) for DecisionCore DTOs. -->
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
||||
@@ -68,6 +68,24 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
lastUpdatedUtc: signal.OccurredAtUtc);
|
||||
}
|
||||
|
||||
// Device-Only ESP detection (plan §2.7): arm on first DeviceSetup, cancel on AccountSetup.
|
||||
// See DecisionEngine.SelfDeploying.cs for the deadline-fired handler.
|
||||
var effectsList = new List<DecisionEffect>();
|
||||
if (enrollmentPhase == EnrollmentPhase.DeviceSetup &&
|
||||
state.DeviceSetupEnteredUtc == null)
|
||||
{
|
||||
var devOnlyDl = BuildDeviceOnlyEspDetectionDeadline(signal.OccurredAtUtc);
|
||||
builder.AddDeadline(devOnlyDl);
|
||||
effectsList.Add(new DecisionEffect(DecisionEffectKind.ScheduleDeadline, deadline: devOnlyDl));
|
||||
}
|
||||
else if (enrollmentPhase == EnrollmentPhase.AccountSetup)
|
||||
{
|
||||
builder.CancelDeadline(DeadlineNames.DeviceOnlyEspDetection);
|
||||
effectsList.Add(new DecisionEffect(
|
||||
DecisionEffectKind.CancelDeadline,
|
||||
cancelDeadlineName: DeadlineNames.DeviceOnlyEspDetection));
|
||||
}
|
||||
|
||||
var newState = builder.Build();
|
||||
|
||||
var transition = BuildTakenTransition(
|
||||
@@ -77,7 +95,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
nextStepIndex: nextStep,
|
||||
trigger: nameof(DecisionSignalKind.EspPhaseChanged));
|
||||
|
||||
return new DecisionStep(newState, transition, Array.Empty<DecisionEffect>());
|
||||
return new DecisionStep(newState, transition, effectsList.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AutopilotMonitor.DecisionCore.Signals;
|
||||
using AutopilotMonitor.DecisionCore.State;
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
|
||||
namespace AutopilotMonitor.DecisionCore.Engine
|
||||
{
|
||||
// SelfDeploying-v1 + Device-Only handlers. Plan §2.5 partial-class layout.
|
||||
public sealed partial class DecisionEngine
|
||||
{
|
||||
// Plan §2.7: "5min timer after DeviceSetup; if no AccountSetup -> classified as device-only".
|
||||
internal static readonly TimeSpan s_deviceOnlyEspDetectionWindow = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// Hypothesis-reason tokens for <see cref="DecisionState.DeviceOnlyDeployment"/>.
|
||||
/// Plan §2.3 values UserPresent / DeviceOnly / Ambiguous — expressed through the
|
||||
/// <see cref="Hypothesis.Reason"/> field so we do not need a new enum.
|
||||
/// </summary>
|
||||
internal static class DeviceOnlyReasons
|
||||
{
|
||||
public const string UserPresent = "user_present";
|
||||
public const string DeviceOnly = "device_only";
|
||||
public const string Ambiguous = "ambiguous";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle <see cref="DecisionSignalKind.DeviceSetupProvisioningComplete"/>.
|
||||
/// <para>
|
||||
/// The canonical SelfDeploying / Device-Only terminal signal. Cancels the
|
||||
/// <see cref="DeadlineNames.DeviceOnlyEspDetection"/> deadline if still armed,
|
||||
/// finalizes the <see cref="DecisionState.DeviceOnlyDeployment"/> hypothesis based
|
||||
/// on whether any user-presence fact was observed, and transitions the session to
|
||||
/// <see cref="SessionStage.Completed"/> with <see cref="SessionOutcome.EnrollmentComplete"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private DecisionStep HandleDeviceSetupProvisioningCompleteV1(DecisionState state, DecisionSignal signal)
|
||||
{
|
||||
var nextStep = state.StepIndex + 1;
|
||||
|
||||
// Determine DeviceOnlyDeployment verdict.
|
||||
var hasUserPresence =
|
||||
(state.AadJoinedWithUser != null && state.AadJoinedWithUser.Value) ||
|
||||
state.HelloResolvedUtc != null ||
|
||||
state.DesktopArrivedUtc != null;
|
||||
|
||||
var deviceOnlyReason = hasUserPresence
|
||||
? DeviceOnlyReasons.UserPresent
|
||||
: DeviceOnlyReasons.DeviceOnly;
|
||||
|
||||
var updatedHypothesis = state.DeviceOnlyDeployment.With(
|
||||
level: HypothesisLevel.Confirmed,
|
||||
reason: deviceOnlyReason,
|
||||
score: 100,
|
||||
lastUpdatedUtc: signal.OccurredAtUtc);
|
||||
|
||||
var builder = state.ToBuilder()
|
||||
.WithStage(SessionStage.Completed)
|
||||
.WithOutcome(SessionOutcome.EnrollmentComplete)
|
||||
.WithStepIndex(nextStep)
|
||||
.WithLastAppliedSignalOrdinal(signal.SessionSignalOrdinal)
|
||||
.ClearDeadlines();
|
||||
builder.DeviceOnlyDeployment = updatedHypothesis;
|
||||
|
||||
// Also strengthen EnrollmentType: observing DeviceSetupProvisioningComplete
|
||||
// without preceding AccountSetup strongly implies SelfDeploying-v1.
|
||||
if (state.AccountSetupEnteredUtc == null &&
|
||||
state.EnrollmentType.Level < HypothesisLevel.Strong)
|
||||
{
|
||||
builder.EnrollmentType = state.EnrollmentType.With(
|
||||
level: HypothesisLevel.Strong,
|
||||
reason: "selfdeploying_provisioning_complete",
|
||||
score: 80,
|
||||
lastUpdatedUtc: signal.OccurredAtUtc);
|
||||
}
|
||||
|
||||
var newState = builder.Build();
|
||||
|
||||
var transition = BuildTakenTransition(
|
||||
before: state,
|
||||
signal: signal,
|
||||
toStage: SessionStage.Completed,
|
||||
nextStepIndex: nextStep,
|
||||
trigger: nameof(DecisionSignalKind.DeviceSetupProvisioningComplete));
|
||||
|
||||
var effects = new[] { BuildEnrollmentCompleteEffect() };
|
||||
|
||||
return new DecisionStep(newState, transition, effects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the shared <c>DeadlineFired</c> router when
|
||||
/// <see cref="DeadlineNames.DeviceOnlyEspDetection"/> elapses without an AccountSetup
|
||||
/// phase having arrived. Confirms the <see cref="DecisionState.DeviceOnlyDeployment"/>
|
||||
/// hypothesis as DeviceOnly but does NOT complete the session — the terminal still
|
||||
/// needs an explicit <see cref="DecisionSignalKind.DeviceSetupProvisioningComplete"/>.
|
||||
/// </summary>
|
||||
private DecisionStep HandleDeviceOnlyEspDetectionDeadlineFired(DecisionState state, DecisionSignal signal)
|
||||
{
|
||||
var nextStep = state.StepIndex + 1;
|
||||
var builder = state.ToBuilder()
|
||||
.WithStepIndex(nextStep)
|
||||
.WithLastAppliedSignalOrdinal(signal.SessionSignalOrdinal)
|
||||
.CancelDeadline(DeadlineNames.DeviceOnlyEspDetection);
|
||||
|
||||
builder.DeviceOnlyDeployment = state.DeviceOnlyDeployment.With(
|
||||
level: HypothesisLevel.Strong,
|
||||
reason: DeviceOnlyReasons.DeviceOnly,
|
||||
score: 70,
|
||||
lastUpdatedUtc: signal.OccurredAtUtc);
|
||||
|
||||
var newState = builder.Build();
|
||||
|
||||
var transition = BuildTakenTransition(
|
||||
before: state,
|
||||
signal: signal,
|
||||
toStage: state.Stage,
|
||||
nextStepIndex: nextStep,
|
||||
trigger: $"DeadlineFired:{DeadlineNames.DeviceOnlyEspDetection}");
|
||||
|
||||
return new DecisionStep(newState, transition, Array.Empty<DecisionEffect>());
|
||||
}
|
||||
|
||||
// ============================================================== internal helpers
|
||||
|
||||
/// <summary>
|
||||
/// Called from the Classic <see cref="HandleEspPhaseChangedV1"/> handler when
|
||||
/// <c>DeviceSetup</c> is observed for the first time — arms the
|
||||
/// <see cref="DeadlineNames.DeviceOnlyEspDetection"/> deadline. The Classic handler
|
||||
/// adds the deadline effect to its own effect list.
|
||||
/// </summary>
|
||||
internal ActiveDeadline BuildDeviceOnlyEspDetectionDeadline(DateTime fromUtc) =>
|
||||
new ActiveDeadline(
|
||||
name: DeadlineNames.DeviceOnlyEspDetection,
|
||||
dueAtUtc: fromUtc.Add(s_deviceOnlyEspDetectionWindow),
|
||||
firesSignalKind: DecisionSignalKind.DeadlineFired,
|
||||
firesPayload: new Dictionary<string, string>
|
||||
{
|
||||
[SignalPayloadKeys.Deadline] = DeadlineNames.DeviceOnlyEspDetection,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -152,6 +152,8 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
{
|
||||
case DeadlineNames.HelloSafety:
|
||||
return HandleHelloSafetyDeadlineFired(state, signal);
|
||||
case DeadlineNames.DeviceOnlyEspDetection:
|
||||
return HandleDeviceOnlyEspDetectionDeadlineFired(state, signal);
|
||||
default:
|
||||
// Deadline name not recognized in this sub-milestone. Cancel it from state
|
||||
// and record a neutral taken transition — M3.3+ adds handlers for
|
||||
|
||||
@@ -69,7 +69,10 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
(DecisionSignalKind.ImeUserSessionCompleted, 1) => HandleImeUserSessionCompletedV1(state, signal),
|
||||
(DecisionSignalKind.AadUserJoinedLate, 1) => HandleAadUserJoinedLateV1(state, signal),
|
||||
|
||||
// ----- Not yet wired (SelfDeploying in M3.2, WhiteGlove in M3.3/M3.4, …) -----
|
||||
// ----- SelfDeploying + Device-Only (DecisionEngine.SelfDeploying.cs) -----
|
||||
(DecisionSignalKind.DeviceSetupProvisioningComplete, 1) => HandleDeviceSetupProvisioningCompleteV1(state, signal),
|
||||
|
||||
// ----- Not yet wired (WhiteGlove in M3.3/M3.4, …) -----
|
||||
_ => HandleUnhandledSignal(state, signal),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Scenario: SelfDeploying Device-Only with ESP-Exit-Unknown
|
||||
# DeviceSetup phase arrives, but no AccountSetup follows within the 5-minute
|
||||
# DeviceOnlyEspDetection window — the deadline fires and classifies the session
|
||||
# as DeviceOnly. Terminal comes via DeviceSetupProvisioningComplete afterwards.
|
||||
# Expected: Stage=Completed, Outcome=EnrollmentComplete,
|
||||
# DeviceOnlyDeployment=Confirmed/Strong progression, no user-presence facts.
|
||||
# Plan: §4 M3.2 — "SelfDeploying-v1 mit ESP-Exit-Unknown -> Device-Only-ESP-Timer -> Complete".
|
||||
{"SessionSignalOrdinal":0,"SessionTraceOrdinal":0,"Kind":"SessionStarted","KindSchemaVersion":1,"OccurredAtUtc":"2026-04-21T10:00:00Z","SourceOrigin":"orchestrator","Evidence":{"Kind":"Synthetic","Identifier":"session:device-only:started","Summary":"Session start anchor"}}
|
||||
{"SessionSignalOrdinal":1,"SessionTraceOrdinal":1,"Kind":"EspPhaseChanged","KindSchemaVersion":1,"OccurredAtUtc":"2026-04-21T10:01:00Z","SourceOrigin":"EspAndHelloTracker","Evidence":{"Kind":"Derived","Identifier":"esp-phase-detector-v1","Summary":"ESP phase=DeviceSetup","DerivationInputs":{"rawValue":"1"}},"Payload":{"phase":"DeviceSetup"}}
|
||||
{"SessionSignalOrdinal":2,"SessionTraceOrdinal":2,"Kind":"DeadlineFired","KindSchemaVersion":1,"OccurredAtUtc":"2026-04-21T10:06:00Z","SourceOrigin":"DeadlineScheduler","Evidence":{"Kind":"Synthetic","Identifier":"deadline:device_only_esp_detection:fired","Summary":"Device-only ESP detection window elapsed without AccountSetup"},"Payload":{"deadline":"device_only_esp_detection"}}
|
||||
{"SessionSignalOrdinal":3,"SessionTraceOrdinal":3,"Kind":"DeviceSetupProvisioningComplete","KindSchemaVersion":1,"OccurredAtUtc":"2026-04-21T10:08:00Z","SourceOrigin":"ProvisioningStatusTracker","Evidence":{"Kind":"Derived","Identifier":"provisioning-status-tracker-v1","Summary":"Device setup provisioning complete","DerivationInputs":{"registryKey":"HKLM\\...Provisioning\\Status","rawValue":"Complete"}}}
|
||||
@@ -0,0 +1,10 @@
|
||||
# Scenario: SelfDeploying-v1 Happy (SkipUserStatusPage)
|
||||
# Device-level ESP only, no AccountSetup phase, no Hello, no Desktop.
|
||||
# Terminal signal is DeviceSetupProvisioningComplete.
|
||||
# Expected: Stage=Completed, Outcome=EnrollmentComplete,
|
||||
# DeviceOnlyDeployment=Confirmed(reason=device_only) (no user-presence facts),
|
||||
# EnrollmentType=Strong(selfdeploying_provisioning_complete).
|
||||
# Plan: §4 M3.2 canonical SelfDeploying scenario.
|
||||
{"SessionSignalOrdinal":0,"SessionTraceOrdinal":0,"Kind":"SessionStarted","KindSchemaVersion":1,"OccurredAtUtc":"2026-04-21T09:00:00Z","SourceOrigin":"orchestrator","Evidence":{"Kind":"Synthetic","Identifier":"session:selfdeploying-happy:started","Summary":"Session start anchor"}}
|
||||
{"SessionSignalOrdinal":1,"SessionTraceOrdinal":1,"Kind":"EspPhaseChanged","KindSchemaVersion":1,"OccurredAtUtc":"2026-04-21T09:01:00Z","SourceOrigin":"EspAndHelloTracker","Evidence":{"Kind":"Derived","Identifier":"esp-phase-detector-v1","Summary":"ESP phase=DeviceSetup (SkipUserStatusPage)","DerivationInputs":{"rawValue":"1","skipUserStatusPage":"true"}},"Payload":{"phase":"DeviceSetup"}}
|
||||
{"SessionSignalOrdinal":2,"SessionTraceOrdinal":2,"Kind":"DeviceSetupProvisioningComplete","KindSchemaVersion":1,"OccurredAtUtc":"2026-04-21T09:03:00Z","SourceOrigin":"ProvisioningStatusTracker","Evidence":{"Kind":"Derived","Identifier":"provisioning-status-tracker-v1","Summary":"Device setup provisioning complete","DerivationInputs":{"registryKey":"HKLM\\...Provisioning\\Status","rawValue":"Complete"}}}
|
||||
Reference in New Issue
Block a user