feat(audit): wire AddAuditEmission into 9 services (AUDIT-002)

- Wire StellaOps.Audit.Emission DI in: Authority, Policy, Release-Orchestrator,
  EvidenceLocker, Notify, Scanner, Scheduler, Integrations, Platform
- Add AuditEmission__TimelineBaseUrl to compose defaults
- Endpoint filter annotation deferred to follow-up pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-08 16:20:39 +03:00
parent 65106afe4c
commit f5a9f874d0
34 changed files with 1865 additions and 24 deletions

View File

@@ -0,0 +1,108 @@
using System;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using StellaOps.Cryptography;
namespace StellaOps.Cryptography.DependencyInjection;
/// <summary>
/// Extension methods for registering tenant-aware crypto provider resolution.
/// </summary>
public static class TenantAwareCryptoProviderRegistryExtensions
{
/// <summary>
/// Decorates the existing <see cref="ICryptoProviderRegistry"/> with tenant-aware resolution.
/// <para>
/// When a tenant context is available and the tenant has configured crypto provider preferences,
/// the decorated registry will prefer the tenant's chosen providers. Falls back to the default
/// ordering when no tenant context exists or no preferences are set.
/// </para>
/// <para>
/// Prerequisites:
/// <list type="bullet">
/// <item><see cref="ICryptoProviderRegistry"/> must already be registered (via <c>AddStellaOpsCrypto</c>).</item>
/// <item><see cref="ITenantCryptoPreferenceProvider"/> must be registered by the caller.</item>
/// </list>
/// </para>
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="tenantIdAccessorFactory">
/// Factory that creates a function returning the current tenant ID (or null when no tenant context).
/// Example: <c>sp => () => sp.GetService&lt;IStellaOpsTenantAccessor&gt;()?.TenantId</c>
/// </param>
/// <param name="cacheTtl">
/// How long tenant preferences are cached before refresh. Default: 5 minutes.
/// </param>
/// <returns>The service collection.</returns>
public static IServiceCollection AddTenantAwareCryptoResolution(
this IServiceCollection services,
Func<IServiceProvider, Func<string?>> tenantIdAccessorFactory,
TimeSpan? cacheTtl = null)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(tenantIdAccessorFactory);
// Manual decorator pattern: find the existing ICryptoProviderRegistry registration,
// replace it with a factory that wraps the original in TenantAwareCryptoProviderRegistry.
var innerDescriptor = services.LastOrDefault(d => d.ServiceType == typeof(ICryptoProviderRegistry));
if (innerDescriptor is null)
{
throw new InvalidOperationException(
"ICryptoProviderRegistry is not registered. Call AddStellaOpsCrypto() before AddTenantAwareCryptoResolution().");
}
services.Remove(innerDescriptor);
services.AddSingleton<ICryptoProviderRegistry>(sp =>
{
// Resolve the inner (original) registry
var innerRegistry = ResolveInner(sp, innerDescriptor);
var preferenceProvider = sp.GetService<ITenantCryptoPreferenceProvider>();
if (preferenceProvider is null)
{
// No preference provider registered; tenant-aware resolution is a no-op.
// This is expected in CLI / background worker scenarios.
return innerRegistry;
}
var tenantIdAccessor = tenantIdAccessorFactory(sp);
var timeProvider = sp.GetService<TimeProvider>() ?? TimeProvider.System;
var logger = sp.GetRequiredService<ILoggerFactory>()
.CreateLogger<TenantAwareCryptoProviderRegistry>();
return new TenantAwareCryptoProviderRegistry(
innerRegistry,
preferenceProvider,
tenantIdAccessor,
timeProvider,
logger,
cacheTtl);
});
return services;
}
private static ICryptoProviderRegistry ResolveInner(IServiceProvider sp, ServiceDescriptor descriptor)
{
if (descriptor.ImplementationInstance is ICryptoProviderRegistry instance)
{
return instance;
}
if (descriptor.ImplementationFactory is not null)
{
return (ICryptoProviderRegistry)descriptor.ImplementationFactory(sp);
}
if (descriptor.ImplementationType is not null)
{
return (ICryptoProviderRegistry)ActivatorUtilities.CreateInstance(sp, descriptor.ImplementationType);
}
throw new InvalidOperationException(
$"Cannot resolve inner ICryptoProviderRegistry from descriptor: {descriptor}");
}
}