feat(client): AddComment + comment CLI, drop Galaxy-era alarm fallback, serialize CLI tests (#598); Client.Shared off obsolete sync SDK APIs (#621)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 06:53:57 -04:00
parent 3baa332d6b
commit 48bd266e19
18 changed files with 363 additions and 211 deletions
@@ -0,0 +1,39 @@
using Opc.Ua;
namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Adapters;
/// <summary>
/// The one <see cref="ITelemetryContext" /> the shared client hands the SDK's non-obsolete (telemetry-taking)
/// overloads. An empty builder keeps SDK internals off any logging provider; the client logs through Serilog.
/// </summary>
internal static class ClientTelemetry
{
private static readonly Lazy<ITelemetryContext> Instance = new(() => DefaultTelemetry.Create(_ => { }));
/// <summary>Gets the shared telemetry context.</summary>
public static ITelemetryContext Context => Instance.Value;
}
/// <summary>
/// Bounded sync-over-async for the <c>Dispose</c> paths, which cannot await. Runs on the thread pool so a
/// caller on a synchronization context (the Avalonia UI thread) cannot deadlock, and waits at most
/// <see cref="Timeout" /> so a dead server cannot hang teardown.
/// </summary>
internal static class DisposeBridge
{
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(5);
/// <summary>Runs <paramref name="work" /> to completion (or the timeout), swallowing every failure.</summary>
public static void Run(Func<CancellationToken, Task> work)
{
using var cts = new CancellationTokenSource(Timeout);
try
{
Task.Run(() => work(cts.Token), cts.Token).Wait(Timeout);
}
catch
{
// Teardown is best-effort, exactly as the synchronous Close/Delete it replaces.
}
}
}