Harden runtime HTTP transport lifecycles

This commit is contained in:
master
2026-04-05 23:52:14 +03:00
parent 1151c30e3a
commit 751546084e
44 changed files with 1173 additions and 136 deletions

View File

@@ -0,0 +1,190 @@
using StellaOps.Integrations.Contracts;
using StellaOps.Integrations.Core;
using System.Net.Http.Headers;
namespace StellaOps.Integrations.WebService;
/// <summary>
/// Feed mirror providers all target the Concelier mirror surface and currently differ only by upstream feed family.
/// These plugins expose the missing provider identities so the Integration Catalog can manage them explicitly.
/// </summary>
public abstract class FeedMirrorConnectorPluginBase : IIntegrationConnectorPlugin
{
public const string HttpClientName = "IntegrationsFeedMirrorProbe";
private static readonly HttpClient SharedHttpClient =
IntegrationHttpClientDefaults.CreateSharedClient(TimeSpan.FromSeconds(30));
private readonly IHttpClientFactory? _httpClientFactory;
private readonly TimeProvider _timeProvider;
protected FeedMirrorConnectorPluginBase(
IHttpClientFactory? httpClientFactory = null,
TimeProvider? timeProvider = null)
{
_httpClientFactory = httpClientFactory;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public abstract string Name { get; }
public IntegrationType Type => IntegrationType.FeedMirror;
public abstract IntegrationProvider Provider { get; }
public bool IsAvailable(IServiceProvider services) => true;
public async Task<TestConnectionResult> TestConnectionAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
{
var startTime = _timeProvider.GetUtcNow();
try
{
using var request = CreateHealthRequest(config);
using var response = await GetHttpClient().SendAsync(request, cancellationToken);
var duration = _timeProvider.GetUtcNow() - startTime;
return response.IsSuccessStatusCode
? new TestConnectionResult(
Success: true,
Message: "Feed mirror connection successful",
Details: new Dictionary<string, string>
{
["endpoint"] = config.Endpoint,
["provider"] = Provider.ToString()
},
Duration: duration)
: new TestConnectionResult(
Success: false,
Message: $"Feed mirror returned {response.StatusCode}",
Details: new Dictionary<string, string>
{
["endpoint"] = config.Endpoint,
["statusCode"] = ((int)response.StatusCode).ToString()
},
Duration: duration);
}
catch (Exception ex)
{
var duration = _timeProvider.GetUtcNow() - startTime;
return new TestConnectionResult(
Success: false,
Message: $"Connection failed: {ex.Message}",
Details: new Dictionary<string, string>
{
["endpoint"] = config.Endpoint,
["error"] = ex.GetType().Name
},
Duration: duration);
}
}
public async Task<HealthCheckResult> CheckHealthAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
{
var startTime = _timeProvider.GetUtcNow();
try
{
using var request = CreateHealthRequest(config);
using var response = await GetHttpClient().SendAsync(request, cancellationToken);
var duration = _timeProvider.GetUtcNow() - startTime;
return response.IsSuccessStatusCode
? new HealthCheckResult(
Status: HealthStatus.Healthy,
Message: "Feed mirror service is healthy",
Details: new Dictionary<string, string>
{
["provider"] = Provider.ToString()
},
CheckedAt: _timeProvider.GetUtcNow(),
Duration: duration)
: new HealthCheckResult(
Status: HealthStatus.Unhealthy,
Message: $"Feed mirror returned {response.StatusCode}",
Details: new Dictionary<string, string>
{
["statusCode"] = ((int)response.StatusCode).ToString()
},
CheckedAt: _timeProvider.GetUtcNow(),
Duration: duration);
}
catch (Exception ex)
{
var duration = _timeProvider.GetUtcNow() - startTime;
return new HealthCheckResult(
Status: HealthStatus.Unhealthy,
Message: $"Health check failed: {ex.Message}",
Details: new Dictionary<string, string>
{
["error"] = ex.GetType().Name
},
CheckedAt: _timeProvider.GetUtcNow(),
Duration: duration);
}
}
private HttpClient GetHttpClient()
=> _httpClientFactory?.CreateClient(HttpClientName) ?? SharedHttpClient;
private static HttpRequestMessage CreateHealthRequest(IntegrationConfig config)
{
var request = new HttpRequestMessage(HttpMethod.Get, BuildHealthUri(config.Endpoint));
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (!string.IsNullOrWhiteSpace(config.ResolvedSecret))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", config.ResolvedSecret);
}
return request;
}
private static Uri BuildHealthUri(string endpoint)
{
var endpointUri = new Uri(endpoint, UriKind.Absolute);
return new Uri($"{endpointUri.GetLeftPart(UriPartial.Authority)}/health");
}
}
public sealed class StellaOpsMirrorConnectorPlugin : FeedMirrorConnectorPluginBase
{
public StellaOpsMirrorConnectorPlugin(
IHttpClientFactory? httpClientFactory = null,
TimeProvider? timeProvider = null)
: base(httpClientFactory, timeProvider)
{
}
public override string Name => "stellaops-mirror";
public override IntegrationProvider Provider => IntegrationProvider.StellaOpsMirror;
}
public sealed class NvdMirrorConnectorPlugin : FeedMirrorConnectorPluginBase
{
public NvdMirrorConnectorPlugin(
IHttpClientFactory? httpClientFactory = null,
TimeProvider? timeProvider = null)
: base(httpClientFactory, timeProvider)
{
}
public override string Name => "nvd-mirror";
public override IntegrationProvider Provider => IntegrationProvider.NvdMirror;
}
public sealed class OsvMirrorConnectorPlugin : FeedMirrorConnectorPluginBase
{
public OsvMirrorConnectorPlugin(
IHttpClientFactory? httpClientFactory = null,
TimeProvider? timeProvider = null)
: base(httpClientFactory, timeProvider)
{
}
public override string Name => "osv-mirror";
public override IntegrationProvider Provider => IntegrationProvider.OsvMirror;
}

View File

@@ -58,18 +58,18 @@ public sealed class LoggingAuditLogger : IIntegrationAuditLogger
/// In production, integrate with Authority service.
/// URI format: authref://vault/{path}#{key}
/// </summary>
public sealed class StubAuthRefResolver : IAuthRefResolver
public sealed class VaultAuthRefResolver : IAuthRefResolver
{
private readonly ILogger<StubAuthRefResolver> _logger;
public const string HttpClientName = "VaultClient";
private readonly ILogger<VaultAuthRefResolver> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly string _vaultAddr;
private readonly string _vaultToken;
public StubAuthRefResolver(ILogger<StubAuthRefResolver> logger, IHttpClientFactory httpClientFactory)
public VaultAuthRefResolver(ILogger<VaultAuthRefResolver> logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
_vaultAddr = Environment.GetEnvironmentVariable("VAULT_ADDR") ?? "http://vault.stella-ops.local:8200";
_vaultToken = Environment.GetEnvironmentVariable("VAULT_TOKEN") ?? "stellaops-dev-root-token-2026";
}
@@ -88,8 +88,7 @@ public sealed class StubAuthRefResolver : IAuthRefResolver
var path = hashIndex >= 0 ? remainder[..hashIndex] : remainder;
var key = hashIndex >= 0 ? remainder[(hashIndex + 1)..] : "value";
var client = _httpClientFactory.CreateClient("VaultClient");
client.BaseAddress = new Uri(_vaultAddr);
var client = _httpClientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Add("X-Vault-Token", _vaultToken);
var response = await client.GetAsync($"/v1/secret/data/{path}", cancellationToken);

View File

@@ -0,0 +1,17 @@
using System.Net.Http.Headers;
namespace StellaOps.Integrations.WebService;
internal static class IntegrationHttpClientDefaults
{
public static HttpClient CreateSharedClient(TimeSpan timeout)
{
var client = new HttpClient
{
Timeout = timeout
};
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("StellaOps", "1.0"));
return client;
}
}

View File

@@ -14,11 +14,15 @@ namespace StellaOps.Integrations.WebService;
public sealed class IntegrationPluginLoader
{
private readonly ILogger<IntegrationPluginLoader>? _logger;
private readonly IServiceProvider? _serviceProvider;
private readonly List<IIntegrationConnectorPlugin> _plugins = [];
public IntegrationPluginLoader(ILogger<IntegrationPluginLoader>? logger = null)
public IntegrationPluginLoader(
ILogger<IntegrationPluginLoader>? logger = null,
IServiceProvider? serviceProvider = null)
{
_logger = logger;
_serviceProvider = serviceProvider;
}
/// <summary>
@@ -26,6 +30,29 @@ public sealed class IntegrationPluginLoader
/// </summary>
public IReadOnlyList<IIntegrationConnectorPlugin> Plugins => _plugins;
/// <summary>
/// Registers a plugin instance directly.
/// Primarily used by tests and deterministic in-process setups.
/// </summary>
public void Register(IIntegrationConnectorPlugin plugin)
{
ArgumentNullException.ThrowIfNull(plugin);
_plugins.Add(plugin);
}
/// <summary>
/// Registers plugin instances directly.
/// </summary>
public void RegisterRange(IEnumerable<IIntegrationConnectorPlugin> plugins)
{
ArgumentNullException.ThrowIfNull(plugins);
foreach (var plugin in plugins)
{
Register(plugin);
}
}
/// <summary>
/// Discovers and loads integration connector plugins from the specified directory.
/// </summary>
@@ -52,7 +79,9 @@ public sealed class IntegrationPluginLoader
foreach (var pluginAssembly in result.Plugins)
{
var connectorPlugins = PluginLoader.LoadPlugins<IIntegrationConnectorPlugin>(new[] { pluginAssembly.Assembly });
var connectorPlugins = PluginLoader.LoadPlugins<IIntegrationConnectorPlugin>(
[pluginAssembly.Assembly],
_serviceProvider);
loadedPlugins.AddRange(connectorPlugins);
foreach (var plugin in connectorPlugins)
@@ -71,7 +100,7 @@ public sealed class IntegrationPluginLoader
/// </summary>
public IReadOnlyList<IIntegrationConnectorPlugin> LoadFromAssemblies(IEnumerable<Assembly> assemblies)
{
var loadedPlugins = PluginLoader.LoadPlugins<IIntegrationConnectorPlugin>(assemblies);
var loadedPlugins = PluginLoader.LoadPlugins<IIntegrationConnectorPlugin>(assemblies, _serviceProvider);
_plugins.AddRange(loadedPlugins);
return loadedPlugins;
}
@@ -92,6 +121,14 @@ public sealed class IntegrationPluginLoader
return _plugins.Where(p => p.Type == type).ToList();
}
/// <summary>
/// Gets a discovery-capable plugin by provider.
/// </summary>
public IIntegrationDiscoveryPlugin? GetDiscoveryByProvider(IntegrationProvider provider)
{
return GetByProvider(provider) as IIntegrationDiscoveryPlugin;
}
/// <summary>
/// Gets all available plugins (checking IsAvailable).
/// </summary>

View File

@@ -0,0 +1,137 @@
using StellaOps.Integrations.Contracts;
using StellaOps.Integrations.Core;
namespace StellaOps.Integrations.WebService;
/// <summary>
/// Minimal S3-compatible storage connector used for local MinIO and other health-probeable object stores.
/// </summary>
public sealed class S3CompatibleConnectorPlugin : IIntegrationConnectorPlugin
{
public const string HttpClientName = "IntegrationsObjectStorageProbe";
private static readonly HttpClient SharedHttpClient =
IntegrationHttpClientDefaults.CreateSharedClient(TimeSpan.FromSeconds(30));
private readonly IHttpClientFactory? _httpClientFactory;
private readonly TimeProvider _timeProvider;
public S3CompatibleConnectorPlugin()
: this(null, null)
{
}
public S3CompatibleConnectorPlugin(
IHttpClientFactory? httpClientFactory = null,
TimeProvider? timeProvider = null)
{
_httpClientFactory = httpClientFactory;
_timeProvider = timeProvider ?? TimeProvider.System;
}
public string Name => "s3-compatible";
public IntegrationType Type => IntegrationType.ObjectStorage;
public IntegrationProvider Provider => IntegrationProvider.S3Compatible;
public bool IsAvailable(IServiceProvider services) => true;
public async Task<TestConnectionResult> TestConnectionAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
{
var startedAt = _timeProvider.GetTimestamp();
try
{
using var request = new HttpRequestMessage(HttpMethod.Get, BuildProbeUri(config.Endpoint));
using var response = await GetHttpClient().SendAsync(request, cancellationToken);
var duration = _timeProvider.GetElapsedTime(startedAt);
if (response.IsSuccessStatusCode)
{
return new TestConnectionResult(
true,
"S3-compatible storage probe succeeded.",
new Dictionary<string, string>
{
["probeUri"] = request.RequestUri?.ToString() ?? config.Endpoint,
["statusCode"] = ((int)response.StatusCode).ToString()
},
duration);
}
return new TestConnectionResult(
false,
$"S3-compatible storage probe returned {(int)response.StatusCode} {response.ReasonPhrase}.",
new Dictionary<string, string>
{
["probeUri"] = request.RequestUri?.ToString() ?? config.Endpoint,
["statusCode"] = ((int)response.StatusCode).ToString()
},
duration);
}
catch (Exception ex)
{
return new TestConnectionResult(
false,
ex.Message,
new Dictionary<string, string>
{
["probeUri"] = BuildProbeUri(config.Endpoint).ToString()
},
_timeProvider.GetElapsedTime(startedAt));
}
}
public async Task<HealthCheckResult> CheckHealthAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
{
var startedAt = _timeProvider.GetTimestamp();
var checkedAt = _timeProvider.GetUtcNow();
try
{
using var request = new HttpRequestMessage(HttpMethod.Get, BuildProbeUri(config.Endpoint));
using var response = await GetHttpClient().SendAsync(request, cancellationToken);
var duration = _timeProvider.GetElapsedTime(startedAt);
var status = response.IsSuccessStatusCode ? HealthStatus.Healthy : HealthStatus.Unhealthy;
return new HealthCheckResult(
status,
response.IsSuccessStatusCode
? "S3-compatible storage probe is healthy."
: $"S3-compatible storage probe returned {(int)response.StatusCode} {response.ReasonPhrase}.",
new Dictionary<string, string>
{
["probeUri"] = request.RequestUri?.ToString() ?? config.Endpoint,
["statusCode"] = ((int)response.StatusCode).ToString()
},
checkedAt,
duration);
}
catch (Exception ex)
{
return new HealthCheckResult(
HealthStatus.Unhealthy,
ex.Message,
new Dictionary<string, string>
{
["probeUri"] = BuildProbeUri(config.Endpoint).ToString()
},
checkedAt,
_timeProvider.GetElapsedTime(startedAt));
}
}
private static Uri BuildProbeUri(string endpoint)
{
var endpointUri = new Uri(endpoint, UriKind.Absolute);
if (string.IsNullOrEmpty(endpointUri.AbsolutePath) || endpointUri.AbsolutePath == "/")
{
return new Uri(endpointUri, "/minio/health/live");
}
return endpointUri;
}
private HttpClient GetHttpClient()
=> _httpClientFactory?.CreateClient(HttpClientName) ?? SharedHttpClient;
}

View File

@@ -23,6 +23,7 @@ using StellaOps.Infrastructure.Postgres.Migrations;
using StellaOps.Infrastructure.Postgres.Options;
using StellaOps.Localization;
using StellaOps.Router.AspNet;
using System.Net.Http.Headers;
var builder = WebApplication.CreateBuilder(args);
// Add services
@@ -55,13 +56,28 @@ builder.Services.AddStartupMigrations(
builder.Services.AddScoped<IIntegrationRepository, PostgresIntegrationRepository>();
// HttpClient factory (used by AuthRef resolver for Vault)
builder.Services.AddHttpClient();
builder.Services.AddHttpClient(VaultAuthRefResolver.HttpClientName, client =>
{
var vaultAddr = builder.Configuration["VAULT_ADDR"] ?? "http://vault.stella-ops.local:8200";
client.BaseAddress = new Uri(vaultAddr.TrimEnd('/') + "/");
client.Timeout = TimeSpan.FromSeconds(15);
});
builder.Services.AddHttpClient(S3CompatibleConnectorPlugin.HttpClientName, client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("StellaOps", "1.0"));
});
builder.Services.AddHttpClient(FeedMirrorConnectorPluginBase.HttpClientName, client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("StellaOps", "1.0"));
});
// Plugin loader
builder.Services.AddSingleton<IntegrationPluginLoader>(sp =>
{
var logger = sp.GetRequiredService<ILogger<IntegrationPluginLoader>>();
var loader = new IntegrationPluginLoader(logger);
var loader = new IntegrationPluginLoader(logger, sp);
// Load from plugins directory
var pluginsDir = builder.Configuration.GetValue<string>("Integrations:PluginsDirectory")
@@ -97,7 +113,7 @@ builder.Services.AddSingleton(TimeProvider.System);
// Infrastructure
builder.Services.AddScoped<IIntegrationEventPublisher, LoggingEventPublisher>();
builder.Services.AddScoped<IIntegrationAuditLogger, LoggingAuditLogger>();
builder.Services.AddScoped<IAuthRefResolver, StubAuthRefResolver>();
builder.Services.AddScoped<IAuthRefResolver, VaultAuthRefResolver>();
// Core service
builder.Services.AddScoped<IntegrationService>();

View File

@@ -4,6 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
| Task ID | Status | Notes |
| --- | --- | --- |
| SPRINT_20260405_011-XPORT-HTTP | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named Vault auth-ref client registration, DI-aware plugin loading, and factory/shared lifecycle cleanup for the built-in feed/object connector plugins. |
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Integrations/StellaOps.Integrations.WebService/StellaOps.Integrations.WebService.md. |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |

View File

@@ -1,6 +1,10 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Integrations.Contracts;
using StellaOps.Integrations.Core;
using StellaOps.Integrations.Plugin.DockerRegistry;
using StellaOps.Integrations.Plugin.GitLab;
using StellaOps.Integrations.WebService;
using Xunit;
@@ -77,4 +81,127 @@ public class IntegrationPluginLoaderTests
// Assert
result.Should().BeEmpty();
}
[Trait("Category", "Unit")]
[Fact]
public void Register_WithDiscoveryPlugin_ExposesDiscoveryLookup()
{
var loader = new IntegrationPluginLoader(NullLogger<IntegrationPluginLoader>.Instance);
var plugin = new FakeDiscoveryPlugin();
loader.Register(plugin);
loader.Plugins.Should().ContainSingle();
loader.GetByProvider(IntegrationProvider.Custom).Should().BeSameAs(plugin);
loader.GetDiscoveryByProvider(IntegrationProvider.Custom).Should().BeSameAs(plugin);
}
[Trait("Category", "Unit")]
[Fact]
public void LoadFromAssemblies_WithBuiltInAssemblies_LoadsAliasProviders()
{
var services = new ServiceCollection();
services.AddHttpClient(S3CompatibleConnectorPlugin.HttpClientName);
services.AddHttpClient(FeedMirrorConnectorPluginBase.HttpClientName);
services.AddSingleton(TimeProvider.System);
var serviceProvider = services.BuildServiceProvider();
var loader = new IntegrationPluginLoader(NullLogger<IntegrationPluginLoader>.Instance, serviceProvider);
var loaded = loader.LoadFromAssemblies(
[
typeof(Program).Assembly,
typeof(GitLabConnectorPlugin).Assembly,
typeof(DockerRegistryConnectorPlugin).Assembly
]);
loaded.Should().Contain(plugin => plugin.Provider == IntegrationProvider.GitLabCi && plugin.Type == IntegrationType.CiCd);
loaded.Should().Contain(plugin => plugin.Provider == IntegrationProvider.GitLabContainerRegistry && plugin.Type == IntegrationType.Registry);
loaded.Should().Contain(plugin => plugin.Provider == IntegrationProvider.S3Compatible && plugin.Type == IntegrationType.ObjectStorage);
loaded.Should().Contain(plugin => plugin.Provider == IntegrationProvider.StellaOpsMirror && plugin.Type == IntegrationType.FeedMirror);
loaded.Should().Contain(plugin => plugin.Provider == IntegrationProvider.NvdMirror && plugin.Type == IntegrationType.FeedMirror);
loaded.Should().Contain(plugin => plugin.Provider == IntegrationProvider.OsvMirror && plugin.Type == IntegrationType.FeedMirror);
}
[Trait("Category", "Unit")]
[Fact]
public void LoadFromAssemblies_WithServiceProvider_LoadsPluginsThatRequireDependencyInjection()
{
var services = new ServiceCollection()
.AddSingleton(new LoaderDependency("di"))
.BuildServiceProvider();
var loader = new IntegrationPluginLoader(NullLogger<IntegrationPluginLoader>.Instance, services);
var loaded = loader.LoadFromAssemblies([typeof(ServiceProviderOnlyPlugin).Assembly]);
loaded.Should().ContainSingle(plugin => plugin.Provider == IntegrationProvider.Bitbucket);
}
[Trait("Category", "Unit")]
[Fact]
public void LoadFromAssemblies_WithoutServiceProvider_SkipsPluginsThatRequireDependencyInjection()
{
var loader = new IntegrationPluginLoader(NullLogger<IntegrationPluginLoader>.Instance);
var loaded = loader.LoadFromAssemblies([typeof(ServiceProviderOnlyPlugin).Assembly]);
loaded.Should().NotContain(plugin => plugin.Provider == IntegrationProvider.Bitbucket);
}
private sealed class FakeDiscoveryPlugin : IIntegrationConnectorPlugin, IIntegrationDiscoveryPlugin
{
public string Name => "fake";
public IntegrationType Type => IntegrationType.Scm;
public IntegrationProvider Provider => IntegrationProvider.Custom;
public bool IsAvailable(IServiceProvider services) => true;
public IReadOnlyList<string> SupportedResourceTypes => [IntegrationDiscoveryResourceTypes.Repositories];
public Task<TestConnectionResult> TestConnectionAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
{
return Task.FromResult(new TestConnectionResult(true, "ok", null, TimeSpan.Zero));
}
public Task<HealthCheckResult> CheckHealthAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
{
return Task.FromResult(new HealthCheckResult(HealthStatus.Healthy, "ok", null, DateTimeOffset.UtcNow, TimeSpan.Zero));
}
public Task<IReadOnlyList<DiscoveredIntegrationResource>> DiscoverAsync(
IntegrationConfig config,
string resourceType,
IReadOnlyDictionary<string, string>? filter,
CancellationToken cancellationToken = default)
{
return Task.FromResult<IReadOnlyList<DiscoveredIntegrationResource>>([]);
}
}
}
public sealed class ServiceProviderOnlyPlugin : IIntegrationConnectorPlugin
{
private readonly LoaderDependency _dependency;
public ServiceProviderOnlyPlugin(LoaderDependency dependency)
{
_dependency = dependency;
}
public string Name => $"service-provider-only-{_dependency.Name}";
public IntegrationType Type => IntegrationType.Scm;
public IntegrationProvider Provider => IntegrationProvider.Bitbucket;
public bool IsAvailable(IServiceProvider services) => true;
public Task<TestConnectionResult> TestConnectionAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
=> Task.FromResult(new TestConnectionResult(true, Name, null, TimeSpan.Zero));
public Task<HealthCheckResult> CheckHealthAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
=> Task.FromResult(new HealthCheckResult(HealthStatus.Healthy, Name, null, DateTimeOffset.UtcNow, TimeSpan.Zero));
}
public sealed record LoaderDependency(string Name);

View File

@@ -8,5 +8,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
| SPRINT_20260208_040-TESTS | DONE | Deterministic AI Code Guard run service and endpoint coverage. |
| SPRINT_20260405_011-XPORT-HTTP | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: DI-aware IntegrationPluginLoader regression coverage for service-provider-backed plugin activation. |