`ScopedLayers.effect` already returns an exact `() => void`, so the inherited
`() => void dispose()` wrapper voided a void — two lint rules, four errors.
The scoped layer's own teardown had no test, which is the registry-contribution
disposal contract the testing policy requires and the only path that calls
`TaskLayer.isEmpty()`: `ScopedLayers` prunes a scope's layer when its last
contribution disposes. The new case mounts one plugin contributing both a
surface and a listener into one scope, then unloads it and observes that the
agents which joined that scope are refused again.
Refs #2141