fix: address final ackmsg-as-operator review findings
ci / nightly-windev (push) Has been skipped
ci / java (push) Successful in 2m0s
ci / windows-x86 (push) Successful in 2m11s
ci / portable (push) Failing after 2m21s

- AlarmAckWriter: classify AuthenticateUser and AddItem/Advise/Write
  failures by HRESULT for any interop exception (ArgumentException for
  E_INVALIDARG, UnauthorizedAccessException, COMException); server-gone
  HRESULTs at AuthenticateUser tear down the registration and report
  WriteRejected.
- Bound the ack item cache with a 256-entry LRU (eviction best-effort
  UnAdvise+RemoveItem).
- After an Unconfirmed ack, drop and re-add the item before the next
  write so a late OnWriteComplete on the old handle is not credited.
- Pass StaRuntime.PumpPendingMessages to the ack writer through a
  pump-aware alarm handler factory so long completion waits refresh the
  STA watchdog heartbeat.
- Rust CLI: --operator is required for acknowledge-alarm.
- Docs: full ack status table, credential-oracle trade-off, upgrade note.

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
This commit is contained in:
Joseph Doherty
2026-10-19 19:58:24 -04:00
parent 1998ee3e20
commit e7f56622c1
9 changed files with 332 additions and 31 deletions
@@ -48,7 +48,10 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
private readonly IMxAccessEventSink eventSink;
private readonly MxAccessEventQueue eventQueue;
private readonly StaRuntime staRuntime;
private readonly Func<MxAccessEventQueue, Action, IMxAccessComObjectFactory, IAlarmCommandHandler>? alarmCommandHandlerFactory;
// Pump-aware form: the fourth argument is the STA runtime's pump step, so
// handler work that pumps while it waits (the alarm ack writer's
// OnWriteComplete wait) also refreshes the STA watchdog heartbeat.
private readonly Func<MxAccessEventQueue, Action, IMxAccessComObjectFactory, Action, IAlarmCommandHandler>? alarmCommandHandlerFactory;
private StaCommandDispatcher? commandDispatcher;
private MxAccessSession? session;
private IAlarmCommandHandler? alarmCommandHandler;
@@ -96,6 +99,23 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
{
}
/// <summary>
/// Initializes a new instance of <see cref="MxAccessStaSession"/> with default dependencies and a
/// pump-aware alarm-command handler factory. Its fourth argument is a pump step bound to this
/// session's <see cref="StaRuntime"/> (<see cref="StaRuntime.PumpPendingMessages"/>), which
/// refreshes the STA watchdog heartbeat on every pump.
/// </summary>
/// <param name="pumpAwareAlarmCommandHandlerFactory">Factory that constructs the alarm-command handler.</param>
internal MxAccessStaSession(Func<MxAccessEventQueue, Action, IMxAccessComObjectFactory, Action, IAlarmCommandHandler> pumpAwareAlarmCommandHandlerFactory)
{
staRuntime = new StaRuntime();
factory = new MxAccessComObjectFactory();
eventQueue = new MxAccessEventQueue(MxAccessEventQueue.ResolveCapacity());
eventSink = new MxAccessBaseEventSink(eventQueue);
alarmCommandHandlerFactory = pumpAwareAlarmCommandHandlerFactory
?? throw new ArgumentNullException(nameof(pumpAwareAlarmCommandHandlerFactory));
}
/// <summary>
/// Initializes a new instance of <see cref="MxAccessStaSession"/> with custom STA runtime and factory.
/// </summary>
@@ -183,7 +203,9 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
this.eventSink = eventSink ?? throw new ArgumentNullException(nameof(eventSink));
this.eventQueue = eventQueue ?? throw new ArgumentNullException(nameof(eventQueue));
this.alarmCommandHandlerFactory = alarmCommandHandlerFactory;
this.alarmCommandHandlerFactory = alarmCommandHandlerFactory is null
? null
: (eq, affinity, comFactory, _) => alarmCommandHandlerFactory(eq, affinity, comFactory);
}
/// <summary>
@@ -288,7 +310,8 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
alarmCommandHandler = alarmCommandHandlerFactory(
eventQueue,
EnsureOnAlarmConsumerThread,
factory);
factory,
() => staRuntime.PumpPendingMessages());
}
commandDispatcher = new StaCommandDispatcher(
staRuntime,