feat(siteruntime): WaitForAsync/WaitResult + quality-gated WaitAsync (spec §3, §4.2)

This commit is contained in:
Joseph Doherty
2026-06-17 09:05:12 -04:00
parent 0f6da8a106
commit 61048a4ecf
7 changed files with 463 additions and 23 deletions
@@ -27,6 +27,16 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Instance;
/// </param>
/// <param name="Timeout">How long to wait before self-evicting with a timeout reply.</param>
/// <param name="OccurredAtUtc">When the request was issued (UTC).</param>
/// <param name="RequireGoodQuality">
/// Quality-gated ("Good"-only) mode (spec §4.2): when <see langword="true"/>, a
/// match additionally requires the attribute quality to be exactly
/// <c>"Good"</c> (<see cref="System.StringComparison.Ordinal"/>) — a value that
/// reaches the target / satisfies the predicate at Bad/Uncertain quality is NOT a
/// match and the waiter stays pending until the value satisfies the test at Good
/// quality (or times out). Defaults to <see langword="false"/> (quality-agnostic:
/// the match tests the value only). Trailing/defaulted so existing positional
/// constructions compile unchanged.
/// </param>
public record WaitForAttributeRequest(
string CorrelationId,
string InstanceName,
@@ -34,7 +44,8 @@ public record WaitForAttributeRequest(
string? TargetValueEncoded,
Func<object?, bool>? Predicate,
TimeSpan Timeout,
DateTimeOffset OccurredAtUtc);
DateTimeOffset OccurredAtUtc,
bool RequireGoodQuality = false);
/// <summary>
/// Reply to a <see cref="WaitForAttributeRequest"/>. Exactly one of
@@ -0,0 +1,21 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Types;
/// <summary>
/// Rich result of an <c>Attributes.WaitForAsync</c> wait (spec §3) — the full
/// outcome of waiting for an attribute to reach a value / satisfy a predicate /
/// change at all, bounded by a timeout. The <c>Attributes.WaitAsync</c> helpers
/// surface only <see cref="Matched"/>; <c>WaitForAsync</c> returns this struct so
/// a script can also read the matched <see cref="Value"/>, its <see cref="Quality"/>,
/// and distinguish a genuine timeout (<see cref="TimedOut"/>) from a non-match.
/// </summary>
/// <param name="Matched">
/// <see langword="true"/> when the attribute reached the target / satisfied the
/// predicate within the timeout (and, in quality-gated mode, at "Good" quality).
/// </param>
/// <param name="Value">The matched value; <see langword="null"/> on timeout / error.</param>
/// <param name="Quality">
/// The attribute quality at match time; <see langword="null"/> on the non-match
/// paths (timeout / error / cap-exceeded).
/// </param>
/// <param name="TimedOut"><see langword="true"/> when the timeout fired before a match.</param>
public readonly record struct WaitResult(bool Matched, object? Value, string? Quality, bool TimedOut);