Hand-woven concurrency gates cannot come back

A SemaphoreSlim, ManualResetEventSlim, AutoResetEvent or CountdownEvent parks a thread. On this mesh that thread is usually a single-threaded action block or a grain turn, so the message the gate is waiting for can never be processed — a deadlock nothing recovers from.

Serialization already belongs to the hub, and concurrency bounding already belongs to the I/O pool. A new guard makes that enforceable rather than merely written down.

Two tiers

Why the test tree counts too

In a test the same primitive fails more subtly: it strands a blocked worker when an assertion throws before the release runs. Measured this week — a pool test put its release after an await of the method-timeout token, so on timeout the release never ran and a blocked leaf held a pool thread into the next test. The replacement is an observable the producer completes, released in a finally.

Proven by breaking it

The guard's self-test plants a real directory tree and runs the real scan over it, rather than matching its pattern against strings — a guard whose self-test never calls its own scanner can lose half its scope and stay green. Each arm was then verified by injecting a violation and watching it go red: a new gate in src/, a new file in test/, and a listed file whose count grows.

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.