Files
AutopilotMonitor/src
Oliver Kieselbach caa573678f fix(decisioncore,agent/v2): defang ESP terminal failure when ContinueAnyway + AccountSetup entered
Session 4fa5a2d4 in tenant c9787ba2 hit 199/202 false-positive enrollment_failed events: ESP
profile sets BlockInStatusPage=6 (ContinueAnyway=true), Windows transitioned to AccountSetup,
then wrote DeviceSetupCategory=failed (Certificates) 13 min later — the reducer terminated
even though the device demonstrably kept progressing. PR1 Tier-1 defang downgrades these
EspTerminalFailure signals to a non-terminal esp_failure_advisory event while keeping
monitoring armed (Hello/Desktop/IME completion paths continue to drive the session).

DecisionEngine:
- New SignalFact<DateTime>? EspAdvisoryFailureRecordedUtc on DecisionState as the fire-once
  gate (additive ctor param, optional at end — preserves Newtonsoft positional-binding for
  pre-PR1 v4 snapshots).
- HandleEspTerminalFailureV1: when ScenarioObservations.EspAllowContinueAnyway==true AND
  AccountSetupEnteredUtc!=null, emit esp_failure_advisory (severity=Warning,
  advisoryReason=esp_failure_defanged_continueanyway_with_accountsetup) instead of
  transitioning to Failed. Stage / deadlines / outcome stay untouched.
- Duplicate handling: a second EspTerminalFailure dead-ends UNLESS the duplicate carries
  registry-specific keys (errorCode/failedSubcategory/category) — those only appear in the
  ProvisioningStatusTracker path and indicate enrichment of an earlier ShellCoreTracker
  advisory. failureType alone is NOT a registry marker (both adapters emit it).
- New BuildEspFailureAuditTrail helper merges a whitelisted set of failure-context keys into
  the audit-trail typedPayload so HRESULT/subcategory survive the wire. Closes a pre-existing
  gap on the enrollment_failed path too.

Bootstrap:
- EnrollmentOrchestrator.PostEspConfigDetectedBootstrap switched from EspSkipConfigurationProbe.
  Read() (tuple SkipUser/SkipDevice) to ReadFull() so EspAllowContinueAnyway +
  EspSyncFailureTimeoutMinutes are synchronously available before any EspTerminalFailure
  signal — defeats the fire-and-forget DeviceInfoCollector.CollectAll race that would have
  left Tier 1 with ScenarioObservations.EspAllowContinueAnyway==null.

Tests:
- EspFailureAdvisoryDefangTests (9 new): advisory path, fire-once idempotency, enrichment
  on registry-detail second-fire, dead-end on ShellCore-only duplicate, three negative paths
  (ContinueAnyway false / unknown / no AccountSetup), TypedPayload whitelist merge on both
  advisory + Failed paths.
- SerializationTests: backcompat test for old v4 snapshots without EspAdvisoryFailureRecordedUtc.
- DecisionStateSnapshotBuilderTests: fact count bumped from 14 to 15.
- EdgeScenarioTests + esp-failure-advisory-continueanyway-v1.jsonl: end-to-end replay against
  the canonical Session 4fa5a2d4 signal stream including a duplicate dead-end.
- EnrollmentOrchestratorEspConfigBootstrapTests: 4 existing tests migrated to ScopedFullOverride,
  4 new tests cover ContinueAnyway + SyncFailureTimeout propagation, class gated behind
  [Collection("SerialThreading")] since the override targets a static probe slot.

UI:
- EventTimeline renders esp_failure_advisory with an amber "Advisory (ContinueAnyway)" badge
  alongside the HRESULT/subcategory chips so analysts can distinguish defanged from real
  failures at a glance.
- gather-rules/eventTypes.ts mirrors the new constant for autocomplete.

Build: dotnet build AutopilotMonitor.sln green; xUnit Backend(1937) + V2.Core(1139) +
Agent.Core(538) + DecisionCore(357) + SummaryDialog(44) = 4015 passed, 0 failed.
Web tsc clean, vitest 384/384 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 03:07:27 +02:00
..