mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
feat(agent-v2): report RealmJoin release channel alongside version
RealmJoin.exe inverts the usual .NET SDK version-resource layout: the
FileVersion STRING carries the full SemVer including the channel tag
("4.21.6-canary+476277.d320cac0") while ProductVersion is the bare
"4.21.6". The previous ProductVersion-first read therefore never saw
the channel.
- RealmJoinInfo: read FileVersion first, parse SemVer into bare version
+ release channel (prerelease tag = channel; untagged = "release",
confirmed by the RJ developer; build metadata dropped)
- realmjoin_detected signal + timeline event carry releaseChannel
- RealmJoinFacts.ReleaseChannel set-once fact + audit-trail field
realmjoinReleaseChannel
Suites: DecisionCore 360, V2.Core 1307 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+67
@@ -0,0 +1,67 @@
|
||||
using AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals;
|
||||
using Xunit;
|
||||
|
||||
namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring.SystemSignals
|
||||
{
|
||||
/// <summary>
|
||||
/// Parser tests for <see cref="RealmJoinInfo.ParseVersionAndChannel"/>. The RJ binary's
|
||||
/// FileVersion string resource carries a full SemVer where the prerelease tag IS the
|
||||
/// release channel: "4.21.6-canary+476277.d320cac0" (canary/beta) vs "4.21.6" (stable
|
||||
/// release — confirmed by the RJ developer that release builds carry no tag).
|
||||
/// </summary>
|
||||
public sealed class RealmJoinInfoParseTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("4.21.6-canary+476277.d320cac0", "4.21.6", "canary")]
|
||||
[InlineData("4.21.6-beta+123456.abcdef00", "4.21.6", "beta")]
|
||||
[InlineData("4.21.6-canary", "4.21.6", "canary")]
|
||||
public void Tagged_version_yields_channel_from_prerelease_tag(string raw, string expectedVersion, string expectedChannel)
|
||||
{
|
||||
var info = RealmJoinInfo.ParseVersionAndChannel(raw);
|
||||
|
||||
Assert.Equal(expectedVersion, info.ProductVersion);
|
||||
Assert.Equal(expectedChannel, info.ReleaseChannel);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("4.21.6")]
|
||||
[InlineData("4.21.6+476277.d320cac0")]
|
||||
[InlineData(" 4.21.6 ")]
|
||||
public void Untagged_version_yields_stable_release_channel(string raw)
|
||||
{
|
||||
var info = RealmJoinInfo.ParseVersionAndChannel(raw);
|
||||
|
||||
Assert.Equal("4.21.6", info.ProductVersion);
|
||||
Assert.Equal(RealmJoinInfo.ReleaseChannelStable, info.ReleaseChannel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_metadata_is_dropped_from_both_fields()
|
||||
{
|
||||
var info = RealmJoinInfo.ParseVersionAndChannel("4.21.6-canary+476277.d320cac0");
|
||||
|
||||
Assert.DoesNotContain("+", info.ProductVersion);
|
||||
Assert.DoesNotContain("476277", info.ReleaseChannel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Trailing_dash_without_tag_falls_back_to_stable_channel()
|
||||
{
|
||||
// Defensive: a malformed "4.21.6-" must not produce an empty channel string.
|
||||
var info = RealmJoinInfo.ParseVersionAndChannel("4.21.6-");
|
||||
|
||||
Assert.Equal("4.21.6", info.ProductVersion);
|
||||
Assert.Equal(RealmJoinInfo.ReleaseChannelStable, info.ReleaseChannel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Empty_after_metadata_strip_yields_null_fields()
|
||||
{
|
||||
// Defensive: a string that is ONLY build metadata carries no version information.
|
||||
var info = RealmJoinInfo.ParseVersionAndChannel("+476277.d320cac0");
|
||||
|
||||
Assert.Null(info.ProductVersion);
|
||||
Assert.Null(info.ReleaseChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -85,6 +85,28 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.SignalAdapters
|
||||
Assert.Contains("version=3.5.21.0", info.Payload[SignalPayloadKeys.Message]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TriggerDetectedFromTest_includes_ReleaseChannel_when_provided()
|
||||
{
|
||||
using var f = new Fixture();
|
||||
using var adapter = new RealmJoinWatcherAdapter(f.Watcher, f.Ingress, f.Clock);
|
||||
|
||||
adapter.TriggerDetectedFromTest(phase: 100, productVersion: "4.21.6", releaseChannel: "canary");
|
||||
|
||||
var decision = f.Ingress.Posted.Single(p => p.Kind == DecisionSignalKind.RealmJoinDetected);
|
||||
Assert.Equal("4.21.6", decision.Payload![DecisionEngine.RealmJoinPayloadKeys.ProductVersion]);
|
||||
Assert.Equal("canary", decision.Payload[DecisionEngine.RealmJoinPayloadKeys.ReleaseChannel]);
|
||||
|
||||
var info = f.Ingress.Posted.Single(p =>
|
||||
p.Kind == DecisionSignalKind.InformationalEvent
|
||||
&& p.Payload != null
|
||||
&& p.Payload.TryGetValue(SignalPayloadKeys.EventType, out var et)
|
||||
&& et == SharedEventTypes.RealmJoinDetected);
|
||||
Assert.Equal("canary", info.Payload!["releaseChannel"]);
|
||||
Assert.Contains("version=4.21.6", info.Payload[SignalPayloadKeys.Message]);
|
||||
Assert.Contains("channel=canary", info.Payload[SignalPayloadKeys.Message]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TriggerDetectedFromTest_omits_ProductVersion_when_null_or_empty()
|
||||
{
|
||||
@@ -95,6 +117,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.SignalAdapters
|
||||
|
||||
var decision = f.Ingress.Posted.Single(p => p.Kind == DecisionSignalKind.RealmJoinDetected);
|
||||
Assert.False(decision.Payload!.ContainsKey(DecisionEngine.RealmJoinPayloadKeys.ProductVersion));
|
||||
Assert.False(decision.Payload.ContainsKey(DecisionEngine.RealmJoinPayloadKeys.ReleaseChannel));
|
||||
|
||||
var info = f.Ingress.Posted.Single(p =>
|
||||
p.Kind == DecisionSignalKind.InformationalEvent
|
||||
@@ -102,7 +125,9 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.SignalAdapters
|
||||
&& p.Payload.TryGetValue(SignalPayloadKeys.EventType, out var et)
|
||||
&& et == SharedEventTypes.RealmJoinDetected);
|
||||
Assert.False(info.Payload!.ContainsKey("productVersion"));
|
||||
Assert.False(info.Payload.ContainsKey("releaseChannel"));
|
||||
Assert.DoesNotContain("version=", info.Payload[SignalPayloadKeys.Message]);
|
||||
Assert.DoesNotContain("channel=", info.Payload[SignalPayloadKeys.Message]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+70
-7
@@ -103,25 +103,36 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
|
||||
public const string RealmJoinExePathX86 = @"C:\Program Files (x86)\RealmJoin\RealmJoin.exe";
|
||||
|
||||
/// <summary>
|
||||
/// Best-effort read of <c>RealmJoin.exe</c>'s ProductVersion via
|
||||
/// Release-channel name reported when the version string carries no SemVer prerelease
|
||||
/// tag. Per the RJ developer, only beta/canary builds are tagged — untagged == stable.
|
||||
/// </summary>
|
||||
public const string ReleaseChannelStable = "release";
|
||||
|
||||
/// <summary>
|
||||
/// Best-effort read of <c>RealmJoin.exe</c>'s version + release channel via
|
||||
/// <see cref="FileVersionInfo.GetVersionInfo"/>. Wrapped in a defensive try/catch so a
|
||||
/// missing/locked/inaccessible binary returns <c>null</c> instead of throwing —
|
||||
/// missing/locked/inaccessible binary returns an empty result instead of throwing —
|
||||
/// version is observability-only and must never block the detection signal. Probes the
|
||||
/// canonical 64-bit path first, then the (x86) fallback.
|
||||
/// </summary>
|
||||
public static string? TryReadRealmJoinProductVersion()
|
||||
public static RealmJoinVersionInfo TryReadRealmJoinVersionInfo()
|
||||
{
|
||||
return TryReadVersion(RealmJoinExePath) ?? TryReadVersion(RealmJoinExePathX86);
|
||||
return TryReadVersionInfo(RealmJoinExePath) ?? TryReadVersionInfo(RealmJoinExePathX86) ?? RealmJoinVersionInfo.Empty;
|
||||
}
|
||||
|
||||
private static string? TryReadVersion(string path)
|
||||
private static RealmJoinVersionInfo? TryReadVersionInfo(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(path)) return null;
|
||||
var info = FileVersionInfo.GetVersionInfo(path);
|
||||
var version = info.ProductVersion;
|
||||
return string.IsNullOrWhiteSpace(version) ? info.FileVersion : version;
|
||||
// RJ inverts the usual .NET SDK resource layout: the FileVersion STRING entry
|
||||
// carries the full SemVer including the channel tag and build metadata
|
||||
// ("4.21.6-canary+476277.d320cac0") while the ProductVersion string is the
|
||||
// channel-less "4.21.6". Prefer FileVersion, fall back to ProductVersion.
|
||||
var raw = !string.IsNullOrWhiteSpace(info.FileVersion) ? info.FileVersion : info.ProductVersion;
|
||||
if (string.IsNullOrWhiteSpace(raw)) return null;
|
||||
return ParseVersionAndChannel(raw!);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -129,6 +140,33 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Split a SemVer-shaped version string (<c>version[-prerelease][+buildmetadata]</c>)
|
||||
/// into the bare version and the RJ release channel. The prerelease tag IS the channel
|
||||
/// ("beta", "canary"); absence of a tag means <see cref="ReleaseChannelStable"/>.
|
||||
/// Build metadata is dropped.
|
||||
/// </summary>
|
||||
internal static RealmJoinVersionInfo ParseVersionAndChannel(string raw)
|
||||
{
|
||||
var value = raw.Trim();
|
||||
var plus = value.IndexOf('+');
|
||||
if (plus >= 0) value = value.Substring(0, plus);
|
||||
|
||||
var dash = value.IndexOf('-');
|
||||
if (dash < 0)
|
||||
{
|
||||
return new RealmJoinVersionInfo(
|
||||
productVersion: value.Length == 0 ? null : value,
|
||||
releaseChannel: value.Length == 0 ? null : ReleaseChannelStable);
|
||||
}
|
||||
|
||||
var version = value.Substring(0, dash).Trim();
|
||||
var channel = value.Substring(dash + 1).Trim();
|
||||
return new RealmJoinVersionInfo(
|
||||
productVersion: version.Length == 0 ? null : version,
|
||||
releaseChannel: channel.Length == 0 ? ReleaseChannelStable : channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the <c>DeploymentPhase</c> DWORD from the Parameters key. Returns null when the
|
||||
/// key/value does not exist or is unreadable.
|
||||
@@ -225,6 +263,31 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Version + release channel of the installed <c>RealmJoin.exe</c>, parsed from its
|
||||
/// file-version resource by <see cref="RealmJoinInfo.TryReadRealmJoinVersionInfo"/>.
|
||||
/// Both fields are <c>null</c> when the binary is missing or unreadable.
|
||||
/// </summary>
|
||||
internal readonly struct RealmJoinVersionInfo
|
||||
{
|
||||
public static readonly RealmJoinVersionInfo Empty = new RealmJoinVersionInfo(null, null);
|
||||
|
||||
public RealmJoinVersionInfo(string? productVersion, string? releaseChannel)
|
||||
{
|
||||
ProductVersion = productVersion;
|
||||
ReleaseChannel = releaseChannel;
|
||||
}
|
||||
|
||||
/// <summary>Bare version without prerelease tag or build metadata, e.g. <c>4.21.6</c>.</summary>
|
||||
public string? ProductVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// SemVer prerelease tag ("beta", "canary") or <see cref="RealmJoinInfo.ReleaseChannelStable"/>
|
||||
/// when the version string carries no tag.
|
||||
/// </summary>
|
||||
public string? ReleaseChannel { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read-only snapshot of a single RealmJoin package sub-key. Built by
|
||||
/// <see cref="RealmJoinInfo.TryReadPackage"/>; consumed by <c>RealmJoinWatcher</c> to
|
||||
|
||||
+19
-10
@@ -13,16 +13,25 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
|
||||
{
|
||||
public int DeploymentPhase { get; }
|
||||
/// <summary>
|
||||
/// ProductVersion read from <c>C:\Program Files\RealmJoin\RealmJoin.exe</c>
|
||||
/// (or the x86 fallback). <c>null</c> when the binary is missing, locked, or read
|
||||
/// fails — version is observability-only and never blocks detection.
|
||||
/// Bare version parsed from <c>C:\Program Files\RealmJoin\RealmJoin.exe</c>'s
|
||||
/// file-version resource (or the x86 fallback), e.g. <c>4.21.6</c>. <c>null</c> when
|
||||
/// the binary is missing, locked, or read fails — version is observability-only and
|
||||
/// never blocks detection.
|
||||
/// </summary>
|
||||
public string? ProductVersion { get; }
|
||||
|
||||
public RealmJoinDetectedEventArgs(int deploymentPhase, string? productVersion = null)
|
||||
/// <summary>
|
||||
/// RJ release channel parsed from the version string's SemVer prerelease tag
|
||||
/// ("beta", "canary"); <see cref="RealmJoinInfo.ReleaseChannelStable"/> when untagged;
|
||||
/// <c>null</c> when the version was unreadable.
|
||||
/// </summary>
|
||||
public string? ReleaseChannel { get; }
|
||||
|
||||
public RealmJoinDetectedEventArgs(int deploymentPhase, string? productVersion = null, string? releaseChannel = null)
|
||||
{
|
||||
DeploymentPhase = deploymentPhase;
|
||||
ProductVersion = productVersion;
|
||||
ReleaseChannel = releaseChannel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,12 +555,12 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
|
||||
if (fireDetected)
|
||||
{
|
||||
var initial = phase ?? 0;
|
||||
// Read RJ ProductVersion best-effort (fail-soft to null) ONLY when the Detected
|
||||
// event actually fires — single read per session lifetime keeps any potential
|
||||
// file-system cost to a single hit.
|
||||
var productVersion = RealmJoinInfo.TryReadRealmJoinProductVersion();
|
||||
_logger.Info($"RealmJoinWatcher: RJ detected (phase={initial}, productVersion={productVersion ?? "<unknown>"})");
|
||||
try { RealmJoinDetected?.Invoke(this, new RealmJoinDetectedEventArgs(initial, productVersion)); }
|
||||
// Read RJ version + release channel best-effort (fail-soft to null) ONLY when
|
||||
// the Detected event actually fires — single read per session lifetime keeps
|
||||
// any potential file-system cost to a single hit.
|
||||
var versionInfo = RealmJoinInfo.TryReadRealmJoinVersionInfo();
|
||||
_logger.Info($"RealmJoinWatcher: RJ detected (phase={initial}, productVersion={versionInfo.ProductVersion ?? "<unknown>"}, releaseChannel={versionInfo.ReleaseChannel ?? "<unknown>"})");
|
||||
try { RealmJoinDetected?.Invoke(this, new RealmJoinDetectedEventArgs(initial, versionInfo.ProductVersion, versionInfo.ReleaseChannel)); }
|
||||
catch (Exception ex) { _logger.Error("RealmJoinWatcher: RealmJoinDetected handler threw", ex); }
|
||||
}
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ namespace AutopilotMonitor.Agent.V2.Core.SignalAdapters
|
||||
}
|
||||
|
||||
// Test seams
|
||||
internal void TriggerDetectedFromTest(int phase, string? productVersion = null) =>
|
||||
OnDetected(this, new RealmJoinDetectedEventArgs(phase, productVersion));
|
||||
internal void TriggerDetectedFromTest(int phase, string? productVersion = null, string? releaseChannel = null) =>
|
||||
OnDetected(this, new RealmJoinDetectedEventArgs(phase, productVersion, releaseChannel));
|
||||
internal void TriggerResolvedFromTest(int phase) => OnResolved(this, new RealmJoinResolvedEventArgs(phase));
|
||||
internal void TriggerPhaseChangedFromTest(int prev, int curr) => OnPhaseChanged(this, new RealmJoinPhaseChangedEventArgs(prev, curr));
|
||||
internal void TriggerPackageStartedFromTest(string scope, RealmJoinPackageSnapshot snap) => OnPackageStarted(this, new RealmJoinPackageEventArgs(scope, snap));
|
||||
@@ -68,6 +68,10 @@ namespace AutopilotMonitor.Agent.V2.Core.SignalAdapters
|
||||
{
|
||||
payload[DecisionEngine.RealmJoinPayloadKeys.ProductVersion] = e.ProductVersion!;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(e.ReleaseChannel))
|
||||
{
|
||||
payload[DecisionEngine.RealmJoinPayloadKeys.ReleaseChannel] = e.ReleaseChannel!;
|
||||
}
|
||||
|
||||
_ingress.Post(
|
||||
kind: DecisionSignalKind.RealmJoinDetected,
|
||||
@@ -75,7 +79,7 @@ namespace AutopilotMonitor.Agent.V2.Core.SignalAdapters
|
||||
sourceOrigin: SourceLabel,
|
||||
evidence: BuildEvidence(
|
||||
"realmjoin-detected-v1",
|
||||
$"RealmJoin Parameters key observed (phase={e.DeploymentPhase}, productVersion={e.ProductVersion ?? "<unknown>"})"),
|
||||
$"RealmJoin Parameters key observed (phase={e.DeploymentPhase}, productVersion={e.ProductVersion ?? "<unknown>"}, releaseChannel={e.ReleaseChannel ?? "<unknown>"})"),
|
||||
payload: payload);
|
||||
|
||||
var data = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
@@ -87,11 +91,16 @@ namespace AutopilotMonitor.Agent.V2.Core.SignalAdapters
|
||||
{
|
||||
data["productVersion"] = e.ProductVersion!;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(e.ReleaseChannel))
|
||||
{
|
||||
data["releaseChannel"] = e.ReleaseChannel!;
|
||||
}
|
||||
var versionTail = string.IsNullOrEmpty(e.ProductVersion) ? string.Empty : $", version={e.ProductVersion}";
|
||||
var channelTail = string.IsNullOrEmpty(e.ReleaseChannel) ? string.Empty : $", channel={e.ReleaseChannel}";
|
||||
_post.Emit(
|
||||
eventType: SharedConstants.EventTypes.RealmJoinDetected,
|
||||
source: SourceLabel,
|
||||
message: $"RealmJoin deployment detected (phase={e.DeploymentPhase}{versionTail})",
|
||||
message: $"RealmJoin deployment detected (phase={e.DeploymentPhase}{versionTail}{channelTail})",
|
||||
severity: EventSeverity.Info,
|
||||
immediateUpload: true,
|
||||
data: data,
|
||||
|
||||
@@ -92,6 +92,35 @@ namespace AutopilotMonitor.DecisionCore.Tests
|
||||
e.Deadline.Name == DeadlineNames.RealmJoinTimeout);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RealmJoinDetected_records_ProductVersion_and_ReleaseChannel_facts()
|
||||
{
|
||||
var engine = new DecisionEngine();
|
||||
var state = PrimeClassicAwaitingDesktop(engine);
|
||||
|
||||
var step = engine.Reduce(state, MakeSignal(5, DecisionSignalKind.RealmJoinDetected, T0.AddMinutes(5),
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
[DecisionEngine.RealmJoinPayloadKeys.DeploymentPhase] = "100",
|
||||
[DecisionEngine.RealmJoinPayloadKeys.ProductVersion] = "4.21.6",
|
||||
[DecisionEngine.RealmJoinPayloadKeys.ReleaseChannel] = "canary",
|
||||
}));
|
||||
|
||||
Assert.Equal("4.21.6", step.NewState.RealmJoinFacts.ProductVersion!.Value);
|
||||
Assert.Equal("canary", step.NewState.RealmJoinFacts.ReleaseChannel!.Value);
|
||||
|
||||
// Set-once: a replayed Detected signal with different values must not overwrite.
|
||||
var replay = engine.Reduce(step.NewState, MakeSignal(6, DecisionSignalKind.RealmJoinDetected, T0.AddMinutes(6),
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
[DecisionEngine.RealmJoinPayloadKeys.DeploymentPhase] = "100",
|
||||
[DecisionEngine.RealmJoinPayloadKeys.ProductVersion] = "9.9.9",
|
||||
[DecisionEngine.RealmJoinPayloadKeys.ReleaseChannel] = "beta",
|
||||
}));
|
||||
Assert.Equal("4.21.6", replay.NewState.RealmJoinFacts.ProductVersion!.Value);
|
||||
Assert.Equal("canary", replay.NewState.RealmJoinFacts.ReleaseChannel!.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RealmJoinGate_closed_defers_finalizing_when_DesktopArrived_arrives_with_hello_already_resolved()
|
||||
{
|
||||
|
||||
@@ -147,6 +147,10 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
{
|
||||
data["realmjoinProductVersion"] = postState.RealmJoinFacts.ProductVersion.Value;
|
||||
}
|
||||
if (postState.RealmJoinFacts.ReleaseChannel != null)
|
||||
{
|
||||
data["realmjoinReleaseChannel"] = postState.RealmJoinFacts.ReleaseChannel.Value;
|
||||
}
|
||||
data["realmjoinPackages"] = BuildRealmJoinPackageSummary(postState.RealmJoinFacts.Packages);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,11 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
public const string Scope = "scope"; // "machine" | "user"
|
||||
public const string Success = "success"; // "true" | "false"
|
||||
public const string LastExitCode = "lastExitCode";
|
||||
// Set on RealmJoinDetected by the agent adapter from RealmJoin.exe ProductVersion.
|
||||
// Set on RealmJoinDetected by the agent adapter from RealmJoin.exe's
|
||||
// file-version resource: bare version + release channel ("release"/"beta"/"canary"
|
||||
// — the SemVer prerelease tag, absent tag == stable release).
|
||||
public const string ProductVersion = "productVersion";
|
||||
public const string ReleaseChannel = "releaseChannel";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,6 +83,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
|
||||
var phase = TryReadPhase(signal);
|
||||
var productVersion = TryReadString(signal, RealmJoinPayloadKeys.ProductVersion);
|
||||
var releaseChannel = TryReadString(signal, RealmJoinPayloadKeys.ReleaseChannel);
|
||||
var updatedFacts = state.RealmJoinFacts.WithDetected(signal.OccurredAtUtc, signal.SessionSignalOrdinal);
|
||||
if (phase.HasValue)
|
||||
{
|
||||
@@ -89,6 +93,10 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
{
|
||||
updatedFacts = updatedFacts.WithProductVersion(productVersion!, signal.SessionSignalOrdinal);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(releaseChannel))
|
||||
{
|
||||
updatedFacts = updatedFacts.WithReleaseChannel(releaseChannel!, signal.SessionSignalOrdinal);
|
||||
}
|
||||
builder.RealmJoinFacts = updatedFacts;
|
||||
|
||||
var effects = Array.Empty<DecisionEffect>();
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
outcome: null,
|
||||
selfDeployingDeferredCompletion: null,
|
||||
productVersion: null,
|
||||
releaseChannel: null,
|
||||
packages: Array.Empty<RealmJoinPackageFact>());
|
||||
|
||||
public RealmJoinFacts(
|
||||
@@ -42,6 +43,7 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
SignalFact<string>? outcome,
|
||||
SignalFact<bool>? selfDeployingDeferredCompletion,
|
||||
SignalFact<string>? productVersion,
|
||||
SignalFact<string>? releaseChannel,
|
||||
IReadOnlyList<RealmJoinPackageFact> packages)
|
||||
{
|
||||
DetectedUtc = detectedUtc;
|
||||
@@ -50,6 +52,7 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
Outcome = outcome;
|
||||
SelfDeployingDeferredCompletion = selfDeployingDeferredCompletion;
|
||||
ProductVersion = productVersion;
|
||||
ReleaseChannel = releaseChannel;
|
||||
Packages = packages ?? Array.Empty<RealmJoinPackageFact>();
|
||||
}
|
||||
|
||||
@@ -74,12 +77,19 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
public SignalFact<bool>? SelfDeployingDeferredCompletion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <c>ProductVersion</c> read from <c>C:\Program Files\RealmJoin\RealmJoin.exe</c> at
|
||||
/// detection time. Observability-only — never gates a decision. Null when the binary
|
||||
/// was missing or unreadable.
|
||||
/// Bare version parsed from <c>C:\Program Files\RealmJoin\RealmJoin.exe</c>'s
|
||||
/// file-version resource at detection time. Observability-only — never gates a
|
||||
/// decision. Null when the binary was missing or unreadable.
|
||||
/// </summary>
|
||||
public SignalFact<string>? ProductVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// RJ release channel parsed from the version string's SemVer prerelease tag at
|
||||
/// detection time: <c>"release"</c> (untagged stable), <c>"beta"</c> or <c>"canary"</c>.
|
||||
/// Observability-only — never gates a decision. Null when the version was unreadable.
|
||||
/// </summary>
|
||||
public SignalFact<string>? ReleaseChannel { get; }
|
||||
|
||||
/// <summary>Tracked per-package install rows (machine + user scope combined).</summary>
|
||||
public IReadOnlyList<RealmJoinPackageFact> Packages { get; }
|
||||
|
||||
@@ -93,6 +103,7 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
outcome: Outcome,
|
||||
selfDeployingDeferredCompletion: SelfDeployingDeferredCompletion,
|
||||
productVersion: ProductVersion,
|
||||
releaseChannel: ReleaseChannel,
|
||||
packages: Packages);
|
||||
}
|
||||
|
||||
@@ -112,6 +123,27 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
outcome: Outcome,
|
||||
selfDeployingDeferredCompletion: SelfDeployingDeferredCompletion,
|
||||
productVersion: new SignalFact<string>(productVersion, sourceSignalOrdinal),
|
||||
releaseChannel: ReleaseChannel,
|
||||
packages: Packages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set-once observability fact carrying the RJ release channel parsed from the
|
||||
/// <c>RealmJoin.exe</c> version string. No-op when <paramref name="releaseChannel"/>
|
||||
/// is empty or already set. Pure metadata — never participates in a gate decision.
|
||||
/// </summary>
|
||||
public RealmJoinFacts WithReleaseChannel(string releaseChannel, long sourceSignalOrdinal)
|
||||
{
|
||||
if (ReleaseChannel != null) return this;
|
||||
if (string.IsNullOrEmpty(releaseChannel)) return this;
|
||||
return new RealmJoinFacts(
|
||||
detectedUtc: DetectedUtc,
|
||||
resolvedUtc: ResolvedUtc,
|
||||
lastDeploymentPhase: LastDeploymentPhase,
|
||||
outcome: Outcome,
|
||||
selfDeployingDeferredCompletion: SelfDeployingDeferredCompletion,
|
||||
productVersion: ProductVersion,
|
||||
releaseChannel: new SignalFact<string>(releaseChannel, sourceSignalOrdinal),
|
||||
packages: Packages);
|
||||
}
|
||||
|
||||
@@ -125,6 +157,7 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
outcome: new SignalFact<string>(OutcomeResolved, sourceSignalOrdinal),
|
||||
selfDeployingDeferredCompletion: SelfDeployingDeferredCompletion,
|
||||
productVersion: ProductVersion,
|
||||
releaseChannel: ReleaseChannel,
|
||||
packages: Packages);
|
||||
}
|
||||
|
||||
@@ -138,6 +171,7 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
outcome: Outcome,
|
||||
selfDeployingDeferredCompletion: SelfDeployingDeferredCompletion,
|
||||
productVersion: ProductVersion,
|
||||
releaseChannel: ReleaseChannel,
|
||||
packages: Packages);
|
||||
}
|
||||
|
||||
@@ -151,6 +185,7 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
outcome: new SignalFact<string>(OutcomeTimeout, sourceSignalOrdinal),
|
||||
selfDeployingDeferredCompletion: SelfDeployingDeferredCompletion,
|
||||
productVersion: ProductVersion,
|
||||
releaseChannel: ReleaseChannel,
|
||||
packages: Packages);
|
||||
}
|
||||
|
||||
@@ -164,6 +199,7 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
outcome: Outcome,
|
||||
selfDeployingDeferredCompletion: new SignalFact<bool>(true, sourceSignalOrdinal),
|
||||
productVersion: ProductVersion,
|
||||
releaseChannel: ReleaseChannel,
|
||||
packages: Packages);
|
||||
}
|
||||
|
||||
@@ -183,6 +219,7 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
outcome: Outcome,
|
||||
selfDeployingDeferredCompletion: null,
|
||||
productVersion: ProductVersion,
|
||||
releaseChannel: ReleaseChannel,
|
||||
packages: Packages);
|
||||
}
|
||||
|
||||
@@ -237,6 +274,7 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
outcome: Outcome,
|
||||
selfDeployingDeferredCompletion: SelfDeployingDeferredCompletion,
|
||||
productVersion: ProductVersion,
|
||||
releaseChannel: ReleaseChannel,
|
||||
packages: copy);
|
||||
}
|
||||
|
||||
@@ -305,6 +343,7 @@ namespace AutopilotMonitor.DecisionCore.State
|
||||
outcome: Outcome,
|
||||
selfDeployingDeferredCompletion: SelfDeployingDeferredCompletion,
|
||||
productVersion: ProductVersion,
|
||||
releaseChannel: ReleaseChannel,
|
||||
packages: copy);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user