fix(r2-02): range-clamp ResilienceConfig overrides in the parser (01/S-6) — timeout/retry/breaker clamped with diagnostics, never brick

This commit is contained in:
Joseph Doherty
2026-07-13 09:53:28 -04:00
parent 4be13429b6
commit 294d74c1de
3 changed files with 184 additions and 5 deletions
@@ -83,15 +83,16 @@ public static class DriverResilienceOptionsParser
{
if (!Enum.TryParse<DriverCapability>(capName, ignoreCase: true, out var capability))
{
parseDiagnostic ??= $"Unknown capability '{capName}' in ResilienceConfig; skipped.";
AppendDiagnostic(ref parseDiagnostic, $"Unknown capability '{capName}' in ResilienceConfig; skipped.");
continue;
}
var basePolicy = merged[capability];
merged[capability] = new CapabilityPolicy(
var mergedPolicy = new CapabilityPolicy(
TimeoutSeconds: overridePolicy.TimeoutSeconds ?? basePolicy.TimeoutSeconds,
RetryCount: overridePolicy.RetryCount ?? basePolicy.RetryCount,
BreakerFailureThreshold: overridePolicy.BreakerFailureThreshold ?? basePolicy.BreakerFailureThreshold);
merged[capability] = ClampPolicy(capability, mergedPolicy, basePolicy, ref parseDiagnostic);
}
}
@@ -102,9 +103,9 @@ public static class DriverResilienceOptionsParser
if (shape.RecycleIntervalSeconds is int secs)
{
if (secs <= 0)
parseDiagnostic ??= $"RecycleIntervalSeconds must be positive; got {secs} — ignoring.";
AppendDiagnostic(ref parseDiagnostic, $"RecycleIntervalSeconds must be positive; got {secs} — ignoring.");
else if (tier != DriverTier.C)
parseDiagnostic ??= $"RecycleIntervalSeconds is Tier C only; Tier {tier} driver will not scheduled-recycle.";
AppendDiagnostic(ref parseDiagnostic, $"RecycleIntervalSeconds is Tier C only; Tier {tier} driver will not scheduled-recycle.");
else
recycleIntervalSeconds = secs;
}
@@ -119,6 +120,72 @@ public static class DriverResilienceOptionsParser
};
}
/// <summary>
/// Clamp a merged per-capability override to the legal <see cref="ResiliencePolicyRanges"/> so a
/// downstream Polly pipeline build can never throw <c>ValidationException</c> (01/S-6). Each clamp
/// appends a diagnostic naming the field + what happened. A non-positive timeout snaps to the
/// capability's <paramref name="tierDefault"/> (a nonsense value, not merely an extreme — snapping
/// to 1 s could be a 30× surprise on a Discover policy); everything else clamps to the nearest legal
/// value. Preserves maximum operator intent while never bricking.
/// </summary>
private static CapabilityPolicy ClampPolicy(
DriverCapability capability,
CapabilityPolicy merged,
CapabilityPolicy tierDefault,
ref string? diagnostic)
{
var timeout = merged.TimeoutSeconds;
if (timeout <= 0)
{
AppendDiagnostic(ref diagnostic,
$"{capability}.timeoutSeconds {timeout} is non-positive; using the tier default ({tierDefault.TimeoutSeconds}s).");
timeout = tierDefault.TimeoutSeconds;
}
else if (timeout > ResiliencePolicyRanges.MaxTimeoutSeconds)
{
AppendDiagnostic(ref diagnostic,
$"{capability}.timeoutSeconds {timeout} exceeds the max ({ResiliencePolicyRanges.MaxTimeoutSeconds}s); clamped.");
timeout = ResiliencePolicyRanges.MaxTimeoutSeconds;
}
var retry = merged.RetryCount;
if (retry < ResiliencePolicyRanges.MinRetryCount)
{
AppendDiagnostic(ref diagnostic, $"{capability}.retryCount {retry} is negative; clamped to {ResiliencePolicyRanges.MinRetryCount}.");
retry = ResiliencePolicyRanges.MinRetryCount;
}
else if (retry > ResiliencePolicyRanges.MaxRetryCount)
{
AppendDiagnostic(ref diagnostic, $"{capability}.retryCount {retry} exceeds the cap ({ResiliencePolicyRanges.MaxRetryCount}); clamped.");
retry = ResiliencePolicyRanges.MaxRetryCount;
}
var breaker = merged.BreakerFailureThreshold;
if (breaker < ResiliencePolicyRanges.MinBreakerFailureThreshold)
{
AppendDiagnostic(ref diagnostic, $"{capability}.breakerFailureThreshold {breaker} is negative; breaker disabled.");
breaker = ResiliencePolicyRanges.MinBreakerFailureThreshold;
}
else if (breaker == 1)
{
AppendDiagnostic(ref diagnostic,
$"{capability}.breakerFailureThreshold 1 is below Polly's minimum throughput; raised to {ResiliencePolicyRanges.MinEnabledBreakerFailureThreshold} (closest to open-on-first-failure intent).");
breaker = ResiliencePolicyRanges.MinEnabledBreakerFailureThreshold;
}
else if (breaker > ResiliencePolicyRanges.MaxBreakerFailureThreshold)
{
AppendDiagnostic(ref diagnostic,
$"{capability}.breakerFailureThreshold {breaker} exceeds the cap ({ResiliencePolicyRanges.MaxBreakerFailureThreshold}); clamped.");
breaker = ResiliencePolicyRanges.MaxBreakerFailureThreshold;
}
return merged with { TimeoutSeconds = timeout, RetryCount = retry, BreakerFailureThreshold = breaker };
}
/// <summary>Append a diagnostic to the running channel (joined with "; ") so multiple clamps all surface.</summary>
private static void AppendDiagnostic(ref string? diagnostic, string message)
=> diagnostic = diagnostic is null ? message : $"{diagnostic}; {message}";
private sealed class ResilienceConfigShape
{
/// <summary>Gets or sets the maximum concurrent bulkhead requests.</summary>