Merge review R1 fixes: removed driver's resilience tracker released; SDK timed-unshelve no-op (engine owns expiry); commented re-ack stored as AddComment; native-ack Primary verdict published from start; drain gate SharesRowsWithPeer; malformed cache bytes don't adopt the revision; HostAbortedException passthrough; ClusterRoleInfo seed filtered
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -268,19 +268,15 @@ public sealed class AlarmCommandRouterTests : IDisposable
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>OnTimedUnshelve fires with the SDK's system context (no session, no user identity) —
|
||||
/// the real SDK path when a TimedShelve duration expires. The gate must NOT veto: the result must be
|
||||
/// Good, the router must be invoked exactly once with Operation == "Unshelve", and no UnshelveAtUtc is
|
||||
/// carried.
|
||||
/// <para>
|
||||
/// Regression (OpcUaServer-007): the routed command's <see cref="AlarmCommand.User"/> must be a
|
||||
/// NON-EMPTY system user (<c>"system"</c>). The engine's <c>Part9StateMachine.ApplyUnshelve</c> rejects
|
||||
/// a null/whitespace user with <c>ArgumentException("User required.")</c>; that exception is swallowed
|
||||
/// downstream, so an empty user made the timed auto-unshelve SILENTLY no-op — a TimedShelve would never
|
||||
/// auto-expire. Asserting a non-empty user here pins the value the state machine requires.
|
||||
/// </para></summary>
|
||||
/// <summary>OnTimedUnshelve fires with the SDK's system context (no session, no user identity) when the
|
||||
/// SDK's own TimedShelve timer expires. It must return Good and route NOTHING (review R1 M2, Gitea #597):
|
||||
/// the scripted engine owns timed-shelve expiry (its shelving check records an "AutoUnshelve" by
|
||||
/// "system"), and since #597 arms the SDK timer for the real remaining time, an "Unshelve" routed from
|
||||
/// here pre-empted that check on every expiry, from both pair nodes, as an operator-style unshelve.
|
||||
/// <para>Positive control: on the same condition and router, a client Unshelve (OnShelve with
|
||||
/// <c>shelving:false</c>) DOES route — so the empty capture is not a disconnected router.</para></summary>
|
||||
[Fact]
|
||||
public async Task OnTimedUnshelve_with_system_context_returns_good_and_routes_unshelve()
|
||||
public async Task OnTimedUnshelve_with_system_context_returns_good_and_routes_nothing()
|
||||
{
|
||||
var (host, server) = await BootAsync();
|
||||
var nm = server.NodeManager!;
|
||||
@@ -299,14 +295,13 @@ public sealed class AlarmCommandRouterTests : IDisposable
|
||||
var result = condition.OnTimedUnshelve!(ctx, condition);
|
||||
|
||||
result.ShouldBe(ServiceResult.Good);
|
||||
captured.Count.ShouldBe(1);
|
||||
var cmd = captured[0];
|
||||
cmd.AlarmId.ShouldBe("alm-tu"); // == ScriptedAlarmId / condition NodeId identifier
|
||||
cmd.Operation.ShouldBe("Unshelve");
|
||||
// System-initiated (no client principal), but the user MUST be non-empty so ApplyUnshelve accepts it.
|
||||
cmd.User.ShouldNotBeNullOrWhiteSpace();
|
||||
cmd.User.ShouldBe("system");
|
||||
cmd.UnshelveAtUtc.ShouldBeNull();
|
||||
captured.ShouldBeEmpty();
|
||||
|
||||
// Positive control: the router is live for a genuine operator Unshelve on the same condition.
|
||||
var operatorCtx = SessionContext(server, "grace", OpcUaDataPlaneRoles.AlarmAck);
|
||||
condition.OnShelve!(operatorCtx, condition, shelving: false, oneShot: false, shelvingTime: 0.0)
|
||||
.ShouldBe(ServiceResult.Good);
|
||||
captured.Select(c => c.Operation).ShouldBe(["Unshelve"]);
|
||||
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user