From c0b7b504579bb494c3038da79b799570a4f5e353 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Mon, 4 May 2026 11:25:35 +0200 Subject: [PATCH] =?UTF-8?q?fix(v2):=20PR-X1=20=E2=80=94=20skip=20summary?= =?UTF-8?q?=20dialog=20+=20enrollment=5Fsummary=5Fshown=20on=20WG=20Part?= =?UTF-8?q?=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WhiteGlove Part 1 sealing has no end-user session and no completed app installs, so launching the summary dialog there either lands invisibly in SYSTEM context (about to be reseal-rebooted) or fails outright; the enrollment_summary_shown event was semantically wrong and polluting the timeline. Gate RunBuildAndLaunchDialog (and the state==null warn-only branch) behind !isWhiteGlovePart1; Completed / Failed / WhiteGloveCompletedPart2 paths unchanged. Plan reference: bdb3cf9d-wg-part1-part2-followups.md PR-X1. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../EnrollmentTerminationHandlerTests.cs | 51 +++++++++++++++++++ .../EnrollmentTerminationHandler.cs | 22 +++++--- .../buildcounter.txt | 2 +- .../AutopilotMonitor.Agent/buildcounter.txt | 2 +- 4 files changed, 69 insertions(+), 8 deletions(-) diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Termination/EnrollmentTerminationHandlerTests.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Termination/EnrollmentTerminationHandlerTests.cs index 6debe090..a4053311 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Termination/EnrollmentTerminationHandlerTests.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Termination/EnrollmentTerminationHandlerTests.cs @@ -424,6 +424,57 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Termination Assert.Contains("enrollment_summary_shown", rig.EmittedEventTypes); } + // ============================================================= PR-X1 (bdb3cf9d, 2026-05-04) + // WhiteGlove Part 1 must NOT launch the summary dialog or emit + // enrollment_summary_shown — the device is about to be reseal-rebooted, the user + // session does not exist yet, and apps have not installed. The other terminal + // stages (Completed / Failed / WhiteGloveCompletedPart2) still launch the dialog. + + [Fact] + public void Handle_skips_summary_dialog_on_whiteglove_part1_even_with_dialog_enabled() + { + using var rig = new Rig(); + rig.State = new DecisionStateBuilder(DecisionState.CreateInitial("S1", "T1")) { Stage = SessionStage.WhiteGloveSealed }.Build(); + // Self-destruct stays off so we don't tear down between assertions, dialog is + // explicitly enabled to prove the Part-1 stage gate beats the config flag. + var cfg = rig.BuildConfig(showDialog: true, selfDestruct: false); + + rig.Build(cfg).Handle(sender: null!, + Args(EnrollmentTerminationReason.DecisionTerminalStage, EnrollmentTerminationOutcome.Succeeded, SessionStage.WhiteGloveSealed)); + + Assert.DoesNotContain("enrollment_summary_shown", rig.EmittedEventTypes); + Assert.False(File.Exists(Path.Combine(rig.StateDir, SummaryDialogLauncher.FinalStatusFileName)), + "final-status.json must not be written on WG Part 1 — the dialog launch is skipped."); + } + + [Fact] + public void Handle_emits_enrollment_summary_shown_on_whiteglove_part2_completion() + { + using var rig = new Rig(); + rig.State = new DecisionStateBuilder(DecisionState.CreateInitial("S1", "T1")) { Stage = SessionStage.WhiteGloveCompletedPart2 }.Build(); + var cfg = rig.BuildConfig(showDialog: true, selfDestruct: false); + + rig.Build(cfg).Handle(sender: null!, + Args(EnrollmentTerminationReason.DecisionTerminalStage, EnrollmentTerminationOutcome.Succeeded, SessionStage.WhiteGloveCompletedPart2)); + + Assert.Contains("enrollment_summary_shown", rig.EmittedEventTypes); + Assert.True(File.Exists(Path.Combine(rig.StateDir, SummaryDialogLauncher.FinalStatusFileName))); + } + + [Fact] + public void Handle_emits_enrollment_summary_shown_on_failed_termination() + { + using var rig = new Rig(); + rig.State = new DecisionStateBuilder(DecisionState.CreateInitial("S1", "T1")) { Stage = SessionStage.Failed }.Build(); + var cfg = rig.BuildConfig(showDialog: true, selfDestruct: false); + + rig.Build(cfg).Handle(sender: null!, + Args(EnrollmentTerminationReason.DecisionTerminalStage, EnrollmentTerminationOutcome.Failed, SessionStage.Failed)); + + Assert.Contains("enrollment_summary_shown", rig.EmittedEventTypes); + Assert.True(File.Exists(Path.Combine(rig.StateDir, SummaryDialogLauncher.FinalStatusFileName))); + } + // ============================================================= Plan §6.2 agent_shutting_down [Fact] diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Termination/EnrollmentTerminationHandler.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Termination/EnrollmentTerminationHandler.cs index 812cba70..0b186871 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Termination/EnrollmentTerminationHandler.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Termination/EnrollmentTerminationHandler.cs @@ -191,13 +191,23 @@ namespace AutopilotMonitor.Agent.V2.Core.Termination // also ends the running agent process. EmitAgentShuttingDown(args); - if (state == null) + // PR-X1 (bdb3cf9d follow-up, 2026-05-04) — WhiteGlove Part 1 sealing has + // no end-user session and no completed app installs. Launching the summary + // dialog there either lands invisibly in the SYSTEM context (about to be + // reseal-rebooted) or fails outright; either way the enrollment_summary_shown + // event is semantically wrong and pollutes the timeline. Skip the dialog + // build + launch + final-status.json write entirely on Part 1; the regular + // Completed / Failed / WhiteGloveCompletedPart2 paths still run unchanged. + if (!isWhiteGlovePart1) { - _logger.Warning("EnrollmentTerminationHandler: current state unavailable — skipping FinalStatus + SummaryDialog."); - } - else - { - RunBuildAndLaunchDialog(state, args); + if (state == null) + { + _logger.Warning("EnrollmentTerminationHandler: current state unavailable — skipping FinalStatus + SummaryDialog."); + } + else + { + RunBuildAndLaunchDialog(state, args); + } } // Plan §5 Fix 4b — emit app_tracking_summary terminal event before the diff --git a/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt index 30ce1158..3bfc51a6 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt @@ -1 +1 @@ -559 +562 diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index bd9b5070..cd2bf3dd 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -1242 +1243