diff --git a/archreview/plans/R2-02-resilience-config-hardening-plan.md.tasks.json b/archreview/plans/R2-02-resilience-config-hardening-plan.md.tasks.json
index 84181ada..f7ef95bf 100644
--- a/archreview/plans/R2-02-resilience-config-hardening-plan.md.tasks.json
+++ b/archreview/plans/R2-02-resilience-config-hardening-plan.md.tasks.json
@@ -7,7 +7,7 @@
{ "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": "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": "completed", "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": "completed", "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": 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": "pending", "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": "pending", "blockedBy": [7, 5] },
diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceOptions.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceOptions.cs
index ad97bca9..62629cf7 100644
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceOptions.cs
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceOptions.cs
@@ -69,8 +69,10 @@ public sealed record DriverResilienceOptions
///
/// Per-tier per-capability default policy table, per the Phase 6.1
- /// Stream A.2 specification. Retries skipped on and
- /// regardless of tier.
+ /// Stream A.2 specification. The parser enforces no-retry on
+ /// and
+ /// as an invariant (R2-02/S-8) — a JSON retryCount override on those capabilities is forced back
+ /// to 0; per-tag opt-in via is the future relaxation.
///
/// The driver tier to get defaults for.
/// The default policy dictionary for the specified tier.
diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceOptionsParser.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceOptionsParser.cs
index a91b0285..e48e6211 100644
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceOptionsParser.cs
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceOptionsParser.cs
@@ -160,6 +160,18 @@ public static class DriverResilienceOptionsParser
retry = ResiliencePolicyRanges.MaxRetryCount;
}
+ // Spec invariant (01/S-8 = 03/S12): Write and AlarmAcknowledge are write-shaped — a duplicate is
+ // not harmless (pulse coils, counter increments, Galaxy supervisory writes, alarm acks). Retries are
+ // "skipped regardless of tier" per Phase 6.1; the tier defaults are already 0, so only an override can
+ // differ — force it back to 0 here so the JSON→options funnel enforces the invariant. When per-tag
+ // opt-in via WriteIdempotentAttribute ships, this is the one line to relax.
+ if ((capability == DriverCapability.Write || capability == DriverCapability.AlarmAcknowledge) && retry > 0)
+ {
+ AppendDiagnostic(ref diagnostic,
+ $"{capability} never retries (writes are treated as non-idempotent until per-tag opt-in ships); retryCount override ignored.");
+ retry = 0;
+ }
+
var breaker = merged.BreakerFailureThreshold;
if (breaker < ResiliencePolicyRanges.MinBreakerFailureThreshold)
{
diff --git a/tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/DriverResilienceOptionsParserTests.cs b/tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/DriverResilienceOptionsParserTests.cs
index 17f28e84..65f10beb 100644
--- a/tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/DriverResilienceOptionsParserTests.cs
+++ b/tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests/Resilience/DriverResilienceOptionsParserTests.cs
@@ -319,6 +319,43 @@ public sealed class DriverResilienceOptionsParserTests
read.BreakerFailureThreshold.ShouldBe(ResiliencePolicyRanges.MinEnabledBreakerFailureThreshold);
}
+ // ---- 01/S-8 = 03/S12: write-shaped capabilities never retry ----
+
+ /// A Write retryCount override is forced to 0 with a diagnostic (writes are non-idempotent).
+ [Fact]
+ public void WriteRetryOverride_IsForcedToZero_WithDiagnostic()
+ {
+ var options = DriverResilienceOptionsParser.ParseOrDefaults(
+ DriverTier.A, "{\"capabilityPolicies\":{\"Write\":{\"retryCount\":5}}}", out var diag);
+
+ options.Resolve(DriverCapability.Write).RetryCount.ShouldBe(0);
+ diag.ShouldNotBeNull();
+ diag.ShouldContain("Write never retries");
+ }
+
+ /// An AlarmAcknowledge retryCount override is forced to 0 with a diagnostic.
+ [Fact]
+ public void AlarmAcknowledgeRetryOverride_IsForcedToZero_WithDiagnostic()
+ {
+ var options = DriverResilienceOptionsParser.ParseOrDefaults(
+ DriverTier.A, "{\"capabilityPolicies\":{\"AlarmAcknowledge\":{\"retryCount\":3}}}", out var diag);
+
+ options.Resolve(DriverCapability.AlarmAcknowledge).RetryCount.ShouldBe(0);
+ diag.ShouldNotBeNull();
+ diag.ShouldContain("AlarmAcknowledge never retries");
+ }
+
+ /// A Read (idempotent) retryCount override is still honored — the invariant is write-shaped only.
+ [Fact]
+ public void ReadRetryOverride_StillHonored()
+ {
+ var options = DriverResilienceOptionsParser.ParseOrDefaults(
+ DriverTier.A, "{\"capabilityPolicies\":{\"Read\":{\"retryCount\":5}}}", out var diag);
+
+ options.Resolve(DriverCapability.Read).RetryCount.ShouldBe(5);
+ diag.ShouldBeNull();
+ }
+
/// In-range overrides pass through untouched with no diagnostic.
[Fact]
public void InRangeOverrides_PassThrough_NoDiagnostic()