Remove customer identifiers from comments, tests and a fixture (D-338)

Customer domains, tenant-ID fragments, two full customer tenant GUIDs and
four real session GUIDs had found their way into code comments, test
constants and a fixture comment. They are replaced by neutral descriptions
("field case", "kiosk tenant") and synthetic values; no behaviour changes.
A check of every onboarded tenant's domain and ID prefix against the tracked
files finds no customer identifier left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-10-09 21:34:07 +02:00
parent c287cb3eae
commit e8b8a8de0c
42 changed files with 77 additions and 78 deletions
@@ -144,7 +144,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Configuration
Assert.Equal(4, svc.LastFetchAttempts);
}
// ── Network-link gate (tenant aebdce78 audit 2026-08-24) ────────────
// ── Network-link gate (kiosk-tenant audit 2026-08-24) ────────────
[Fact]
public async Task FetchConfig_InitialFetch_WaitsForNetworkLink()
@@ -174,7 +174,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring
[Fact]
public void Reevaluate_does_not_re_emit_starved_warnings()
{
// sits-d Cloud-PC fix (2026-08-19): the synthesis re-check now runs on EVERY IME
// Cloud-PC fix (2026-08-19): the synthesis re-check now runs on EVERY IME
// app-state transition. It must not turn the one-shot starvation warning into a
// per-transition stream — the re-check path suppresses the emission entirely
// (the per-appId dedupe would swallow the duplicates, but walking the probe
@@ -198,7 +198,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring
[Fact]
public void Starved_apps_that_settle_later_still_complete_the_enrollment()
{
// The real sits-d shape: the ESP exits with apps still starved (warning emitted),
// The real field shape: the ESP exits with apps still starved (warning emitted),
// the apps then all settle, and the re-check must open the gate — the warning
// already on the timeline is history, not a permanent veto.
using var f = new Fixture();
@@ -172,8 +172,8 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring
}
// ------------------------------------------------------------------------------
// sits-d Cloud-PC fix (2026-08-19) — the synthesis is no longer edge-only.
// Sessions 8110e262 / a89aac2d: the ESP page exited while 138 required user-ESP apps
// Cloud-PC fix (2026-08-19) — the synthesis is no longer edge-only.
// Two field sessions: the ESP page exited while 138 required user-ESP apps
// were still in flight, they all reached a terminal state (0 failed) minutes later,
// and because nothing re-checked, the enrollment never completed.
// ------------------------------------------------------------------------------
@@ -145,7 +145,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Orchestration
[Fact]
public void Ime_app_state_callback_is_chained_into_the_user_apps_settled_recheck()
{
// sits-d Cloud-PC fix (2026-08-19): the AccountSetup synthesis is re-evaluated on every
// Cloud-PC fix (2026-08-19): the AccountSetup synthesis is re-evaluated on every
// terminal IME app transition, which only works while the factory keeps this chain
// wired. It is five lines that a refactor can drop silently and whose absence costs a
// whole class of sessions their completion — so pin it here.
@@ -11,7 +11,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Program
{
/// <summary>
/// Contract tests for <see cref="AgentBootstrap.RetrySelfUpdateAfterEnrollment"/> —
/// the post-cert one-shot self-update retry (field case sits-d.cloud, 2026-08-09):
/// the post-cert one-shot self-update retry (field case 2026-08-09):
/// guard on <see cref="SelfUpdater.LastVersionCheckSucceeded"/>, the relaxed
/// timeouts/trigger it passes to the updater, and the swallow-everything exception
/// contract (bootstrap must continue on any updater failure). Uses the
@@ -182,7 +182,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Program
Assert.Equal("FromCache", data["outcome"]);
}
// ── Post-registration recovery (Phase 6.5, tenant aebdce78 audit 2026-08-24) ─
// ── Post-registration recovery (Phase 6.5, kiosk-tenant audit 2026-08-24) ─
[Fact]
public void EmitFetchFailedIfAny_AfterRecovery_MarksRecoveredAndDemotesToInfo()
@@ -107,7 +107,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Termination
[Fact]
public void Succeeded_outcome_is_an_info_about_tail_only_coverage_without_alarm_framing()
{
// The 659c3a90 shape: healthy enrollment, bootstrap merely ran late in the ESP queue.
// The field shape: healthy enrollment, bootstrap merely ran late in the ESP queue.
Describe(EnrollmentTerminationOutcome.Succeeded, "in_progress",
out var severity, out _, out var note);
@@ -14,7 +14,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Transport
/// 401/403 classification in <see cref="BackendApiClient"/>: a backend JSON 403 is a real
/// device-authorization verdict, while an HTML body means the response came from the Azure
/// platform / an edge proxy (stopped or retired app) and must NOT be reported as
/// "device is not authorized". Field case sits-d.cloud 2026-08-09: a pre-cutover agent hit
/// "device is not authorized". Field case 2026-08-09: a pre-cutover agent hit
/// the stopped legacy Function App and support chased a non-existent authorization problem.
/// </summary>
public sealed class BackendApiClientAuthClassificationTests
@@ -46,7 +46,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Configuration
/// Upper bound for the pre-fetch network-link wait on the initial startup fetch.
/// Deliberately longer than the registration gate's 15s: the fetch runs FIRST after a
/// boot-time relaunch, when Wi-Fi may still be associating (802.1X, OOBE), and a fetch
/// burned into a dead link strands the whole session on built-in defaults — tenant aebdce78
/// burned into a dead link strands the whole session on built-in defaults — kiosk-tenant
/// audit 2026-08-24: ~50% of sessions lost their tenant config exactly this way.
/// </summary>
internal static readonly TimeSpan InitialFetchNetworkLinkWaitMax = TimeSpan.FromSeconds(60);
@@ -50,12 +50,12 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
// Session caa6cf50 gate-starvation fix (2026-06-11) — fire-once guard for the
// user-ESP-apps-settled AccountSetup synthesis (see MaybeSynthesizeAccountSetupComplete).
// Interlocked rather than a plain bool: since the sits-d Cloud-PC fix the synthesis is
// Interlocked rather than a plain bool: since the Cloud-PC fix the synthesis is
// ALSO re-evaluated from the IME app-state-change thread, so two threads can reach the
// guard concurrently (Shell-Core watcher thread + IME log thread).
private int _userAppsSettledSynthesisFired;
// sits-d Cloud-PC fix (2026-08-19) — set once a CONFIRMED post-AccountSetup Shell-Core
// Cloud-PC fix (2026-08-19) — set once a CONFIRMED post-AccountSetup Shell-Core
// exit has been observed (live OR backfill). The synthesis needs two facts: the ESP exit
// is an EDGE that happens once, the settled user-ESP apps are a LEVEL that may only be
// reached minutes later. Recording the edge lets ReevaluateUserAppsSettledSynthesis
@@ -374,7 +374,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
=> _shellCoreTracker?.BackfillUpdatePageTelemetry(lookbackMinutes);
/// <summary>
/// sits-d Cloud-PC fix (2026-08-19) — re-check the user-apps-settled AccountSetup
/// Cloud-PC fix (2026-08-19) — re-check the user-apps-settled AccountSetup
/// synthesis after the ESP exit has already been observed. Wired to the IME tracker's
/// app-state-change callback in <c>DefaultComponentFactory</c>.
/// <para>
@@ -138,7 +138,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
// - Explicit "Required app failed" lines from the ESP required-app enforcer
//
// TODO(stall-probe-imelog-scan): Also scan IntuneManagementExtension.log (not just
// AppWorkload.log). Learning from the Oriflame session b4b5d37e-a993-453e-b16b-9b75098022e4
// AppWorkload.log). Learning from a field session
// (MS Intune AAD token service outage 2026-04-08): the ESP blocker was
// "Failed to get AAD token. errorCode = 3399548929" (= 0xCAA90001 AADSTS_SERVER_ERROR)
// and this string lives in IntuneManagementExtension.log, NOT AppWorkload.log. Add a third
@@ -284,7 +284,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Transport
/// Throws <see cref="BackendAuthException"/> for 401/403 responses so callers can
/// distinguish authentication failures from transient server errors.
/// <para>
/// Field case (sits-d.cloud, 2026-08-09): a pre-cutover agent build pointed at a
/// Field case (2026-08-09): a pre-cutover agent build pointed at a
/// retired Function App; Azure answers for a stopped app with a platform-level 403
/// carrying an HTML error page ("Web App - Unavailable"). Our backend never returns
/// HTML on 401/403 — an HTML body therefore means the response did not come from the
@@ -11,7 +11,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Transport
/// signal once per second for at most <paramref name="maxWait"/>. It cannot prove backend
/// reachability — the callers' retry loops handle that — it only keeps retry budgets from
/// being burned into a link that is provably still down (BootTrigger relaunch after a
/// mid-enrollment reboot, Wi-Fi still associating; tenant aebdce78 audits 2026-08-23/24).
/// mid-enrollment reboot, Wi-Fi still associating; kiosk-tenant audits 2026-08-23/24).
/// Free on the normal path — a live link returns immediately. Probe errors end the wait,
/// never the caller's operation.
/// </summary>
@@ -258,7 +258,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Orchestration
// on esp_exiting events long after Start.
ImeLogHost? imeLogHostRef = null;
// sits-d Cloud-PC fix (2026-08-19), narrowed after review: the Shell-Core ESP-exit
// Cloud-PC fix (2026-08-19), narrowed after review: the Shell-Core ESP-exit
// replay is restart recovery, so it stays OFF on a first run and the happy path keeps
// its pre-fix behaviour exactly. See ResolveEspExitBackfillLookbackMinutes.
var espExitBackfillLookbackMinutes = ResolveEspExitBackfillLookbackMinutes(
@@ -453,7 +453,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Orchestration
imeLogHost.Tracker.OnUserTokenAcquired = lineUtc => aadJoinHost.NotifyUserTokenAcquired(lineUtc);
imeLogHost.Tracker.OnTokenFailureLine = (code, lineUtc) => aadJoinHost.NotifyTokenFailureLine(code, lineUtc);
// sits-d Cloud-PC fix (2026-08-19): the user-apps-settled AccountSetup synthesis used
// Cloud-PC fix (2026-08-19): the user-apps-settled AccountSetup synthesis used
// to run ONLY on the EspExited edge. On a tenant with a large required-app set the ESP
// page exits while apps are still in flight, the single attempt misses, and nothing
// ever re-checks — sessions 8110e262 / a89aac2d reached 138/138 apps with 0 failed and
@@ -118,7 +118,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Orchestration
/// the Shell-Core watcher only ever saw events written AFTER <see cref="Start"/>. Any
/// agent restart therefore lost an ESP exit (62407) or Hello-wizard start (62404) that
/// happened while the agent was down — and after a mid-ESP reboot that is exactly the
/// window the completion signal lands in (sits-d Cloud PCs, 2026-08-19: five sessions
/// window the completion signal lands in (Cloud PCs, 2026-08-19: five sessions
/// hung in AccountSetup until the server-side timeout while the devices were fine).
/// </para>
/// <para>
@@ -27,7 +27,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Security
/// <item>6 attempts total; between attempts <c>2^attempt</c> seconds (2s, 4s, 8s, 16s, 32s —
/// ~62 s of backoff). The original V1 budget of 5 attempts / ~30 s was too short for the
/// reboot-relaunch on Wi-Fi kiosks: the relaunched agent gave up and exited 7 before
/// the network came back, leaving the session silent forever (tenant aebdce78,
/// the network came back, leaving the session silent forever (kiosk-tenant
/// 2026-08-23 audit). A relaunch that misses here is lost — nothing retries later.</item>
/// <item>On <c>response.Success == true</c> → stop immediately.</item>
/// <item>On <see cref="BackendAuthException"/> (401/403) → report to the
@@ -824,7 +824,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Termination
/// IME-log event) but the agent actually observed only the end-state. Severity and phrasing
/// are outcome-calibrated via <see cref="ObservationCoverage.DescribeLateStart"/>: a failure
/// stays a Warning with the post-mortem/hung-script note, a success is an Info about
/// tail-only coverage (fleet 2026-07-28: tenant 659c3a90 collected 12 Warnings on healthy
/// tail-only coverage (fleet 2026-07-28: one tenant collected 12 Warnings on healthy
/// enrollments whose bootstrap merely ran late). No state mutation.
/// </summary>
private void MaybeEmitAgentLateStart(EnrollmentTerminatedEventArgs args)
@@ -188,7 +188,7 @@ namespace AutopilotMonitor.Agent.V2.Runtime
/// network yet — its failure says nothing about the latest version. The certificate is
/// proof of connectivity, and this is the last chance to swap a stranded old build
/// (stale baked-in ApiBaseUrl) for a current one BEFORE session registration hits a
/// retired endpoint (field case sits-d.cloud, 2026-08-09). Timeouts are relaxed vs. the
/// retired endpoint (field case 2026-08-09). Timeouts are relaxed vs. the
/// boot-speed-tuned startup check; enrollment is already past OOBE's critical path here.
/// On update success the process restarts and this method never returns; the
/// await-enrollment config was already removed, so the new binary proceeds normally.
@@ -76,7 +76,7 @@ namespace AutopilotMonitor.Agent.V2.Runtime
/// <summary>
/// Phase 6.5 — config self-heal. When the Phase 4 fetch ran before the network link was
/// up (agent relaunched at boot, Wi-Fi still associating — tenant aebdce78 audit 2026-08-24:
/// up (agent relaunched at boot, Wi-Fi still associating — kiosk-tenant audit 2026-08-24:
/// ~50% of sessions ran on ConfigVersion=0 exactly this way), a successful session
/// registration has just proven the backend reachable. Retry the fetch once and apply
/// the live tenant config instead of stranding the whole session on cache/defaults.
@@ -79,7 +79,7 @@ namespace AutopilotMonitor.Agent.V2.Runtime
// write to the same state file; per-enrollment lifecycle like the other state files.
var startupEventGate = new AutopilotMonitor.Agent.V2.Core.Persistence.StartupEventGate(stateSubdir, logger);
// sits-d Cloud-PC fix (2026-08-19): a reboot_kill means the agent was down across the
// Cloud-PC fix (2026-08-19): a reboot_kill means the agent was down across the
// forced mid-ESP restart. Hand the boot timestamp to the factory so the Shell-Core
// ESP-exit backfill can reach back over the whole downtime instead of 5 minutes.
var previousBootUtc =
@@ -65,7 +65,7 @@ namespace AutopilotMonitor.DecisionCore.Tests
bool helloPolicyEnabledRead = false,
bool allowContinueAnyway = false)
{
var state = DecisionState.CreateInitial("sess-2dac8298", "tenant-047b2e1f", T0);
var state = DecisionState.CreateInitial("sess-2dac8298", "tenant-a", T0);
state = engine.Reduce(state, MakeSignal(0, DecisionSignalKind.SessionStarted, T0)).NewState;
state = engine.Reduce(state, MakeSignal(
5, DecisionSignalKind.EspConfigDetected, T0.AddMinutes(1),
@@ -74,7 +74,7 @@ namespace AutopilotMonitor.DecisionCore.Tests
/// </summary>
private static DecisionState SetupSession(DecisionEngine engine)
{
var state = DecisionState.CreateInitial("sess-4910a5a5", "tenant-e46bc88e", T0);
var state = DecisionState.CreateInitial("sess-4910a5a5", "tenant-a", T0);
state = engine.Reduce(state, MakeSignal(0, DecisionSignalKind.SessionStarted, T0)).NewState;
state = engine.Reduce(state, MakeSignal(
5, DecisionSignalKind.EspConfigDetected, T0.AddMinutes(1),
@@ -105,7 +105,7 @@ namespace AutopilotMonitor.DecisionCore.Tests
["failureType"] = "Provisioning_DeviceSetup_Apps_Failed",
["failedSubcategory"] = "Apps",
["category"] = "DeviceSetup",
["likelyCulpritApps"] = "Oriflame Cosmectics - Teams Backgroundsv v2",
["likelyCulpritApps"] = "Contoso Branding - Teams Backgrounds v2",
["likelyCulpritAppCount"] = "1",
}));
@@ -129,7 +129,7 @@ namespace AutopilotMonitor.DecisionCore.Tests
var advisory = FindTimelineEffect(step, "esp_failure_advisory");
Assert.NotNull(advisory);
Assert.Equal("Oriflame Cosmectics - Teams Backgroundsv v2", advisory!.Parameters!["likelyCulpritApps"]);
Assert.Equal("Contoso Branding - Teams Backgrounds v2", advisory!.Parameters!["likelyCulpritApps"]);
}
// ===================================================== recovery hook ====
@@ -102,7 +102,7 @@ namespace AutopilotMonitor.DecisionCore.Tests
var builder = DecisionState.CreateInitial("s", "t", Fixed.AddDays(-1))
.ToBuilder()
.WithStage(SessionStage.EspAccountSetup)
.WithStepIndex(452) // the restored StepIndex the sits-d sessions showed
.WithStepIndex(452) // the restored StepIndex the field sessions showed
.WithLastAppliedSignalOrdinal(9);
if (accountSetupEntered)
@@ -194,7 +194,7 @@ namespace AutopilotMonitor.DecisionCore.Tests
bool helloEnabled = false,
bool includeDesktop = true)
{
var state = DecisionState.CreateInitial("sess-caa6cf50", "tenant-e46bc88e", T0);
var state = DecisionState.CreateInitial("sess-caa6cf50", "tenant-a", T0);
state = engine.Reduce(state, MakeSignal(0, DecisionSignalKind.SessionStarted, T0, null)).NewState;
state = engine.Reduce(state, MakeSignal(1, DecisionSignalKind.EspPhaseChanged, T0.AddMinutes(1),
new Dictionary<string, string> { [SignalPayloadKeys.EspPhase] = "DeviceSetup" })).NewState;
@@ -8,8 +8,7 @@ using Xunit;
namespace AutopilotMonitor.DecisionCore.Tests
{
/// <summary>
/// sits-d Cloud-PC fix (2026-08-20, sessions 8110e262 / a89aac2d / e7ba63c9 / cb4a485a /
/// 3d6278fb). On a SkipUser=true flow Windows never renders the user ESP page: Shell-Core
/// Cloud-PC fix (2026-08-20, five field sessions). On a SkipUser=true flow Windows never renders the user ESP page: Shell-Core
/// 62407 does not fire and the AccountSetup provisioning categories are never written —
/// verified live (Shell-Core watcher armed across three agent runs, zero 62407 in six
/// hours; SkipUserStatusPage read 10+ times across all runs, True every time). Arm B of
@@ -20,14 +19,14 @@ namespace AutopilotMonitor.DecisionCore.Tests
/// <para>
/// The fix lets the observed skip stand in for the final-exit evidence inside
/// <c>HandleImeUserSessionCompletedV1</c>'s completion attempt. These tests replay the real
/// 8110e262 signal ordering end to end (including the Fix-10 AccountSetup bounce-back a
/// field session's signal ordering end to end (including the Fix-10 AccountSetup bounce-back a
/// restart re-emission triggers) and pin the negative space: no skip observed, no desktop,
/// ghost IME completion, and Device Preparation flows are all unchanged.
/// </para>
/// </summary>
public sealed class ClassicSkipUserEspUserSessionCompletionTests
{
// 8110e262 run 1: agent_started 08:30:08Z.
// Field session run 1: agent_started 08:30:08Z.
private static readonly DateTime T0 = new DateTime(2026, 8, 19, 8, 30, 8, DateTimeKind.Utc);
[Fact]
@@ -35,7 +34,7 @@ namespace AutopilotMonitor.DecisionCore.Tests
{
var engine = new DecisionEngine();
var state = DecisionState.CreateInitial(
"8110e262-da57-4282-9290-bb0c96d05614", "5ca2b350", T0);
"33333333-3333-3333-3333-333333333333", "tenant-a", T0);
state = engine.Reduce(state, MakeSignal(0, DecisionSignalKind.SessionStarted, T0, null)).NewState;
@@ -94,7 +93,7 @@ namespace AutopilotMonitor.DecisionCore.Tests
[Fact]
public void Fix10_bounceback_is_healed_by_the_ime_reemission()
{
// Restart shape (8110e262 run 3): the promoted AwaitingHello gets bounced back by a
// Restart shape (field session run 3): the promoted AwaitingHello gets bounced back by a
// re-emitted AccountSetup phase line (Fix 10 cancels HelloSafety, deliberately — it
// guards the premature-promotion case). The IME phase line always precedes the
// user-session-complete line in log order, so the re-emitted completion re-knocks.
@@ -116,7 +115,7 @@ namespace AutopilotMonitor.DecisionCore.Tests
[Theory]
// No EspConfigDetected at all — SkipUserEsp unknown.
[InlineData(null)]
// Explicit SkipUser=false (the two sits-d machines that succeeded had this shape and
// Explicit SkipUser=false (the two field machines that succeeded had this shape and
// a REAL page: exit + registry evidence. Without the exit the gate must stay shut.)
[InlineData("false")]
public void No_promotion_without_an_observed_skip(string? skipUser)
@@ -9,7 +9,7 @@ using Xunit;
namespace AutopilotMonitor.DecisionCore.Tests
{
/// <summary>
/// Continue-Anyway observation mode (tenant c9787ba2, session 53d1e9f6, 2026-08-08).
/// Continue-Anyway observation mode (field case 2026-08-08).
/// A Device-phase ESP terminal failure (AccountSetup never entered) on a
/// Continue-Anyway-enabled profile is a hard fail by default; with the tenant opt-in
/// (<c>EspContinueAnywayObservationEnabled</c> observation) it is defanged into a 60-min
@@ -72,7 +72,7 @@ namespace AutopilotMonitor.DecisionCore.Tests
bool continueAnyway = true,
bool helloPolicyDisabled = true)
{
var state = DecisionState.CreateInitial("sess-53d1e9f6", "tenant-c9787ba2", T0);
var state = DecisionState.CreateInitial("sess-53d1e9f6", "tenant-a", T0);
state = engine.Reduce(state, MakeSignal(0, DecisionSignalKind.SessionStarted, T0)).NewState;
var espConfig = new Dictionary<string, string>
{
@@ -409,7 +409,7 @@ namespace AutopilotMonitor.DecisionCore.Tests
[Fact]
public void Timeout_fire_with_recent_first_deployment_activity_extends_instead_of_timing_out()
{
// Report 55e6afd61c9d (Douglas): timer armed at detection (phase 0, RJ agent MSI
// Field report: timer armed at detection (phase 0, RJ agent MSI
// install during DeviceSetup), first deployment only started 16 min later, Office
// completed 3 s before the deadline — the hard cut truncated an actively working
// deployment. The fire must now re-arm to lastActivity + 60 min instead.
@@ -70,7 +70,7 @@ namespace AutopilotMonitor.DecisionCore.Tests.Scenarios
{
// PR1 (Session 4fa5a2d4, 2026-05-22) — ContinueAnyway-aware ESP terminal-failure
// defang. The fixture replays the canonical race that produced 199/202 false
// positives in tenant c9787ba2: ContinueAnyway=true profile, AccountSetup entered,
// positives in one tenant: ContinueAnyway=true profile, AccountSetup entered,
// then DeviceSetup/Certificates registry-write flips to failed. Without PR1 this
// would land in Failed; PR1 records EspAdvisoryFailureRecordedUtc and keeps the
// stage at EspAccountSetup so the agent stays in monitoring.
@@ -222,7 +222,7 @@ public class AppHomingServiceTests
[Fact]
public async Task Probe_reports_only_add_on_roles_when_manifest_and_add_on_roles_are_both_missing()
{
// Live 2026-09-03 shape (thesrgroup, 1 s after consent): validation role still
// Live 2026-09-03 shape (1 s after consent): validation role still
// propagating AND the add-on missing. The add-on decides (permanent); the propagating
// manifest role is not the admin's problem and must not appear in the to-do list.
SetupProbe(GraphTokenResult.Success(Jwt()));
@@ -355,7 +355,7 @@ public class EnrollmentTimeoutClassifierTests
[Fact]
public void Classify_device_provisioned_user_phase_pending_within_grace_is_AwaitingUser()
{
// The dominant crcins.com case: DeviceSetup 4/4, AccountSetup 0/5, silent, 6h in.
// The dominant field case: DeviceSetup 4/4, AccountSetup 0/5, silent, 6h in.
var (status, reason) = Classify(new[] { Esp(DeviceSetup44), Esp(AccountSetup05) }, hoursSinceStart: 6);
Assert.Equal(SessionStatus.AwaitingUser, status);
Assert.Contains("Device Setup completed", reason);
@@ -382,7 +382,7 @@ public class EnrollmentTimeoutClassifierTests
public void ExtractRollup_agent_timeout_enrollment_failed_is_not_explicit_failure()
{
// The max-lifetime watchdog's enrollment_failed(failureType=agent_timeout) is "the agent
// gave up waiting", not a failure verdict — it must not poison rule 1 (tenant a53e67ec).
// gave up waiting", not a failure verdict — it must not poison rule 1 (misclassification audit 2026-07-16).
var timeout = Evt("enrollment_failed",
data: new Dictionary<string, object> { ["failureType"] = "agent_timeout" });
Assert.False(EnrollmentTimeoutClassifier.ExtractRollup(new[] { timeout }).HasExplicitFailure);
@@ -589,7 +589,7 @@ public class EnrollmentTimeoutClassifierTests
}
}
// -------- WhiteGlove Part-2 awaiting-user gate (fairstone.ca analysis 2026-08-21) --------
// -------- WhiteGlove Part-2 awaiting-user gate (field analysis 2026-08-21) --------
private static readonly DateTime Resumed = Start.AddMinutes(20);
@@ -603,7 +603,7 @@ public class EnrollmentTimeoutClassifierTests
return (status, reason);
}
/// <summary>The fairstone event shape: Part 1 sealed, Part 2 resumed, nobody signed in.</summary>
/// <summary>The field event shape: Part 1 sealed, Part 2 resumed, nobody signed in.</summary>
private static EnrollmentEvent[] WgParkedEvents() => new[]
{
Esp(DeviceSetup44), Evt("whiteglove_complete"), Esp(AccountSetup05),
@@ -660,7 +660,7 @@ public class EnrollmentTimeoutClassifierTests
[Fact]
public void Classify_whiteglove_part2_parked_within_grace_is_AwaitingUser()
{
// The fairstone shape: technician powers the device off at the logon screen minutes
// The field shape: technician powers the device off at the logon screen minutes
// after the reseal-reboot. Part-1 whiteglove_complete must NOT reconcile to Succeeded.
var (status, reason) = ClassifyWhiteGlove(WgParkedEvents(), hoursSinceStart: 6);
Assert.Equal(SessionStatus.AwaitingUser, status);
@@ -734,7 +734,7 @@ public class EnrollmentTimeoutClassifierTests
Assert.Equal(SessionStatus.Succeeded, status);
}
// -------- Self-deploying profile gate (kiosk tenant aebdce78, audit 2026-08-23) --------
// -------- Self-deploying profile gate (kiosk tenant, audit 2026-08-23) --------
private static (SessionStatus, string) ClassifySelfDeploying(
IReadOnlyList<EnrollmentEvent> events, double hoursSinceStart = 3, int grace = 51)
@@ -68,8 +68,8 @@ public class GetImeVersionHistoryRedactionTests
{
var json = SerializeFor(hasGlobalScope: false, MakeEntry());
Assert.DoesNotContain("57f34dd1-6a42-47f9-9bc0-b921fa6caa30", json, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("baae0453-6d19-47b5-aa58-8bed5a572fc8", json, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("aaaaaaaa-1111-4222-8333-444444444444", json, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("bbbbbbbb-1111-4222-8333-444444444444", json, StringComparison.OrdinalIgnoreCase);
}
[Fact]
@@ -109,8 +109,8 @@ public class GetImeVersionHistoryRedactionTests
using var doc = JsonDocument.Parse(SerializeFor(hasGlobalScope: true, MakeEntry()));
var row = doc.RootElement[0];
Assert.Equal("57f34dd1-6a42-47f9-9bc0-b921fa6caa30", row.GetProperty("firstSeenTenantId").GetString());
Assert.Equal("baae0453-6d19-47b5-aa58-8bed5a572fc8", row.GetProperty("firstSeenSessionId").GetString());
Assert.Equal("aaaaaaaa-1111-4222-8333-444444444444", row.GetProperty("firstSeenTenantId").GetString());
Assert.Equal("bbbbbbbb-1111-4222-8333-444444444444", row.GetProperty("firstSeenSessionId").GetString());
}
[Fact]
@@ -300,8 +300,8 @@ public class GetImeVersionHistoryRedactionTests
{
Version = version,
FirstSeenAt = firstSeenAt ?? First,
FirstSeenSessionId = "baae0453-6d19-47b5-aa58-8bed5a572fc8",
FirstSeenTenantId = "57f34dd1-6a42-47f9-9bc0-b921fa6caa30",
FirstSeenSessionId = "bbbbbbbb-1111-4222-8333-444444444444",
FirstSeenTenantId = "aaaaaaaa-1111-4222-8333-444444444444",
LastSeenAt = Last,
SessionCount = 3109,
MsiArchiveStatus = "Archived",
@@ -28,7 +28,7 @@ public class PreviewWhitelistRequesterUpnTests
[InlineData("user@contoso.com")]
[InlineData("admin@tenant.onmicrosoft.com")]
[InlineData("first.last@example.de")]
[InlineData("cadm_user@xx3t8.onmicrosoft.com")]
[InlineData("cadm_user@contoso.onmicrosoft.com")]
public void IsRealUserUpn_AcceptsRealUpnShapes(string upn)
{
Assert.True(TenantApprovalService.IsRealUserUpn(upn));
@@ -9,7 +9,7 @@ namespace AutopilotMonitor.Functions.Tests;
/// <summary>
/// Pins ANALYZE-ESP-004 ("ESP Gave Up on a Blocking App with 'Continue Anyway' — Soft Failure").
///
/// Background (tenant c9787ba2, session cb7036a6): a slow blocking app (Encompass
/// Background (field case): a slow blocking app (Encompass
/// Hybrid Installer) does not finish inside the 30-min Device-ESP window, so the ESP
/// fails terminally in DeviceSetup. The profile allows "Continue anyway", so the user
/// most likely dismissed the failure screen and reached the desktop — but the agent
@@ -25,8 +25,8 @@ namespace AutopilotMonitor.Functions.Tests;
/// </summary>
public class RuleEngineEspSoftFailureTests
{
private const string TenantId = "c9787ba2-29de-4944-91f0-73594c12f85d";
private const string SessionId = "cb7036a6-2c7c-470f-851b-24e5e537991c";
private const string TenantId = "11111111-1111-1111-1111-111111111111";
private const string SessionId = "22222222-2222-2222-2222-222222222222";
private const string FailedAppName = "Encompass Hybrid Installer";
[Fact]
@@ -146,7 +146,7 @@ public class RuleEngineEspSoftFailureTests
Assert.Empty(outcome.Results);
}
// ===== Event builders (faithful to session cb7036a6's actual payloads) =====
// ===== Event builders (faithful to the field session's actual payloads) =====
private static EnrollmentEvent EnrollmentFailedEvent(string? mayHaveContinuedAnyway) =>
new()
@@ -10,7 +10,7 @@ namespace AutopilotMonitor.Functions.Tests;
/// Pins the merge collision guard in <see cref="AnalyzeRuleService.GetAllRulesForTenantAsync"/>
/// and <see cref="GatherRuleService.GetAllRulesForTenantAsync"/>: a tenant-partition row whose
/// RuleId collides with a merged global rule (legacy debris from the pre-global-partition
/// seeding era, e.g. tenant 5ca2b350's ANALYZE-ID-002 copy from 2026-03) must be skipped —
/// seeding era, e.g. one tenant's ANALYZE-ID-002 copy from 2026-03) must be skipped —
/// the global definition wins. Without the guard the same RuleId appears twice in the merged
/// list and every ToDictionary(r =&gt; r.RuleId) consumer throws (prod: ArgumentException in
/// AnalyzeOnEnrollmentEndHandler.SafeNotifyRuleChannelsAsync, killing rule notifications).
@@ -46,7 +46,7 @@ public class SessionStatusBucketsTests
[Fact]
public void Failure_rate_denominator_is_terminal_only_excluding_incomplete()
{
// 1 succeeded, 1 failed, 8 incomplete (the crcins shape). Honest rate = 1/(1+1) = 50%,
// 1 succeeded, 1 failed, 8 incomplete (the field shape). Honest rate = 1/(1+1) = 50%,
// NOT 1/10 = 10% (Incomplete must not dilute) and NOT counting Incomplete as failures.
var b = Tally("Succeeded", "Failed", "Incomplete", "Incomplete", "Incomplete", "Incomplete",
"Incomplete", "Incomplete", "Incomplete", "Incomplete");
@@ -17,7 +17,7 @@ namespace AutopilotMonitor.Functions.Services
/// perpetually <c>"in_progress"</c> even at DeviceSetup 4/4 and AccountSetup 5/5), so the
/// authoritative rollup is the per-category "N of M subcategories completed" line — which
/// the agent authors in one place (ProvisioningStatusTracker) and which was validated at
/// scale against real crcins.com data. The 30s "all subcategories succeeded but
/// scale against real field data. The 30s "all subcategories succeeded but
/// categorySucceeded was not confirmed … treating as complete" fallback line is treated
/// as all-succeeded for its category.
/// </summary>
@@ -137,7 +137,7 @@ namespace AutopilotMonitor.Functions.Services
// enrollment failure verdict. Counting it as an explicit failure would send
// every timed-out-but-actually-provisioned session through rule 1 straight to
// Failed, defeating the honest classification this class exists for
// (misclassification audit 2026-07-16, tenant a53e67ec cluster).
// (misclassification audit 2026-07-16).
if (!Eq(TryGetDataString(evt, "failureType") ?? string.Empty, "agent_timeout"))
hasFailure = true;
else
@@ -259,7 +259,7 @@ namespace AutopilotMonitor.Functions.Services
}
/// <summary>
/// WhiteGlove Part-2 awaiting-user gate (fairstone.ca analysis 2026-08-21). After the
/// WhiteGlove Part-2 awaiting-user gate (field analysis 2026-08-21). After the
/// reseal-reboot the session is BY DEFINITION in the user-driven final phase: the agent
/// resumed (<c>whiteglove_resumed</c> → ResumedAt, the only writer of that column), the
/// Part-2 Device ESP re-confirms in minutes, and the device sits at the logon screen.
@@ -303,8 +303,8 @@ namespace AutopilotMonitor.Functions.Services
/// Device ESP reaching all-succeeded IS the enrollment's end, and the agent's own
/// SelfDeploying terminal only adds a 5-min confirmation window on top of it. When the
/// agent goes silent after that point (reboot into the kiosk autologon, powered off and
/// boxed for the school term, WiFi never re-associating for the relaunch — tenant
/// aebdce78, 836 sessions 2026-08-17..21), "awaiting user / Account Setup" is a factual
/// boxed for the school term, WiFi never re-associating for the relaunch — a kiosk
/// tenant, 836 sessions 2026-08-17..21), "awaiting user / Account Setup" is a factual
/// error and the later Incomplete("Account Setup 0/5") is worse: nobody will ever sign
/// in, the devices are finished and in service. Demands the registration-time
/// registry-confirmed profile flag (CloudAssignedOobeConfig 0x20|0x40), Device Setup
@@ -524,7 +524,7 @@ namespace AutopilotMonitor.Functions.Services
// enrollment_failed(failureType=agent_timeout) is the max-lifetime watchdog
// giving up, NOT an enrollment failure verdict. Hard-failing here misdeclared
// fully provisioned WhiteGlove Part-2 sessions whose user simply never logged
// in (misclassification audit 2026-07-16, tenant a53e67ec: honest verdict
// in (misclassification audit 2026-07-16: honest verdict
// AwaitingUser). Route through the same honest classification the maintenance
// sweep uses instead.
(statusTransitioned, failureReason) = await ApplyAgentGaveUpVerdictAsync(
@@ -665,7 +665,7 @@ namespace AutopilotMonitor.Functions.Services
if (c.SessionStalledEvent != null)
{
// WhiteGlove Part-2 awaiting-user gate (fairstone.ca analysis 2026-08-21): the
// WhiteGlove Part-2 awaiting-user gate (field analysis 2026-08-21): the
// agent-side stall probe also fires on a resumed Part-2 device that simply sits
// at the logon screen with nobody signing in. With zero user evidence since the
// resume that is the expected parking state between technician and end user, so
@@ -436,7 +436,7 @@ namespace AutopilotMonitor.Functions.Services
if (lastContactAt > silenceCutoff)
continue; // provably alive on the server clock
// WhiteGlove Part-2 awaiting-user gate (fairstone.ca analysis
// WhiteGlove Part-2 awaiting-user gate (field analysis
// 2026-08-21): after the reseal-reboot the technician routinely powers
// the device off at the logon screen to box it — that silence is the
// expected parking state between technician and end user, not a stall.
@@ -449,7 +449,7 @@ namespace AutopilotMonitor.Functions.Services
continue;
}
// Self-deploying profile gate (kiosk tenant aebdce78, 2026-08-23): a
// Self-deploying profile gate (kiosk tenant, 2026-08-23): a
// silent agent after Device ESP all-succeeded is a finished device
// (rebooted into the kiosk autologon / boxed), not a stall and never
// "awaiting user" — reconcile to Succeeded right here instead of
@@ -782,7 +782,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
/// signal's instant.
/// </para>
/// <para>
/// sits-d Cloud PCs (2026-08-20): on SkipUser=true flows the observed skip stands in
/// Cloud PCs (2026-08-20): on SkipUser=true flows the observed skip stands in
/// for the final-exit evidence — Windows never renders the user ESP page, so no
/// Shell-Core 62407 and no AccountSetup provisioning category can ever exist, and this
/// handler is the only remaining site that can knock on the (open) arm-B gate. See the
@@ -837,7 +837,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
// in HandleAccountSetupProvisioningCompleteV1) — AwaitingHello/AwaitingDesktop/
// Finalizing/terminal stages keep their existing rails untouched.
//
// sits-d Cloud PCs (2026-08-20): on a SkipUser=true flow Windows never renders the
// Cloud PCs (2026-08-20): on a SkipUser=true flow Windows never renders the
// user ESP page — Shell-Core 62407 does not fire and the AccountSetup provisioning
// categories are never written, so IsPostAccountSetupFinalExit is unsatisfiable by
// construction (there is no exit to record). Arm B of ShouldTransitionToAwaitingHello
@@ -164,8 +164,8 @@ namespace AutopilotMonitor.DecisionCore.Engine
/// <c>esp_failure_advisory</c> timeline entry, the stage stays unchanged, deadlines
/// remain armed, and normal completion paths (Hello/Desktop, IME pattern,
/// AccountSetupProvisioningComplete) continue to drive the session. Session 4fa5a2d4
/// (2026-05-22) — defangs the 199/202 false-positive failures observed in tenant
/// c9787ba2 where ContinueAnyway was on and the device kept progressing.
/// (2026-05-22) — defangs the 199/202 false-positive failures observed in one
/// tenant where ContinueAnyway was on and the device kept progressing.
/// </item>
/// <item>
/// <b>Terminal failure</b> — default path. Transitions to <see cref="SessionStage.Failed"/>
@@ -239,7 +239,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
var continueAnywayEnabled = observations?.EspAllowContinueAnyway?.Value == true;
var advisoryEligible = continueAnywayEnabled && state.AccountSetupEnteredUtc != null;
// Continue-Anyway observation mode (tenant c9787ba2, session 53d1e9f6, 2026-08-08):
// Continue-Anyway observation mode (field case 2026-08-08):
// a Device-phase ESP terminal failure (AccountSetup never entered) is a hard fail
// by default — the classic advisory gate above requires AccountSetupEnteredUtc.
// Fleets held at the ESP timeout wall by one slow blocking app then show 100%
@@ -1073,7 +1073,7 @@ export function TenantConfigProvider({ children }: { children: React.ReactNode }
// AAD consent-propagation awareness: right after a successful admin consent the app-only
// token often mints with an EMPTY roles claim for 30-90s (observed live 2026-07-31,
// tenant 5ca2b350: token 43s post-consent with GrantedRoleCount=0) — a single-shot probe
// one tenant: token 43s post-consent with GrantedRoleCount=0) — a single-shot probe
// misclassified that window as "permission not granted" and showed a hard error while
// everything was actually fine. The access-check invalidates + re-mints fresh on every
// call, so a bounded poll converges as soon as Microsoft has propagated the role. The