fix(audit): robust central options binding + interval clamps + doc/contract fixes (review)
This commit is contained in:
@@ -333,6 +333,24 @@ public static class ServiceCollectionExtensions
|
||||
.Bind(config.GetSection(PartitionMaintenanceSectionName));
|
||||
services.AddHostedService<AuditLogPartitionMaintenanceService>();
|
||||
|
||||
// I1 (review): bind the two central-singleton options HERE rather than in
|
||||
// AddAuditLogCentralReconciliationClient. AkkaHostedService.RegisterCentralActors
|
||||
// resolves IOptions<AuditLogPurgeOptions> / <SiteAuditReconciliationOptions>
|
||||
// via GetRequiredService when it wires the AuditLogPurgeActor +
|
||||
// SiteAuditReconciliationActor singletons; AddAuditLogCentralMaintenance is
|
||||
// ALWAYS called on the central path (the reconciliation-client helper is the
|
||||
// one that could in principle be dropped), so binding the options here means
|
||||
// the singletons get a valid IOptions even if the gRPC-client helper is not
|
||||
// wired — instead of a cryptic InvalidOperationException at GetRequiredService.
|
||||
// Defaults are fine when the section is absent (24 h purge cadence /
|
||||
// 5 min reconciliation tick); production exposes IntervalHours /
|
||||
// ReconciliationIntervalSeconds only — the test-only *Override knobs are
|
||||
// not intended to be set from config (see the options classes' remarks).
|
||||
services.AddOptions<AuditLogPurgeOptions>()
|
||||
.Bind(config.GetSection(PurgeSectionName));
|
||||
services.AddOptions<SiteAuditReconciliationOptions>()
|
||||
.Bind(config.GetSection(ReconciliationSectionName));
|
||||
|
||||
// M6 Bundle E (T8 + T9): central health snapshot — a single object
|
||||
// that owns the CentralAuditWriteFailures + AuditRedactionFailure
|
||||
// Interlocked counters AND surfaces them on
|
||||
@@ -397,19 +415,21 @@ public static class ServiceCollectionExtensions
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The production <see cref="ISiteEnumerator"/> (<see cref="SiteEnumerator"/>,
|
||||
/// wrapping the scoped <c>ISiteRepository</c>) IS registered here, alongside
|
||||
/// the <see cref="AuditLogPurgeOptions"/> + <see cref="SiteAuditReconciliationOptions"/>
|
||||
/// bindings — so the two central singletons wired in the Host
|
||||
/// (<see cref="AuditLogPurgeActor"/> + <see cref="SiteAuditReconciliationActor"/>)
|
||||
/// can resolve their collaborators + options from the same central-only
|
||||
/// helper. Keeping the enumerator + options on this central path preserves
|
||||
/// the "every <c>Add*</c> call is safe from any composition root" invariant:
|
||||
/// a site host never calls this helper, so it never registers a
|
||||
/// site-dialing enumerator.
|
||||
/// wrapping the scoped <c>ISiteRepository</c>) IS registered here — so the
|
||||
/// <see cref="SiteAuditReconciliationActor"/> singleton wired in the Host can
|
||||
/// resolve its enumerator + gRPC client from this central-only helper. Keeping
|
||||
/// the enumerator on this central path preserves the "every <c>Add*</c> call is
|
||||
/// safe from any composition root" invariant: a site host never calls this
|
||||
/// helper, so it never registers a site-dialing enumerator. The
|
||||
/// <see cref="AuditLogPurgeOptions"/> + <see cref="SiteAuditReconciliationOptions"/>
|
||||
/// bindings live in <see cref="AddAuditLogCentralMaintenance"/> instead (I1
|
||||
/// review fix) — that helper is unconditionally called on the central path, so
|
||||
/// the two maintenance singletons get a valid <c>IOptions</c> even if this
|
||||
/// gRPC-client helper is ever dropped.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="services">The service collection to register into.</param>
|
||||
/// <param name="config">Application configuration used to bind the purge + reconciliation options sections.</param>
|
||||
/// <param name="config">Application configuration used to bind the gRPC client's communication options (purge + reconciliation options are bound by <see cref="AddAuditLogCentralMaintenance"/>).</param>
|
||||
/// <returns>The same <see cref="IServiceCollection"/> for chaining.</returns>
|
||||
public static IServiceCollection AddAuditLogCentralReconciliationClient(
|
||||
this IServiceCollection services,
|
||||
@@ -425,15 +445,12 @@ public static class ServiceCollectionExtensions
|
||||
// in SiteAuditReconciliationActor / AuditLogPurgeActor).
|
||||
services.TryAddSingleton<ISiteEnumerator>(sp => new SiteEnumerator(sp));
|
||||
|
||||
// Bind the two central-singleton options to their config sections.
|
||||
// Defaults are fine when the section is absent (24 h purge cadence /
|
||||
// 5 min reconciliation tick); production exposes IntervalHours /
|
||||
// ReconciliationIntervalSeconds only — the test-only *Override knobs
|
||||
// are intentionally not bound.
|
||||
services.AddOptions<AuditLogPurgeOptions>()
|
||||
.Bind(config.GetSection(PurgeSectionName));
|
||||
services.AddOptions<SiteAuditReconciliationOptions>()
|
||||
.Bind(config.GetSection(ReconciliationSectionName));
|
||||
// I1 (review): the AuditLogPurgeOptions / SiteAuditReconciliationOptions
|
||||
// bindings moved to AddAuditLogCentralMaintenance — that helper is always
|
||||
// called on the central path, so the two maintenance singletons resolve a
|
||||
// valid IOptions even if this gRPC-client helper is ever dropped. Keep the
|
||||
// ISiteEnumerator + gRPC client registrations here (they dial sites and are
|
||||
// central-only by design).
|
||||
|
||||
// The invoker owns the per-endpoint GrpcChannel cache, so it must be a
|
||||
// singleton — a fresh invoker per resolution would leak channels.
|
||||
|
||||
Reference in New Issue
Block a user