cf5fbd02b8
`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