mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
fix(web): split WhiteGlove timeline at Part-2 agent_started, not at whiteglove_resumed
The post-reseal agent_started + its agent_version_check are part of the User Enrollment block in the user's mental model, but the resumed-marker-based split kept them inside Part 1 because whiteglove_resumed is emitted ~0-2s after the boot. Walk back from whiteglove_resumed to the agent_started that sits between Part-1 close and resume, and split there. Falls back to the resumed marker when the boot event isn't present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,10 +7,14 @@ import { EnrollmentEvent } from "@/types";
|
||||
// (Part 1 still in progress, or session is not WhiteGlove at all).
|
||||
//
|
||||
// Resolution order — mirrors the agent-side V1-symmetric resume mechanic (PR-A):
|
||||
// 1. `whiteglove_resumed` is the definitive Part 2 marker (emitted by the orchestrator
|
||||
// after Archive-and-Reset detects WhiteGloveSealed snapshot). Returns its sequence - 1.
|
||||
// 2. Fallback for older agents that never emit `whiteglove_resumed`: the first
|
||||
// `agent_started` AFTER `whiteglove_complete` is the Part 2 boot. Returns its sequence - 1.
|
||||
// 1. `whiteglove_resumed` indicates a Part-2 resume happened. The boundary the user
|
||||
// perceives is the Part-2 boot itself, not the resumed marker that follows it
|
||||
// ~0–2s later. Look for the `agent_started` that sits between whiteglove_complete
|
||||
// and whiteglove_resumed and split THERE; this keeps the post-reseal boot
|
||||
// (agent_started + its agent_version_check) inside the User Enrollment block.
|
||||
// Falls back to `resumed.sequence - 1` if no agent_started is found in between.
|
||||
// 2. Older agents that never emit `whiteglove_resumed`: the first `agent_started`
|
||||
// AFTER `whiteglove_complete` is the Part 2 boot. Returns its sequence - 1.
|
||||
// 3. Pre-provisioning only (no Part 2 yet): the last cleanup event after `whiteglove_complete`
|
||||
// (`agent_shutdown`) ends Part 1; otherwise the `whiteglove_complete` sequence itself.
|
||||
// 4. Nothing: -1.
|
||||
@@ -19,6 +23,21 @@ export function computeWhiteGloveSplitSequence(events: EnrollmentEvent[]): numbe
|
||||
const resumedEvent = events.find(e => e.eventType === "whiteglove_resumed");
|
||||
|
||||
if (resumedEvent) {
|
||||
// Lower bound = whiteglove_complete iff it precedes resumed; in the race case
|
||||
// (Windows writes whiteglove_complete AFTER the Part-2 reboot) we drop the bound to 0.
|
||||
const lowerBound = (wgEvent && wgEvent.sequence < resumedEvent.sequence)
|
||||
? wgEvent.sequence
|
||||
: 0;
|
||||
// The most recent agent_started before whiteglove_resumed is the post-reseal Part-2 boot.
|
||||
// Sort candidates desc so we don't depend on the input being pre-sorted by sequence.
|
||||
const part2Boot = events
|
||||
.filter(e =>
|
||||
e.eventType === "agent_started" &&
|
||||
e.sequence > lowerBound &&
|
||||
e.sequence < resumedEvent.sequence
|
||||
)
|
||||
.sort((a, b) => b.sequence - a.sequence)[0];
|
||||
if (part2Boot) return part2Boot.sequence - 1;
|
||||
return resumedEvent.sequence - 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,18 +27,34 @@ describe("computeWhiteGloveSplitSequence", () => {
|
||||
expect(computeWhiteGloveSplitSequence(events)).toBe(-1);
|
||||
});
|
||||
|
||||
it("returns whiteglove_resumed.sequence-1 when the agent emitted the V1-symmetric Part 2 marker", () => {
|
||||
// PR-A: orchestrator archives state and emits whiteglove_resumed after Part-1 reseal.
|
||||
it("splits at the Part-2 agent_started that sits between whiteglove_complete and whiteglove_resumed", () => {
|
||||
// PR-A: orchestrator archives state and emits whiteglove_resumed ~0-2s after the
|
||||
// Part-2 agent_started boot. The user-perceived boundary is the boot itself, so the
|
||||
// post-reseal agent_started + its agent_version_check belong to the User Enrollment block.
|
||||
const events: EnrollmentEvent[] = [
|
||||
ev(1, "agent_started"),
|
||||
ev(2, "whiteglove_complete"),
|
||||
ev(3, "agent_shutdown"),
|
||||
ev(4, "agent_started"), // Part 2 boot
|
||||
ev(5, "whiteglove_resumed"), // definitive Part 2 marker (post Archive-and-Reset)
|
||||
ev(4, "agent_started"), // Part 2 boot — the actual split point
|
||||
ev(5, "whiteglove_resumed"), // resumed marker arrives moments later
|
||||
ev(6, "esp_phase_changed"),
|
||||
ev(7, "enrollment_complete"),
|
||||
];
|
||||
expect(computeWhiteGloveSplitSequence(events)).toBe(4); // 5 - 1
|
||||
expect(computeWhiteGloveSplitSequence(events)).toBe(3); // 4 - 1
|
||||
});
|
||||
|
||||
it("falls back to whiteglove_resumed.sequence-1 when no agent_started is found between Part-1 close and resume", () => {
|
||||
// Defensive: if the Part-2 boot event somehow isn't in the list (replay/filter), keep
|
||||
// the resumed marker as the boundary so Part-2 events still group correctly.
|
||||
const events: EnrollmentEvent[] = [
|
||||
ev(1, "agent_started"),
|
||||
ev(2, "whiteglove_complete"),
|
||||
ev(3, "agent_shutdown"),
|
||||
ev(4, "whiteglove_resumed"),
|
||||
ev(5, "esp_phase_changed"),
|
||||
ev(6, "enrollment_complete"),
|
||||
];
|
||||
expect(computeWhiteGloveSplitSequence(events)).toBe(3); // 4 - 1
|
||||
});
|
||||
|
||||
it("falls back to first agent_started after whiteglove_complete when no whiteglove_resumed (older agents)", () => {
|
||||
@@ -70,17 +86,18 @@ describe("computeWhiteGloveSplitSequence", () => {
|
||||
expect(computeWhiteGloveSplitSequence(events)).toBe(2);
|
||||
});
|
||||
|
||||
it("prefers whiteglove_resumed over the agent_started fallback even if both are present", () => {
|
||||
it("splits at agent_started even when whiteglove_complete arrives late (race condition)", () => {
|
||||
// Race: Windows writes the WhiteGlove success event after the Part-2 reboot, so the
|
||||
// event list contains both signals. The resumed marker is authoritative.
|
||||
// event list contains both signals. The Part-2 boot still is the boundary the user
|
||||
// perceives — whiteglove_complete then naturally lands inside the User Enrollment block.
|
||||
const events: EnrollmentEvent[] = [
|
||||
ev(1, "agent_started"),
|
||||
ev(2, "agent_shutdown"),
|
||||
ev(3, "agent_started"), // Part 2 boot — would be the fallback split
|
||||
ev(4, "whiteglove_resumed"), // primary trigger wins
|
||||
ev(3, "agent_started"), // Part 2 boot — split point
|
||||
ev(4, "whiteglove_resumed"),
|
||||
ev(5, "whiteglove_complete"), // arrived late from Windows
|
||||
ev(6, "enrollment_complete"),
|
||||
];
|
||||
expect(computeWhiteGloveSplitSequence(events)).toBe(3); // 4 - 1, NOT 2
|
||||
expect(computeWhiteGloveSplitSequence(events)).toBe(2); // 3 - 1
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user