close
Closes explicitly (recorded as a client request), then drops the liveness stream. Single-flight and idempotent: concurrent and repeated callers share one Disconnect RPC (60 s gRPC deadline, mapped, under a longer outer bound that maps distinctly) and one shared completion that resolves only after the RPC plus the bounded observe collector/scope teardown. The close transition is atomic under stateMutex: the shared completion is published and closing is marked in one critical section, so no admission passes after close ownership exists. Every caller awaits the same full outcome and rethrows the same primary failure; a cleanup error becomes primary only when there is no Disconnect error, otherwise it is suppressed — never swallowed silently. If observation already dropped, the server has detached the devices and Disconnect may report unknown-connection (mapped); the scope is still cleaned up exactly once.