Merge hw5-592 (#592)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-09 07:15:30 -04:00
12 changed files with 595 additions and 15 deletions
@@ -221,7 +221,9 @@ public sealed class CipConnection : ICipChannel, IAsyncDisposable
_o.RpiMicros, size, _route);
var frame = ConnectionManagerServices.ForwardOpen(p);
_diagnostics.CountForwardOpen();
return CipReply.Parse(await client.SendRRDataAsync(frame, _o.Timeout, ct).ConfigureAwait(false));
var raw = await client.SendRRDataAsync(frame, _o.Timeout, ct).ConfigureAwait(false);
_diagnostics.NotifyExchange(frame, raw); // evidence tap: the granted RPIs / connection ids (#566 gate)
return CipReply.Parse(raw);
}
private ushort NextConnectionSerial()
@@ -92,8 +92,8 @@ internal sealed class ConnectionDiagnostics
}
/// <summary>
/// Evidence tap for the gate harness (#582 / #572): sees the CIP request and the raw CIP reply of every
/// completed exchange. Null in production; set only by the read-only differential harness. A throwing
/// Evidence tap for the gate harness (#582 / #572 / #566): sees the CIP request and the raw CIP reply of every
/// completed exchange, Forward Opens included (their replies carry the granted RPIs). Null in production; set only by the read-only differential harness. A throwing
/// observer is swallowed so it can never affect a request.
/// </summary>
internal Action<ReadOnlyMemory<byte>, ReadOnlyMemory<byte>>? ExchangeObserver { get; set; }