fix(alarms): SDK timed-unshelve timer is a no-op; commented re-ack comment is durable (#597)

Review R1 M2: since #597 the SDK timer fires at real expiry, and OnTimedUnshelve routed an operator-style
Unshelve by 'system' from both pair nodes, pre-empting the engine's AutoUnshelve. The engine owns expiry,
so the callback now routes nothing. Integration test: an engine stand-in that acts on a routed Unshelve and
expires late, plus a wrapped OnTimedUnshelve (positive control: the SDK timer fired).
Review R1 m1: Part9StateMachine.ApplyAcknowledge records a commented re-ack as AddComment (persisted and
replicated) instead of a NoOp; an uncommented re-ack is still a no-op.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 07:47:28 -04:00
parent 8a9bf23048
commit 83e9ffd27a
7 changed files with 140 additions and 71 deletions
@@ -79,6 +79,28 @@ public sealed class Part9StateMachineTests
r.Emission.ShouldBe(EmissionKind.None);
}
/// <summary>
/// Gitea #597 review m1 — a COMMENTED re-ack records its comment (as AddComment would) so the comment the
/// OPC UA event advertised is durable; the original acknowledge's ack fields are untouched.
/// </summary>
[Fact]
public void Commented_reack_records_the_comment_without_reacknowledging()
{
var active = Part9StateMachine.ApplyPredicate(Fresh(), true, T0.AddSeconds(1)).State;
var acked = Part9StateMachine.ApplyAcknowledge(active, "alice", "first look", T0.AddSeconds(2)).State;
var r = Part9StateMachine.ApplyAcknowledge(acked, "bob", "still on it", T0.AddSeconds(3));
r.Emission.ShouldBe(EmissionKind.CommentAdded);
r.State.Comments.Count.ShouldBe(2);
r.State.Comments[1].User.ShouldBe("bob");
r.State.Comments[1].Kind.ShouldBe("AddComment");
r.State.Comments[1].Text.ShouldBe("still on it");
r.State.LastAckUser.ShouldBe("alice");
r.State.LastAckComment.ShouldBe("first look");
r.State.LastAckUtc.ShouldBe(T0.AddSeconds(2));
}
/// <summary>Verifies that acknowledging without a user throws ArgumentException.</summary>
[Fact]
public void Acknowledge_without_user_throws()