fix(r2-02): factory-seam guard — hostile ResilienceConfig cannot brick the spawn path (01/S-6 wiring proof)
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
{ "id": 1, "subject": "Add ResiliencePolicyRanges constants to Core.Abstractions (single source of truth for parser/builder/AdminUI, derived from Polly.Core 8.6.6 validation)", "status": "completed", "blockedBy": [] },
|
{ "id": 1, "subject": "Add ResiliencePolicyRanges constants to Core.Abstractions (single source of truth for parser/builder/AdminUI, derived from Polly.Core 8.6.6 validation)", "status": "completed", "blockedBy": [] },
|
||||||
{ "id": 2, "subject": "S-6 parser: clamp timeout/retry/breaker overrides with appending diagnostics (timeout<=0 -> tier default; breaker==1 -> 2; caps) + 8 parser tests", "status": "completed", "blockedBy": [0, 1] },
|
{ "id": 2, "subject": "S-6 parser: clamp timeout/retry/breaker overrides with appending diagnostics (timeout<=0 -> tier default; breaker==1 -> 2; caps) + 8 parser tests", "status": "completed", "blockedBy": [0, 1] },
|
||||||
{ "id": 3, "subject": "S-6 builder: belt-and-braces clamp in Build so pipeline construction can never throw ValidationException + Build_NeverThrows_ForHostileDirectOptions (turns task 0 green)", "status": "completed", "blockedBy": [0, 1] },
|
{ "id": 3, "subject": "S-6 builder: belt-and-braces clamp in Build so pipeline construction can never throw ValidationException + Build_NeverThrows_ForHostileDirectOptions (turns task 0 green)", "status": "completed", "blockedBy": [0, 1] },
|
||||||
{ "id": 4, "subject": "S-6 production-wiring guard: DriverCapabilityInvokerFactory.Create with hostile JSON yields a working invoker + logged clamp diagnostic; re-run ResilienceInvokerFactoryRegistrationTests", "status": "pending", "blockedBy": [2, 3] },
|
{ "id": 4, "subject": "S-6 production-wiring guard: DriverCapabilityInvokerFactory.Create with hostile JSON yields a working invoker + logged clamp diagnostic; re-run ResilienceInvokerFactoryRegistrationTests", "status": "completed", "blockedBy": [2, 3] },
|
||||||
{ "id": 5, "subject": "S-6 AdminUI: ResilienceFormModel.Validate() range messages (via ResiliencePolicyRanges through the transitive Core.Abstractions ref) + warning block in DriverResilienceSection", "status": "pending", "blockedBy": [1] },
|
{ "id": 5, "subject": "S-6 AdminUI: ResilienceFormModel.Validate() range messages (via ResiliencePolicyRanges through the transitive Core.Abstractions ref) + warning block in DriverResilienceSection", "status": "pending", "blockedBy": [1] },
|
||||||
{ "id": 6, "subject": "S-8 parser layer: force Write/AlarmAcknowledge retryCount overrides to 0 with diagnostic (spec invariant, honest dead-knob surfacing) + tests + GetTierDefaults doc", "status": "pending", "blockedBy": [2] },
|
{ "id": 6, "subject": "S-8 parser layer: force Write/AlarmAcknowledge retryCount overrides to 0 with diagnostic (spec invariant, honest dead-knob surfacing) + tests + GetTierDefaults doc", "status": "pending", "blockedBy": [2] },
|
||||||
{ "id": 7, "subject": "S-8 dispatch layer: flip HandleWriteAsync to isIdempotent:false — RED the DriverInstanceActorResilienceWiringTests expectation first, then flip; full Runtime suite green", "status": "pending", "blockedBy": [6] },
|
{ "id": 7, "subject": "S-8 dispatch layer: flip HandleWriteAsync to isIdempotent:false — RED the DriverInstanceActorResilienceWiringTests expectation first, then flip; full Runtime suite green", "status": "pending", "blockedBy": [6] },
|
||||||
|
|||||||
+25
@@ -136,6 +136,31 @@ public sealed class DriverCapabilityInvokerFactoryTests
|
|||||||
logger.Entries.ShouldContain(e => e.Level == LogLevel.Warning && e.Message.Contains("resilience config", StringComparison.OrdinalIgnoreCase));
|
logger.Entries.ShouldContain(e => e.Level == LogLevel.Warning && e.Message.Contains("resilience config", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 01/S-6 production-wiring proof: the exact <c>DriverHostActor.SpawnChild</c> path
|
||||||
|
/// (<c>factory.Create(id, type, hostileJson)</c>) must yield a WORKING invoker whose capability
|
||||||
|
/// call succeeds, and must log the clamp diagnostic as a warning — a hostile ResilienceConfig can
|
||||||
|
/// never brick the spawn path.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_with_out_of_range_config_yields_working_invoker()
|
||||||
|
{
|
||||||
|
var logger = new CapturingLogger();
|
||||||
|
var factory = MakeFactory(new DriverResiliencePipelineBuilder(), logger: logger);
|
||||||
|
|
||||||
|
var invoker = factory.Create("i1", "Modbus",
|
||||||
|
resilienceConfigJson: "{\"capabilityPolicies\":{\"Subscribe\":{\"timeoutSeconds\":0,\"breakerFailureThreshold\":1}}}");
|
||||||
|
|
||||||
|
var result = await invoker.ExecuteAsync(
|
||||||
|
DriverCapability.Subscribe, "host-1", _ => ValueTask.FromResult(99), CancellationToken.None);
|
||||||
|
|
||||||
|
result.ShouldBe(99, "a hostile ResilienceConfig must not brick the Subscribe capability call");
|
||||||
|
logger.Entries.ShouldContain(e =>
|
||||||
|
e.Level == LogLevel.Warning &&
|
||||||
|
e.Message.Contains("resilience config", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
(e.Message.Contains("timeoutSeconds") || e.Message.Contains("breakerFailureThreshold")));
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class CapturingLogger : ILogger
|
private sealed class CapturingLogger : ILogger
|
||||||
{
|
{
|
||||||
public List<(LogLevel Level, string Message)> Entries { get; } = new();
|
public List<(LogLevel Level, string Message)> Entries { get; } = new();
|
||||||
|
|||||||
Reference in New Issue
Block a user