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>
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>
- Fix 500 error on download URL: replace GenerateSasUri() with
blobClient.Uri (SAS already embedded in service-level connection string)
- Add AdminNote field to SessionReportMetadata shared model
- Read AdminNote from Table Storage in GetAllReportsAsync
- Add UpdateAdminNoteAsync to SessionReportService
- New PATCH /api/galactic/session-reports/{reportId}/note endpoint
- Frontend: editable Admin Note textarea in report detail modal with
inline save feedback; Admin Note column in reports table with icon
- Add copy-to-clipboard buttons for Session ID, Tenant ID,
Submitted By, and Email fields in report detail modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tenant Admins can report sessions for analysis via a new button on the
session details page. Creates a ZIP with session JSON, events JSON,
analysis results, timeline TXT, events CSV, optional agent log (5 MB
limit), and optional screenshot — uploaded to central blob storage.
Admin-config page gets a new Session Reports table (Galactic Admin only)
showing all submitted reports with comment, email, and blob reference.
Export utilities (generateUiExport, generateCsvExport) extracted to
shared lib/sessionExportUtils.ts for reuse across admin export and
report modal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add AgentSelfMetricsCollector that measures the agent's own CPU, memory,
threads, handles, and HTTP network traffic (bytes up/down, latency,
request count) with zero overhead — no WMI, no PerformanceCounters,
just Process properties and lock-free Interlocked counters.
NetworkMetrics tracks all BackendApiClient HTTP calls via Stopwatch and
byte counting in finally blocks. The collector emits agent_metrics_snapshot
events (Severity.Debug) through the existing spool/upload pipeline.
Add Galactic Admin-only /platform-metrics page that aggregates agent
footprint data across sessions with CPU/memory/network assessment badges,
per-session breakdowns, and agent version distribution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New RuleStates table stores per-tenant enabled/disabled state separately
from rule definitions, so built-in/community rule updates propagate
centrally without losing tenant preferences
- New IsCommunity flag on GatherRule and AnalyzeRule models; community
rules behave like built-in but show a Community badge in the UI
- Merge logic updated: global partition holds definitions (built-in +
community), RuleStates holds per-tenant toggles, tenant partition
holds custom rules only
- Fixed AnalyzeRule Trigger field not being persisted to/from Table Storage
- Fixed EnsureBuiltInRulesSeededAsync comparison to include Version,
Trigger, and TriggerPhase so definition changes auto-propagate
- Removed Edit (Global) and Delete (Global) from Gather/Analyze rules UI;
rule management is now exclusively via Git + Reseed
- Added Community filter option to type filter dropdowns
- ReseedFromGitHubFunction now deletes both IsBuiltIn and IsCommunity
rules from global partition before reseeding
- GalacticAdminService dependency removed from Gather/AnalyzeRulesFunction
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Rules as GitHub single source of truth
- Add 75 individual JSON rule files (2 gather, 18 analyze, 55 IME patterns)
under rules/gather/, rules/analyze/, rules/ime-log-patterns/
- Add JSON Schemas for all three rule types with VS Code IntelliSense support
- Add CI combine script (rules/scripts/combine.js) + GitHub Action that
validates schemas with ajv-cli and generates rules/dist/ combined files
- Convert BuiltIn*.cs from hardcoded C# objects to embedded JSON resource loaders
- Add GitHubRuleRepository service to fetch dist/ files from raw.githubusercontent.com
- Add ReseedFromGitHubFunction: POST /api/rules/reseed-from-github?type=all|gather|analyze|ime
## IME Log Patterns
- Extend ImeLogPattern model with Description and IsBuiltIn properties
- Add ImeLogPatterns table + API constants
- Add TableStorageService CRUD methods for IME log patterns
- Add ImeLogPatternService (CRUD, merge, auto-seed from embedded JSON)
- Add ImeLogPatternsFunction: GET/PUT /api/rules/ime-log-patterns (Phase 1: read-only
for tenants, Galactic Admin can enable/disable/edit globally)
- Add IME Log Patterns management page (/ime-log-patterns)
- Wire GetAgentConfigFunction to use ImeLogPatternService instead of static class
## API route consolidation
- All rule endpoints now under /api/rules/* for single certificate exclusion path:
/api/rules/gather, /api/rules/analyze, /api/rules/ime-log-patterns,
/api/rules/reseed-from-github
## Export buttons
- Add per-rule and export-all JSON download buttons to gather-rules,
analyze-rules, and ime-log-patterns pages
## Admin configuration
- Replace old individual reseed buttons with unified "Fetch & Reseed from GitHub"
(calls new GitHub endpoint) plus compact individual-type reseed buttons
- Add CDN cache hint: GitHub raw.githubusercontent.com caches up to 5 minutes
## Preview mode
- ?preview=1 URL param seeds localStorage.previewMode (persists across navigation)
- ?preview=0 clears it; toggle in Navbar settings dropdown (indigo, eye icon)
- IME Log Patterns nav link gated behind galacticAdminMode OR previewMode
- Preview badge shown on nav link when active for normal users
## Tests
- Add xUnit test project (net8.0) with 5 tests for embedded resource loading,
known IME actions validation, and valid categories check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Agent:
- Rewrite EventLog collector from System.Diagnostics.EventLog to
EventLogReader — supports operational/analytic logs (e.g.,
Microsoft-Windows-Shell-Core/Operational), not just classic logs
- New parameters: eventId (XPath filter), messageFilter (*contains*),
source (provider name), maxEntries (1-50)
- Event entries now include Event ID in output: [ID:62407]
UI (gather-rules):
- Dynamic target placeholder and hint text per collector type
- EventLog parameter fields: Event ID, Max Entries, Message Filter
- buildParameters() serializes params per collector type (create+edit)
- on_event placeholder updated to show enrollment_complete/failed
- Documentation link in header → /docs#gather-rules
Docs:
- New "Gather Rules" section with all 6 collector types documented
- Each collector: target format, parameters, allowlists, examples
- Trigger types documented (startup, interval, phase_change, on_event)
- Hash-based deep linking from gather-rules page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The event sequence counter reset to 0 on every agent restart, causing three
bugs in WhiteGlove (pre-provisioning) sessions: spool sort order mixed boot
events, backend status regressed from Pending to InProgress, and UI timeline
split broke. Additionally, EventSpool.RemoveEvents used a glob pattern that
could delete files from both boots sharing the same sequence number.
Changes:
- SessionPersistence: persist sequence in session.seq alongside session.id
- EventSpool: RemoveEvents uses exact filename instead of glob, new
GetMaxSequence for crash recovery
- MonitoringService: init sequence from persistence + spool ceiling, periodic
save every 50 events + on critical events, save before WhiteGlove exit
- TableStorageService: document WhiteGlove guard as promotion rule
- EnrollmentEvent: clarify Timestamp is agent-side, not server-side
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename HelloDetector → EspAndHelloTracker (class tracks both ESP and Hello)
- Detect CommercialOOBE_ESPProgress_WhiteGlove_Success (EventID 62407) and emit
whiteglove_complete event; WhiteGlove check placed before generic OOBE_ESP.*Exiting
regex to avoid false match; idempotency guard prevents duplicate events
- Detect CommercialOOBE_ESPProgress_Failure (EventID 62407) and emit esp_failure
event with Error severity for timeline visibility
- Agent exits gracefully after WhiteGlove (Environment.Exit, no DeleteSessionId,
no enrollment-complete marker); scheduled task restarts on next boot
- EnrollmentTracker subscribes to WhiteGloveCompleted event, stops summary timer,
emits whiteglove_complete without writing completion marker or deleting IME state
- Backend: whiteglove_complete transitions session to SessionStatus.Pending;
SetSessionPreProvisionedAsync marks session for split-timeline rendering
- Backend: Pending → InProgress auto-resume on agent re-registration (WhiteGlove Part 2)
- Backend: GetExcessiveDataSendersAsync excludes IsPreProvisioned sessions to prevent
false blocking of devices resuming after weeks in storage
- Shared: SessionStatus.Pending added; IsPreProvisioned added to SessionSummary;
WhiteGloveComplete event type constant added
- UI: Pending status badge (amber) in dashboard and session detail
- UI: Split timeline for WhiteGlove sessions — Pre-Provisioning Part + User Enrollment
Part; Awaiting User Enrollment info box when Part 2 has no events yet
- UI: Mark as Failed button enabled for Pending sessions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Devices that continuously send data beyond a configurable window (default
24h) are now automatically blocked by the daily maintenance function,
regardless of session status. Introduces LastEventAt tracking on session
entities (written on every event batch ingest) to enable efficient
per-tenant queries without scanning the Events table.
- Add LastEventAt field to SessionSummary and session entity writes
- Add GetExcessiveDataSendersAsync query (StartedAt < cutoff AND LastEventAt > cutoff)
- Add BlockExcessiveDataSendersAsync maintenance step with idempotency check
- Add MaxSessionWindowHours and MaintenanceBlockDurationHours to AdminConfiguration
- Expose both settings in Device Block Management section of Admin Config UI
- Include HelloDetector agent changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Galactic Admins can block a device by serial number from the admin-configuration
page. The block is enforced at ingest time (after security validation, before
body decompression) and is configurable in duration (default 12h, max 30 days).
The agent receives a DeviceBlocked signal in the IngestEventsResponse and stops
all uploads for the session. Blocks persist across backend restarts via a new
BlockedDevices table in Azure Table Storage, with an in-memory cache for fast
ingest lookups.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Agent/Backend: configurable MaxCollectorDurationHours (default 4h) stops interval-based collectors on stuck sessions; setting exposed in global admin config
- Backend: IngestEventsFunction now runs all security checks (cert, rate limit, hardware whitelist, Autopilot validation) before touching the request body, eliminating unnecessary gzip decompression and JSON parsing on rejected requests; added TenantId header/body cross-check
- Backend/Frontend: SignalR eventStream refactored from full event payload to signal-only (newEventCount); frontend fetches canonical event state from Table Storage on each signal, eliminating gap issues and phase-name race conditions
- Frontend: fetchEvents replaces (not merges) event list; phaseName computed at render time in eventsByPhase useMemo; 30s catch-up fetch added as safety net
- Frontend: session event export tool in admin-configuration (teal block) — fetch by sessionId+tenantId, download timeline export (.txt with UI phase grouping) and raw CSV export
- Frontend: changelog page (/changelog) with Private Preview context and first architecture entry; dashboard feedback banner updated to link changelog; Navbar overflow menu gains Preview Changelog link
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove UploadBundleRequest, UploadBundleResponse classes (ApiModels.cs)
- Remove UploadBundle API endpoint constant (Constants.cs)
- Remove GetBundleUploadUrlAsync method (BackendApiClient.cs) — no callers anywhere
- Fix HardwareInfo: Lenovo devices report the marketing model name in
Win32_ComputerSystemProduct.Version, not Win32_ComputerSystem.Model;
add Lenovo-specific WMI fallback for correct model string
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of delivering the long-lived SAS URL in the agent config response
(where it was cached on disk for the full enrollment period), the agent now
requests a short-lived URL via POST /api/diagnostics/upload-url just before
uploading — the URL is held in memory only and never written to config.
Backend: new GetDiagnosticsUploadUrlFunction with same cert/hardware
security validation as all other agent endpoints; GetAgentConfigFunction
now sends DiagnosticsUploadEnabled (bool) instead of the SAS URL;
ConfigVersion bumped to 6.
Agent: BackendApiClient.GetDiagnosticsUploadUrlAsync added;
DiagnosticsPackageService fetches URL on-demand via the API client;
AgentConfiguration.DiagnosticsBlobSasUrl replaced by DiagnosticsUploadEnabled.
UI: settings page parses SAS URL expiry (se= param) and shows expiry date
inline with colour coding; expired or near-expiry URLs trigger
error/warning notifications at the top of the page on load.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the hardcoded app_state_regression source with a generic
event_correlation source that allows correlating any two event types
by a shared field (e.g. appId), with optional time windows and filters
on both events — all configurable via JSON without backend code changes.
RuleCondition gains 5 new nullable properties:
- CorrelateEventType: second event type (Event B)
- JoinField: field to match across both events (e.g. "appId")
- TimeWindowSeconds: max time between Event A and B
- EventAFilterField/Operator/Value: optional filter on Event A
ANALYZE-APP-010 migrated to use event_correlation source.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous rule incorrectly matched any app_install_completed event
against any app_install_failed event without correlating by appId.
Changes:
- RuleEngine: add app_state_regression source that finds apps where
app_install_completed is followed by app_install_failed for the same
appId, with optional DataField filter on the failed event; returns
appId, appName, both event details and errorDetail as evidence
- AnalyzeRule: document new app_state_regression source in comment
- ANALYZE-APP-010: rewrite to use single app_state_regression condition
(IME-ERROR-REPORT filter) + enrollment_complete check; evidence now
contains the exact app name and id that regressed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sends a Telegram message to @AutopilotMonitor-PrivatePreview whenever
a new tenant signs up. The bot webhook URL is stored in the new
PreviewConfig table (PartitionKey=TelegramBot, RowKey=config,
WebhookUrl=<url>) — same lifecycle as PreviewWhitelist, easy to remove
after GA. Notification is fire-and-forget and non-fatal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix for analyze rule reboot loop
added completed timestamp to progress portal
longer os age default in bootstrapper
removed legacy filed name in events