mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-23 20:26:10 +00:00
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:
+1
-1
@@ -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()
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+2
-2
@@ -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.
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+3
-3
@@ -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>
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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 =
|
||||
|
||||
+1
-1
@@ -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 ====
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+6
-7
@@ -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 => 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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Scenario: ESP Terminal Failure DEFANGED by ContinueAnyway (PR1, Session 4fa5a2d4, 2026-05-22)
|
||||
# Tenant c9787ba2 observed 199/202 sessions failing with esp_terminal_failure, even though
|
||||
# One tenant observed 199/202 sessions failing with esp_terminal_failure, even though
|
||||
# customers were demonstrably deploying 1200+ devices through the same profile. Real root cause:
|
||||
# - ESP profile sets BlockInStatusPage=6 (ContinueAnyway=true)
|
||||
# - DeviceSetup completes Apps successfully
|
||||
|
||||
Reference in New Issue
Block a user