refactor(r2-02): delete the dead bulkhead knob — options + parser (01/U-6); stored JSON keys become ignored unknowns

This commit is contained in:
Joseph Doherty
2026-07-13 10:08:27 -04:00
parent 99b424240a
commit 56854139c9
4 changed files with 16 additions and 27 deletions
@@ -11,7 +11,7 @@
{ "id": 7, "subject": "S-8 dispatch layer: flip HandleWriteAsync to isIdempotent:false — RED the DriverInstanceActorResilienceWiringTests expectation first, then flip; full Runtime suite green", "status": "completed", "blockedBy": [6] },
{ "id": 8, "subject": "S-8/P-4 invoker: cache the no-retry options snapshot once per invoker + add RecordCallStart/Complete tracker accounting to the non-idempotent arm + 2 CapabilityInvokerTests", "status": "completed", "blockedBy": [7] },
{ "id": 9, "subject": "S-8 docs + form: fix WriteIdempotentAttribute's false 'reads via reflection' claim; note the non-idempotent production default; disable Write/Ack retry cells in the razor", "status": "completed", "blockedBy": [7, 5] },
{ "id": 10, "subject": "U-6 delete (Core): remove BulkheadMaxConcurrent/MaxQueue from DriverResilienceOptions + parser shape/merge/doc-example; add BulkheadKeys_InStoredJson_AreIgnored (migration-free contract)", "status": "pending", "blockedBy": [2, 6] },
{ "id": 10, "subject": "U-6 delete (Core): remove BulkheadMaxConcurrent/MaxQueue from DriverResilienceOptions + parser shape/merge/doc-example; add BulkheadKeys_InStoredJson_AreIgnored (migration-free contract)", "status": "completed", "blockedBy": [2, 6] },
{ "id": 11, "subject": "U-6 delete (AdminUI): strip bulkhead fields from ResilienceFormModel + the two razor inputs; update ResilienceFormModelTests", "status": "pending", "blockedBy": [5, 10] },
{ "id": 12, "subject": "U-6 doc sweep (seam xmldocs, OTOPCUA0001 message + analyzer tests, operator docs; leave migrations/WedgeDetector/historical plans) + Options_properties_are_exactly_the_pipeline_wired_set knob-inertness guard", "status": "pending", "blockedBy": [10, 11] },
{ "id": 13, "subject": "C-7 RED tests: unknown top-level key / unknown capability / unknown per-policy field survive round-trip; malformed JSON sets ParseFailed and ToJson returns the original text", "status": "pending", "blockedBy": [11] },
@@ -19,15 +19,6 @@ public sealed record DriverResilienceOptions
public IReadOnlyDictionary<DriverCapability, CapabilityPolicy> CapabilityPolicies { get; init; }
= new Dictionary<DriverCapability, CapabilityPolicy>();
/// <summary>Bulkhead (max concurrent in-flight calls) for every capability. Default 32.</summary>
public int BulkheadMaxConcurrent { get; init; } = 32;
/// <summary>
/// Bulkhead queue depth. Zero = no queueing; overflow fails fast with
/// <c>BulkheadRejectedException</c>. Default 64.
/// </summary>
public int BulkheadMaxQueue { get; init; } = 64;
/// <summary>
/// Periodic scheduled recycle interval for Tier C out-of-process hosts, in seconds.
/// Null (the default) = no scheduled recycle; the driver's Host process keeps running
@@ -13,8 +13,6 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Resilience;
/// <para>Example JSON shape per Phase 6.1 Stream A.2:</para>
/// <code>
/// {
/// "bulkheadMaxConcurrent": 16,
/// "bulkheadMaxQueue": 64,
/// "capabilityPolicies": {
/// "Read": { "timeoutSeconds": 5, "retryCount": 5, "breakerFailureThreshold": 3 },
/// "Write": { "timeoutSeconds": 5, "retryCount": 0, "breakerFailureThreshold": 5 }
@@ -114,8 +112,6 @@ public static class DriverResilienceOptionsParser
{
Tier = tier,
CapabilityPolicies = merged,
BulkheadMaxConcurrent = shape.BulkheadMaxConcurrent ?? baseOptions.BulkheadMaxConcurrent,
BulkheadMaxQueue = shape.BulkheadMaxQueue ?? baseOptions.BulkheadMaxQueue,
RecycleIntervalSeconds = recycleIntervalSeconds,
};
}
@@ -200,10 +196,6 @@ public static class DriverResilienceOptionsParser
private sealed class ResilienceConfigShape
{
/// <summary>Gets or sets the maximum concurrent bulkhead requests.</summary>
public int? BulkheadMaxConcurrent { get; set; }
/// <summary>Gets or sets the maximum bulkhead queue size.</summary>
public int? BulkheadMaxQueue { get; set; }
/// <summary>Gets or sets the scheduled recycle interval in seconds.</summary>
public int? RecycleIntervalSeconds { get; set; }
/// <summary>Gets or sets the per-capability resilience policies.</summary>
@@ -98,18 +98,23 @@ public sealed class DriverResilienceOptionsParserTests
read.BreakerFailureThreshold.ShouldBe(tierDefault.BreakerFailureThreshold);
}
/// <summary>Verifies that bulkhead overrides are honored.</summary>
/// <summary>
/// 01/U-6: the deleted bulkhead knob's keys in stored JSON become ignored unknown keys — a
/// config carrying them parses clean with no diagnostic (migration-free contract).
/// </summary>
[Fact]
public void BulkheadOverrides_AreHonored()
public void BulkheadKeys_InStoredJson_AreIgnored()
{
var json = """
{ "bulkheadMaxConcurrent": 100, "bulkheadMaxQueue": 500 }
""";
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.B, json, out _);
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.B, json, out var diag);
options.BulkheadMaxConcurrent.ShouldBe(100);
options.BulkheadMaxQueue.ShouldBe(500);
diag.ShouldBeNull();
// Known capabilities untouched — the stale bulkhead keys are simply ignored.
options.Resolve(DriverCapability.Read).ShouldBe(
DriverResilienceOptions.GetTierDefaults(DriverTier.B)[DriverCapability.Read]);
}
/// <summary>Verifies that unknown capability surfaces in diagnostic but does not fail.</summary>
@@ -133,17 +138,18 @@ public sealed class DriverResilienceOptionsParserTests
DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Read]);
}
/// <summary>Verifies that property names are case insensitive.</summary>
/// <summary>Verifies that top-level property names are case insensitive.</summary>
[Fact]
public void PropertyNames_AreCaseInsensitive()
{
var json = """
{ "BULKHEADMAXCONCURRENT": 42 }
{ "RECYCLEINTERVALSECONDS": 3600 }
""";
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out _);
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.C, json, out var diag);
options.BulkheadMaxConcurrent.ShouldBe(42);
diag.ShouldBeNull();
options.RecycleIntervalSeconds.ShouldBe(3600);
}
/// <summary>Verifies that capability name is case insensitive.</summary>