20ccf88acd
Review caught that the agent path fixed the pending-descriptor race but not the failure path. `resolveConnectionForActiveAgent` caught a `getConnectionFor` rejection and returned null, so `Promise.all` resolved as `[null, activate]` and the switch published anyway: the activation thunk ran and `$activeGatewayProfile` advanced, while only `setConnection` was skipped. That is the same mixed state this PR exists to remove, except it does not close on its own. The pending-descriptor window ends when the descriptor arrives; a failed lookup never arrives, so `$gateway` named the new backend while `$connection` described the old one until an unrelated reconnect or switch happened to repair it. Anything branching on connection mode in between (plugins, `MEDIA:`, `/api/fs/*`, `/api/media`, image attach) saw the pair disagree. Let the rejection propagate, matching `resolveConnectionForProfile`, whose contract is already exactly this: null means "no desktop bridge" and nothing else, and a bridge rejection aborts the whole switch before anything is published. Both doors now fail closed identically, and the caller can retry. The existing "leaves the prior connection intact when the descriptor fetch fails" test asserted the old best-effort behaviour, so it pinned the defect rather than a contract worth keeping. Replaced with a rejected-descriptor test that asserts none of the three atoms moved and the activation thunk was never called. The pending-descriptor case keeps its own separate test, so the success and failure contracts are pinned independently. Also reworded the publication comments: these are sequential atom writes with no asynchronous gap between them, not a transaction, and describing them as one "frame" overstated the guarantee.