042f5e3d82
- EffectiveSessionConfiguration: add DetachGraceSeconds field; GatewayConfigurationProvider forwards value.Sessions.DetachGraceSeconds (blocker fix). - GatewaySession.InvokeAsync and ReadEventsAsync: switch TouchClientActivity calls from DateTimeOffset.UtcNow to _eventStreaming.TimeProvider.GetUtcNow() so Task 12 fake-clock control works end-to-end (split-clock fix). - TOCTOU fix: add TryBeginCloseIfExpired(now, out alreadyClosing) to GatewaySession that re-checks IsLeaseExpiredCore/IsDetachGraceExpiredCore AND _activeEventSubscriberCount==0 under _syncRoot before transitioning to Closing; CloseExpiredLeasesAsync calls it before CloseSessionCoreAsync so a reattach that wins the race leaves the session Ready/usable. - Minors: lease-expiry-takes-precedence comment in CloseExpiredLeasesAsync; TOCTOU comment block; sweep-cycle latency note added to SessionOptions.DetachGraceSeconds XML doc and to GatewayConfiguration.md DetachGraceSeconds row. - New tests: TryBeginCloseIfExpired_ReattachedSubscriberWinsRace_DeclinesClose (GatewaySession), CloseExpiredLeasesAsync_DoesNotCloseSessionThatReattachedBeforeSweepCloses (SessionManager), plus IsLeaseExpiredCore/IsDetachGraceExpiredCore private helpers used by the guard.