Oliver Kieselbach 92d300d4c2 v2 single-rail 7: codex-review feedback — 4 findings fixed
Addresses all 4 findings from the Codex review on PR #48. 564 V2.Core +
111 DecisionCore tests green (up from 559 + 109; 7 new regression tests).

Finding 1 (P1) — terminate_session drain deadlock
  TelemetryUploadOrchestrator raised ServerResponseReceived synchronously
  while holding _drainGuard. The terminate callback then invoked
  orchestrator.Stop() on the main thread, which tried a terminal
  DrainAllAsync and blocked on the same guard — timing out before the
  final agent_shutting_down event could be uploaded.
  Fix: buffer signal-bearing UploadResults during drain, release the
  guard, then raise events. Also added a defensive re-entry short-circuit
  in onTerminateRequested (skips if shutdown.IsSet) for the pathological
  case of a nested drain producing a different ActionId.
  Files: TelemetryUploadOrchestrator.cs, Program.cs
  Test: Handler_may_invoke_reentrant_drain_without_deadlock

Finding 2 (P1) — structured Event.Data was degenerating to type-name strings
  InformationalEventPost.StringifyData fell through to kv.Value.ToString()
  for nested containers, so e.g. network_adapters.adapters became the
  literal string "System.Collections.Generic.List\`1[...]" on the wire.
  Fix (redesigned after review): typed-sidecar through the bus, mirroring
  the existing DecisionEffect.ClassifierSnapshot pattern.
    DecisionSignal.TypedPayload (object?)   ← new
    DecisionEffect.TypedPayload (object?)   ← new
    ISignalIngressSink.Post(..., typedPayload)
    EventTimelineEmitter.Emit(..., typedPayload) uses it as Data verbatim
  Live path does ZERO serialization for structured event data — the
  Dictionary<string,object> flows object-identity-preserving from
  collector to TelemetryEventEmitter, where JsonConvert.SerializeObject
  runs once at the wire boundary. Persistence writes TypedPayload to
  JSONL once at the disk boundary (SignalSerializer); replay restores
  it as JObject/JArray tokens that re-serialize identically.
  Files: DecisionSignal.cs, DecisionEffect.cs, DecisionEngine.Shared.cs,
    SignalSerializer.cs, ISignalIngressSink.cs, SignalIngress.cs,
    EnrollmentOrchestrator.cs (LazyIngressSinkRelay), IEventTimelineEmitter.cs,
    EventTimelineEmitter.cs, EffectRunner.cs, InformationalEventPost.cs,
    + FakeSignalIngressSink, FakeEventTimelineEmitter test doubles
  Tests: TypedPayload_dictionary_becomes_EnrollmentEvent_Data_verbatim_on_wire,
    TypedPayload_missing_falls_back_to_string_parameter_reconstruction,
    Emit_EnrollmentEvent_overload_forwards_Data_as_typed_payload_by_reference,
    Emit_EnrollmentEvent_overload_passes_DateTime_as_live_object_through_typed_payload,
    SignalSerializer_roundtrip_preserves_TypedPayload_structure,
    SignalSerializer_roundtrip_null_TypedPayload_stays_null

Finding 3 (P2) — ImmediateUpload=false silently flipped to true
  Helper wrote the ImmediateUpload payload key only when true; the emitter
  defaulted "missing key → true". Net effect: a caller passing
  immediateUpload:false (e.g. performance_snapshot, agent_metrics_snapshot)
  had their intent silently overridden; every snapshot was flushed
  immediately.
  Fix: helper now writes the key both-ways ("true" or "false"). Emitter
  default stays true because reducer-synthesised terminal events
  (whiteglove_complete, enrollment_complete, enrollment_failed) construct
  effect parameters with just eventType and rely on that default for
  immediate flush — documented inline.
  Files: InformationalEventPost.cs, EventTimelineEmitter.cs (doc-only)
  Tests: ImmediateUpload_false_writes_explicit_false_key_to_payload,
    Emit_optional_payload_keys_are_set_only_when_caller_provides_them (updated)

Finding 4 (P2) — PeriodicCollectorLifecycleHost saw only its own posts
  IdleActivityObservingIngressSink wrapped the ingress per-host, so
  _lastRealEventTimeUtc was only updated by signals routed through this
  host's own _post. Every other component (Program.cs lifecycle, IME,
  ESP, DeviceInfo, Analyzer, Gather, StallProbe, Network, AAD, Desktop,
  etc.) constructs its own InformationalEventPost against the raw shared
  ingress and bypassed the wrapper — and periodic events posted by the
  host itself were correctly filtered out. Result: the idle clock never
  advanced past session start; collectors stopped after idle timeout
  regardless of live enrollment activity.
  Fix: moved observation into SignalIngress itself as an event
  SignalPosted, fired after every successful enqueue (fast path + slow
  path). PeriodicCollectorLifecycleHost subscribes via the concrete
  SignalIngress type at Start, unsubscribes at Stop. The observer now
  sees ALL signal kinds (ESP, IME, Classifier, DeadlineFired, etc.), not
  just InformationalEvents. Deleted the obsolete wrapper class.
  Files: SignalIngress.cs, DefaultComponentFactory.cs
  Tests: SignalPosted_fires_for_every_successful_post_regardless_of_kind,
    SignalPosted_handler_exception_does_not_break_ingress,
    SignalPosted_is_raised_even_when_channel_took_slow_path

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:58:14 +02:00
2026-04-22 19:36:19 +02:00
2026-04-22 19:36:19 +02:00
2026-03-17 18:24:01 +01:00
2026-03-26 07:02:33 +01:00
2026-04-04 19:17:01 +02:00

Autopilot Monitor

Advanced monitoring and troubleshooting solution for Windows Autopilot deployments. Gain full visibility into every enrollment session with a detailed event timeline, fleet health dashboards, and session reporting. Define custom analysis rules to automatically detect issues and gather rules to collect targeted evidence. Retrieve diagnostics packages on demand, configure agent settings like auto-reboot behavior and automatic timezone adjustment — all managed centrally from the web dashboard.

Private Preview

Autopilot Monitor is currently running as a Private Preview. Visit autopilotmonitor.com to request access and learn more.

Overview

Autopilot Monitor provides real-time tracking, intelligent diagnostics, and automated troubleshooting for Windows Autopilot enrollment processes. It consists of:

  • Bootstrap Script — PowerShell script deployed via Intune that starts monitoring early in the enrollment process
  • Monitoring Agent — Lightweight .NET application that collects telemetry and evidence during enrollment
  • Backend API — Azure Functions-based ingestion and processing pipeline
  • Web Dashboard — Next.js application for real-time monitoring and fleet analytics

Architecture

For detailed information about the system architecture, components, and data flow, see Architecture Documentation.

Documentation

Full documentation is available at autopilotmonitor.com/docs

License

This project uses a split licensing model:

  • MIT License — Agent (src/Agent/) and Shared library (src/Shared/) — unrestricted use on end-user devices
  • AGPL-3.0 — Backend (src/Backend/), Web Dashboard (src/Web/), and MCP Server (src/McpServer/) — server-side components remain open source

See LICENSE for full details.

S
Description
Windows Autopilot & Intune enrollment monitoring: live session timelines, fleet health, diagnostics, and an MCP server for AI-assisted troubleshooting.
Readme AGPL-3.0 60 MiB
Languages
C# 72.7%
TypeScript 25.7%
PowerShell 1%
JavaScript 0.5%