Track executor registrations across connection refresh and recovery (#46555)

## Why

An executor can renew its registration while retaining its Noise identity. Connection refresh must distinguish registrations even when their keys match, and a stale registry lookup must not retire a session that recovery has already renewed.

## What changed

- Track the installed session's registration and expose it through `Environment::cached_executor_registration_id()` without connecting. Publish registration changes only when the new connection is installed.
- Require both registration and executor key to match before refresh reuses a session. Retry stale lookups and prevent recovery from installing a connection after retirement.
- Pin session recovery to the original Noise key while allowing registration renewal to preserve the session and running processes.
- Expose `CodexThread::active_turn_environment_selections()` so hosts can authorize steering against the active turn's selections, including starting and failed environments, independently of later settings updates.

## Testing

Add regression coverage for registration replacement, renewal with a running process, stale refresh lookups, missing sessions, and rejection of changed Noise keys. Extend active-turn tests to check selection snapshots across settings updates, deferred startup, and turn completion.

GitOrigin-RevId: 4395bc6c57f137691887e6585f52dcc5b958101c
This commit is contained in:
viyatb-oai
2026-09-19 01:27:02 +00:00
committed by copyberry
parent a633ebc124
commit 328feb0c29
9 changed files with 445 additions and 30 deletions
+25 -8
View File
@@ -85,6 +85,9 @@ impl LazyRemoteExecServerClient {
let _refresh = self.refresh_lock.lock().await;
let (previous, attempt) = loop {
let observed = self.cached_client();
let observed_registration = observed
.as_ref()
.and_then(ExecServerClient::executor_registration_id);
let mut transport = self.transport_params.clone().ok_or_else(|| {
ExecServerError::Protocol(
"connection refresh requires a Noise registry".to_string(),
@@ -111,6 +114,7 @@ impl LazyRemoteExecServerClient {
)
})??;
let executor_public_key = bundle.executor_public_key.clone();
let executor_registration_id = bundle.executor_registration_id.clone();
*provider = Arc::new(PrefetchedConnectProvider {
bundle: StdMutex::new(Some(bundle)),
provider: Arc::clone(provider),
@@ -124,8 +128,8 @@ impl LazyRemoteExecServerClient {
.current_client
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
// An ordinary connection may have finished during the lookup. Re-read the
// registry rather than retire a newer client using a superseded response.
// A new client makes the lookup stale. Recovery can also renew the
// registration on the same client, so compare it under the connection lock.
if !match (&observed, &*current) {
(Some(observed), Some(current)) => Arc::ptr_eq(&observed.inner, &current.inner),
(None, None) => true,
@@ -133,18 +137,31 @@ impl LazyRemoteExecServerClient {
} {
continue;
}
// is_disconnected means terminally failed, not temporarily recovering.
// Preserve same-executor recovery; the final probe fails fast if still recovering.
if current.as_ref().is_some_and(|client| {
!client.is_disconnected()
if let Some(client) = current.as_ref() {
let connection = client
.inner
.connection
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if connection.executor_registration_id != observed_registration {
continue;
}
// Preserve same-executor recovery; the final probe fails fast if still recovering.
if !matches!(connection.status, ConnectionStatus::Failed(_))
&& connection.executor_registration_id.as_ref()
== Some(&executor_registration_id)
&& matches!(
client.inner.reconnect_strategy.as_ref(),
Some(ExecServerReconnectStrategy::NoiseRendezvous {
executor_public_key: key, ..
}) if key == &executor_public_key
)
}) {
break (current.clone(), None);
{
break (current.clone(), None);
}
// Claim retirement before recovery can install another registration.
// Transport shutdown and pending-work cleanup happen outside these locks.
client.inner.retired.cancel();
}
// Cancellation and connection installation use the same lock. A late
// handshake cannot install a client after its attempt has been superseded.