tests: cross-language parity manifest + agent safety nets (audit P3/P4)

P3 — shared manifest infrastructure: SharedManifestParityTests reflects the
wire-relevant catalogs out of AutopilotMonitor.Shared into a committed JSON
manifest (regenerate: AM_WRITE_SHARED_MANIFESTS=1 dotnet test); a web codegen
lifts it into literal types; sharedManifestChecks.ts enforces
AdminConfiguration field parity at COMPILE time and 12 vitest tests pin the
value catalogs (sources/operators, lanes/verdicts, severities incl. the
missing Trace=-1, phases both directions, terminal statuses, event-type
typo guard). KnownSources/KnownOperators made internal for the export.

P4 — agent safety nets: DecisionState builder/serializer completeness
(4-site sync tripwire), signal-kind fixture coverage with a shrink-only debt
set (found and fixed realmjoin-timeout-v1.json, which never deserialized),
RemoteConfigMerger completeness tripwire, DefaultComponentFactory ordering
invariants as real construction tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-08-13 17:18:18 +02:00
parent 4c413337ca
commit c48f95b88c
12 changed files with 2324 additions and 3 deletions
@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AutopilotMonitor.Agent.V2.Core.Configuration;
using AutopilotMonitor.Shared.Models;
using Xunit;
namespace AutopilotMonitor.Agent.V2.Core.Tests.Configuration
{
/// <summary>
/// Completeness tripwire for <see cref="RemoteConfigMerger"/>: the merger hand-maps
/// <see cref="AgentConfigResponse"/> fields onto <c>AgentConfiguration</c>, and
/// historically new tenant knobs were forgotten there and silently ignored. Every public
/// property of <see cref="AgentConfigResponse"/> must be classified in exactly one of the
/// two lists below. A new property fails this test until a human either wires it through
/// <see cref="RemoteConfigMerger.Merge"/> or documents where else it is consumed.
/// </summary>
public sealed class RemoteConfigMergerCompletenessTests
{
/// <summary>
/// Fields <see cref="RemoteConfigMerger.Merge"/> maps onto <c>AgentConfiguration</c>.
/// Keep in sync with the Merge body — the test only proves classification coverage,
/// the value-level mapping itself is covered by <see cref="RemoteConfigMergerTests"/>.
/// </summary>
private static readonly string[] MergedByMerger =
{
nameof(AgentConfigResponse.SelfDestructOnComplete),
nameof(AgentConfigResponse.KeepLogFile),
nameof(AgentConfigResponse.RebootOnComplete),
nameof(AgentConfigResponse.EnableGeoLocation),
nameof(AgentConfigResponse.LogLevel),
nameof(AgentConfigResponse.RebootDelaySeconds),
nameof(AgentConfigResponse.ShowEnrollmentSummary),
nameof(AgentConfigResponse.EnrollmentSummaryTimeoutSeconds),
nameof(AgentConfigResponse.EnrollmentSummaryBrandingImageUrl),
nameof(AgentConfigResponse.EnrollmentSummaryLaunchRetrySeconds),
nameof(AgentConfigResponse.NtpServer),
nameof(AgentConfigResponse.EnableTimezoneAutoSet),
nameof(AgentConfigResponse.DiagnosticsUploadEnabled),
nameof(AgentConfigResponse.DiagnosticsUploadMode),
nameof(AgentConfigResponse.DiagnosticsLogPaths),
nameof(AgentConfigResponse.SendTraceEvents),
nameof(AgentConfigResponse.EnableEspContinueAnywayObservation),
nameof(AgentConfigResponse.UnrestrictedMode),
nameof(AgentConfigResponse.EnableImeMatchLog),
nameof(AgentConfigResponse.EnableGatherRuleDebugLog),
nameof(AgentConfigResponse.UploadIntervalSeconds),
nameof(AgentConfigResponse.MaxBatchSize),
nameof(AgentConfigResponse.MaxAuthFailures),
nameof(AgentConfigResponse.AuthFailureTimeoutMinutes),
// Merge reads two Collectors knobs (AgentMaxLifetimeMinutes, HelloWaitTimeoutSeconds);
// the remaining CollectorConfiguration flags flow untouched to DefaultComponentFactory.
nameof(AgentConfigResponse.Collectors),
};
/// <summary>
/// Fields deliberately NOT mapped by the merger because another component consumes them
/// directly from the remote config. One-line justification per entry — verify the
/// consumer before classifying a new field here.
/// </summary>
private static readonly string[] IntentionallyNotMerged =
{
// Schema/version metadata — logged and cached by RemoteConfigService, not a runtime knob.
nameof(AgentConfigResponse.ConfigVersion),
// Consumed by DefaultComponentFactory → GatherRuleExecutorHost (rules run against the raw remote list).
nameof(AgentConfigResponse.GatherRules),
// Consumed by DefaultComponentFactory → ImeLogHost (pattern list handed to ImeLogTracker).
nameof(AgentConfigResponse.ImeLogPatterns),
// Passed by AgentRuntimeHost into IComponentFactory.CreateCollectorHosts → ImeLogHost sealing re-emit.
nameof(AgentConfigResponse.WhiteGloveSealingPatternIds),
// Consumed by DefaultComponentFactory (RealmJoin/KeepAwake/ConsoleBypass host gates) + AgentAnalyzerManager.
nameof(AgentConfigResponse.Analyzers),
// Self-update ZIP integrity second trust channel — BinaryIntegrityVerifier / Program update path.
nameof(AgentConfigResponse.LatestAgentSha256),
// Running-EXE integrity verification — BinaryIntegrityVerifier post-config check.
nameof(AgentConfigResponse.LatestAgentExeSha256),
// Self-updater force-update downgrade gate — BinaryIntegrityVerifier / Program update path.
nameof(AgentConfigResponse.AllowAgentDowngrade),
// Control-channel block signal — RemoteConfigService/ServerControlPlane; stripped from the disk cache.
nameof(AgentConfigResponse.DeviceBlocked),
// Control-channel kill signal — ServerControlPlane / Program.Guards; live fetch only.
nameof(AgentConfigResponse.DeviceKillSignal),
// Temporary-block expiry accompanying DeviceBlocked — ServerControlPlane.
nameof(AgentConfigResponse.UnblockAt),
// Endpoint migration — EndpointMigration/RemoteConfigService re-run the fetch, live fetch only.
nameof(AgentConfigResponse.MigrateToApiBaseUrl),
};
[Fact]
public void Every_AgentConfigResponse_property_is_classified_exactly_once()
{
var properties = typeof(AgentConfigResponse)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(p => p.Name)
.OrderBy(n => n, StringComparer.Ordinal)
.ToArray();
var merged = new HashSet<string>(MergedByMerger, StringComparer.Ordinal);
var notMerged = new HashSet<string>(IntentionallyNotMerged, StringComparer.Ordinal);
var inBoth = merged.Intersect(notMerged, StringComparer.Ordinal).ToArray();
Assert.True(
inBoth.Length == 0,
$"Fields listed in BOTH classification lists: [{string.Join(", ", inBoth)}].");
var unclassified = properties
.Where(p => !merged.Contains(p) && !notMerged.Contains(p))
.ToArray();
Assert.True(
unclassified.Length == 0,
"New AgentConfigResponse field(s) without a classification: " +
$"[{string.Join(", ", unclassified)}]. Either map them in RemoteConfigMerger.Merge " +
"(+ MergedByMerger here) or verify the consuming component and add them to " +
"IntentionallyNotMerged with a justification comment.");
// Stale entries (renamed/removed properties) must be cleaned up too.
var known = new HashSet<string>(properties, StringComparer.Ordinal);
var stale = merged.Concat(notMerged).Where(n => !known.Contains(n)).ToArray();
Assert.True(
stale.Length == 0,
$"Classification lists reference non-existent AgentConfigResponse properties: [{string.Join(", ", stale)}].");
}
}
}
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AutopilotMonitor.Agent.V2.Core.Configuration;
using AutopilotMonitor.Agent.V2.Core.Logging;
using AutopilotMonitor.Agent.V2.Core.Orchestration;
using AutopilotMonitor.Agent.V2.Core.Tests.Harness;
using AutopilotMonitor.Shared.Models;
using Xunit;
namespace AutopilotMonitor.Agent.V2.Core.Tests.Orchestration
{
/// <summary>
/// Enforces the comment-only ordering invariants of <see cref="DefaultComponentFactory"/> by
/// building the REAL host list (construction only — no host is started) and asserting the
/// relative positions of the order-sensitive host types. The orchestrator starts hosts in
/// list order, so list order IS start order:
/// <list type="bullet">
/// <item>DeviceInfoHost before EspAndHelloHost — L9 (delta review 2026-07-02): DeviceInfoHost
/// must subscribe to SignalPosted before EspAndHelloHost.Start can post the registry-backfill
/// EspPhaseChanged(DeviceSetup), or the enrollment-start re-collect never runs.</item>
/// <item>OsBuildChangeHost before WindowsUpdateWatcherHost — the WU channel census reads
/// OsBuildChangeHost.BuildChanged at its own start.</item>
/// <item>DeliveryOptimizationHost before OfficeInstallDetectorHost — the DO host must have
/// subscribed to the Office process-start signal before the watcher starts, or an install
/// already in flight misses its wake.</item>
/// <item>DesktopArrivalHost before AadJoinHost — the AadJoinHost ctor takes the desktop
/// host's RequestResetForRealUserSwitch callback, so the target must exist first.</item>
/// </list>
/// </summary>
public sealed class DefaultComponentFactoryOrderingTests : IDisposable
{
private readonly TempDirectory _tmp = new TempDirectory();
public void Dispose() => _tmp.Dispose();
/// <summary>
/// Build the production host list with default remote config (all order-sensitive hosts
/// enabled by default) and system-boundary fakes only: temp state dir, fake ingress,
/// virtual clock, no network metrics, no telemetry spool.
/// </summary>
private CollectorSurfaces CreateSurfaces()
{
var factory = new DefaultComponentFactory(
agentConfig: new AgentConfiguration
{
ApiBaseUrl = "https://example",
TenantId = "t",
SessionId = "s",
},
remoteConfig: new AgentConfigResponse
{
Collectors = CollectorConfiguration.CreateDefault(),
},
networkMetrics: null,
agentVersion: "test",
stateDirectory: _tmp.Path,
startupEventGate: null);
return factory.CreateCollectorHosts(
sessionId: "s",
tenantId: "t",
logger: new AgentLogger(_tmp.Path, AgentLogLevel.Info),
whiteGloveSealingPatternIds: Array.Empty<string>(),
ingress: new FakeSignalIngressSink(),
clock: new VirtualClock(new DateTime(2026, 4, 20, 10, 0, 0, DateTimeKind.Utc)),
telemetrySpool: null,
timelineEvents: null);
}
private static int IndexOf<THost>(IReadOnlyList<ICollectorHost> hosts) where THost : ICollectorHost
{
var indices = hosts
.Select((host, index) => (host, index))
.Where(t => t.host is THost)
.Select(t => t.index)
.ToArray();
Assert.True(
indices.Length == 1,
$"Expected exactly one {typeof(THost).Name} in the host list, found {indices.Length}. " +
$"Hosts: [{string.Join(", ", hosts.Select(h => h.GetType().Name))}]");
return indices[0];
}
private static void AssertBefore<TFirst, TSecond>(IReadOnlyList<ICollectorHost> hosts, string reason)
where TFirst : ICollectorHost
where TSecond : ICollectorHost
{
var first = IndexOf<TFirst>(hosts);
var second = IndexOf<TSecond>(hosts);
Assert.True(
first < second,
$"{typeof(TFirst).Name} (index {first}) must come before {typeof(TSecond).Name} (index {second}): {reason}");
}
[Fact]
public void Order_sensitive_hosts_keep_their_documented_relative_order()
{
var surfaces = CreateSurfaces();
var hosts = surfaces.Hosts;
try
{
AssertBefore<DeviceInfoHost, EspAndHelloHost>(
hosts,
"L9 — DeviceInfoHost must subscribe to SignalPosted before EspAndHelloHost's " +
"registry backfill can post EspPhaseChanged(DeviceSetup).");
AssertBefore<OsBuildChangeHost, WindowsUpdateWatcherHost>(
hosts,
"the Windows Update channel census reads OsBuildChangeHost.BuildChanged at its own start.");
AssertBefore<DeliveryOptimizationHost, OfficeInstallDetectorHost>(
hosts,
"the DO host must subscribe to the Office process-start signal before the watcher starts.");
AssertBefore<DesktopArrivalHost, AadJoinHost>(
hosts,
"AadJoinHost wires its onRealUserJoined callback to the already-constructed DesktopArrivalHost.");
// Closure-latch sanity (factory lines ~139/297): the EspAndHelloHost probes close
// over the ImeLogHost reference, which is assigned during the same factory call —
// the host must therefore exist in the returned list.
Assert.Equal(1, hosts.Count(h => h is ImeLogHost));
}
finally
{
foreach (var host in hosts)
{
try { host.Dispose(); } catch { /* construction-only test — best-effort cleanup */ }
}
}
}
}
}
@@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using AutopilotMonitor.DecisionCore.Serialization;
using AutopilotMonitor.DecisionCore.Signals;
using Newtonsoft.Json.Linq;
using Xunit;
namespace AutopilotMonitor.DecisionCore.Tests
{
/// <summary>
/// Enforces the fixture convention documented on <see cref="DecisionSignalKind"/>:
/// "New kinds or version bumps require a replay fixture in
/// <c>tests/fixtures/signal-kinds/{kind}-v{n}.json</c> — missing fixture = merge block."
/// The comment alone enforced nothing; this test does. Kinds without a fixture today are
/// carried in an explicit debt set that is only allowed to shrink.
/// </summary>
public sealed class SignalKindFixtureCoverageTests
{
// DEBT: backfill — new kinds must NOT be added here. Every entry predates this test;
// when a fixture is added for one of these kinds, the test forces its removal.
private static readonly HashSet<DecisionSignalKind> KnownMissingFixtures = new HashSet<DecisionSignalKind>
{
DecisionSignalKind.EspPhaseChanged,
DecisionSignalKind.EspExiting,
DecisionSignalKind.EspResumed,
DecisionSignalKind.EspTerminalFailure,
DecisionSignalKind.DesktopArrived,
DecisionSignalKind.HelloResolved,
DecisionSignalKind.ImeUserSessionCompleted,
DecisionSignalKind.DeviceSetupProvisioningComplete,
DecisionSignalKind.WhiteGloveShellCoreSuccess,
DecisionSignalKind.WhiteGloveSealingPatternDetected,
DecisionSignalKind.AadUserJoinedLate,
DecisionSignalKind.DeviceInfoCollected,
DecisionSignalKind.AutopilotProfileRead,
DecisionSignalKind.EspConfigDetected,
DecisionSignalKind.HelloPolicyDetected,
DecisionSignalKind.ClassifierVerdictIssued,
DecisionSignalKind.SessionStarted,
DecisionSignalKind.SessionAborted,
};
/// <summary>
/// Kinds that exist in the enum but are never posted as their own signal: they are
/// dispatched over the <see cref="DecisionSignalKind.DeadlineFired"/> rail (the reducer's
/// DeadlineFiredV1 switch routes on the <c>deadline</c> payload). Their fixture is named
/// after the kind, but its declared <c>Kind</c> is <c>DeadlineFired</c> — e.g.
/// <c>realmjoin-timeout-v1.json</c> carries <c>Payload.deadline = "realmjoin_timeout"</c>.
/// </summary>
private static readonly HashSet<DecisionSignalKind> DeadlineDispatchedKinds = new HashSet<DecisionSignalKind>
{
DecisionSignalKind.RealmJoinTimeout,
};
/// <summary>Locate <c>tests/fixtures/signal-kinds</c> by walking up to the repo root (AutopilotMonitor.sln).</summary>
private static string FixtureRoot()
{
var dir = AppContext.BaseDirectory;
for (int i = 0; i < 12; i++)
{
if (File.Exists(Path.Combine(dir, "AutopilotMonitor.sln")))
{
return Path.Combine(dir, "tests", "fixtures", "signal-kinds");
}
var parent = Directory.GetParent(dir)?.FullName;
if (parent == null || parent == dir) break;
dir = parent;
}
throw new DirectoryNotFoundException(
"Could not locate repo root (AutopilotMonitor.sln) walking up from " + AppContext.BaseDirectory);
}
private static IReadOnlyList<string> FixtureFiles()
{
var root = FixtureRoot();
Assert.True(Directory.Exists(root), $"Fixture directory not found: {root}");
var files = Directory.GetFiles(root, "*.json").OrderBy(f => f, StringComparer.Ordinal).ToArray();
Assert.NotEmpty(files);
return files;
}
/// <summary>Signal JSONL lines of one fixture ('#' comments and blank lines stripped).</summary>
private static IReadOnlyList<string> SignalLines(string path)
{
var lines = File.ReadAllLines(path)
.Select(l => l.Trim())
.Where(l => l.Length > 0 && l[0] != '#')
.ToArray();
Assert.True(lines.Length > 0, $"Fixture contains no signal lines: {path}");
return lines;
}
/// <summary>
/// Strictly parse the raw <c>Kind</c> of a fixture line. Deliberately NOT via
/// <see cref="SignalSerializer"/> alone: its unknown-kind fallback maps typos to
/// <see cref="DecisionSignalKind.SessionStarted"/>, which would silently mis-count coverage.
/// </summary>
private static DecisionSignalKind StrictKind(string line, string path)
{
var raw = JObject.Parse(line).Value<string>("Kind");
Assert.False(string.IsNullOrEmpty(raw), $"Fixture line has no Kind: {path}");
Assert.True(
Enum.TryParse<DecisionSignalKind>(raw, ignoreCase: false, out var kind)
&& Enum.IsDefined(typeof(DecisionSignalKind), kind),
$"Fixture {Path.GetFileName(path)} declares unknown Kind '{raw}'.");
return kind;
}
/// <summary>Dash-insensitive lowercase — "realmjoin-detected-v1" and "RealmJoinDetected" both normalize consistently.</summary>
private static string Normalize(string value) => value.Replace("-", string.Empty).ToLowerInvariant();
/// <summary>
/// Map a fixture file name to the kind it is named after: the longest enum member whose
/// normalized name prefixes the normalized file name, followed by <c>v{n}[-variant]</c>
/// (dash placement per the existing corpus, e.g. <c>realmjoin-detected-v1.json</c> /
/// <c>admin-preemption-detected-v1-failed.json</c>).
/// </summary>
private static DecisionSignalKind KindFromFileName(string fileNameWithoutExtension)
{
var normalized = Normalize(fileNameWithoutExtension);
var match = ((DecisionSignalKind[])Enum.GetValues(typeof(DecisionSignalKind)))
.Where(k =>
{
var prefix = Normalize(k.ToString());
return normalized.StartsWith(prefix, StringComparison.Ordinal)
&& Regex.IsMatch(normalized.Substring(prefix.Length), "^v[0-9]+");
})
.OrderByDescending(k => k.ToString().Length)
.Cast<DecisionSignalKind?>()
.FirstOrDefault();
Assert.True(
match.HasValue,
$"Fixture file name '{fileNameWithoutExtension}.json' does not follow " +
"'{kind}-v{n}[-variant].json' for any DecisionSignalKind.");
return match!.Value;
}
/// <summary>
/// Load every fixture through the REAL deserialization path and return the set of kinds
/// the corpus covers. Also enforces per-file consistency: the declared Kind must match
/// the file name (or be the DeadlineFired dispatch shape for deadline-dispatched kinds).
/// </summary>
private static ISet<DecisionSignalKind> LoadAndVerifyFixtures()
{
var covered = new HashSet<DecisionSignalKind>();
foreach (var file in FixtureFiles())
{
var fileName = Path.GetFileNameWithoutExtension(file);
var namedKind = KindFromFileName(fileName);
foreach (var line in SignalLines(file))
{
var declaredKind = StrictKind(line, file);
// Real deserialization path — the same call FixtureLoader/ReplayHarness use.
var signal = SignalSerializer.Deserialize(line);
Assert.Equal(declaredKind, signal.Kind);
Assert.True(signal.KindSchemaVersion >= 1, $"KindSchemaVersion must be >= 1 in {fileName}.");
if (declaredKind == namedKind)
{
covered.Add(declaredKind);
}
else
{
// Only the documented dispatch exception is allowed: a fixture named after
// a deadline-dispatched kind carries a DeadlineFired signal with the
// deadline name in its payload.
string? deadlineName = null;
signal.Payload?.TryGetValue("deadline", out deadlineName);
Assert.True(
DeadlineDispatchedKinds.Contains(namedKind)
&& declaredKind == DecisionSignalKind.DeadlineFired
&& !string.IsNullOrEmpty(deadlineName),
$"Fixture '{fileName}.json' is named after {namedKind} but declares Kind={declaredKind}. " +
"Either the file is misnamed, or a new deadline-dispatched kind must be " +
"registered in DeadlineDispatchedKinds (with a payload.deadline entry).");
covered.Add(namedKind);
covered.Add(declaredKind); // it IS also a genuine DeadlineFired fixture
}
}
}
return covered;
}
[Fact]
public void Every_fixture_deserializes_through_the_real_signal_path_and_matches_its_filename()
{
LoadAndVerifyFixtures();
}
[Fact]
public void Every_kind_is_either_fixtured_or_registered_as_known_debt()
{
var fixtured = LoadAndVerifyFixtures();
var allKinds = (DecisionSignalKind[])Enum.GetValues(typeof(DecisionSignalKind));
var unfixturedAndUntracked = allKinds
.Where(k => !fixtured.Contains(k) && !KnownMissingFixtures.Contains(k))
.ToArray();
Assert.True(
unfixturedAndUntracked.Length == 0,
"DecisionSignalKind values without a replay fixture in tests/fixtures/signal-kinds/: " +
$"[{string.Join(", ", unfixturedAndUntracked)}]. The convention on DecisionSignalKind is " +
"'new kind => fixture' — add a {kind}-v{n}.json fixture (do NOT extend KnownMissingFixtures).");
// Debt only shrinks: once a fixture exists, the kind must leave the debt set.
var fixturedButStillListedAsDebt = KnownMissingFixtures.Where(fixtured.Contains).ToArray();
Assert.True(
fixturedButStillListedAsDebt.Length == 0,
"Kinds now have fixtures but are still listed in KnownMissingFixtures — remove them: " +
$"[{string.Join(", ", fixturedButStillListedAsDebt)}].");
}
}
}
@@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AutopilotMonitor.DecisionCore.Serialization;
using AutopilotMonitor.DecisionCore.Signals;
using AutopilotMonitor.DecisionCore.State;
using AutopilotMonitor.Shared.Models;
using Xunit;
namespace AutopilotMonitor.DecisionCore.Tests.State
{
/// <summary>
/// Drift tripwire for the 4-site sync required when adding a field to
/// <see cref="DecisionState"/>: ctor parameter + <see cref="DecisionStateBuilder"/>
/// copy-ctor line + builder property + <c>Build()</c> argument. A forgotten copy-ctor
/// line silently resets the fact on every reducer step; a forgotten <c>Build()</c>
/// argument resets it on the first copy-with-changes. These tests are fully
/// reflection-driven so a new field fails them until all four sites are wired
/// (and until a value factory for any brand-new property type is added below).
/// </summary>
public sealed class DecisionStateBuilderCompletenessTests
{
private static readonly DateTime BaseUtc = new DateTime(2026, 4, 20, 10, 0, 0, DateTimeKind.Utc);
private static PropertyInfo[] StateProperties() =>
typeof(DecisionState)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.OrderBy(p => p.Name, StringComparer.Ordinal)
.ToArray();
private static PropertyInfo[] BuilderProperties() =>
typeof(DecisionStateBuilder)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.SetMethod != null && p.SetMethod.IsPublic)
.OrderBy(p => p.Name, StringComparer.Ordinal)
.ToArray();
/// <summary>
/// Produce a distinct, non-default value for a builder property. <paramref name="seed"/>
/// is folded into every value so no two properties of the same type ever share an
/// instance — a swapped <c>Build()</c> argument (e.g. espFinalExitUtc passed into
/// desktopArrivedUtc) is therefore detected, not masked. A property type without a
/// factory entry fails loudly: extend the factory when a new field type appears.
/// </summary>
private static object CreateDistinctValue(Type type, int seed)
{
long ordinal = 100 + seed;
if (type == typeof(string)) return "test-value-" + seed;
if (type == typeof(int)) return 7 + seed;
if (type == typeof(long)) return 7L + seed;
if (type == typeof(DateTime?)) return BaseUtc.AddMinutes(seed);
if (type == typeof(SessionStage)) return SessionStage.AwaitingHello;
if (type == typeof(SessionOutcome?)) return SessionOutcome.EnrollmentComplete;
if (type == typeof(SignalFact<DateTime>)) return new SignalFact<DateTime>(BaseUtc.AddMinutes(seed), ordinal);
if (type == typeof(SignalFact<string>)) return new SignalFact<string>("test-value-" + seed, ordinal);
if (type == typeof(SignalFact<bool>)) return new SignalFact<bool>(true, ordinal);
if (type == typeof(SignalFact<EnrollmentPhase>)) return new SignalFact<EnrollmentPhase>(EnrollmentPhase.AccountSetup, ordinal);
if (type == typeof(List<ActiveDeadline>))
{
return new List<ActiveDeadline>
{
new ActiveDeadline(
name: "deadline-" + seed,
dueAtUtc: BaseUtc.AddMinutes(seed),
firesSignalKind: DecisionSignalKind.DeadlineFired,
firesPayload: new Dictionary<string, string> { ["k"] = "v" + seed }),
};
}
// Immutable fact aggregates — built through their public APIs so the instances are
// reference-distinct from their Empty defaults AND fully serializable (a dropped
// copy-ctor line or Build() argument falls back to Empty → reference inequality).
if (type == typeof(AppInstallFacts)) return AppInstallFacts.Empty.WithCompleted("Installed").WithFailed("app-" + seed);
if (type == typeof(EnrollmentScenarioProfile))
{
return EnrollmentScenarioProfile.Empty.With(
mode: EnrollmentMode.WhiteGlove,
confidence: ProfileConfidence.High,
evidenceOrdinal: ordinal,
reason: "test-value-" + seed);
}
if (type == typeof(EnrollmentScenarioObservations)) return EnrollmentScenarioObservations.Empty.WithSkipUserEsp(true, ordinal);
if (type == typeof(ClassifierOutcomes))
{
return ClassifierOutcomes.Empty.WithWhiteGloveSealing(new Hypothesis(
level: HypothesisLevel.Strong,
reason: "test-value-" + seed,
score: 50,
lastUpdatedUtc: BaseUtc.AddMinutes(seed),
lastClassifierVerdictId: "verdict-" + seed));
}
if (type == typeof(RealmJoinFacts)) return RealmJoinFacts.Empty.WithDetected(BaseUtc.AddMinutes(seed), ordinal);
throw new Xunit.Sdk.XunitException(
$"No value factory for builder property type {type}. A new DecisionState field " +
"introduced a new type — add a distinct-value factory entry so the completeness " +
"tests can populate it.");
}
/// <summary>Populate every settable builder property with a distinct non-default value.</summary>
private static Dictionary<string, object> PopulateBuilder(DecisionStateBuilder builder)
{
var assigned = new Dictionary<string, object>(StringComparer.Ordinal);
int seed = 0;
foreach (var prop in BuilderProperties())
{
var value = CreateDistinctValue(prop.PropertyType, seed++);
prop.SetValue(builder, value);
assigned[prop.Name] = value;
}
return assigned;
}
private static void AssertMatches(string propertyName, object expected, object? actual)
{
// Deadlines: Build() copies the List into an array and the copy-ctor re-wraps it —
// the container is a new instance by design, but the elements must be the SAME refs.
if (expected is List<ActiveDeadline> expectedDeadlines)
{
var actualDeadlines = Assert.IsAssignableFrom<IEnumerable<ActiveDeadline>>(actual).ToList();
Assert.Equal(expectedDeadlines.Count, actualDeadlines.Count);
for (int i = 0; i < expectedDeadlines.Count; i++)
{
Assert.Same(expectedDeadlines[i], actualDeadlines[i]);
}
return;
}
// Value types (enums, int, long, DateTime?) and strings compare by value; every
// reference-typed fact must be the identical instance (a re-created equal-looking
// value would still hide a dropped pass-through).
if (expected is string || expected.GetType().IsValueType)
{
Assert.True(
expected.Equals(actual),
$"{propertyName}: expected {expected}, got {actual ?? "(null)"}.");
}
else
{
Assert.Same(expected, actual);
}
}
// ============================================================= (a) name-set parity
[Fact]
public void Builder_property_set_matches_DecisionState_property_set()
{
var stateNames = StateProperties().Select(p => p.Name).ToArray();
var builderNames = BuilderProperties().Select(p => p.Name).ToArray();
var missingOnBuilder = stateNames.Except(builderNames, StringComparer.Ordinal).ToArray();
var missingOnState = builderNames.Except(stateNames, StringComparer.Ordinal).ToArray();
Assert.True(
missingOnBuilder.Length == 0 && missingOnState.Length == 0,
"DecisionState and DecisionStateBuilder property sets drifted apart. " +
$"State-only: [{string.Join(", ", missingOnBuilder)}]; " +
$"Builder-only: [{string.Join(", ", missingOnState)}]. " +
"Adding a DecisionState field requires: ctor parameter, builder copy-ctor line, " +
"builder property, and Build() argument.");
}
// ============================================================= (b) Build() completeness
[Fact]
public void Build_carries_every_builder_property_onto_the_state()
{
var builder = DecisionState.CreateInitial("seed-session", "seed-tenant").ToBuilder();
var assigned = PopulateBuilder(builder);
var state = builder.Build();
foreach (var stateProp in StateProperties())
{
Assert.True(
assigned.TryGetValue(stateProp.Name, out var expected),
$"DecisionState.{stateProp.Name} has no builder counterpart — see the name-set parity test.");
AssertMatches($"DecisionState.{stateProp.Name}", expected!, stateProp.GetValue(state));
}
}
// ============================================================= (c) copy-ctor completeness
[Fact]
public void ToBuilder_copies_every_property_back_from_the_state()
{
var builder = DecisionState.CreateInitial("seed-session", "seed-tenant").ToBuilder();
var assigned = PopulateBuilder(builder);
var state = builder.Build();
var roundtrippedBuilder = state.ToBuilder();
foreach (var builderProp in BuilderProperties())
{
var expected = assigned[builderProp.Name];
AssertMatches($"DecisionStateBuilder.{builderProp.Name}", expected, builderProp.GetValue(roundtrippedBuilder));
}
}
// ============================================================= (d) serializer roundtrip
[Fact]
public void StateSerializer_roundtrip_of_fully_populated_state_is_lossless()
{
// Catches Newtonsoft ctor-binding mismatches: deserialization binds JSON properties
// onto the single public ctor by (case-insensitive) parameter name, so a renamed
// ctor parameter silently drops the field back to its default. Serializing the
// deserialized state must therefore reproduce the exact same JSON.
var builder = DecisionState.CreateInitial("seed-session", "seed-tenant").ToBuilder();
PopulateBuilder(builder);
var state = builder.Build();
var json = StateSerializer.Serialize(state);
var roundtrippedJson = StateSerializer.Serialize(StateSerializer.Deserialize(json));
Assert.Equal(json, roundtrippedJson);
}
}
}
@@ -0,0 +1,146 @@
using System.Reflection;
using System.Text.Json;
using AutopilotMonitor.Functions.Functions.Rules;
using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using AutopilotMonitor.Shared.Models.Notifications;
using Xunit;
namespace AutopilotMonitor.Functions.Tests;
/// <summary>
/// Cross-language parity anchor: reflects the wire-relevant catalogs out of
/// AutopilotMonitor.Shared (models, enums, string vocabularies) into a canonical JSON
/// manifest committed at <c>src/Web/autopilot-monitor-web/utils/shared-manifests.json</c>.
/// The web's vitest suite and compile-time checks compare their hand-written TS mirrors
/// against that manifest — so a C# change that would silently drift a TS mirror first
/// fails HERE, and the TS side fails until its mirror follows.
///
/// Regenerate after intentional changes:
/// AM_WRITE_SHARED_MANIFESTS=1 dotnet test --filter SharedManifestParityTests
/// then run the web codegen: node scripts/generate-shared-manifest-types.js
/// </summary>
public sealed class SharedManifestParityTests
{
private const string ManifestRepoPath = "src/Web/autopilot-monitor-web/utils/shared-manifests.json";
private const string WriteEnvVar = "AM_WRITE_SHARED_MANIFESTS";
[Fact]
public void Committed_manifest_matches_shared_assembly()
{
var expected = BuildManifestJson();
var path = Path.Combine(FindRepoRoot(), ManifestRepoPath.Replace('/', Path.DirectorySeparatorChar));
if (Environment.GetEnvironmentVariable(WriteEnvVar) == "1")
{
File.WriteAllText(path, expected);
return;
}
Assert.True(File.Exists(path),
$"Manifest missing at {ManifestRepoPath}. Regenerate: {WriteEnvVar}=1 dotnet test --filter SharedManifestParityTests");
var committed = File.ReadAllText(path).Replace("\r\n", "\n");
Assert.True(string.Equals(expected, committed, StringComparison.Ordinal),
$"shared-manifests.json is out of date with AutopilotMonitor.Shared. " +
$"Regenerate: {WriteEnvVar}=1 dotnet test --filter SharedManifestParityTests " +
$"(then: node scripts/generate-shared-manifest-types.js in the web folder).");
}
// ── manifest construction ───────────────────────────────────────────────
private static string BuildManifestJson()
{
var manifest = new Dictionary<string, object?>
{
["$comment"] = "GENERATED from AutopilotMonitor.Shared by SharedManifestParityTests — do not edit by hand. " +
"Regenerate: AM_WRITE_SHARED_MANIFESTS=1 dotnet test --filter SharedManifestParityTests, " +
"then node scripts/generate-shared-manifest-types.js.",
["adminConfiguration"] = new Dictionary<string, object?>
{
["fields"] = WireFieldNames(typeof(AdminConfiguration)),
},
["tenantConfiguration"] = new Dictionary<string, object?>
{
["fields"] = WireFieldNames(typeof(TenantConfiguration)),
},
["sessionSummary"] = new Dictionary<string, object?>
{
["fields"] = WireFieldsWithOptionality(typeof(SessionSummary)),
},
// Enum member order is load-bearing (append-only ordinals) — declaration order kept.
["sessionStatuses"] = Enum.GetNames(typeof(SessionStatus)),
["enrollmentPhases"] = EnumMap<EnrollmentPhase>(),
["eventSeverities"] = EnumMap<EventSeverity>(),
["webhookProviderTypes"] = EnumMap<WebhookProviderType>(),
["annotationLanes"] = AnnotationLanes.All,
["annotationVerdicts"] = AnnotationVerdicts.All,
["tenantRoles"] = ConstStrings(typeof(Constants.TenantRoles)),
["globalRoles"] = ConstStrings(typeof(Constants.GlobalRoles)),
["delegatedRoles"] = ConstStrings(typeof(Constants.DelegatedRoles)),
["analyzeRuleSources"] = DryRunAnalyzeRuleFunction.KnownSources.OrderBy(s => s, StringComparer.Ordinal).ToArray(),
["analyzeRuleOperators"] = DryRunAnalyzeRuleFunction.KnownOperators.OrderBy(s => s, StringComparer.Ordinal).ToArray(),
["eventTypes"] = ConstStrings(typeof(Constants.EventTypes)).OrderBy(s => s, StringComparer.Ordinal).ToArray(),
};
var json = JsonSerializer.Serialize(manifest, new JsonSerializerOptions
{
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
});
// Normalized to LF + trailing newline so the file is byte-stable across OSes.
return json.Replace("\r\n", "\n") + "\n";
}
/// <summary>Wire (camelCase) names of the public instance properties, declaration order.</summary>
private static string[] WireFieldNames(Type type)
=> WireProperties(type).Select(p => JsonNamingPolicy.CamelCase.ConvertName(p.Name)).ToArray();
/// <summary>
/// Wire names plus whether the field can be ABSENT from the JSON: nullable fields are
/// omitted under WhenWritingNull, so nullable ⇒ the TS mirror must type it optional.
/// </summary>
private static object[] WireFieldsWithOptionality(Type type)
{
var nullability = new NullabilityInfoContext();
return WireProperties(type)
.Select(p => (object)new Dictionary<string, object?>
{
["name"] = JsonNamingPolicy.CamelCase.ConvertName(p.Name),
["optional"] = IsNullable(p, nullability),
})
.ToArray();
}
private static IEnumerable<PropertyInfo> WireProperties(Type type)
=> type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetMethod != null && p.GetIndexParameters().Length == 0);
private static bool IsNullable(PropertyInfo p, NullabilityInfoContext ctx)
{
if (Nullable.GetUnderlyingType(p.PropertyType) != null) return true;
if (p.PropertyType.IsValueType) return false;
return ctx.Create(p).ReadState != NullabilityState.NotNull;
}
private static Dictionary<string, int> EnumMap<TEnum>() where TEnum : struct, Enum
=> Enum.GetValues<TEnum>()
.ToDictionary(v => v.ToString(), v => Convert.ToInt32(v));
private static string[] ConstStrings(Type constClass)
=> constClass.GetFields(BindingFlags.Public | BindingFlags.Static)
.Where(f => f.IsLiteral && f.FieldType == typeof(string))
.Select(f => (string)f.GetRawConstantValue()!)
.ToArray();
private static string FindRepoRoot()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir != null && !File.Exists(Path.Combine(dir.FullName, "AutopilotMonitor.sln")))
{
dir = dir.Parent;
}
Assert.NotNull(dir);
return dir!.FullName;
}
}
@@ -131,13 +131,14 @@ namespace AutopilotMonitor.Functions.Functions.Rules
// switch is case-sensitive ("event_type", not "Event_Type"), and an unknown source/operator
// silently evaluates to false in production — for a dry-run that silence is exactly the
// confusion we want to catch up front.
private static readonly HashSet<string> KnownSources = new(StringComparer.Ordinal)
// internal: SharedManifestParityTests exports these into the cross-language manifest.
internal static readonly HashSet<string> KnownSources = new(StringComparer.Ordinal)
{
"event_type", "event_data", "event_data_array", "event_count",
"phase_duration", "app_install_duration", "event_correlation",
};
private static readonly HashSet<string> KnownOperators = new(StringComparer.OrdinalIgnoreCase)
internal static readonly HashSet<string> KnownOperators = new(StringComparer.OrdinalIgnoreCase)
{
"equals", "not_equals", "contains", "not_contains", "regex", "not_regex",
"gt", "lt", "gte", "lte", "exists", "not_exists",
@@ -0,0 +1,48 @@
/**
* Generate utils/shared-manifests.generated.ts from utils/shared-manifests.json.
*
* The JSON is produced by the backend reflection guard (SharedManifestParityTests,
* regenerated via AM_WRITE_SHARED_MANIFESTS=1) — this script only lifts it into an
* `as const` TS module so the manifest's entries exist as LITERAL types, enabling the
* compile-time drift checks in utils/sharedManifestChecks.ts.
*
* Run: node scripts/generate-shared-manifest-types.js
* Freshness is pinned by utils/__tests__/sharedManifestParity.test.ts.
*/
const fs = require("fs");
const path = require("path");
const WEB_ROOT = path.resolve(__dirname, "..");
const SOURCE = path.join(WEB_ROOT, "utils", "shared-manifests.json");
const DEST = path.join(WEB_ROOT, "utils", "shared-manifests.generated.ts");
/** Pure builder so the vitest freshness check can reuse it. */
function buildGeneratedSource(manifestJsonText) {
const manifest = JSON.parse(manifestJsonText);
return (
"// GENERATED from shared-manifests.json — do not edit by hand.\n" +
"// Regenerate: node scripts/generate-shared-manifest-types.js\n" +
"// (after AM_WRITE_SHARED_MANIFESTS=1 dotnet test --filter SharedManifestParityTests)\n" +
"\n" +
"export const SHARED_MANIFEST = " +
JSON.stringify(manifest, null, 2) +
" as const;\n"
);
}
function main() {
if (!fs.existsSync(SOURCE)) {
console.error(`[shared-manifests] Source not found: ${SOURCE}`);
process.exit(1);
}
const out = buildGeneratedSource(fs.readFileSync(SOURCE, "utf8"));
fs.writeFileSync(DEST, out, "utf8");
console.log(`[shared-manifests] Wrote ${path.relative(WEB_ROOT, DEST)}`);
}
if (require.main === module) {
main();
}
module.exports = { buildGeneratedSource };
@@ -0,0 +1,115 @@
/**
* Runtime parity between hand-written TS mirrors and the C#-generated shared manifest.
* The manifest is the single cross-language anchor: the backend reflection guard
* (SharedManifestParityTests) pins JSON ↔ C#, these tests pin TS ↔ JSON.
* Compile-time (key-level) checks live in utils/sharedManifestChecks.ts.
*/
import { describe, expect, it } from "vitest";
import { createRequire } from "node:module";
import fs from "node:fs";
import path from "node:path";
import { SHARED_MANIFEST } from "../shared-manifests.generated";
import { SEVERITY_INT } from "../sessionExportUtils";
import { isTerminalStatus } from "../sessionStatus";
import { OPERATORS, PRECONDITION_OPERATORS, SOURCES } from "@/app/analyze-rules/types";
import {
ANNOTATION_LANES,
ANNOTATION_VERDICTS,
} from "@/app/sessions/components/sessionAnnotationLogic";
import { V1_PHASE_NAMES, V2_PHASE_NAMES } from "@/app/sessions/utils/phaseConstants";
import { KNOWN_EVENT_TYPES } from "@/app/gather-rules/eventTypes";
const require = createRequire(import.meta.url);
const { buildGeneratedSource } = require("../../scripts/generate-shared-manifest-types.js");
const utilsDir = path.resolve(__dirname, "..");
describe("shared-manifests.generated.ts freshness", () => {
it("matches a fresh run of the codegen over shared-manifests.json", () => {
const json = fs.readFileSync(path.join(utilsDir, "shared-manifests.json"), "utf8");
const committed = fs.readFileSync(
path.join(utilsDir, "shared-manifests.generated.ts"),
"utf8"
);
expect(committed.replace(/\r\n/g, "\n")).toBe(buildGeneratedSource(json));
});
});
describe("analyze-rule builder catalogs", () => {
it("SOURCES equals the backend's KnownSources", () => {
expect([...SOURCES].sort()).toEqual([...SHARED_MANIFEST.analyzeRuleSources].sort());
});
it("OPERATORS equals the backend's KnownOperators", () => {
expect([...OPERATORS].sort()).toEqual([...SHARED_MANIFEST.analyzeRuleOperators].sort());
});
it("PRECONDITION_OPERATORS is a subset of the backend's KnownOperators", () => {
const known = new Set<string>(SHARED_MANIFEST.analyzeRuleOperators);
for (const op of PRECONDITION_OPERATORS) expect(known).toContain(op);
});
});
describe("annotation lanes and verdicts", () => {
it("lanes match", () => {
expect([...ANNOTATION_LANES]).toEqual([...SHARED_MANIFEST.annotationLanes]);
});
it("verdicts match", () => {
expect([...ANNOTATION_VERDICTS]).toEqual([...SHARED_MANIFEST.annotationVerdicts]);
});
});
describe("event severities", () => {
it("SEVERITY_INT mirrors the C# EventSeverity enum exactly (names and ordinals)", () => {
expect(SEVERITY_INT).toEqual(SHARED_MANIFEST.eventSeverities);
});
});
describe("enrollment phases", () => {
it("every numeric phase key used by the UI exists on the C# enum", () => {
// Display STRINGS are deliberately surface-specific (V1 vs V2 wording); the
// numeric ordinals are the wire contract.
const enumValues = new Set<number>(Object.values(SHARED_MANIFEST.enrollmentPhases));
for (const key of [...Object.keys(V1_PHASE_NAMES), ...Object.keys(V2_PHASE_NAMES)]) {
expect(enumValues).toContain(Number(key));
}
});
it("every C# enum ordinal has a UI name in both version maps", () => {
for (const value of Object.values(SHARED_MANIFEST.enrollmentPhases)) {
expect(V1_PHASE_NAMES[value], `V1 name for phase ${value}`).toBeTruthy();
expect(V2_PHASE_NAMES[value], `V2 name for phase ${value}`).toBeTruthy();
}
});
});
describe("session statuses", () => {
it("the terminal trio exists on the C# enum and is classified terminal", () => {
for (const status of ["Succeeded", "Failed", "Incomplete"]) {
expect(SHARED_MANIFEST.sessionStatuses).toContain(status);
expect(isTerminalStatus(status)).toBe(true);
}
});
it("non-terminal enum members are not classified terminal", () => {
for (const status of SHARED_MANIFEST.sessionStatuses) {
if (["Succeeded", "Failed", "Incomplete"].includes(status)) continue;
expect(isTerminalStatus(status), `${status} must not be terminal`).toBe(false);
}
});
});
describe("gather-rule event-type autocomplete", () => {
it("contains no event type the agent/backend does not know", () => {
// Typo guard: every autocomplete entry must be a real Constants.EventTypes value.
// (Completeness in the other direction is a deliberate curation question — the
// catalog carries ~3x more types than the rule builder surfaces today.)
const known = new Set<string>(SHARED_MANIFEST.eventTypes);
for (const entry of KNOWN_EVENT_TYPES) {
expect(known, `unknown event type '${entry.value}' in KNOWN_EVENT_TYPES`).toContain(
entry.value
);
}
});
});
@@ -0,0 +1,643 @@
// GENERATED from shared-manifests.json — do not edit by hand.
// Regenerate: node scripts/generate-shared-manifest-types.js
// (after AM_WRITE_SHARED_MANIFESTS=1 dotnet test --filter SharedManifestParityTests)
export const SHARED_MANIFEST = {
"$comment": "GENERATED from AutopilotMonitor.Shared by SharedManifestParityTests — do not edit by hand. Regenerate: AM_WRITE_SHARED_MANIFESTS=1 dotnet test --filter SharedManifestParityTests, then node scripts/generate-shared-manifest-types.js.",
"adminConfiguration": {
"fields": [
"partitionKey",
"rowKey",
"lastUpdated",
"updatedBy",
"globalRateLimitRequestsPerMinute",
"userRateLimitRequestsPerMinute",
"globalAdminRateLimitRequestsPerMinute",
"planTierDefinitionsJson",
"platformStatsBlobSasUrl",
"collectorIdleTimeoutMinutes",
"desktopDetectorNoCandidateTimeoutMinutes",
"excessiveEventCountThreshold",
"excessiveEventAutoActionMode",
"excessiveEventAutoActionThreshold",
"excessiveEventAutoActionDurationHours",
"opsEventRetentionDays",
"slaNotificationCooldownHours",
"feedbackEnabled",
"feedbackMinTenantAgeDays",
"feedbackCooldownDays",
"diagnosticsGlobalLogPathsJson",
"maxDiagnosticsDownloadSizeMB",
"diagnosticsDownloadTimeoutSeconds",
"mcpAccessPolicy",
"nvdApiKey",
"opsAlertRulesJson",
"opsAlertTelegramEnabled",
"opsAlertTelegramChatId",
"opsAlertTeamsEnabled",
"opsAlertTeamsWebhookUrl",
"opsAlertSlackEnabled",
"opsAlertSlackWebhookUrl",
"latestAgentV2Version",
"latestAgentV2Sha256",
"latestAgentV2ExeSha256",
"latestBootstrapV2ScriptVersion",
"allowAgentDowngrade",
"agentMigrateApiBaseUrl",
"agentMigrateTenantOverridesJson",
"modernDeploymentHarmlessEventIdsJson",
"whiteGloveSealingPatternIdsJson",
"vulnerabilityCorrelationEnabled",
"enableIndexDualWrite",
"autoApproveNewTenants",
"selfServiceAppHomingEnabled",
"sessionDeletionKillSwitch",
"vulnerabilityDataLastSyncUtc",
"msrcLastSyncUtc"
]
},
"tenantConfiguration": {
"fields": [
"tenantId",
"domainName",
"lastUpdated",
"updatedBy",
"onboardedBy",
"contactEmail",
"onboardedAt",
"homedAppClientId",
"lastAuthClientId",
"lastAuthClientIdSince",
"disabled",
"disabledReason",
"disabledUntil",
"customRateLimitRequestsPerMinute",
"customUserRateLimitRequestsPerMinute",
"planTier",
"trialExpiresUtc",
"trialStartedUtc",
"trialConsumed",
"trialGrantedBy",
"manufacturerWhitelist",
"modelWhitelist",
"validateAutopilotDevice",
"validateCorporateIdentifier",
"validateDeviceAssociation",
"validateCloudPcDevice",
"allowInsecureAgentRequests",
"dataRetentionDays",
"sessionTimeoutHours",
"sessionGraceHours",
"maxNdjsonPayloadSizeMB",
"enablePerformanceCollector",
"performanceCollectorIntervalSeconds",
"helloWaitTimeoutSeconds",
"maxAuthFailures",
"authFailureTimeoutMinutes",
"agentMaxLifetimeMinutes",
"absoluteMaxSessionHours",
"selfDestructOnComplete",
"keepLogFile",
"rebootOnComplete",
"rebootDelaySeconds",
"enableGeoLocation",
"ntpServer",
"enableTimezoneAutoSet",
"enableImeMatchLog",
"enableGatherRuleDebugLog",
"enableEspContinueAnywayObservation",
"logLevel",
"maxBatchSize",
"showEnrollmentSummary",
"enrollmentSummaryTimeoutSeconds",
"enrollmentSummaryBrandingImageUrl",
"enrollmentSummaryLaunchRetrySeconds",
"showScriptOutput",
"enableLocalAdminAnalyzer",
"enableSoftwareInventoryAnalyzer",
"enableIntegrityBypassAnalyzer",
"enableRealmJoinWatcher",
"keepAwakeDuringUserEsp",
"enableConsoleBypassDetection",
"localAdminAllowedAccountsJson",
"bootstrapTokenEnabled",
"unrestrictedModeEnabled",
"unrestrictedMode",
"entraAppRolesEnabled",
"diagnosticsLogPathsJson",
"diagnosticsBlobSasUrl",
"diagnosticsUploadMode",
"diagnosticsUploadDestination",
"sendTraceEvents",
"teamsWebhookUrl",
"teamsNotifyOnSuccess",
"teamsNotifyOnFailure",
"teamsNotifyOnStart",
"webhookProviderType",
"webhookUrl",
"webhookNotifyOnSuccess",
"webhookNotifyOnFailure",
"webhookNotifyOnHardwareRejection",
"webhookNotifyOnStart",
"webhookCustomHeadersJson",
"notificationChannelsJson",
"slaTargetSuccessRate",
"slaTargetMaxDurationMinutes",
"slaTargetAppInstallSuccessRate",
"slaNotifyOnSuccessRateBreach",
"slaSuccessRateNotifyThreshold",
"slaNotifyOnDurationBreach",
"slaNotifyOnAppInstallBreach",
"slaNotifyOnConsecutiveFailures",
"slaConsecutiveFailureThreshold"
]
},
"sessionSummary": {
"fields": [
{
"name": "sessionId",
"optional": false
},
{
"name": "tenantId",
"optional": false
},
{
"name": "serialNumber",
"optional": false
},
{
"name": "deviceName",
"optional": false
},
{
"name": "manufacturer",
"optional": false
},
{
"name": "model",
"optional": false
},
{
"name": "startedAt",
"optional": false
},
{
"name": "completedAt",
"optional": true
},
{
"name": "currentPhase",
"optional": false
},
{
"name": "currentPhaseDetail",
"optional": false
},
{
"name": "status",
"optional": false
},
{
"name": "failureReason",
"optional": false
},
{
"name": "failureSource",
"optional": false
},
{
"name": "reconcileReason",
"optional": false
},
{
"name": "espSoftFailure",
"optional": false
},
{
"name": "completionSource",
"optional": false
},
{
"name": "adminMarkedAction",
"optional": true
},
{
"name": "validatedBy",
"optional": false
},
{
"name": "eventCount",
"optional": false
},
{
"name": "durationSeconds",
"optional": true
},
{
"name": "avgApiLatencyMs",
"optional": true
},
{
"name": "apiRequestCount",
"optional": true
},
{
"name": "connectionType",
"optional": true
},
{
"name": "enrollmentType",
"optional": false
},
{
"name": "diagnosticsBlobName",
"optional": false
},
{
"name": "diagnosticsBlobDestination",
"optional": true
},
{
"name": "lastEventAt",
"optional": true
},
{
"name": "isPreProvisioned",
"optional": false
},
{
"name": "resumedAt",
"optional": true
},
{
"name": "stalledAt",
"optional": true
},
{
"name": "isHybridJoin",
"optional": false
},
{
"name": "isSelfDeployingProfile",
"optional": false
},
{
"name": "isCloudPc",
"optional": false
},
{
"name": "osName",
"optional": false
},
{
"name": "osBuild",
"optional": false
},
{
"name": "osDisplayVersion",
"optional": false
},
{
"name": "osEdition",
"optional": false
},
{
"name": "osLanguage",
"optional": false
},
{
"name": "isUserDriven",
"optional": false
},
{
"name": "agentVersion",
"optional": false
},
{
"name": "imeAgentVersion",
"optional": false
},
{
"name": "geoCountry",
"optional": false
},
{
"name": "geoRegion",
"optional": false
},
{
"name": "geoCity",
"optional": false
},
{
"name": "geoLoc",
"optional": false
},
{
"name": "platformScriptCount",
"optional": false
},
{
"name": "remediationScriptCount",
"optional": false
},
{
"name": "rebootCount",
"optional": false
},
{
"name": "excessiveEventsAlerted",
"optional": false
},
{
"name": "excessiveEventsAutoActioned",
"optional": false
},
{
"name": "pendingActionsJson",
"optional": false
},
{
"name": "pendingActionsQueuedAt",
"optional": true
},
{
"name": "failureSnapshotJson",
"optional": false
},
{
"name": "deletionState",
"optional": false
},
{
"name": "pendingDeletionManifestId",
"optional": true
}
]
},
"sessionStatuses": [
"InProgress",
"Pending",
"Stalled",
"Succeeded",
"Failed",
"Unknown",
"AwaitingUser",
"Incomplete"
],
"enrollmentPhases": {
"Start": 0,
"DevicePreparation": 1,
"DeviceSetup": 2,
"AppsDevice": 3,
"AccountSetup": 4,
"AppsUser": 5,
"FinalizingSetup": 6,
"Complete": 7,
"Failed": 99,
"Unknown": -1
},
"eventSeverities": {
"Debug": 0,
"Info": 1,
"Warning": 2,
"Error": 3,
"Critical": 4,
"Trace": -1
},
"webhookProviderTypes": {
"None": 0,
"TeamsLegacyConnector": 1,
"TeamsWorkflowWebhook": 2,
"Slack": 10,
"GenericJson": 20
},
"annotationLanes": [
"operator",
"tenantadmin",
"globaladmin"
],
"annotationVerdicts": [
"root_cause_confirmed",
"analysis_wrong",
"different_problem",
"inconclusive"
],
"tenantRoles": [
"Admin",
"Operator",
"Viewer"
],
"globalRoles": [
"GlobalAdmin",
"GlobalReader"
],
"delegatedRoles": [
"DelegatedReader",
"DelegatedAdmin"
],
"analyzeRuleSources": [
"app_install_duration",
"event_correlation",
"event_count",
"event_data",
"event_data_array",
"event_type",
"phase_duration"
],
"analyzeRuleOperators": [
"contains",
"count_gte",
"count_per_group_gte",
"equals",
"exists",
"gt",
"gte",
"in",
"lt",
"lte",
"not_contains",
"not_equals",
"not_exists",
"not_in",
"not_regex",
"regex"
],
"eventTypes": [
"aad_join_status",
"aad_placeholder_user_detected",
"aad_user_joined_observed",
"admin_marked_session",
"agent_emergency_break",
"agent_late_start",
"agent_metrics_collector_stopped",
"agent_metrics_snapshot",
"agent_shutdown",
"agent_shutting_down",
"agent_started",
"agent_trace",
"agent_unrestricted_mode_changed",
"agent_version_check",
"all_apps_completed",
"app_download_started",
"app_install_completed",
"app_install_failed",
"app_install_skipped",
"app_install_started",
"app_install_starved",
"app_tracking_summary",
"autologon_analysis",
"autopilot_profile",
"autopilot_profile_missing",
"bitlocker_status",
"boot_time",
"cert_validation",
"collector_degraded",
"completion_check",
"completion_waiting",
"configmgr_client_detected",
"console_prefetch_detected",
"decision_process_completion",
"desktop_arrived",
"desktop_detector_first_poll",
"desktop_detector_no_candidate",
"desktop_detector_started",
"device_location",
"diagnostics_collecting",
"diagnostics_upload_failed",
"diagnostics_uploaded",
"disk_space_low",
"dns_configuration",
"do_telemetry",
"download_progress",
"enrollment_complete",
"enrollment_failed",
"enrollment_summary_shown",
"enrollment_type_detected",
"enrollment_type_mismatch",
"error_detected",
"esp_apps_failure_correlation",
"esp_appx_failure_analysis",
"esp_config_detected",
"esp_exiting",
"esp_failure",
"esp_failure_advisory",
"esp_failure_advisory_resolved",
"esp_failure_recovered",
"esp_failure_retry_detected",
"esp_failure_settle_recovered",
"esp_failure_settle_started",
"esp_phase_changed",
"esp_policy_provider_stalled",
"esp_provisioning_raw",
"esp_provisioning_settle_started",
"esp_provisioning_status",
"esp_resumed",
"esp_state_change",
"esp_ui_state",
"gather_result",
"gather_rules_collection_completed",
"gather_rules_collection_started",
"hardware_spec",
"hello_completion_timeout",
"hello_pin_status",
"hello_policy_detected",
"hello_policy_detection_mismatch",
"hello_processing_started",
"hello_processing_stopped",
"hello_provisioning_blocked",
"hello_provisioning_completed",
"hello_provisioning_failed",
"hello_skipped",
"hello_wait_timeout",
"hello_wizard_started",
"historic_ime_replay_detected",
"hybrid_login_pending",
"ime_agent_version",
"ime_process_exited",
"ime_session_change",
"ime_user_session_completed",
"ingress_backpressure",
"integrity_bypass_analysis",
"keep_awake_engaged",
"keep_awake_released",
"local_admin_analysis",
"log_entry",
"mdm_policy_reboot_required",
"modern_deployment_error",
"modern_deployment_log",
"modern_deployment_warning",
"network_adapters",
"network_bandwidth_estimate",
"network_connectivity_check",
"network_interface_info",
"network_state_change",
"ntp_time_check",
"office_install_completed",
"office_install_failed",
"office_install_progress",
"office_install_started",
"office_preinstalled_detected",
"oobe_console_spawned",
"oobe_state_completed",
"os_build_changed",
"os_info",
"outbound_ip",
"performance_collector_stopped",
"performance_snapshot",
"phase_transition",
"power_state_check",
"previous_crash_detected",
"prior_run_died_with_state",
"provisioning_package_scan",
"proxy_configuration",
"realmjoin_detected",
"realmjoin_first_deployment_incomplete",
"realmjoin_package_completed",
"realmjoin_package_started",
"realmjoin_phase_changed",
"realmjoin_resolved",
"realmjoin_timeout",
"reboot_triggered",
"remote_config_fetch_failed",
"script_completed",
"script_failed",
"script_started",
"script_timeout_suspected",
"secureboot_status",
"security_audit",
"security_warning",
"server_action_executed",
"server_action_failed",
"server_action_received",
"session_parked_without_deadline",
"session_stalled",
"session_timeout",
"shadow_discrepancy",
"software_inventory_analysis",
"spool_pressure_detected",
"stall_probe_check",
"stall_probe_result",
"state_quarantine_recovered",
"system_reboot_detected",
"telemetry_upload_blocked",
"telemetry_upload_poisoned",
"timezone_auto_set",
"tpm_status",
"vulnerability_report",
"waiting_for_hello",
"whiteglove_classification",
"whiteglove_complete",
"whiteglove_part1_complete",
"whiteglove_resumed",
"whiteglove_started",
"wifi_signal_info",
"windows_update_channel_census",
"windows_update_failed",
"windows_update_history",
"windows_update_reboot_pending",
"windows_update_started",
"windows_update_succeeded"
]
} as const;
@@ -0,0 +1,639 @@
{
"$comment": "GENERATED from AutopilotMonitor.Shared by SharedManifestParityTests — do not edit by hand. Regenerate: AM_WRITE_SHARED_MANIFESTS=1 dotnet test --filter SharedManifestParityTests, then node scripts/generate-shared-manifest-types.js.",
"adminConfiguration": {
"fields": [
"partitionKey",
"rowKey",
"lastUpdated",
"updatedBy",
"globalRateLimitRequestsPerMinute",
"userRateLimitRequestsPerMinute",
"globalAdminRateLimitRequestsPerMinute",
"planTierDefinitionsJson",
"platformStatsBlobSasUrl",
"collectorIdleTimeoutMinutes",
"desktopDetectorNoCandidateTimeoutMinutes",
"excessiveEventCountThreshold",
"excessiveEventAutoActionMode",
"excessiveEventAutoActionThreshold",
"excessiveEventAutoActionDurationHours",
"opsEventRetentionDays",
"slaNotificationCooldownHours",
"feedbackEnabled",
"feedbackMinTenantAgeDays",
"feedbackCooldownDays",
"diagnosticsGlobalLogPathsJson",
"maxDiagnosticsDownloadSizeMB",
"diagnosticsDownloadTimeoutSeconds",
"mcpAccessPolicy",
"nvdApiKey",
"opsAlertRulesJson",
"opsAlertTelegramEnabled",
"opsAlertTelegramChatId",
"opsAlertTeamsEnabled",
"opsAlertTeamsWebhookUrl",
"opsAlertSlackEnabled",
"opsAlertSlackWebhookUrl",
"latestAgentV2Version",
"latestAgentV2Sha256",
"latestAgentV2ExeSha256",
"latestBootstrapV2ScriptVersion",
"allowAgentDowngrade",
"agentMigrateApiBaseUrl",
"agentMigrateTenantOverridesJson",
"modernDeploymentHarmlessEventIdsJson",
"whiteGloveSealingPatternIdsJson",
"vulnerabilityCorrelationEnabled",
"enableIndexDualWrite",
"autoApproveNewTenants",
"selfServiceAppHomingEnabled",
"sessionDeletionKillSwitch",
"vulnerabilityDataLastSyncUtc",
"msrcLastSyncUtc"
]
},
"tenantConfiguration": {
"fields": [
"tenantId",
"domainName",
"lastUpdated",
"updatedBy",
"onboardedBy",
"contactEmail",
"onboardedAt",
"homedAppClientId",
"lastAuthClientId",
"lastAuthClientIdSince",
"disabled",
"disabledReason",
"disabledUntil",
"customRateLimitRequestsPerMinute",
"customUserRateLimitRequestsPerMinute",
"planTier",
"trialExpiresUtc",
"trialStartedUtc",
"trialConsumed",
"trialGrantedBy",
"manufacturerWhitelist",
"modelWhitelist",
"validateAutopilotDevice",
"validateCorporateIdentifier",
"validateDeviceAssociation",
"validateCloudPcDevice",
"allowInsecureAgentRequests",
"dataRetentionDays",
"sessionTimeoutHours",
"sessionGraceHours",
"maxNdjsonPayloadSizeMB",
"enablePerformanceCollector",
"performanceCollectorIntervalSeconds",
"helloWaitTimeoutSeconds",
"maxAuthFailures",
"authFailureTimeoutMinutes",
"agentMaxLifetimeMinutes",
"absoluteMaxSessionHours",
"selfDestructOnComplete",
"keepLogFile",
"rebootOnComplete",
"rebootDelaySeconds",
"enableGeoLocation",
"ntpServer",
"enableTimezoneAutoSet",
"enableImeMatchLog",
"enableGatherRuleDebugLog",
"enableEspContinueAnywayObservation",
"logLevel",
"maxBatchSize",
"showEnrollmentSummary",
"enrollmentSummaryTimeoutSeconds",
"enrollmentSummaryBrandingImageUrl",
"enrollmentSummaryLaunchRetrySeconds",
"showScriptOutput",
"enableLocalAdminAnalyzer",
"enableSoftwareInventoryAnalyzer",
"enableIntegrityBypassAnalyzer",
"enableRealmJoinWatcher",
"keepAwakeDuringUserEsp",
"enableConsoleBypassDetection",
"localAdminAllowedAccountsJson",
"bootstrapTokenEnabled",
"unrestrictedModeEnabled",
"unrestrictedMode",
"entraAppRolesEnabled",
"diagnosticsLogPathsJson",
"diagnosticsBlobSasUrl",
"diagnosticsUploadMode",
"diagnosticsUploadDestination",
"sendTraceEvents",
"teamsWebhookUrl",
"teamsNotifyOnSuccess",
"teamsNotifyOnFailure",
"teamsNotifyOnStart",
"webhookProviderType",
"webhookUrl",
"webhookNotifyOnSuccess",
"webhookNotifyOnFailure",
"webhookNotifyOnHardwareRejection",
"webhookNotifyOnStart",
"webhookCustomHeadersJson",
"notificationChannelsJson",
"slaTargetSuccessRate",
"slaTargetMaxDurationMinutes",
"slaTargetAppInstallSuccessRate",
"slaNotifyOnSuccessRateBreach",
"slaSuccessRateNotifyThreshold",
"slaNotifyOnDurationBreach",
"slaNotifyOnAppInstallBreach",
"slaNotifyOnConsecutiveFailures",
"slaConsecutiveFailureThreshold"
]
},
"sessionSummary": {
"fields": [
{
"name": "sessionId",
"optional": false
},
{
"name": "tenantId",
"optional": false
},
{
"name": "serialNumber",
"optional": false
},
{
"name": "deviceName",
"optional": false
},
{
"name": "manufacturer",
"optional": false
},
{
"name": "model",
"optional": false
},
{
"name": "startedAt",
"optional": false
},
{
"name": "completedAt",
"optional": true
},
{
"name": "currentPhase",
"optional": false
},
{
"name": "currentPhaseDetail",
"optional": false
},
{
"name": "status",
"optional": false
},
{
"name": "failureReason",
"optional": false
},
{
"name": "failureSource",
"optional": false
},
{
"name": "reconcileReason",
"optional": false
},
{
"name": "espSoftFailure",
"optional": false
},
{
"name": "completionSource",
"optional": false
},
{
"name": "adminMarkedAction",
"optional": true
},
{
"name": "validatedBy",
"optional": false
},
{
"name": "eventCount",
"optional": false
},
{
"name": "durationSeconds",
"optional": true
},
{
"name": "avgApiLatencyMs",
"optional": true
},
{
"name": "apiRequestCount",
"optional": true
},
{
"name": "connectionType",
"optional": true
},
{
"name": "enrollmentType",
"optional": false
},
{
"name": "diagnosticsBlobName",
"optional": false
},
{
"name": "diagnosticsBlobDestination",
"optional": true
},
{
"name": "lastEventAt",
"optional": true
},
{
"name": "isPreProvisioned",
"optional": false
},
{
"name": "resumedAt",
"optional": true
},
{
"name": "stalledAt",
"optional": true
},
{
"name": "isHybridJoin",
"optional": false
},
{
"name": "isSelfDeployingProfile",
"optional": false
},
{
"name": "isCloudPc",
"optional": false
},
{
"name": "osName",
"optional": false
},
{
"name": "osBuild",
"optional": false
},
{
"name": "osDisplayVersion",
"optional": false
},
{
"name": "osEdition",
"optional": false
},
{
"name": "osLanguage",
"optional": false
},
{
"name": "isUserDriven",
"optional": false
},
{
"name": "agentVersion",
"optional": false
},
{
"name": "imeAgentVersion",
"optional": false
},
{
"name": "geoCountry",
"optional": false
},
{
"name": "geoRegion",
"optional": false
},
{
"name": "geoCity",
"optional": false
},
{
"name": "geoLoc",
"optional": false
},
{
"name": "platformScriptCount",
"optional": false
},
{
"name": "remediationScriptCount",
"optional": false
},
{
"name": "rebootCount",
"optional": false
},
{
"name": "excessiveEventsAlerted",
"optional": false
},
{
"name": "excessiveEventsAutoActioned",
"optional": false
},
{
"name": "pendingActionsJson",
"optional": false
},
{
"name": "pendingActionsQueuedAt",
"optional": true
},
{
"name": "failureSnapshotJson",
"optional": false
},
{
"name": "deletionState",
"optional": false
},
{
"name": "pendingDeletionManifestId",
"optional": true
}
]
},
"sessionStatuses": [
"InProgress",
"Pending",
"Stalled",
"Succeeded",
"Failed",
"Unknown",
"AwaitingUser",
"Incomplete"
],
"enrollmentPhases": {
"Start": 0,
"DevicePreparation": 1,
"DeviceSetup": 2,
"AppsDevice": 3,
"AccountSetup": 4,
"AppsUser": 5,
"FinalizingSetup": 6,
"Complete": 7,
"Failed": 99,
"Unknown": -1
},
"eventSeverities": {
"Debug": 0,
"Info": 1,
"Warning": 2,
"Error": 3,
"Critical": 4,
"Trace": -1
},
"webhookProviderTypes": {
"None": 0,
"TeamsLegacyConnector": 1,
"TeamsWorkflowWebhook": 2,
"Slack": 10,
"GenericJson": 20
},
"annotationLanes": [
"operator",
"tenantadmin",
"globaladmin"
],
"annotationVerdicts": [
"root_cause_confirmed",
"analysis_wrong",
"different_problem",
"inconclusive"
],
"tenantRoles": [
"Admin",
"Operator",
"Viewer"
],
"globalRoles": [
"GlobalAdmin",
"GlobalReader"
],
"delegatedRoles": [
"DelegatedReader",
"DelegatedAdmin"
],
"analyzeRuleSources": [
"app_install_duration",
"event_correlation",
"event_count",
"event_data",
"event_data_array",
"event_type",
"phase_duration"
],
"analyzeRuleOperators": [
"contains",
"count_gte",
"count_per_group_gte",
"equals",
"exists",
"gt",
"gte",
"in",
"lt",
"lte",
"not_contains",
"not_equals",
"not_exists",
"not_in",
"not_regex",
"regex"
],
"eventTypes": [
"aad_join_status",
"aad_placeholder_user_detected",
"aad_user_joined_observed",
"admin_marked_session",
"agent_emergency_break",
"agent_late_start",
"agent_metrics_collector_stopped",
"agent_metrics_snapshot",
"agent_shutdown",
"agent_shutting_down",
"agent_started",
"agent_trace",
"agent_unrestricted_mode_changed",
"agent_version_check",
"all_apps_completed",
"app_download_started",
"app_install_completed",
"app_install_failed",
"app_install_skipped",
"app_install_started",
"app_install_starved",
"app_tracking_summary",
"autologon_analysis",
"autopilot_profile",
"autopilot_profile_missing",
"bitlocker_status",
"boot_time",
"cert_validation",
"collector_degraded",
"completion_check",
"completion_waiting",
"configmgr_client_detected",
"console_prefetch_detected",
"decision_process_completion",
"desktop_arrived",
"desktop_detector_first_poll",
"desktop_detector_no_candidate",
"desktop_detector_started",
"device_location",
"diagnostics_collecting",
"diagnostics_upload_failed",
"diagnostics_uploaded",
"disk_space_low",
"dns_configuration",
"do_telemetry",
"download_progress",
"enrollment_complete",
"enrollment_failed",
"enrollment_summary_shown",
"enrollment_type_detected",
"enrollment_type_mismatch",
"error_detected",
"esp_apps_failure_correlation",
"esp_appx_failure_analysis",
"esp_config_detected",
"esp_exiting",
"esp_failure",
"esp_failure_advisory",
"esp_failure_advisory_resolved",
"esp_failure_recovered",
"esp_failure_retry_detected",
"esp_failure_settle_recovered",
"esp_failure_settle_started",
"esp_phase_changed",
"esp_policy_provider_stalled",
"esp_provisioning_raw",
"esp_provisioning_settle_started",
"esp_provisioning_status",
"esp_resumed",
"esp_state_change",
"esp_ui_state",
"gather_result",
"gather_rules_collection_completed",
"gather_rules_collection_started",
"hardware_spec",
"hello_completion_timeout",
"hello_pin_status",
"hello_policy_detected",
"hello_policy_detection_mismatch",
"hello_processing_started",
"hello_processing_stopped",
"hello_provisioning_blocked",
"hello_provisioning_completed",
"hello_provisioning_failed",
"hello_skipped",
"hello_wait_timeout",
"hello_wizard_started",
"historic_ime_replay_detected",
"hybrid_login_pending",
"ime_agent_version",
"ime_process_exited",
"ime_session_change",
"ime_user_session_completed",
"ingress_backpressure",
"integrity_bypass_analysis",
"keep_awake_engaged",
"keep_awake_released",
"local_admin_analysis",
"log_entry",
"mdm_policy_reboot_required",
"modern_deployment_error",
"modern_deployment_log",
"modern_deployment_warning",
"network_adapters",
"network_bandwidth_estimate",
"network_connectivity_check",
"network_interface_info",
"network_state_change",
"ntp_time_check",
"office_install_completed",
"office_install_failed",
"office_install_progress",
"office_install_started",
"office_preinstalled_detected",
"oobe_console_spawned",
"oobe_state_completed",
"os_build_changed",
"os_info",
"outbound_ip",
"performance_collector_stopped",
"performance_snapshot",
"phase_transition",
"power_state_check",
"previous_crash_detected",
"prior_run_died_with_state",
"provisioning_package_scan",
"proxy_configuration",
"realmjoin_detected",
"realmjoin_first_deployment_incomplete",
"realmjoin_package_completed",
"realmjoin_package_started",
"realmjoin_phase_changed",
"realmjoin_resolved",
"realmjoin_timeout",
"reboot_triggered",
"remote_config_fetch_failed",
"script_completed",
"script_failed",
"script_started",
"script_timeout_suspected",
"secureboot_status",
"security_audit",
"security_warning",
"server_action_executed",
"server_action_failed",
"server_action_received",
"session_parked_without_deadline",
"session_stalled",
"session_timeout",
"shadow_discrepancy",
"software_inventory_analysis",
"spool_pressure_detected",
"stall_probe_check",
"stall_probe_result",
"state_quarantine_recovered",
"system_reboot_detected",
"telemetry_upload_blocked",
"telemetry_upload_poisoned",
"timezone_auto_set",
"tpm_status",
"vulnerability_report",
"waiting_for_hello",
"whiteglove_classification",
"whiteglove_complete",
"whiteglove_part1_complete",
"whiteglove_resumed",
"whiteglove_started",
"wifi_signal_info",
"windows_update_channel_census",
"windows_update_failed",
"windows_update_history",
"windows_update_reboot_pending",
"windows_update_started",
"windows_update_succeeded"
]
}
@@ -0,0 +1,28 @@
/**
* COMPILE-TIME drift checks between hand-written TS mirrors and the C#-generated
* shared manifest (utils/shared-manifests.json → shared-manifests.generated.ts).
*
* A red squiggle / failing `tsc --noEmit` here means one side changed without the
* other: either add the field to the TS mirror, or regenerate the manifest
* (AM_WRITE_SHARED_MANIFESTS=1 dotnet test --filter SharedManifestParityTests,
* then node scripts/generate-shared-manifest-types.js).
*
* Runtime (value-level) parity lives in utils/__tests__/sharedManifestParity.test.ts.
*/
import type { AdminConfiguration } from "@/types/adminConfig";
import { SHARED_MANIFEST } from "./shared-manifests.generated";
/** Resolves only when T is empty — a non-empty union here IS the drift report. */
type AssertNever<T extends never> = T;
type AdminWireKey = (typeof SHARED_MANIFEST)["adminConfiguration"]["fields"][number];
/** C# fields the TS interface is missing. Must stay `never`. */
export type AdminConfigFieldsMissingInTs = AssertNever<
Exclude<AdminWireKey, keyof AdminConfiguration>
>;
/** TS fields the C# model no longer has (ghost fields). Must stay `never`. */
export type AdminConfigGhostFieldsInTs = AssertNever<
Exclude<keyof AdminConfiguration, AdminWireKey>
>;
+5 -1
View File
@@ -5,4 +5,8 @@
# Reducer: HandleRealmJoinTimeoutDeadlineFired → sets RealmJoinFacts.Outcome="Timeout",
# emits a realmjoin_timeout timeline event, and (if Hello+Desktop already resolved OR
# SelfDeployingDeferredCompletion is set) routes through CompleteIfDeferredOrBookkeep.
{"SessionSignalOrdinal":99,"SessionTraceOrdinal":99,"Kind":"DeadlineFired","KindSchemaVersion":1,"OccurredAtUtc":"2026-05-21T11:00:00.0000000Z","SourceOrigin":"DeadlineScheduler","Evidence":{"Kind":"Derived","Identifier":"deadline-realmjoin-timeout","Summary":"RealmJoin hard timeout fired"},"Payload":{"deadline":"realmjoin_timeout"}}
# Evidence is Synthetic — production DeadlineFired posts come from
# EnrollmentOrchestrator.OnDeadlineFired with EvidenceKind.Synthetic (Derived evidence would
# require DerivationInputs and fail SignalSerializer.Deserialize; caught by
# SignalKindFixtureCoverageTests 2026-08-13 — this fixture previously could not replay at all).
{"SessionSignalOrdinal":99,"SessionTraceOrdinal":99,"Kind":"DeadlineFired","KindSchemaVersion":1,"OccurredAtUtc":"2026-05-21T11:00:00.0000000Z","SourceOrigin":"DeadlineScheduler","Evidence":{"Kind":"Synthetic","Identifier":"realmjoin_timeout","Summary":"RealmJoin hard timeout fired"},"Payload":{"deadline":"realmjoin_timeout"}}