mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
v2 single-rail 5.9b: ImeLogTrackerAdapter wire missing ImeLog callbacks (B29 part 2)
Plan §5.9 / PR #9 — V1 Parity Issue #3 completion: wire the three tracker callbacks the adapter previously did not touch so the full V1-compatible wire shape reaches the Events table via Signal-Post (Rail A single-rail). Adapter additions: - OnImeAgentVersion → ime_agent_version InformationalEvent (Source=ImeLogTracker); no dedup — matches V1 behavior where the same version fires multiple times per session at each pattern-match ingress. - OnDoTelemetryReceived → do_telemetry InformationalEvent carrying the full DO payload (doFileSize / doTotalBytesDownloaded / doBytesFromPeers / doPercentPeerCaching / doDownloadMode / doDownloadDuration / doBytesFromHttp + app state/intent/targeted/runAs/progress). Tracker guards with HasDoTelemetry, so each app fires at most once per "DO complete" pattern match. - OnScriptCompleted → script_completed InformationalEvent with Source="IME" (not "ImeLogTracker" — matches V1 contract observed in the Events table for both platform and remediation scripts); Severity auto-derives to Error when Result="Failed" or ExitCode != 0. Supports both scriptType=platform (Result + exitCode) and scriptType=remediation (ScriptPart + ComplianceResult). All three callbacks preserve prior handlers via the existing chain-invoke pattern and are restored on Dispose. Tracker's LastMatchedPatternId flows into the payload's "patternId" key to match the V1 shape. Tests: +11 new adapter tests covering each callback (payload/fields/severity, null-guards, prior-handler chaining, Dispose restore). Suite passes 48/48 for ImeLogTrackerAdapterTests and 562/562 for the V2.Core project. Full solution build clean. Parity Issue #3 (V1 reference e99a6805 → V2 Beleg-Session e259c121): the IME-driven events app_install_started / app_install_completed / app_install_failed / app_download_started / download_progress / do_telemetry / script_completed / ime_agent_version / ime_user_session_completed / esp_phase_changed are now emitted by V2 via the single-rail Signal-Post path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+234
@@ -440,5 +440,239 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.SignalAdapters
|
||||
Assert.Throws<ArgumentNullException>(() => new ImeLogTrackerAdapter(f.Tracker, null!, f.Clock));
|
||||
Assert.Throws<ArgumentNullException>(() => new ImeLogTrackerAdapter(f.Tracker, f.Ingress, null!));
|
||||
}
|
||||
|
||||
// -- 5.9b: OnImeAgentVersion / OnDoTelemetryReceived / OnScriptCompleted --------
|
||||
|
||||
[Fact]
|
||||
public void ImeAgentVersion_emits_ime_agent_version_info_event()
|
||||
{
|
||||
using var f = new Fixture();
|
||||
using var adapter = new ImeLogTrackerAdapter(f.Tracker, f.Ingress, f.Clock);
|
||||
|
||||
adapter.TriggerImeAgentVersionFromTest("1.101.109.0");
|
||||
|
||||
var info = Assert.Single(f.InfoEvents(SharedEventTypes.ImeAgentVersion));
|
||||
Assert.Equal("ImeLogTracker", info.Payload![SignalPayloadKeys.Source]);
|
||||
Assert.Equal("1.101.109.0", info.Payload["agentVersion"]);
|
||||
Assert.Equal("IME Agent version: 1.101.109.0", info.Payload[SignalPayloadKeys.Message]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImeAgentVersion_null_or_empty_is_skipped()
|
||||
{
|
||||
using var f = new Fixture();
|
||||
using var adapter = new ImeLogTrackerAdapter(f.Tracker, f.Ingress, f.Clock);
|
||||
|
||||
adapter.TriggerImeAgentVersionFromTest(null!);
|
||||
adapter.TriggerImeAgentVersionFromTest("");
|
||||
|
||||
Assert.Empty(f.Ingress.Posted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImeAgentVersion_same_version_twice_is_not_deduped_matches_V1_behavior()
|
||||
{
|
||||
// V1 reference session shows 2 ime_agent_version events for the same version
|
||||
// in the same session (seq 24 and 60). Adapter must not dedup.
|
||||
using var f = new Fixture();
|
||||
using var adapter = new ImeLogTrackerAdapter(f.Tracker, f.Ingress, f.Clock);
|
||||
|
||||
adapter.TriggerImeAgentVersionFromTest("1.101.109.0");
|
||||
adapter.TriggerImeAgentVersionFromTest("1.101.109.0");
|
||||
|
||||
Assert.Equal(2, f.InfoEvents(SharedEventTypes.ImeAgentVersion).Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoTelemetry_emits_do_telemetry_info_event_with_do_fields()
|
||||
{
|
||||
using var f = new Fixture();
|
||||
using var adapter = new ImeLogTrackerAdapter(f.Tracker, f.Ingress, f.Clock);
|
||||
|
||||
var app = AppPackageState.Restore(
|
||||
id: "0e3be4ce-790b-4081-831c-d72d92f3cc9b",
|
||||
listPos: 0,
|
||||
name: "SAP 2024 Patch 13",
|
||||
runAs: AppRunAs.System,
|
||||
intent: AppIntent.Install,
|
||||
targeted: AppTargeted.Device,
|
||||
dependsOn: new HashSet<string>(),
|
||||
installationState: AppInstallationState.Downloading,
|
||||
downloadingOrInstallingSeen: true,
|
||||
progressPercent: 97,
|
||||
bytesDownloaded: 185_023_504,
|
||||
bytesTotal: 190_266_384,
|
||||
doFileSize: 190_266_384,
|
||||
doTotalBytesDownloaded: 190_266_384,
|
||||
doBytesFromPeers: 0,
|
||||
doPercentPeerCaching: 0,
|
||||
doBytesFromHttp: 190_266_384,
|
||||
doDownloadMode: 1,
|
||||
doDownloadDuration: "00:01:45.159",
|
||||
hasDoTelemetry: true);
|
||||
|
||||
adapter.TriggerDoTelemetryFromTest(app);
|
||||
|
||||
var info = Assert.Single(f.InfoEvents(SharedEventTypes.DoTelemetry));
|
||||
Assert.Equal("ImeLogTracker", info.Payload![SignalPayloadKeys.Source]);
|
||||
Assert.Equal(app.Id, info.Payload["appId"]);
|
||||
Assert.Equal("SAP 2024 Patch 13", info.Payload["appName"]);
|
||||
Assert.Equal("Downloading", info.Payload["state"]);
|
||||
Assert.Equal("97", info.Payload["progressPercent"]);
|
||||
Assert.Equal("185023504", info.Payload["bytesDownloaded"]);
|
||||
Assert.Equal("190266384", info.Payload["doFileSize"]);
|
||||
Assert.Equal("0", info.Payload["doBytesFromPeers"]);
|
||||
Assert.Equal("190266384", info.Payload["doBytesFromHttp"]);
|
||||
Assert.Equal("1", info.Payload["doDownloadMode"]);
|
||||
Assert.Equal("00:01:45.159", info.Payload["doDownloadDuration"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoTelemetry_null_app_or_empty_id_is_ignored()
|
||||
{
|
||||
using var f = new Fixture();
|
||||
using var adapter = new ImeLogTrackerAdapter(f.Tracker, f.Ingress, f.Clock);
|
||||
|
||||
adapter.TriggerDoTelemetryFromTest(null!);
|
||||
|
||||
Assert.Empty(f.Ingress.Posted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptCompleted_platform_success_emits_script_completed_info_source_IME()
|
||||
{
|
||||
using var f = new Fixture();
|
||||
using var adapter = new ImeLogTrackerAdapter(f.Tracker, f.Ingress, f.Clock);
|
||||
|
||||
var script = new ScriptExecutionState
|
||||
{
|
||||
PolicyId = "4453d1ad-8171-4459-ae6b-ad97f722ea11",
|
||||
ScriptType = "platform",
|
||||
ExitCode = 0,
|
||||
RunContext = "System",
|
||||
Result = "Success",
|
||||
Stdout = "Script created at: C:\\ProgramData\\TPF IT\\ShowItemsOnDesktop\\ShowItemsOnDesktop.ps1",
|
||||
};
|
||||
|
||||
adapter.TriggerScriptCompletedFromTest(script);
|
||||
|
||||
var info = Assert.Single(f.InfoEvents(SharedEventTypes.ScriptCompleted));
|
||||
// V1 contract: Source="IME" (not "ImeLogTracker") for script_completed.
|
||||
Assert.Equal("IME", info.Payload![SignalPayloadKeys.Source]);
|
||||
Assert.Equal(script.PolicyId, info.Payload["policyId"]);
|
||||
Assert.Equal("platform", info.Payload["scriptType"]);
|
||||
Assert.Equal("0", info.Payload["exitCode"]);
|
||||
Assert.Equal("System", info.Payload["runContext"]);
|
||||
Assert.Equal("Success", info.Payload["result"]);
|
||||
Assert.Equal(EventSeverity.Info.ToString(), info.Payload[SignalPayloadKeys.Severity]);
|
||||
Assert.Contains("Platform script 4453d1ad: Success (exit: 0)", info.Payload[SignalPayloadKeys.Message]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptCompleted_platform_failure_marks_severity_Error()
|
||||
{
|
||||
using var f = new Fixture();
|
||||
using var adapter = new ImeLogTrackerAdapter(f.Tracker, f.Ingress, f.Clock);
|
||||
|
||||
var script = new ScriptExecutionState
|
||||
{
|
||||
PolicyId = "deadbeef-0000-0000-0000-000000000000",
|
||||
ScriptType = "platform",
|
||||
ExitCode = 1,
|
||||
RunContext = "System",
|
||||
Result = "Failed",
|
||||
Stderr = "Script failed: access denied",
|
||||
};
|
||||
|
||||
adapter.TriggerScriptCompletedFromTest(script);
|
||||
|
||||
var info = Assert.Single(f.InfoEvents(SharedEventTypes.ScriptCompleted));
|
||||
Assert.Equal(EventSeverity.Error.ToString(), info.Payload![SignalPayloadKeys.Severity]);
|
||||
Assert.Equal("Failed", info.Payload["result"]);
|
||||
Assert.Equal("1", info.Payload["exitCode"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptCompleted_remediation_carries_compliance_result()
|
||||
{
|
||||
using var f = new Fixture();
|
||||
using var adapter = new ImeLogTrackerAdapter(f.Tracker, f.Ingress, f.Clock);
|
||||
|
||||
var script = new ScriptExecutionState
|
||||
{
|
||||
PolicyId = "cafebabe-0000-0000-0000-000000000000",
|
||||
ScriptType = "remediation",
|
||||
ScriptPart = "detection",
|
||||
ExitCode = 0,
|
||||
RunContext = "System",
|
||||
ComplianceResult = "True",
|
||||
};
|
||||
|
||||
adapter.TriggerScriptCompletedFromTest(script);
|
||||
|
||||
var info = Assert.Single(f.InfoEvents(SharedEventTypes.ScriptCompleted));
|
||||
Assert.Equal("remediation", info.Payload!["scriptType"]);
|
||||
Assert.Equal("detection", info.Payload["scriptPart"]);
|
||||
Assert.Equal("True", info.Payload["complianceResult"]);
|
||||
Assert.Contains("Remediation script cafebabe: compliance=True", info.Payload[SignalPayloadKeys.Message]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptCompleted_null_or_empty_policyId_is_ignored()
|
||||
{
|
||||
using var f = new Fixture();
|
||||
using var adapter = new ImeLogTrackerAdapter(f.Tracker, f.Ingress, f.Clock);
|
||||
|
||||
adapter.TriggerScriptCompletedFromTest(null!);
|
||||
adapter.TriggerScriptCompletedFromTest(new ScriptExecutionState { PolicyId = null });
|
||||
adapter.TriggerScriptCompletedFromTest(new ScriptExecutionState { PolicyId = "" });
|
||||
|
||||
Assert.Empty(f.Ingress.Posted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void New_callbacks_preserve_prior_action_handlers_chain_invoke()
|
||||
{
|
||||
using var f = new Fixture();
|
||||
int priorVersionCalls = 0;
|
||||
int priorDoCalls = 0;
|
||||
int priorScriptCalls = 0;
|
||||
f.Tracker.OnImeAgentVersion = _ => priorVersionCalls++;
|
||||
f.Tracker.OnDoTelemetryReceived = _ => priorDoCalls++;
|
||||
f.Tracker.OnScriptCompleted = _ => priorScriptCalls++;
|
||||
|
||||
using var adapter = new ImeLogTrackerAdapter(f.Tracker, f.Ingress, f.Clock);
|
||||
|
||||
f.Tracker.OnImeAgentVersion("1.0.0.0");
|
||||
f.Tracker.OnDoTelemetryReceived(new AppPackageState("app-1", 0));
|
||||
f.Tracker.OnScriptCompleted(new ScriptExecutionState { PolicyId = "p", ScriptType = "platform" });
|
||||
|
||||
Assert.Equal(1, priorVersionCalls);
|
||||
Assert.Equal(1, priorDoCalls);
|
||||
Assert.Equal(1, priorScriptCalls);
|
||||
Assert.Equal(3, f.AllInfoEvents().Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_restores_all_prior_action_handlers()
|
||||
{
|
||||
using var f = new Fixture();
|
||||
Action<string> priorVersion = _ => { };
|
||||
Action<AppPackageState> priorDo = _ => { };
|
||||
Action<ScriptExecutionState> priorScript = _ => { };
|
||||
f.Tracker.OnImeAgentVersion = priorVersion;
|
||||
f.Tracker.OnDoTelemetryReceived = priorDo;
|
||||
f.Tracker.OnScriptCompleted = priorScript;
|
||||
|
||||
var adapter = new ImeLogTrackerAdapter(f.Tracker, f.Ingress, f.Clock);
|
||||
Assert.NotSame(priorVersion, f.Tracker.OnImeAgentVersion);
|
||||
Assert.NotSame(priorDo, f.Tracker.OnDoTelemetryReceived);
|
||||
Assert.NotSame(priorScript, f.Tracker.OnScriptCompleted);
|
||||
|
||||
adapter.Dispose();
|
||||
Assert.Same(priorVersion, f.Tracker.OnImeAgentVersion);
|
||||
Assert.Same(priorDo, f.Tracker.OnDoTelemetryReceived);
|
||||
Assert.Same(priorScript, f.Tracker.OnScriptCompleted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,12 +64,18 @@ namespace AutopilotMonitor.Agent.V2.Core.SignalAdapters
|
||||
private readonly Action? _prevOnUserSessionCompleted;
|
||||
private readonly Action<AppPackageState, AppInstallationState, AppInstallationState>? _prevOnAppStateChanged;
|
||||
private readonly Action<string>? _prevOnPatternMatched;
|
||||
private readonly Action<string>? _prevOnImeAgentVersion;
|
||||
private readonly Action<AppPackageState>? _prevOnDoTelemetryReceived;
|
||||
private readonly Action<ScriptExecutionState>? _prevOnScriptCompleted;
|
||||
|
||||
// Our own delegate instances — stored once so Dispose can compare by reference.
|
||||
private readonly Action<string> _ourOnEspPhaseChanged;
|
||||
private readonly Action _ourOnUserSessionCompleted;
|
||||
private readonly Action<AppPackageState, AppInstallationState, AppInstallationState> _ourOnAppStateChanged;
|
||||
private readonly Action<string> _ourOnPatternMatched;
|
||||
private readonly Action<string> _ourOnImeAgentVersion;
|
||||
private readonly Action<AppPackageState> _ourOnDoTelemetryReceived;
|
||||
private readonly Action<ScriptExecutionState> _ourOnScriptCompleted;
|
||||
|
||||
// Dedup state for DecisionSignals.
|
||||
private string? _lastEspPhase;
|
||||
@@ -99,6 +105,9 @@ namespace AutopilotMonitor.Agent.V2.Core.SignalAdapters
|
||||
_prevOnUserSessionCompleted = _tracker.OnUserSessionCompleted;
|
||||
_prevOnAppStateChanged = _tracker.OnAppStateChanged;
|
||||
_prevOnPatternMatched = _tracker.OnPatternMatched;
|
||||
_prevOnImeAgentVersion = _tracker.OnImeAgentVersion;
|
||||
_prevOnDoTelemetryReceived = _tracker.OnDoTelemetryReceived;
|
||||
_prevOnScriptCompleted = _tracker.OnScriptCompleted;
|
||||
|
||||
// Store our delegate instances once — implicit method-group conversions
|
||||
// create a new delegate each time, which would break Dispose's reference check.
|
||||
@@ -106,11 +115,17 @@ namespace AutopilotMonitor.Agent.V2.Core.SignalAdapters
|
||||
_ourOnUserSessionCompleted = OnUserSessionCompleted;
|
||||
_ourOnAppStateChanged = OnAppStateChanged;
|
||||
_ourOnPatternMatched = OnPatternMatched;
|
||||
_ourOnImeAgentVersion = OnImeAgentVersion;
|
||||
_ourOnDoTelemetryReceived = OnDoTelemetryReceived;
|
||||
_ourOnScriptCompleted = OnScriptCompleted;
|
||||
|
||||
_tracker.OnEspPhaseChanged = _ourOnEspPhaseChanged;
|
||||
_tracker.OnUserSessionCompleted = _ourOnUserSessionCompleted;
|
||||
_tracker.OnAppStateChanged = _ourOnAppStateChanged;
|
||||
_tracker.OnPatternMatched = _ourOnPatternMatched;
|
||||
_tracker.OnImeAgentVersion = _ourOnImeAgentVersion;
|
||||
_tracker.OnDoTelemetryReceived = _ourOnDoTelemetryReceived;
|
||||
_tracker.OnScriptCompleted = _ourOnScriptCompleted;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -125,6 +140,12 @@ namespace AutopilotMonitor.Agent.V2.Core.SignalAdapters
|
||||
_tracker.OnAppStateChanged = _prevOnAppStateChanged;
|
||||
if (ReferenceEquals(_tracker.OnPatternMatched, _ourOnPatternMatched))
|
||||
_tracker.OnPatternMatched = _prevOnPatternMatched;
|
||||
if (ReferenceEquals(_tracker.OnImeAgentVersion, _ourOnImeAgentVersion))
|
||||
_tracker.OnImeAgentVersion = _prevOnImeAgentVersion;
|
||||
if (ReferenceEquals(_tracker.OnDoTelemetryReceived, _ourOnDoTelemetryReceived))
|
||||
_tracker.OnDoTelemetryReceived = _prevOnDoTelemetryReceived;
|
||||
if (ReferenceEquals(_tracker.OnScriptCompleted, _ourOnScriptCompleted))
|
||||
_tracker.OnScriptCompleted = _prevOnScriptCompleted;
|
||||
}
|
||||
|
||||
private void OnEspPhaseChanged(string phase)
|
||||
@@ -151,11 +172,32 @@ namespace AutopilotMonitor.Agent.V2.Core.SignalAdapters
|
||||
MaybeEmitWhiteGloveSealingPattern(patternId);
|
||||
}
|
||||
|
||||
private void OnImeAgentVersion(string version)
|
||||
{
|
||||
_prevOnImeAgentVersion?.Invoke(version);
|
||||
EmitImeAgentVersion(version);
|
||||
}
|
||||
|
||||
private void OnDoTelemetryReceived(AppPackageState app)
|
||||
{
|
||||
_prevOnDoTelemetryReceived?.Invoke(app);
|
||||
EmitDoTelemetry(app);
|
||||
}
|
||||
|
||||
private void OnScriptCompleted(ScriptExecutionState script)
|
||||
{
|
||||
_prevOnScriptCompleted?.Invoke(script);
|
||||
EmitScriptCompleted(script);
|
||||
}
|
||||
|
||||
internal void TriggerEspPhaseFromTest(string phase) => EmitEspPhase(phase);
|
||||
internal void TriggerUserSessionCompletedFromTest() => EmitUserSessionCompleted();
|
||||
internal void TriggerAppStateFromTest(AppPackageState app, AppInstallationState oldState, AppInstallationState newState) =>
|
||||
EmitAppState(app, oldState, newState);
|
||||
internal void TriggerPatternMatchedFromTest(string patternId) => MaybeEmitWhiteGloveSealingPattern(patternId);
|
||||
internal void TriggerImeAgentVersionFromTest(string version) => EmitImeAgentVersion(version);
|
||||
internal void TriggerDoTelemetryFromTest(AppPackageState app) => EmitDoTelemetry(app);
|
||||
internal void TriggerScriptCompletedFromTest(ScriptExecutionState script) => EmitScriptCompleted(script);
|
||||
|
||||
private void EmitEspPhase(string phase)
|
||||
{
|
||||
@@ -336,6 +378,125 @@ namespace AutopilotMonitor.Agent.V2.Core.SignalAdapters
|
||||
});
|
||||
}
|
||||
|
||||
private void EmitImeAgentVersion(string version)
|
||||
{
|
||||
if (string.IsNullOrEmpty(version)) return;
|
||||
|
||||
var data = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["agentVersion"] = version,
|
||||
};
|
||||
var patternId = _tracker.LastMatchedPatternId;
|
||||
if (!string.IsNullOrEmpty(patternId))
|
||||
data["patternId"] = patternId!;
|
||||
|
||||
_post.Emit(
|
||||
eventType: SharedEventTypes.ImeAgentVersion,
|
||||
source: SourceLabel,
|
||||
message: $"IME Agent version: {version}",
|
||||
data: data,
|
||||
occurredAtUtc: _clock.UtcNow);
|
||||
}
|
||||
|
||||
private void EmitDoTelemetry(AppPackageState app)
|
||||
{
|
||||
if (app == null || string.IsNullOrEmpty(app.Id)) return;
|
||||
|
||||
var culture = System.Globalization.CultureInfo.InvariantCulture;
|
||||
var data = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["appId"] = app.Id,
|
||||
["appName"] = app.Name ?? string.Empty,
|
||||
["state"] = app.InstallationState.ToString(),
|
||||
["intent"] = app.Intent.ToString(),
|
||||
["targeted"] = app.Targeted.ToString(),
|
||||
["runAs"] = app.RunAs.ToString(),
|
||||
["progressPercent"] = (app.ProgressPercent ?? 0).ToString(culture),
|
||||
["bytesDownloaded"] = app.BytesDownloaded.ToString(culture),
|
||||
["bytesTotal"] = app.BytesTotal.ToString(culture),
|
||||
["isError"] = "false",
|
||||
["isCompleted"] = IsCompletedState(app.InstallationState).ToString().ToLowerInvariant(),
|
||||
["doFileSize"] = app.DoFileSize.ToString(culture),
|
||||
["doTotalBytesDownloaded"] = app.DoTotalBytesDownloaded.ToString(culture),
|
||||
["doBytesFromPeers"] = app.DoBytesFromPeers.ToString(culture),
|
||||
["doPercentPeerCaching"] = app.DoPercentPeerCaching.ToString(culture),
|
||||
["doBytesFromLanPeers"] = app.DoBytesFromLanPeers.ToString(culture),
|
||||
["doBytesFromGroupPeers"] = app.DoBytesFromGroupPeers.ToString(culture),
|
||||
["doBytesFromInternetPeers"] = app.DoBytesFromInternetPeers.ToString(culture),
|
||||
["doDownloadMode"] = app.DoDownloadMode.ToString(culture),
|
||||
["doBytesFromHttp"] = app.DoBytesFromHttp.ToString(culture),
|
||||
};
|
||||
if (!string.IsNullOrEmpty(app.DoDownloadDuration)) data["doDownloadDuration"] = app.DoDownloadDuration!;
|
||||
if (!string.IsNullOrEmpty(app.DetectionResult)) data["detectionResult"] = app.DetectionResult!;
|
||||
var patternId = _tracker.LastMatchedPatternId;
|
||||
if (!string.IsNullOrEmpty(patternId)) data["patternId"] = patternId!;
|
||||
|
||||
var label = string.IsNullOrEmpty(app.Name) ? app.Id : app.Name;
|
||||
var msg = $"{label}: DO complete - {app.DoPercentPeerCaching}% peers, mode={app.DoDownloadMode}";
|
||||
|
||||
_post.Emit(
|
||||
eventType: SharedEventTypes.DoTelemetry,
|
||||
source: SourceLabel,
|
||||
message: msg,
|
||||
data: data,
|
||||
occurredAtUtc: _clock.UtcNow);
|
||||
}
|
||||
|
||||
private void EmitScriptCompleted(ScriptExecutionState script)
|
||||
{
|
||||
if (script == null || string.IsNullOrEmpty(script.PolicyId)) return;
|
||||
|
||||
// V1 contract: Source="IME" (not "ImeLogTracker"), severity derived from result.
|
||||
var isFailure = string.Equals(script.Result, "Failed", StringComparison.OrdinalIgnoreCase)
|
||||
|| (script.ExitCode.HasValue && script.ExitCode.Value != 0);
|
||||
var severity = isFailure ? EventSeverity.Error : EventSeverity.Info;
|
||||
|
||||
var culture = System.Globalization.CultureInfo.InvariantCulture;
|
||||
var data = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["policyId"] = script.PolicyId,
|
||||
};
|
||||
if (!string.IsNullOrEmpty(script.ScriptType)) data["scriptType"] = script.ScriptType!;
|
||||
if (!string.IsNullOrEmpty(script.ScriptPart)) data["scriptPart"] = script.ScriptPart!;
|
||||
if (script.ExitCode.HasValue) data["exitCode"] = script.ExitCode.Value.ToString(culture);
|
||||
if (!string.IsNullOrEmpty(script.RunContext)) data["runContext"] = script.RunContext!;
|
||||
if (!string.IsNullOrEmpty(script.Result)) data["result"] = script.Result!;
|
||||
if (!string.IsNullOrEmpty(script.ComplianceResult)) data["complianceResult"] = script.ComplianceResult!;
|
||||
if (!string.IsNullOrEmpty(script.Stdout)) data["stdout"] = script.Stdout!;
|
||||
if (!string.IsNullOrEmpty(script.Stderr)) data["stderr"] = script.Stderr!;
|
||||
var patternId = _tracker.LastMatchedPatternId;
|
||||
if (!string.IsNullOrEmpty(patternId)) data["patternId"] = patternId!;
|
||||
|
||||
var label = IsRemediation(script.ScriptType) ? "Remediation script" : "Platform script";
|
||||
var shortId = script.PolicyId.Length >= 8 ? script.PolicyId.Substring(0, 8) : script.PolicyId;
|
||||
string messageCore;
|
||||
if (IsRemediation(script.ScriptType))
|
||||
{
|
||||
messageCore = !string.IsNullOrEmpty(script.ComplianceResult)
|
||||
? $"compliance={script.ComplianceResult}"
|
||||
: script.Result ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
messageCore = script.Result ?? "";
|
||||
}
|
||||
var exitSuffix = script.ExitCode.HasValue
|
||||
? $" (exit: {script.ExitCode.Value.ToString(culture)})"
|
||||
: string.Empty;
|
||||
var msg = $"{label} {shortId}: {messageCore}{exitSuffix}";
|
||||
|
||||
_post.Emit(
|
||||
eventType: SharedEventTypes.ScriptCompleted,
|
||||
source: "IME",
|
||||
message: msg,
|
||||
severity: severity,
|
||||
data: data,
|
||||
occurredAtUtc: _clock.UtcNow);
|
||||
}
|
||||
|
||||
private static bool IsRemediation(string? scriptType) =>
|
||||
string.Equals(scriptType, "remediation", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static DecisionSignalKind? ClassifyTerminalState(AppInstallationState state)
|
||||
{
|
||||
switch (state)
|
||||
|
||||
Reference in New Issue
Block a user