fix(abcip): report ALMD transitions with ConditionId = RawPath and a Kind (#641)

Found while verifying item 4 of #641. The host routes a native alarm transition
on an exact ConditionId == RawPath match (DriverHostActor.ForwardNativeAlarm),
but AbCipAlarmProjection emitted "{ref}#active" and no Kind, so every ALMD
raise/clear was dropped as "no alarm condition" — and even if routed, an
Unspecified kind projects no state change. It now reports the subscribed
reference (the RawPath) and Raise/Clear. A host test pins that a decorated id
is dropped.

FOCAS has the same id shape plus a device filter keyed by host address (a
RawPath subscription polls nothing); it needs a mapping decision and is
documented in docs/drivers/FOCAS.md rather than patched here.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 17:08:47 -04:00
parent 3fe6cbb905
commit 64d2514e2a
5 changed files with 57 additions and 4 deletions
@@ -189,6 +189,21 @@ public sealed class AbCipAlarmProjectionTests
await drv.UnsubscribeAlarmsAsync(handle, CancellationToken.None);
await drv.ShutdownAsync(CancellationToken.None);
// Gitea #641 (item 4): the host routes a native transition on ConditionId == the subscribed RawPath and
// projects it from Kind. The projection used to emit "{ref}#active" with an Unspecified kind, so every
// ALMD transition was dropped as "no alarm condition" — and would have projected no state change even if
// it had routed.
AlarmEventArgs raised, cleared;
lock (events)
{
raised = events.First(e => e.Message.Contains("raised"));
cleared = events.First(e => e.Message.Contains("cleared"));
}
raised.ConditionId.ShouldBe("HighTemp");
raised.Kind.ShouldBe(AlarmTransitionKind.Raise);
cleared.ConditionId.ShouldBe("HighTemp");
cleared.Kind.ShouldBe(AlarmTransitionKind.Clear);
}
/// <summary>Verifies that unsubscribing stops the alarm poll loop.</summary>