fix(runtime,galaxy): #869 review round 1 — confirmation off the actor scheduler, stop/async-fault tests

- Start the ack confirmation with Task.Run + ConfigureAwait(false) so it never
  depends on the ReceiveAsync handler's ActorTaskScheduler (the PostStop
  abandon-and-revert report must run after the actor stopped).
- DriverInstanceActorAckConfirmationTests: a confirmation that faults after the
  handler returned is reported; one pending at actor stop is reported; a
  confirmed ack reports nothing (positive control).
- Deterministic tracker matching test (Raise/Clear/Retrigger/other alarm do not
  confirm; case-insensitive match does).
- AdminUI min/max on the confirm-timeout field; Galaxy.md documents the
  reconnect, UNACK_RTN-as-Clear and RPC-budget limits.

Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
Joseph Doherty
2026-11-11 13:11:49 -05:00
parent 4e492e6550
commit 77872b1d1c
5 changed files with 204 additions and 7 deletions
@@ -111,9 +111,9 @@ public sealed class GalaxyAckConfirmationTests
}
/// <summary>
/// Unconfirmed OK + Galaxy's acknowledged transition on the feed → acknowledged. Positive control: the
/// confirmation is still pending after transitions that must NOT confirm (another alarm's ack, this
/// alarm's raise) — it only completes on the matching one.
/// Unconfirmed OK + Galaxy's acknowledged transition on the feed → acknowledged, end to end through the
/// driver's feed hook (non-matching transitions are emitted first; that they do NOT confirm is pinned
/// synchronously by <see cref="Tracker_confirms_only_on_this_alarms_acknowledged_transition"/>).
/// </summary>
[Fact]
public async Task Unconfirmed_ack_is_confirmed_by_the_acknowledged_transition()
@@ -246,6 +246,33 @@ public sealed class GalaxyAckConfirmationTests
}
}
/// <summary>
/// Tracker matching rules, checked synchronously: only an Acknowledge / ActiveAcknowledged transition of the
/// SAME alarm (case-insensitive) confirms. Positive control: the matching transition does flip it.
/// </summary>
[Fact]
public void Tracker_confirms_only_on_this_alarms_acknowledged_transition()
{
var tracker = new GalaxyAckConfirmationTracker(TimeSpan.FromSeconds(30), new CapturingLogger(), "drv");
var watch = tracker.Begin(Ack(AlarmRef)[0], AlarmRef);
tracker.Arm(watch, UnconfirmedDiagnostic);
foreach (var kind in new[]
{
GalaxyAlarmTransitionKind.Raise, GalaxyAlarmTransitionKind.Clear,
GalaxyAlarmTransitionKind.Retrigger, GalaxyAlarmTransitionKind.Unspecified,
})
{
tracker.Observe(Transition(AlarmRef, kind));
}
tracker.Observe(Transition("Other01.Level.Hi", GalaxyAlarmTransitionKind.Acknowledge));
tracker.Observe(Transition("Other01.Level.Hi", GalaxyAlarmTransitionKind.ActiveAcknowledged));
watch.IsConfirmed.ShouldBeFalse("no transition so far says THIS alarm is acknowledged");
tracker.Observe(Transition(AlarmRef.ToLowerInvariant(), GalaxyAlarmTransitionKind.Acknowledge));
watch.IsConfirmed.ShouldBeTrue();
}
[Fact]
public void Options_default_to_ten_seconds_and_map_negative_to_the_default()
{