fix(v2): PR-X1 — skip summary dialog + enrollment_summary_shown on WG Part 1

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) <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-05-04 11:25:35 +02:00
parent ce97a583d0
commit c0b7b50457
4 changed files with 69 additions and 8 deletions
@@ -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]
@@ -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
@@ -1 +1 @@
559
562
@@ -1 +1 @@
1242
1243