Files
codex/codex-rs/code-mode-host/tests/grpc/network_policy_tests.rs
T
cassirer-openai 68b67f0ff4 Preserve rollout tracing across sub-agent reloads (#57615)
## Why

Reloading an evicted sub-agent disabled its rollout tracing, leaving later turns out of the trace. Replacement Code Mode sessions could also reuse cell IDs from earlier sessions.

## What changed

- Reattach reloaded children to the root rollout trace and emit `ThreadResumed` without duplicating `ThreadStarted`.
- Reopen existing threads during replay, preserving their identity and history while clearing stale runtime cell and terminal process mappings.
- Allocate Code Mode cell generations across sessions for both gRPC and stdio hosts to prevent ID reuse after reload.
- Record an aborted trace when session startup fails before the session loop begins.

## Testing

Add regression coverage for child eviction and reload, including a failed reload, continued inference tracing, distinct cell IDs, and preserved mail and follow-up context. Extend replay tests for history preservation and reused runtime handles, and host tests for cell IDs across session replacement and reconnection.

GitOrigin-RevId: 6c146c04b5cda9295afb7d973c0d5474b363a822
2026-10-28 01:29:34 +00:00

101 lines
3.2 KiB
Rust

//! Destination admission and account revocation cover the gRPC request lifetime.
use codex_http_client::DestinationPolicy;
use codex_http_client::HttpClientFactory;
use codex_http_client::NetworkPolicyController;
use codex_http_client::OutboundProxyPolicy;
use pretty_assertions::assert_eq;
use super::*;
#[tokio::test]
async fn restricted_destination_is_rejected_before_connecting_to_the_code_mode_host() -> Result<()>
{
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
let controller = NetworkPolicyController::default();
assert!(controller.publish(
controller.policy().revision(),
DestinationPolicy::Restricted {
allowed_hosts: ["allowed.example".into()].into()
}
));
let provider = Arc::new(GrpcCodeModeSessionProvider::with_http_client_factory(
format!("https://{}", listener.local_addr()?),
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault)
.with_network_policy(controller.policy()),
));
let Err(error) = provider.create_session().await else {
anyhow::bail!("restricted endpoint created a session")
};
assert!(
error.contains("destination denied by application network policy"),
"{error}"
);
assert!(
timeout(Duration::from_millis(/*millis*/ 50), listener.accept())
.await
.is_err()
);
Ok(())
}
#[tokio::test]
async fn account_change_revokes_active_rpc_and_renews_the_cached_client() -> Result<()> {
let host = HostHarness::start("grpc://127.0.0.1:0").await?;
let controller = NetworkPolicyController::default();
assert!(controller.publish(
controller.policy().revision(),
DestinationPolicy::Unrestricted
));
let provider = GrpcCodeModeSessionProvider::with_http_client_factory(
host.endpoint.clone(),
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault)
.with_network_policy(controller.policy()),
);
let session = provider
.create_session()
.await
.map_err(anyhow::Error::msg)?;
let mut pending = request("await new Promise(() => {});");
pending.yield_time_ms = Some(60_000);
let started = session
.execute(
pending,
Arc::new(NoopCodeModeSessionDelegate),
/*preempt*/ None,
)
.await
.map_err(anyhow::Error::msg)?;
controller.policy().invalidate();
assert!(
timeout(TEST_TIMEOUT, started.initial_response())
.await
.context("account revocation did not interrupt the active RPC")?
.is_err()
);
assert!(controller.publish(
controller.policy().revision(),
DestinationPolicy::Unrestricted
));
let replacement = provider
.create_session()
.await
.map_err(anyhow::Error::msg)?;
let response = execute(
&replacement,
request(r#"text("replacement")"#),
Arc::new(NoopCodeModeSessionDelegate),
)
.await?;
assert_eq!(
response,
text_response("g2:1", "replacement", response.code_mode_host_duration())
);
session.shutdown().await.map_err(anyhow::Error::msg)?;
replacement.shutdown().await.map_err(anyhow::Error::msg)?;
Ok(())
}