mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
refactor(v2): extract BackendClientFactory from Program.RunAgent (PR3)
Phase 3 (BackendApiClient + DistressReporter + EmergencyReporter + AuthFailureTracker + AuthCertificateMissing distress emit) and Phase 5 (mTLS HttpClient + BackendTelemetryUploader) move into Runtime/BackendClientFactory.cs. BackendAuthBundle holds the four clients plus the cached manufacturer/model/serial from HardwareInfo so RunAgent + Phase 5 read the same hardware identity. TelemetryClientResult is the discriminated outcome for Phase 5 — Continue with mTLS+uploader, or Exit with the V1-parity codes (4 = mTLS construction failure, 5 = uploader construction failure). Phase 4 (RemoteConfig fetch + merge + binary-integrity + bootstrap- config cleanup) stays inline in RunAgent for PR4. RunAgent reads the Phase 3 clients via local aliases so Phase 4 wiring is unchanged. RunAgent's Phase 3+5 blocks shrink from ~80 lines to ~12 lines of factory invocations + result destructuring. No behavioural change: constructor argument lists, distress-emit guard, exit-code mapping and the disposal-on-failure semantics are preserved. Tests: 4 new BackendClientFactoryTests (auth-bundle smoke, no-cert-lookup-on-bootstrap-auth, TelemetryClientResult.Exit, TelemetryClientResult.Continue). V2.Core 747/750 without the 3 known EnrollmentOrchestrator threading flakes (pass solo). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
using System.IO;
|
||||
using AutopilotMonitor.Agent.V2.Core.Configuration;
|
||||
using AutopilotMonitor.Agent.V2.Core.Logging;
|
||||
using AutopilotMonitor.Agent.V2.Core.Tests.Harness;
|
||||
using AutopilotMonitor.Agent.V2.Runtime;
|
||||
using Xunit;
|
||||
|
||||
namespace AutopilotMonitor.Agent.V2.Core.Tests.Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for <see cref="BackendClientFactory"/>: Phase 3 (auth-clients bundle) and
|
||||
/// Phase 5 (telemetry-clients result) extracts from <c>Program.RunAgent</c>.
|
||||
/// </summary>
|
||||
public sealed class BackendClientFactoryTests
|
||||
{
|
||||
private static AgentLogger NewLogger(string path)
|
||||
=> new AgentLogger(Path.Combine(path, "logs"), AgentLogLevel.Info);
|
||||
|
||||
private static AgentConfiguration MinimalBootstrapTokenConfig()
|
||||
=> new AgentConfiguration
|
||||
{
|
||||
ApiBaseUrl = "https://example.invalid",
|
||||
TenantId = "00000000-0000-0000-0000-000000000123",
|
||||
SessionId = "11111111-1111-1111-1111-111111111111",
|
||||
BootstrapToken = "test-token",
|
||||
UseBootstrapTokenAuth = true,
|
||||
UseClientCertAuth = false,
|
||||
MaxAuthFailures = 3,
|
||||
AuthFailureTimeoutMinutes = 30,
|
||||
};
|
||||
|
||||
// ============================================================ BuildAuthClients
|
||||
|
||||
[Fact]
|
||||
public void BuildAuthClients_returns_bundle_with_all_clients_populated()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var logger = NewLogger(tmp.Path);
|
||||
var config = MinimalBootstrapTokenConfig();
|
||||
|
||||
var bundle = BackendClientFactory.BuildAuthClients(config, agentVersion: "1.2.3.4", logger: logger);
|
||||
|
||||
Assert.NotNull(bundle);
|
||||
Assert.NotNull(bundle.BackendApiClient);
|
||||
Assert.NotNull(bundle.DistressReporter);
|
||||
Assert.NotNull(bundle.EmergencyReporter);
|
||||
Assert.NotNull(bundle.AuthFailureTracker);
|
||||
// HardwareInfo.GetHardwareInfo always returns string fields (possibly empty on
|
||||
// non-WMI environments) — the bundle must surface whatever it produced.
|
||||
Assert.NotNull(bundle.Manufacturer);
|
||||
Assert.NotNull(bundle.Model);
|
||||
Assert.NotNull(bundle.SerialNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAuthClients_does_not_attempt_cert_lookup_when_bootstrap_auth_used()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
var logger = NewLogger(tmp.Path);
|
||||
var config = MinimalBootstrapTokenConfig();
|
||||
|
||||
var bundle = BackendClientFactory.BuildAuthClients(config, agentVersion: "1.2.3.4", logger: logger);
|
||||
|
||||
// With UseClientCertAuth=false, BackendApiClient skips the cert-store search
|
||||
// entirely → ClientCertificate is null and no AuthCertificateMissing distress
|
||||
// is dispatched. Asserts the bundle is consistent with that path.
|
||||
Assert.Null(bundle.BackendApiClient.ClientCertificate);
|
||||
}
|
||||
|
||||
// =========================================================== TelemetryClientResult
|
||||
|
||||
[Fact]
|
||||
public void TelemetryClientResult_Exit_carries_exit_code_and_no_clients()
|
||||
{
|
||||
var result = TelemetryClientResult.Exit(4);
|
||||
|
||||
Assert.True(result.ShouldExit);
|
||||
Assert.Equal(4, result.ExitCode);
|
||||
Assert.Null(result.MtlsHttpClient);
|
||||
Assert.Null(result.Uploader);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TelemetryClientResult_Continue_carries_clients_and_zero_exit_code()
|
||||
{
|
||||
using var tmp = new TempDirectory();
|
||||
using var fakeHttpClient = new System.Net.Http.HttpClient();
|
||||
|
||||
// BackendTelemetryUploader is hard to construct purely (needs auth tracker etc.) —
|
||||
// pass null, the Continue() factory does not validate. The Result-type contract
|
||||
// is what we exercise here.
|
||||
var result = TelemetryClientResult.Continue(fakeHttpClient, uploader: null);
|
||||
|
||||
Assert.False(result.ShouldExit);
|
||||
Assert.Equal(0, result.ExitCode);
|
||||
Assert.Same(fakeHttpClient, result.MtlsHttpClient);
|
||||
Assert.Null(result.Uploader);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,53 +197,13 @@ namespace AutopilotMonitor.Agent.V2
|
||||
var isWhiteGloveResume = bootstrap.IsWhiteGloveResume;
|
||||
var cleanupServiceFactory = bootstrap.CleanupServiceFactory;
|
||||
|
||||
var backendApiClient = new BackendApiClient(
|
||||
baseUrl: agentConfig.ApiBaseUrl,
|
||||
configuration: agentConfig,
|
||||
logger: logger,
|
||||
agentVersion: GetAgentVersion());
|
||||
|
||||
// M4.6.γ — Emergency + Distress reporters. Plumbed into RemoteConfigService so
|
||||
// Config-fetch failures (auth vs network) flow to the correct channel. Also fires
|
||||
// an initial AuthCertificateMissing distress when the MDM cert was expected but
|
||||
// not found (Legacy parity — surfaces pre-MDM-enrollment dead-ends to the backend
|
||||
// via the cert-less distress channel).
|
||||
var hardwareForReporters = HardwareInfo.GetHardwareInfo(logger);
|
||||
var distressReporter = new DistressReporter(
|
||||
baseUrl: agentConfig.ApiBaseUrl,
|
||||
tenantId: agentConfig.TenantId,
|
||||
manufacturer: hardwareForReporters.Manufacturer,
|
||||
model: hardwareForReporters.Model,
|
||||
serialNumber: hardwareForReporters.SerialNumber,
|
||||
agentVersion: GetAgentVersion(),
|
||||
logger: logger);
|
||||
|
||||
var emergencyReporter = new EmergencyReporter(
|
||||
apiClient: backendApiClient,
|
||||
sessionId: agentConfig.SessionId,
|
||||
tenantId: agentConfig.TenantId,
|
||||
agentVersion: GetAgentVersion(),
|
||||
logger: logger);
|
||||
|
||||
if (agentConfig.UseClientCertAuth && backendApiClient.ClientCertificate == null)
|
||||
{
|
||||
_ = distressReporter.TrySendAsync(
|
||||
DistressErrorType.AuthCertificateMissing,
|
||||
"MDM certificate not found in LocalMachine or CurrentUser store");
|
||||
}
|
||||
|
||||
// Central observer for consecutive 401/403 responses. Initialised with the CLI/bootstrap
|
||||
// defaults on AgentConfiguration; UpdateLimits is called after RemoteConfigMerger.Merge
|
||||
// so tenant-policy overrides take effect. ThresholdExceeded is wired below, once the
|
||||
// shutdown signal is available, to trigger a clean agent exit when the limit is hit.
|
||||
// V1 parity — the distress reporter is plumbed in at construction so the tracker is
|
||||
// the single dispatch point for auth-failure distress (first failure only).
|
||||
var authFailureTracker = new AuthFailureTracker(
|
||||
maxFailures: agentConfig.MaxAuthFailures,
|
||||
timeoutMinutes: agentConfig.AuthFailureTimeoutMinutes,
|
||||
clock: SystemClock.Instance,
|
||||
logger: logger,
|
||||
distressReporter: distressReporter);
|
||||
// Phase 3 (BackendApiClient + reporters + auth-failure tracker) is encapsulated
|
||||
// in BackendClientFactory; see Runtime/BackendClientFactory.cs.
|
||||
var auth = Runtime.BackendClientFactory.BuildAuthClients(agentConfig, GetAgentVersion(), logger);
|
||||
var backendApiClient = auth.BackendApiClient;
|
||||
var distressReporter = auth.DistressReporter;
|
||||
var emergencyReporter = auth.EmergencyReporter;
|
||||
var authFailureTracker = auth.AuthFailureTracker;
|
||||
|
||||
var remoteConfigService = new RemoteConfigService(
|
||||
backendApiClient, agentConfig.TenantId, logger, emergencyReporter, distressReporter, authFailureTracker);
|
||||
@@ -315,45 +275,16 @@ namespace AutopilotMonitor.Agent.V2
|
||||
logger.Debug($"BootstrapConfigCleanup outer exception: {ex.Message}");
|
||||
}
|
||||
|
||||
HttpClient mtlsHttpClient;
|
||||
BackendTelemetryUploader uploader;
|
||||
try
|
||||
// Phase 5 (mTLS HttpClient + BackendTelemetryUploader) is encapsulated in
|
||||
// BackendClientFactory. Construction failures map to V1-parity exit codes
|
||||
// (4 = mTLS, 5 = uploader). See Runtime/BackendClientFactory.cs.
|
||||
var telemetry = Runtime.BackendClientFactory.BuildTelemetryClients(agentConfig, auth, GetAgentVersion(), logger);
|
||||
if (telemetry.ShouldExit)
|
||||
{
|
||||
// Share BackendApiClient.NetworkMetrics so the mTLS pipeline records every
|
||||
// outbound request — most importantly BackendTelemetryUploader's POST
|
||||
// /api/agent/telemetry, which dominates per-session traffic. Without this the
|
||||
// agent_metrics_snapshot net_total_requests undercounts ~25-30x (only the
|
||||
// legacy BackendApiClient calls would show up).
|
||||
mtlsHttpClient = MtlsHttpClientFactory.Create(
|
||||
resolver: new DefaultCertificateResolver(),
|
||||
logger: logger,
|
||||
metrics: backendApiClient.NetworkMetrics);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
logger.Error("mTLS HttpClient creation failed — cannot upload telemetry.", ex);
|
||||
return 4;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
uploader = new BackendTelemetryUploader(
|
||||
httpClient: mtlsHttpClient,
|
||||
baseUrl: agentConfig.ApiBaseUrl,
|
||||
tenantId: agentConfig.TenantId,
|
||||
manufacturer: hardwareForReporters.Manufacturer,
|
||||
model: hardwareForReporters.Model,
|
||||
serialNumber: hardwareForReporters.SerialNumber,
|
||||
bootstrapToken: agentConfig.UseBootstrapTokenAuth ? agentConfig.BootstrapToken : null,
|
||||
agentVersion: GetAgentVersion(),
|
||||
authFailureTracker: authFailureTracker,
|
||||
logger: logger); // Plan §5 Fix 5 — upload-cadence logging
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error("BackendTelemetryUploader construction failed.", ex);
|
||||
return 5;
|
||||
return telemetry.ExitCode;
|
||||
}
|
||||
var mtlsHttpClient = telemetry.MtlsHttpClient;
|
||||
var uploader = telemetry.Uploader;
|
||||
|
||||
// V1 parity (MonitoringService.Start, MonitoringService.RegisterSessionAsync) —
|
||||
// POST /api/agent/register-session with 5-retry exponential backoff (2s/4s/8s/16s)
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using AutopilotMonitor.Agent.V2.Core.Configuration;
|
||||
using AutopilotMonitor.Agent.V2.Core.Logging;
|
||||
using AutopilotMonitor.Agent.V2.Core.Monitoring.Transport;
|
||||
using AutopilotMonitor.Agent.V2.Core.Orchestration;
|
||||
using AutopilotMonitor.Agent.V2.Core.Security;
|
||||
using AutopilotMonitor.Agent.V2.Core.Transport.Telemetry;
|
||||
using AutopilotMonitor.DecisionCore.Engine;
|
||||
using AutopilotMonitor.Shared.Models;
|
||||
|
||||
namespace AutopilotMonitor.Agent.V2.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Phase 3 + Phase 5 of <see cref="Program"/>'s <c>RunAgent</c>: backend HTTP-clients
|
||||
/// (BackendApiClient + reporters + auth-failure tracker) and the mTLS-backed telemetry
|
||||
/// uploader. Phase 4 (RemoteConfig fetch + merge + binary-integrity check) lives in
|
||||
/// <c>RunAgent</c> between the two factory calls and consumes Phase 3's clients to
|
||||
/// produce the merged <see cref="AgentConfigResponse"/>.
|
||||
/// </summary>
|
||||
internal static class BackendClientFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Phase 3 — construct the backend HTTP clients, the distress / emergency reporters
|
||||
/// and the auth-failure tracker. Fires a one-shot <c>AuthCertificateMissing</c>
|
||||
/// distress when client-cert auth is configured but no cert was found in either
|
||||
/// LocalMachine or CurrentUser store (Legacy parity — pre-MDM-enrollment dead-end
|
||||
/// surface). Tracker ceilings reflect the CLI/bootstrap defaults; tenant-policy
|
||||
/// overrides are applied later via <see cref="AuthFailureTracker.UpdateLimits"/>
|
||||
/// once <c>RemoteConfigMerger</c> has run.
|
||||
/// </summary>
|
||||
public static BackendAuthBundle BuildAuthClients(
|
||||
AgentConfiguration agentConfig,
|
||||
string agentVersion,
|
||||
AgentLogger logger)
|
||||
{
|
||||
if (agentConfig == null) throw new ArgumentNullException(nameof(agentConfig));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
var backendApiClient = new BackendApiClient(
|
||||
baseUrl: agentConfig.ApiBaseUrl,
|
||||
configuration: agentConfig,
|
||||
logger: logger,
|
||||
agentVersion: agentVersion);
|
||||
|
||||
// M4.6.γ — Emergency + Distress reporters. Plumbed into RemoteConfigService so
|
||||
// Config-fetch failures (auth vs network) flow to the correct channel. Also fires
|
||||
// an initial AuthCertificateMissing distress when the MDM cert was expected but
|
||||
// not found (Legacy parity — surfaces pre-MDM-enrollment dead-ends to the backend
|
||||
// via the cert-less distress channel).
|
||||
var hardware = HardwareInfo.GetHardwareInfo(logger);
|
||||
var distressReporter = new DistressReporter(
|
||||
baseUrl: agentConfig.ApiBaseUrl,
|
||||
tenantId: agentConfig.TenantId,
|
||||
manufacturer: hardware.Manufacturer,
|
||||
model: hardware.Model,
|
||||
serialNumber: hardware.SerialNumber,
|
||||
agentVersion: agentVersion,
|
||||
logger: logger);
|
||||
|
||||
var emergencyReporter = new EmergencyReporter(
|
||||
apiClient: backendApiClient,
|
||||
sessionId: agentConfig.SessionId,
|
||||
tenantId: agentConfig.TenantId,
|
||||
agentVersion: agentVersion,
|
||||
logger: logger);
|
||||
|
||||
if (agentConfig.UseClientCertAuth && backendApiClient.ClientCertificate == null)
|
||||
{
|
||||
_ = distressReporter.TrySendAsync(
|
||||
DistressErrorType.AuthCertificateMissing,
|
||||
"MDM certificate not found in LocalMachine or CurrentUser store");
|
||||
}
|
||||
|
||||
// Central observer for consecutive 401/403 responses. Initialised with the CLI/bootstrap
|
||||
// defaults on AgentConfiguration; UpdateLimits is called after RemoteConfigMerger.Merge
|
||||
// so tenant-policy overrides take effect. ThresholdExceeded is wired in RunAgent, once
|
||||
// the shutdown signal is available, to trigger a clean agent exit when the limit is hit.
|
||||
// V1 parity — the distress reporter is plumbed in at construction so the tracker is
|
||||
// the single dispatch point for auth-failure distress (first failure only).
|
||||
var authFailureTracker = new AuthFailureTracker(
|
||||
maxFailures: agentConfig.MaxAuthFailures,
|
||||
timeoutMinutes: agentConfig.AuthFailureTimeoutMinutes,
|
||||
clock: SystemClock.Instance,
|
||||
logger: logger,
|
||||
distressReporter: distressReporter);
|
||||
|
||||
return new BackendAuthBundle(
|
||||
backendApiClient: backendApiClient,
|
||||
manufacturer: hardware.Manufacturer,
|
||||
model: hardware.Model,
|
||||
serialNumber: hardware.SerialNumber,
|
||||
distressReporter: distressReporter,
|
||||
emergencyReporter: emergencyReporter,
|
||||
authFailureTracker: authFailureTracker);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase 5 — construct the mTLS-backed <see cref="HttpClient"/> and the
|
||||
/// <see cref="BackendTelemetryUploader"/>. The uploader's <see cref="NetworkMetrics"/>
|
||||
/// instance is shared with the legacy <see cref="BackendApiClient"/>'s pipeline so
|
||||
/// the agent_metrics_snapshot net_total_requests reflects every outbound HTTP call,
|
||||
/// including the dominant <c>/api/agent/telemetry</c> POST (Plan §5 Fix 5 — without
|
||||
/// this the counter undercounts ~25-30x).
|
||||
/// </summary>
|
||||
public static TelemetryClientResult BuildTelemetryClients(
|
||||
AgentConfiguration agentConfig,
|
||||
BackendAuthBundle auth,
|
||||
string agentVersion,
|
||||
AgentLogger logger)
|
||||
{
|
||||
if (agentConfig == null) throw new ArgumentNullException(nameof(agentConfig));
|
||||
if (auth == null) throw new ArgumentNullException(nameof(auth));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
HttpClient mtlsHttpClient;
|
||||
try
|
||||
{
|
||||
// Share BackendApiClient.NetworkMetrics so the mTLS pipeline records every
|
||||
// outbound request — most importantly BackendTelemetryUploader's POST
|
||||
// /api/agent/telemetry, which dominates per-session traffic. Without this the
|
||||
// agent_metrics_snapshot net_total_requests undercounts ~25-30x (only the
|
||||
// legacy BackendApiClient calls would show up).
|
||||
mtlsHttpClient = MtlsHttpClientFactory.Create(
|
||||
resolver: new DefaultCertificateResolver(),
|
||||
logger: logger,
|
||||
metrics: auth.BackendApiClient.NetworkMetrics);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
logger.Error("mTLS HttpClient creation failed — cannot upload telemetry.", ex);
|
||||
return TelemetryClientResult.Exit(4);
|
||||
}
|
||||
|
||||
BackendTelemetryUploader uploader;
|
||||
try
|
||||
{
|
||||
uploader = new BackendTelemetryUploader(
|
||||
httpClient: mtlsHttpClient,
|
||||
baseUrl: agentConfig.ApiBaseUrl,
|
||||
tenantId: agentConfig.TenantId,
|
||||
manufacturer: auth.Manufacturer,
|
||||
model: auth.Model,
|
||||
serialNumber: auth.SerialNumber,
|
||||
bootstrapToken: agentConfig.UseBootstrapTokenAuth ? agentConfig.BootstrapToken : null,
|
||||
agentVersion: agentVersion,
|
||||
authFailureTracker: auth.AuthFailureTracker,
|
||||
logger: logger); // Plan §5 Fix 5 — upload-cadence logging
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error("BackendTelemetryUploader construction failed.", ex);
|
||||
return TelemetryClientResult.Exit(5);
|
||||
}
|
||||
|
||||
return TelemetryClientResult.Continue(mtlsHttpClient, uploader);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase 3 outcome — the four backend-facing clients and the cached hardware identity
|
||||
/// they were built with. RunAgent retains this bundle through the rest of startup so
|
||||
/// the lifecycle / termination wiring can read each client without re-construction.
|
||||
/// </summary>
|
||||
internal sealed class BackendAuthBundle
|
||||
{
|
||||
public BackendApiClient BackendApiClient { get; }
|
||||
public string Manufacturer { get; }
|
||||
public string Model { get; }
|
||||
public string SerialNumber { get; }
|
||||
public DistressReporter DistressReporter { get; }
|
||||
public EmergencyReporter EmergencyReporter { get; }
|
||||
public AuthFailureTracker AuthFailureTracker { get; }
|
||||
|
||||
public BackendAuthBundle(
|
||||
BackendApiClient backendApiClient,
|
||||
string manufacturer,
|
||||
string model,
|
||||
string serialNumber,
|
||||
DistressReporter distressReporter,
|
||||
EmergencyReporter emergencyReporter,
|
||||
AuthFailureTracker authFailureTracker)
|
||||
{
|
||||
BackendApiClient = backendApiClient;
|
||||
Manufacturer = manufacturer;
|
||||
Model = model;
|
||||
SerialNumber = serialNumber;
|
||||
DistressReporter = distressReporter;
|
||||
EmergencyReporter = emergencyReporter;
|
||||
AuthFailureTracker = authFailureTracker;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase 5 outcome — either an early exit (V1 parity: 4 = mTLS construction failure,
|
||||
/// 5 = uploader construction failure) or a Continue payload carrying the live mTLS
|
||||
/// <see cref="HttpClient"/> and <see cref="BackendTelemetryUploader"/>.
|
||||
/// </summary>
|
||||
internal sealed class TelemetryClientResult
|
||||
{
|
||||
public bool ShouldExit { get; }
|
||||
public int ExitCode { get; }
|
||||
public HttpClient MtlsHttpClient { get; }
|
||||
public BackendTelemetryUploader Uploader { get; }
|
||||
|
||||
private TelemetryClientResult(bool shouldExit, int exitCode, HttpClient mtls, BackendTelemetryUploader uploader)
|
||||
{
|
||||
ShouldExit = shouldExit;
|
||||
ExitCode = exitCode;
|
||||
MtlsHttpClient = mtls;
|
||||
Uploader = uploader;
|
||||
}
|
||||
|
||||
public static TelemetryClientResult Exit(int code)
|
||||
=> new TelemetryClientResult(true, code, null, null);
|
||||
|
||||
public static TelemetryClientResult Continue(HttpClient mtls, BackendTelemetryUploader uploader)
|
||||
=> new TelemetryClientResult(false, 0, mtls, uploader);
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
367
|
||||
370
|
||||
|
||||
@@ -1 +1 @@
|
||||
1177
|
||||
1179
|
||||
|
||||
Reference in New Issue
Block a user