Review follow-up on the blackhole gate. Two robustness gaps, both bounded to the
disposable dedicated container (never shared infra), fixed:
- If the outer token fired while 'docker pause' was in flight, 'paused' was set
only AFTER the await, so a cancellation there left paused=false while the
container could still complete the pause — the finally then skipped unpause and
leaked a frozen container. Mark paused BEFORE the await, and make the finally's
unpause best-effort (unpausing a never-actually-paused container errors
harmlessly, and a cleanup failure must never mask the real test outcome).
- The pause/unpause shell commands had no wall-clock bound of their own — only the
post-pause read was capped — so a hung SSH handshake would hang CI. Give
RunShellCommandAsync its own 30s hard cap that kills the process and throws
TimeoutException, distinct from an operator's own cancellation.
Offline skip still clean (1 skipped, 9ms, no socket/docker).
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
Its doc said it pinned "the real provider's cancellation path", but the
token is cancelled before ReadAsync so the OCE is raised at the reader's
SemaphoreSlim.WaitAsync gate, before any SqlConnection opens — it never
exercises Microsoft.Data.SqlClient's in-flight command cancellation.
Softened (option (a)) to state accurately what it proves: an already-
cancelled token propagates as OCE and is never swallowed into a Bad
snapshot or an unreachable-database verdict. The in-flight/deadline path
is covered by SqlBlackholeTimeoutTests.
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
Adds the frozen-peer / BadTimeout gate for the Sql driver — the single
highest-value integration test. Mirrors the S7 R2-01 blackhole gate:
docker pause a DEDICATED mssql mid-poll, assert the next read surfaces
BadTimeout within the client-side operationTimeout (≈3s) and well below
the server-side CommandTimeout backstop (30s), the driver degrades
(Degraded + host Stopped), and docker unpause recovers to Healthy/Running.
- Docker/docker-compose.yml: disposable `otopcua-sql-blackhole` mssql on
:14333 (never the shared :14330 ConfigDb server) + one-shot seed.
- Docker/seed.sql: the two sample tables.
- SqlBlackholeTimeoutTests.cs: env-gated (SQL_BLACKHOLE_ENDPOINT); pauses
ONLY the hard-coded container name; skips loudly if the endpoint is the
shared port 14330; bounds its own wait so a wedged impl fails, not hangs.
Offline: clean skip (no socket, no docker shell-out).
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW