- IME shutdown handler: new imeShutdown action marks all active apps as
Postponed instead of incorrectly marking them Installed on IME restart
- Final status dump: writes comprehensive final-status.json on enrollment
complete/failed with all app states, signal timestamps, error summary
- Device vs user error counts: GetSummaryData() now reports deviceErrors
and userErrors separately for granular diagnostics
- Periodic debug state snapshot: 60s timer logs [StateSnapshot] to agent
log for stuck enrollment debugging (log-only, no event storage cost)
- All new timers properly stopped at every terminal event path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Desktop arrival during AccountSetup started Hello wait timer, which
timeout-resolved after 30s and triggered enrollment_complete via
desktop_hello path while ESP was still installing apps.
Three fixes:
- NotifyDesktopArrived: only start Hello timer when ESP is NOT active
- TryEmitEnrollmentComplete: ESP gate now blocks both desktop_arrival
and desktop_hello sources
- OnFinalizingSetupPhaseTriggered: try composite completion after ESP
final exit in case Hello was already resolved
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Multi-signal enrollment completion with 3 independent paths:
- IME pattern (existing), ESP final exit + Hello (composite),
Desktop arrival + Hello (no-ESP/WDP v2)
ESP failure detection: terminal failures emit enrollment_failed immediately,
recoverable failures (ESPProgress_Timeout) get 1-min grace period with
recovery cancellation on ESP phase change.
Agent max lifetime timer (6h default, configurable per-tenant) as safety
net against zombie agents. Desktop arrival detector polls explorer.exe
with WMI owner validation. Enrollment state persistence for crash recovery.
Completion check events with full state machine snapshot for observability.
Backend maintenance interval reduced from daily to every 2 hours.
ConfigVersion 12 → 13.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Periodic collectors (Performance, AgentSelfMetrics) now stop after 15 min
(configurable) of no real enrollment activity instead of a fixed 4h window.
They restart automatically when new activity is detected, preventing session
bloat for stuck/unfinished enrollments while keeping data flowing during
legitimately long ones.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a second device validation method alongside existing Autopilot Device
Validation. Corporate Identifier Validation checks devices against Intune
Corporate Device Identifiers (manufacturer + model + serial number) via the
Graph beta searchExistingIdentities endpoint.
Backend: Extract GraphTokenService from AutopilotDeviceValidator for shared
token/consent logic. Add CorporateIdentifierValidator with caching. Update
SecurityValidator with cascading validation (Autopilot first, Corporate
Identifier fallback). Wire new validator through all agent endpoints.
Frontend: Add second toggle in settings gated behind preview mode. Shared
consent flow skips redirect when consent already exists from other toggle.
Section renamed to "Enrollment Device Validation" in preview mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CollectAutopilotProfile now interprets AutopilotMode (User Driven/Self Deploying/White Glove)
and CloudAssignedDomainJoinMethod (Entra Join/Hybrid Azure AD Join) with human-readable labels
- Added IsHybridJoin as first-class session field (SessionRegistration, SessionSummary, Table Storage)
- DetectHybridJoinStatic reads DomainJoinMethod from registry at registration time
- UI session detail shows Autopilot Mode, improved Domain Join Method labels, HybridJoinSkipDCConnectivityCheck
- Dashboard shows purple "Hybrid" badge for Hybrid Azure AD Join sessions
- Note: IsUserDriven/IsPreProvisioned unchanged — profile fields are capabilities, not live state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevent phase regression in the event timeline when a reboot during User
Enrollment (Part 2) emits a new agent_started with Phase=Start. The
groupEventsByPhase function now accepts a preventPhaseRegression option
that tracks the highest phase index seen and suppresses regressions to
earlier phases. Reboot events stay in the active enrollment phase instead
of resetting the timeline to Start. Also adds timestamp as fallback sort
key to handle potential duplicate sequences from agent persistence gaps.
Applied to both UI rendering and text/CSV export for Part 2 only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When WhiteGlove pre-provisioning completes (status=Pending), all reached
phases should show as completed (green) since pre-provisioning is done.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Frontend: Add per-block durations to WhiteGlove event timeline headers,
pause-excluded combined duration in Session Info, visual dashed separator
between Pre-Provisioning and User Enrollment blocks, and group labels with
vertical separator in the Enrollment Progress tracker.
Backend: Store ResumedAt timestamp when WhiteGlove Part 2 starts, use it
to compute Duration 2 (user enrollment only) for Teams enrollment notifications.
Rename "WhiteGlove Pre-Provisioning" to "Pre-Provisioning" in notification text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Galactic Admin Mode for Audit Logs: banner + cross-tenant endpoint (galactic/audit/logs)
- Audit page pagination (12 entries per page) with Previous/Next controls
- Platform Metrics: moved computation to backend PlatformMetricsService with 5-min cache
- New endpoint GET /api/galactic/metrics/platform replaces N+1 client-side fetches
- IME log tracker enhancements, DO telemetry pattern, and download progress improvements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes Azure Table Storage 64KB truncation by removing the full apps array
from app_tracking_summary (could be 150+ apps) and replacing it with
lightweight state-breakdown counters (downloading, installing, installed,
skipped, failed, pending). Summary emission is now event-driven with
30s timer as backstop. New InstallProgress UI component shows per-app
install state with duration tracking. ConfigVersion 10 -> 11.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend: DeserializeEventData now returns raw string in _rawDataJson key
when JSON parsing fails (truncated data), instead of empty dict.
UI: EventTimeline detects _rawDataJson and shows an amber warning with
the raw text, so truncated events remain inspectable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DataJson and Message are now capped at 30K chars before writing to Table Storage,
preventing PropertyValueTooLarge failures that silently dropped events.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Agent persists a whiteglove.complete marker when Part 1 finishes. On Part 2
boot, the agent detects the marker, emits a whiteglove_resumed event, and
clears it. The backend handles whiteglove_resumed by transitioning the session
from Pending to InProgress — only if actually Pending (guards against
overwriting Succeeded/Failed). Registration also retries with exponential
backoff (5 attempts) to handle delayed network during OOBE boot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Windows EventLog record.TimeCreated returns DateTime with Kind=Local. The Azure
Data Tables SDK rejects Local DateTimes with NotSupportedException, causing
UpdateSessionStatusAsync to fail deterministically for every WhiteGlove session
(Shell-Core EventID 62407 = whiteglove_complete). This was the root cause of the
systematic 6/6 WhiteGlove failures, not ETag conflicts or transient Azure errors.
Agent fix: convert record.TimeCreated to UTC at all 3 EventLog timestamp sources.
Backend fix: EnsureUtc() safety net on all 11 DateTime writes to the sessions table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>