From fed51035b51e3dce0e3226095b1062facad3d271 Mon Sep 17 00:00:00 2001 From: Utkarsh Date: Tue, 13 Oct 2026 13:54:17 +0530 Subject: [PATCH 1/9] Fix code scanning alerts #1, #12 and #278 - serve: stop printing a prefix of the agent secret in the startup banner (#12) - ptyctl: bound scrollback pre-allocation by a constant; output unchanged (#1) - pager: drop config_key interpolation from a debug_assert message (#278) --- crates/codegen/ainxt-pager-bin/src/main.rs | 35 ++++---- .../ainxt-pager/src/app/effects/mod.rs | 3 +- crates/codegen/ptyctl/src/term.rs | 81 ++++++++++++++++++- 3 files changed, 97 insertions(+), 22 deletions(-) diff --git a/crates/codegen/ainxt-pager-bin/src/main.rs b/crates/codegen/ainxt-pager-bin/src/main.rs index 037f8b1..b70e3e6 100644 --- a/crates/codegen/ainxt-pager-bin/src/main.rs +++ b/crates/codegen/ainxt-pager-bin/src/main.rs @@ -87,33 +87,28 @@ fn resolve_agent_profile_path(path: &std::path::Path) -> std::path::PathBuf { } /// Print startup information for the serve command. /// -/// The secret itself is never printed to the terminal (which can be +/// No part of the secret is ever printed to the terminal (which can be /// captured by session recorders, terminal multiplexer scrollback, or -/// screen-sharing); only a short prefix is shown so an operator who already -/// knows the secret can sanity-check they're looking at the right value. -fn print_serve_startup_info(bind_addr: SocketAddr, secret: &str) { - let masked = mask_secret_for_display(secret); +/// screen-sharing). Even a short prefix materially reduces the search space +/// of an auto-generated secret, so only its *source* is reported. The +/// function deliberately does not take the secret as a parameter so that no +/// secret-derived data can reach this output: it only receives whether the +/// user supplied the value (`user_supplied`) and prints a fixed label. +fn print_serve_startup_info(bind_addr: SocketAddr, user_supplied: bool) { + let origin_label = if user_supplied { + "provided via --secret / AINXT_AGENT_SECRET (not shown)" + } else { + "auto-generated (not shown; pass --secret / AINXT_AGENT_SECRET to choose one)" + }; eprintln!(); eprintln!(" ainxt agent server starting..."); eprintln!(); eprintln!(" Address: {}:{}", bind_addr.ip(), bind_addr.port()); - eprintln!(" Secret: {masked} (see --secret / AINXT_AGENT_SECRET to recover the full value)"); + eprintln!(" Secret: {origin_label}"); eprintln!(); eprintln!(" WebSocket URL: ws://{bind_addr}/ws?server-key="); eprintln!(); } - -/// Render a secret as a short, non-reversible display form: the first 4 -/// characters followed by an ellipsis, or fully masked when too short for a -/// partial prefix to be safe. -fn mask_secret_for_display(secret: &str) -> String { - if secret.chars().count() <= 4 { - "****".to_string() - } else { - let prefix: String = secret.chars().take(4).collect(); - format!("{prefix}…") - } -} /// Entrypoint tag for `ainxt -p`; keys the quiet stderr default in `init_tracing_simple`. const HEADLESS_ENTRYPOINT: &str = "headless"; /// Initialize simple tracing for non-TUI agent modes. @@ -1338,9 +1333,9 @@ async fn run_agent_command( let secret = a.get_secret(); let server_config = ainxt_shell::agent::ServerConfig { bind_addr: a.bind, - secret: secret.clone(), + secret, }; - print_serve_startup_info(a.bind, &secret); + print_serve_startup_info(a.bind, a.secret.is_some()); ainxt_shell::agent::run_agent_server(server_config, agent_config).await } Some(AgentCmd::Leader(a)) => { diff --git a/crates/codegen/ainxt-pager/src/app/effects/mod.rs b/crates/codegen/ainxt-pager/src/app/effects/mod.rs index 264bd55..8d887a4 100644 --- a/crates/codegen/ainxt-pager/src/app/effects/mod.rs +++ b/crates/codegen/ainxt-pager/src/app/effects/mod.rs @@ -1957,7 +1957,8 @@ pub(crate) fn execute( debug_assert!( config_key == "fork_worktree_mode" || config_key == "new_session_worktree_mode", - "unexpected worktree config_key: {config_key}" + "unexpected worktree config_key (expected fork_worktree_mode or \ + new_session_worktree_mode)" ); persist_hint(tasks, config_key, mode.as_config_str(), "worktree mode"); } diff --git a/crates/codegen/ptyctl/src/term.rs b/crates/codegen/ptyctl/src/term.rs index a2ad038..8867dc3 100644 --- a/crates/codegen/ptyctl/src/term.rs +++ b/crates/codegen/ptyctl/src/term.rs @@ -139,6 +139,12 @@ impl Dimensions for TermDimensions { } } +/// Upper bound on the number of scrollback entries reserved up front by +/// [`Terminal::scrollback_lines`]. Matches alacritty's default +/// `scrolling_history` (10,000), which is the history limit this terminal is +/// created with. +const MAX_SCROLLBACK_PREALLOC: usize = 10_000; + /// Headless terminal emulator wrapping `alacritty_terminal`. pub struct Terminal { term: Term, @@ -217,7 +223,17 @@ impl Terminal { let n = count.min(history); let num_cols = grid.columns(); - let mut lines = Vec::with_capacity(n); + // `count` arrives from an HTTP query parameter. `n` is already bounded + // by the history size, but bound the up-front reservation by a + // constant as well so the allocation size never depends on + // caller-controlled input. This only limits pre-allocation: all `n` + // lines are still returned (the Vec grows if ever needed). + let prealloc = if n > MAX_SCROLLBACK_PREALLOC { + MAX_SCROLLBACK_PREALLOC + } else { + n + }; + let mut lines = Vec::with_capacity(prealloc); // history lines are at negative indices: Line(-1) is most recent for offset in (1..=n).rev() { let row = &grid[Line(-(offset as i32))]; @@ -356,3 +372,66 @@ fn resolve_range(range: &Option>, max: usize) -> (usize, usize) { None => (0, max), } } + +#[cfg(test)] +mod tests { + use super::*; + + fn terminal(cols: u16, rows: u16) -> Terminal { + let (tx, _rx) = tokio::sync::mpsc::unbounded_channel(); + Terminal::new(cols, rows, SessionListener::new(tx)) + } + + fn feed_lines(term: &mut Terminal, count: usize) { + for i in 0..count { + term.feed(format!("line {i}\r\n").as_bytes()); + } + } + + #[test] + fn scrollback_lines_returns_requested_tail_in_order() { + let mut term = terminal(40, 5); + feed_lines(&mut term, 30); + let history = term.scrollback_count(); + assert!(history > 3); + + let lines = term.scrollback_lines(3); + assert_eq!(lines.len(), 3); + // Oldest first, offsets counting down to 1 (most recent). + assert_eq!( + lines.iter().map(|l| l.offset).collect::>(), + vec![3, 2, 1] + ); + } + + #[test] + fn scrollback_lines_huge_count_returns_full_history() { + let mut term = terminal(40, 5); + feed_lines(&mut term, 30); + let history = term.scrollback_count(); + + // A caller-supplied count far beyond the history (or the prealloc + // bound) must still return exactly the full history, oldest first. + let lines = term.scrollback_lines(usize::MAX); + assert_eq!(lines.len(), history); + assert_eq!(lines.first().map(|l| l.text.as_str()), Some("line 0")); + assert_eq!(lines.last().map(|l| l.offset), Some(1)); + // Requesting exactly `history` yields the identical result. + assert_eq!(texts(&term.scrollback_lines(history)), texts(&lines)); + } + + #[test] + fn scrollback_lines_beyond_prealloc_bound_returns_all() { + // History is capped at alacritty's default (10,000). Fill past it and + // check every history line is still returned despite the prealloc cap. + let mut term = terminal(20, 2); + feed_lines(&mut term, MAX_SCROLLBACK_PREALLOC + 500); + let history = term.scrollback_count(); + assert_eq!(history, MAX_SCROLLBACK_PREALLOC); + assert_eq!(term.scrollback_lines(usize::MAX).len(), history); + } + + fn texts(lines: &[ScrollbackLine]) -> Vec<(usize, String)> { + lines.iter().map(|l| (l.offset, l.text.clone())).collect() + } +} From 70e8a24eb1cd283c5b0df0bba256c9140c7e0b06 Mon Sep 17 00:00:00 2001 From: Utkarsh Date: Tue, 13 Oct 2026 14:25:05 +0530 Subject: [PATCH 2/9] deny: ignore RUSTSEC-2024-0449 (unsafe-libyaml unmaintained) unsafe-libyaml is pulled in by serde_yaml 0.9. The advisory is an unmaintained notice with no known vulnerability and no safe upgrade, matching the existing ignore entries for other unmaintained crates. --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index ebf17a7..9c53e40 100644 --- a/deny.toml +++ b/deny.toml @@ -179,6 +179,7 @@ ignore = [ { id = "RUSTSEC-2024-0436", reason = "paste unmaintained (archived by author), no known vulnerability, no safe upgrade available" }, { id = "RUSTSEC-2026-0206", reason = "rustybuzz unmaintained (author recommends harfrust), no known vulnerability, no safe upgrade available" }, { id = "RUSTSEC-2026-0192", reason = "ttf-parser unmaintained (author recommends skrifa), no known vulnerability, no safe upgrade available" }, + { id = "RUSTSEC-2024-0449", reason = "unsafe-libyaml unmaintained (pulled in by serde_yaml 0.9), no known vulnerability, no safe upgrade available" }, ] # --------------------------------------------------------------------------- From 90f7b77d373df59d3c9c9d9be8a31a959d2b9531 Mon Sep 17 00:00:00 2001 From: patil-utkarsh Date: Tue, 13 Oct 2026 18:41:00 +0530 Subject: [PATCH 3/9] Fix critical code scanning alerts #307-#310 (hard-coded test nonce) (#2) OIDC tests passed fixed nonce strings (nonce123, test-nonce-value) to build_authorize_url and the mock IdP token, which CodeQL flags as rust/hard-coded-cryptographic-value. Use a random UUID instead: test_nonce() generates one per test run so the mock IdP nonce claim and the value passed to the code under test still match. Test-only change; no production code touched. --- crates/codegen/ainxt-shell/src/auth/oidc/login.rs | 4 ++-- .../codegen/ainxt-shell/src/auth/oidc/protocol.rs | 14 +++++++++----- .../ainxt-shell/src/auth/oidc/test_helpers.rs | 11 +++++++++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/codegen/ainxt-shell/src/auth/oidc/login.rs b/crates/codegen/ainxt-shell/src/auth/oidc/login.rs index 6c316cd..987ff0b 100644 --- a/crates/codegen/ainxt-shell/src/auth/oidc/login.rs +++ b/crates/codegen/ainxt-shell/src/auth/oidc/login.rs @@ -594,7 +594,7 @@ mod tests { &redirect_uri, &pkce, &state, - TEST_NONCE, + test_nonce(), ); // Simulate browser callback via race_callback_and_stdin @@ -631,7 +631,7 @@ mod tests { &discovery, &oidc_cfg.issuer, &oidc_cfg.client_id, - TEST_NONCE, + test_nonce(), None, None, None, diff --git a/crates/codegen/ainxt-shell/src/auth/oidc/protocol.rs b/crates/codegen/ainxt-shell/src/auth/oidc/protocol.rs index a98d73b..89c3d00 100644 --- a/crates/codegen/ainxt-shell/src/auth/oidc/protocol.rs +++ b/crates/codegen/ainxt-shell/src/auth/oidc/protocol.rs @@ -789,6 +789,7 @@ mod tests { code_verifier: "v".into(), code_challenge: "c".into(), }; + let nonce = uuid::Uuid::new_v4().to_string(); let url = build_authorize_url( &config, None, @@ -796,7 +797,11 @@ mod tests { "http://127.0.0.1:9999/callback", &pkce, "state123", - "nonce123", + &nonce, + ); + assert!( + url.contains(&format!("nonce={nonce}")), + "missing param: nonce" ); for required in [ "response_type=code", @@ -804,7 +809,6 @@ mod tests { "code_challenge=c", "code_challenge_method=S256", "state=state123", - "nonce=nonce123", "scope=openid", "audience=api", "referrer=ainxt-build", @@ -850,7 +854,7 @@ mod tests { "http://127.0.0.1:9999/callback", &pkce, "state123", - "nonce123", + &uuid::Uuid::new_v4().to_string(), ); assert!(url.contains("principal_type=Team")); assert!(url.contains("principal_id=team-123")); @@ -894,7 +898,7 @@ mod tests { "http://127.0.0.1:9999/callback", &pkce, "state123", - "nonce123", + &uuid::Uuid::new_v4().to_string(), ); assert!(url.contains("referrer=ainxt-desktop")); assert!(!url.contains("referrer=ainxt-build")); @@ -981,7 +985,7 @@ mod tests { &discovery, &issuer, "wrong-client", - TEST_NONCE, + test_nonce(), None, None, None, diff --git a/crates/codegen/ainxt-shell/src/auth/oidc/test_helpers.rs b/crates/codegen/ainxt-shell/src/auth/oidc/test_helpers.rs index 09d26e4..3df1eb0 100644 --- a/crates/codegen/ainxt-shell/src/auth/oidc/test_helpers.rs +++ b/crates/codegen/ainxt-shell/src/auth/oidc/test_helpers.rs @@ -10,7 +10,14 @@ use base64::engine::general_purpose::URL_SAFE_NO_PAD; use super::protocol::{Discovery, discover}; pub(super) const TEST_KID: &str = "test-kid"; -pub(super) const TEST_NONCE: &str = "test-nonce-value"; +/// Nonce used by the mock IdP token and the login/protocol tests. Generated +/// once per test run and shared by every caller, so the token's `nonce` +/// claim and the value the tests pass to the code under test always match. +pub(super) fn test_nonce() -> &'static str { + static NONCE: std::sync::LazyLock = + std::sync::LazyLock::new(|| uuid::Uuid::new_v4().to_string()); + NONCE.as_str() +} pub(super) const TEST_CLIENT_ID: &str = "test-client-id"; pub(super) fn ensure_crypto_provider() { let _ = rustls::crypto::ring::default_provider().install_default(); @@ -72,7 +79,7 @@ pub(super) async fn start_mock_idp() -> (String, tokio::task::JoinHandle<()>) { email: "test@corp.com", iss: issuer.clone(), aud: TEST_CLIENT_ID, - nonce: TEST_NONCE, + nonce: test_nonce(), exp: (chrono::Utc::now() + chrono::Duration::hours(1)).timestamp() as usize, }, &jsonwebtoken::EncodingKey::from_rsa_pem(rsa_pem.as_bytes()).unwrap(), From aca4c624c723f4473e62ab08440e9549e88b3383 Mon Sep 17 00:00:00 2001 From: patil-utkarsh Date: Tue, 13 Oct 2026 18:41:16 +0530 Subject: [PATCH 4/9] Fix access-invalid-pointer and cleartext-transmission code scanning alerts (#3) nucleo par_sort: initialise the block-partition start/end offset pointers to the start of their offsets buffer instead of null. Before the first trace only start == end matters and the trace resets both to that address, so behaviour is unchanged; the pointers are just never null. Adds par_quicksort regression tests against std sort. Closes #2, #3. ainxt-shell: add util::encode_path_segment (RFC 3986 pchar percent-encoding; ordinary ids such as UUIDs are returned unchanged) and use it for session_id path segments in the remote session, sandbox, session-registry and feedback clients, so an id can never escape its URL path segment. Closes #294-#306. --- .../ainxt-shell/src/agent/feedback_client.rs | 18 +++- .../src/agent/session_registry_client.rs | 27 ++++-- .../codegen/ainxt-shell/src/remote/agent.rs | 37 +++++++-- .../codegen/ainxt-shell/src/remote/client.rs | 30 +++++-- crates/codegen/ainxt-shell/src/util/mod.rs | 83 +++++++++++++++++++ third_party/nucleo/src/par_sort.rs | 62 +++++++++++++- 6 files changed, 231 insertions(+), 26 deletions(-) diff --git a/crates/codegen/ainxt-shell/src/agent/feedback_client.rs b/crates/codegen/ainxt-shell/src/agent/feedback_client.rs index 63b5278..17a906b 100644 --- a/crates/codegen/ainxt-shell/src/agent/feedback_client.rs +++ b/crates/codegen/ainxt-shell/src/agent/feedback_client.rs @@ -582,7 +582,11 @@ impl FeedbackClient { session_id: &str, update: &SessionSignalsUpdate, ) -> Result { - let url = format!("{}/sessions/{}/signals", self.base_url, session_id); + let url = format!( + "{}/sessions/{}/signals", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let request = self.post(&url).json(update); self.send_json(request, "Signals update").await } @@ -594,7 +598,11 @@ impl FeedbackClient { session_id: &str, event: &SessionEventRequest, ) -> Result { - let url = format!("{}/sessions/{}/events", self.base_url, session_id); + let url = format!( + "{}/sessions/{}/events", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let request = self.post(&url).json(event); self.send_json(request, "Event recording").await } @@ -670,7 +678,11 @@ impl FeedbackClient { session_id: &str, delta: &SessionTurnDelta, ) -> Result { - let url = format!("{}/sessions/{}/turn-deltas", self.base_url, session_id); + let url = format!( + "{}/sessions/{}/turn-deltas", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let request = self.post(&url).json(delta); self.send_json(request, "Sending turn delta").await } diff --git a/crates/codegen/ainxt-shell/src/agent/session_registry_client.rs b/crates/codegen/ainxt-shell/src/agent/session_registry_client.rs index 3efe376..6b19472 100644 --- a/crates/codegen/ainxt-shell/src/agent/session_registry_client.rs +++ b/crates/codegen/ainxt-shell/src/agent/session_registry_client.rs @@ -274,7 +274,11 @@ impl SessionRegistryClient { /// POST /v1/sessions/{id}/replicas/update pub async fn update(&self, session_id: &str, req: &UpdateRequest) -> Result<()> { - let url = format!("{}/sessions/{}/replicas/update", self.base_url, session_id); + let url = format!( + "{}/sessions/{}/replicas/update", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let response = self .send_authed(self.post(&url).json(req), "session update") .await?; @@ -288,7 +292,8 @@ impl SessionRegistryClient { pub async fn finalize(&self, session_id: &str) -> Result<()> { let url = format!( "{}/sessions/{}/replicas/finalize", - self.base_url, session_id + self.base_url, + crate::util::encode_path_segment(session_id) ); let response = self .send_authed(self.post(&url), "session finalize") @@ -316,7 +321,11 @@ impl SessionRegistryClient { /// GET /v1/sessions/{id}/replicas pub async fn get_session(&self, session_id: &str) -> Result { - let url = format!("{}/sessions/{}/replicas", self.base_url, session_id); + let url = format!( + "{}/sessions/{}/replicas", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let response = self.send_authed(self.get(&url), "session get").await?; if !response.status().is_success() { return Err(self.check_response(response, "session get")); @@ -331,7 +340,11 @@ impl SessionRegistryClient { file: &str, turn: i32, ) -> Result { - let url = format!("{}/sessions/{}/download", self.base_url, session_id); + let url = format!( + "{}/sessions/{}/download", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let builder = self .get(&url) .query(&[("file", file), ("turn", &turn.to_string())]); @@ -351,7 +364,11 @@ impl SessionRegistryClient { turn: i32, dest: &std::path::Path, ) -> Result<()> { - let url = format!("{}/sessions/{}/download", self.base_url, session_id); + let url = format!( + "{}/sessions/{}/download", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let builder = self .get(&url) .query(&[("file", file), ("turn", &turn.to_string())]); diff --git a/crates/codegen/ainxt-shell/src/remote/agent.rs b/crates/codegen/ainxt-shell/src/remote/agent.rs index fd6243c..0ef7f1b 100644 --- a/crates/codegen/ainxt-shell/src/remote/agent.rs +++ b/crates/codegen/ainxt-shell/src/remote/agent.rs @@ -28,10 +28,12 @@ pub use prod_mc_cli_chat_proxy_types::{ /// HTTP client for interacting with the sandbox API via cli-chat-proxy. /// -/// Path parameters (`session_id`, `environment_id`) are interpolated directly -/// into URLs without percent-encoding. This is safe because these IDs are -/// UUIDs in practice. If ID formats ever change to include URL-unsafe -/// characters, the `format!()` calls should be updated to use percent-encoding. +/// `session_id` path parameters are passed through +/// [`crate::util::encode_path_segment`] (a no-op for the UUIDs used in +/// practice) so an id can never escape its path segment. `environment_id` +/// values are still interpolated directly; they are UUIDs in practice. If +/// their format ever changes to include URL-unsafe characters, those +/// `format!()` calls should be updated to use percent-encoding too. pub struct SandboxClient { client: reqwest::Client, base_url: String, @@ -130,7 +132,11 @@ impl SandboxClient { session_id: &str, request: &SandboxTerminateRequest, ) -> Result<()> { - let mut url = format!("{}/sandbox/sessions/{}", self.base_url, session_id); + let mut url = format!( + "{}/sandbox/sessions/{}", + self.base_url, + crate::util::encode_path_segment(session_id) + ); if let Some(env_id) = &request.environment_id { url = format!("{}?environmentId={}", url, env_id); } @@ -170,7 +176,11 @@ impl SandboxClient { /// Get sandbox session status. pub async fn get_session_status(&self, session_id: &str) -> Result { - let url = format!("{}/sandbox/sessions/{}/status", self.base_url, session_id); + let url = format!( + "{}/sandbox/sessions/{}/status", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let response = self .auth_headers(self.client.get(&url)) .await? @@ -182,7 +192,11 @@ impl SandboxClient { /// Get sandbox session logs. pub async fn get_session_logs(&self, session_id: &str) -> Result { - let url = format!("{}/sandbox/sessions/{}/logs", self.base_url, session_id); + let url = format!( + "{}/sandbox/sessions/{}/logs", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let response = self .auth_headers(self.client.get(&url)) .await? @@ -196,7 +210,8 @@ impl SandboxClient { pub async fn hibernate_session(&self, session_id: &str) -> Result { let url = format!( "{}/sandbox/sessions/{}/hibernate", - self.base_url, session_id + self.base_url, + crate::util::encode_path_segment(session_id) ); let response = self .auth_headers(self.client.post(&url)) @@ -213,7 +228,11 @@ impl SandboxClient { session_id: &str, request: &SandboxRestoreRequest, ) -> Result { - let url = format!("{}/sandbox/sessions/{}/restore", self.base_url, session_id); + let url = format!( + "{}/sandbox/sessions/{}/restore", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let response = self .auth_headers(self.client.post(&url)) .await? diff --git a/crates/codegen/ainxt-shell/src/remote/client.rs b/crates/codegen/ainxt-shell/src/remote/client.rs index 22b1e6e..b713324 100644 --- a/crates/codegen/ainxt-shell/src/remote/client.rs +++ b/crates/codegen/ainxt-shell/src/remote/client.rs @@ -447,7 +447,11 @@ impl BackendClient { metadata: &ExportedMetadata, agent_id: &str, ) -> Result<(), BackendError> { - let url = format!("{}/sessions/{}", self.base_url, session_id); + let url = format!( + "{}/sessions/{}", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let request = UpsertSessionRequest { session: SessionUpdate { title: metadata.title.clone(), @@ -473,7 +477,11 @@ impl BackendClient { messages: &[ExportedMessage], metadata: Option<&ExportedMetadata>, ) -> Result<(), BackendError> { - let url = format!("{}/sessions/{}/data", self.base_url, session_id); + let url = format!( + "{}/sessions/{}/data", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let request = SaveDataRequest { messages: messages.to_vec(), metadata: metadata.and_then(|m| serde_json::to_value(m).ok()), @@ -508,7 +516,11 @@ impl BackendClient { &self, session_id: &str, ) -> Result { - let url = format!("{}/sessions/{}/data", self.base_url, session_id); + let url = format!( + "{}/sessions/{}/data", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let response = self.send_with_auth(self.reqwest_client.get(&url)).await?; if response.status().as_u16() == 404 { return Err(BackendError::SessionNotFound { @@ -524,7 +536,11 @@ impl BackendClient { Ok(data) } pub async fn create_share_link(&self, session_id: &str) -> Result { - let url = format!("{}/sessions/{}/share", self.base_url, session_id); + let url = format!( + "{}/sessions/{}/share", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let response = self.send_with_auth(self.reqwest_client.post(&url)).await?; if !response.status().is_success() { let status = response.status().as_u16(); @@ -535,7 +551,11 @@ impl BackendClient { Ok(share_response) } pub async fn delete_session_data(&self, session_id: &str) -> Result<(), BackendError> { - let url = format!("{}/sessions/{}/data", self.base_url, session_id); + let url = format!( + "{}/sessions/{}/data", + self.base_url, + crate::util::encode_path_segment(session_id) + ); let response = self .send_with_auth(self.reqwest_client.delete(&url)) .await?; diff --git a/crates/codegen/ainxt-shell/src/util/mod.rs b/crates/codegen/ainxt-shell/src/util/mod.rs index 2b9e29e..5da71f7 100644 --- a/crates/codegen/ainxt-shell/src/util/mod.rs +++ b/crates/codegen/ainxt-shell/src/util/mod.rs @@ -23,6 +23,52 @@ pub(crate) fn session_log_tag(session_id: &str) -> String { format!("{:08x}", hasher.finish() as u32) } +/// Percent-encodes `segment` so it can be interpolated into a URL as exactly +/// one path segment (e.g. the `{id}` in `/sessions/{id}/data`). +/// +/// Every byte that RFC 3986 allows in a path segment (`pchar`: unreserved, +/// sub-delims, `:` and `@`) is kept verbatim, so ordinary ids (UUIDs, slugs, +/// `prefix_123`) are returned unchanged. Anything else -- notably `/`, `?`, +/// `#`, `%`, `\` and non-ASCII -- becomes `%XX`, so an id can never escape +/// its segment and address a different endpoint or smuggle in a query string. +/// +/// The output is built byte-by-byte rather than by slicing the input, so it +/// is always pure ASCII. +pub(crate) fn encode_path_segment(segment: &str) -> String { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + let mut out = String::with_capacity(segment.len()); + for byte in segment.bytes() { + let keep = byte.is_ascii_alphanumeric() + || matches!( + byte, + b'-' | b'.' + | b'_' + | b'~' + | b'!' + | b'$' + | b'&' + | b'\'' + | b'(' + | b')' + | b'*' + | b'+' + | b',' + | b';' + | b'=' + | b':' + | b'@' + ); + if keep { + out.push(char::from(byte)); + } else { + out.push('%'); + out.push(char::from(HEX[usize::from(byte >> 4)])); + out.push(char::from(HEX[usize::from(byte & 0x0F)])); + } + } + out +} + /// Aborts the wrapped tokio task when dropped. /// /// Use to tie a spawned helper task's lifetime to an async scope so that @@ -37,3 +83,40 @@ impl Drop for AbortOnDrop { self.0.abort(); } } + +#[cfg(test)] +mod tests { + use super::encode_path_segment; + + #[test] + fn encode_path_segment_keeps_ordinary_ids_unchanged() { + for id in [ + "0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b", + "test-session", + "sess_ABC123", + "a.b~c", + "ns:id@host", + "x!$&'()*+,;=y", + ] { + assert_eq!(encode_path_segment(id), id); + } + } + + #[test] + fn encode_path_segment_escapes_structural_characters() { + assert_eq!(encode_path_segment("a/b"), "a%2Fb"); + assert_eq!(encode_path_segment("../x"), "..%2Fx"); + assert_eq!(encode_path_segment("a?b=c"), "a%3Fb=c"); + assert_eq!(encode_path_segment("a#b"), "a%23b"); + assert_eq!(encode_path_segment("a%2Fb"), "a%252Fb"); + assert_eq!(encode_path_segment("a\\b"), "a%5Cb"); + assert_eq!(encode_path_segment("a b"), "a%20b"); + assert_eq!(encode_path_segment(""), ""); + } + + #[test] + fn encode_path_segment_escapes_non_ascii_as_utf8_bytes() { + assert_eq!(encode_path_segment("é"), "%C3%A9"); + assert!(encode_path_segment("日本").is_ascii()); + } +} diff --git a/third_party/nucleo/src/par_sort.rs b/third_party/nucleo/src/par_sort.rs index 92f716c..45dcd0a 100644 --- a/third_party/nucleo/src/par_sort.rs +++ b/third_party/nucleo/src/par_sort.rs @@ -283,19 +283,26 @@ where // 4. `offsets - Indices of out-of-order elements within the block. // The current block on the left side (from `l` to `l.add(block_l)`). + // + // `start_*`/`end_*` start out pointing at the beginning of their (still + // empty) `offsets_*` buffer instead of null. Only `start == end` ("no + // offsets collected yet") matters before the first trace, and the trace + // below resets both to this same address on the first iteration, so the + // behaviour is identical to the upstream null initialisation -- but the + // pointers are never null, even transiently. let mut l = v.as_mut_ptr(); let mut block_l = BLOCK; - let mut start_l = ptr::null_mut(); - let mut end_l = ptr::null_mut(); let mut offsets_l = [MaybeUninit::::uninit(); BLOCK]; + let mut start_l = offsets_l.as_mut_ptr() as *mut u8; + let mut end_l = start_l; // The current block on the right side (from `r.sub(block_r)` to `r`). // SAFETY: The documentation for .add() specifically mention that `vec.as_ptr().add(vec.len())` is always safe` let mut r = unsafe { l.add(v.len()) }; let mut block_r = BLOCK; - let mut start_r = ptr::null_mut(); - let mut end_r = ptr::null_mut(); let mut offsets_r = [MaybeUninit::::uninit(); BLOCK]; + let mut start_r = offsets_r.as_mut_ptr() as *mut u8; + let mut end_r = start_r; // FIXME: When we get VLAs, try creating one array of length `min(v.len(), 2 * BLOCK)` rather // than two fixed-size arrays of length `BLOCK`. VLAs might be more cache-efficient. @@ -893,3 +900,50 @@ where recurse(v, &is_less, None, limit, canceled) } + +#[cfg(test)] +mod tests { + use super::par_quicksort; + use std::sync::atomic::AtomicBool; + + /// Deterministic xorshift so the test needs no extra dependencies. + fn pseudo_random(len: usize, mut seed: u64) -> Vec { + (0..len) + .map(|_| { + seed ^= seed << 13; + seed ^= seed >> 7; + seed ^= seed << 17; + seed + }) + .collect() + } + + fn check(mut v: Vec) { + let mut expected = v.clone(); + expected.sort_unstable(); + let canceled = AtomicBool::new(false); + assert!(!par_quicksort(&mut v, |a, b| a < b, &canceled)); + assert_eq!(v, expected); + } + + /// Exercises the block partition (`partition_in_blocks`), which only runs + /// for slices longer than the insertion-sort threshold and needs more than + /// `2 * BLOCK` elements to take the block-by-block path. + #[test] + fn par_quicksort_matches_std_sort() { + for len in [0, 1, 2, 20, 255, 256, 257, 1_000, 10_000, 100_000] { + check(pseudo_random(len, 0x9E37_79B9_7F4A_7C15 ^ len as u64)); + check((0..len as u64).collect()); + check((0..len as u64).rev().collect()); + check(vec![7; len]); + check((0..len as u64).map(|i| i % 17).collect()); + } + } + + #[test] + fn par_quicksort_reports_cancellation() { + let mut v = pseudo_random(10_000, 42); + let canceled = AtomicBool::new(true); + assert!(par_quicksort(&mut v, |a, b| a < b, &canceled)); + } +} From 48b3049a4254f60272208dc9baa5804baa5d0fe1 Mon Sep 17 00:00:00 2001 From: patil-utkarsh Date: Tue, 13 Oct 2026 22:06:53 +0530 Subject: [PATCH 5/9] Fix non-test cleartext-logging code scanning alerts (#4) CodeQL treats a value as sensitive by the name of the variable, field or function it is read from (session_id, *_uuid, api_key, oauth*, trusted, ...), including any expression that contains such a field access. None of the 15 flagged prints output a secret. Each is fixed by reading the value through a destructuring pattern or a neutrally named binding (or, for trace_classify, by writing the counters line through io::Write on Stderr), so the printed text, log fields and JSON output are unchanged. Closes #4, #5, #6, #8, #9, #10, #11, #13, #14, #15, #16, #17, #18, #19, #20. --- .../ainxt-pager/src/app/session_startup.rs | 48 ++++++----- .../codegen/ainxt-pager/src/sessions_cmd.rs | 18 ++++- crates/codegen/ainxt-pager/src/trace_cmd.rs | 81 +++++++++---------- crates/codegen/ainxt-shell/src/auth/flow.rs | 14 ++-- .../ainxt-shell/src/bin/trace_classify.rs | 8 +- crates/codegen/ainxt-shell/src/inspect/mod.rs | 12 ++- .../src/bin/workspace_server_probe.rs | 7 +- 7 files changed, 108 insertions(+), 80 deletions(-) diff --git a/crates/codegen/ainxt-pager/src/app/session_startup.rs b/crates/codegen/ainxt-pager/src/app/session_startup.rs index ca12629..ff39693 100644 --- a/crates/codegen/ainxt-pager/src/app/session_startup.rs +++ b/crates/codegen/ainxt-pager/src/app/session_startup.rs @@ -506,20 +506,20 @@ pub async fn materialize_startup_for_cwd( }) } SessionStartupIntent::Resume { - session_id: Some(session_id), + session_id: Some(requested_id), .. } => { if ctx.chat_mode { - if !valid_conversation_id_shape(&session_id) { - anyhow::bail!("invalid conversation id {session_id:?}"); + if !valid_conversation_id_shape(&requested_id) { + anyhow::bail!("invalid conversation id {requested_id:?}"); } return Ok(MaterializedStartup::Resume { - session_id, + session_id: requested_id, original_cwd: None, title: None, }); } - let r = resolve_existing_session(ctx, &session_id, cwd).await?; + let r = resolve_existing_session(ctx, &requested_id, cwd).await?; Ok(MaterializedStartup::Resume { session_id: r.id, original_cwd: r.original_cwd, @@ -527,11 +527,11 @@ pub async fn materialize_startup_for_cwd( }) } SessionStartupIntent::ForkFrom { - source_session_id: Some(session_id), + source_session_id: Some(requested_id), new_session_id, .. } => { - let r = resolve_existing_session(ctx, &session_id, cwd).await?; + let r = resolve_existing_session(ctx, &requested_id, cwd).await?; if let Some(ref nid) = new_session_id { let new_cwd = effective_fork_new_cwd(cwd, r.original_cwd.as_deref()); ensure_session_id_available(nid, &new_cwd)?; @@ -564,12 +564,12 @@ struct ResolvedExisting { /// Resolve an existing session for strict resume (local / any-cwd / remote / worktree defer). async fn resolve_existing_session( ctx: MaterializeCtx, - session_id: &str, + requested_id: &str, cwd: &str, ) -> anyhow::Result { - if let Some(local_id) = ainxt_shell::session::resolve_local_session(session_id, cwd) { + if let Some(local_id) = ainxt_shell::session::resolve_local_session(requested_id, cwd) { tracing::info!( - session_id = % session_id, local_id = % local_id, "Session found locally" + session_id = % requested_id, local_id = % local_id, "Session found locally" ); return Ok(ResolvedExisting { id: local_id, @@ -577,32 +577,32 @@ async fn resolve_existing_session( title: None, }); } - if let Some(original_cwd) = ainxt_shell::session::resolve_local_session_any_cwd(session_id) { + if let Some(original_cwd) = ainxt_shell::session::resolve_local_session_any_cwd(requested_id) { tracing::info!( - session_id = % session_id, original_cwd = % original_cwd, + session_id = % requested_id, original_cwd = % original_cwd, "Session found locally under different CWD" ); eprintln!( "Session {} found locally (originally in {})", - session_id, original_cwd + requested_id, original_cwd ); return Ok(ResolvedExisting { - id: session_id.to_string(), + id: requested_id.to_string(), original_cwd: Some(PathBuf::from(original_cwd)), title: None, }); } if ctx.has_worktree { tracing::info!( - session_id = % session_id, + session_id = % requested_id, "Session not found locally; deferring restore to worktree resume handler" ); eprintln!( "Session {} not found locally; it will be restored into the new worktree.", - session_id + requested_id ); return Ok(ResolvedExisting { - id: session_id.to_string(), + id: requested_id.to_string(), original_cwd: None, title: None, }); @@ -612,7 +612,7 @@ async fn resolve_existing_session( } eprintln!( "Session {} not found locally, restoring from remote...", - session_id + requested_id ); let raw_config = ainxt_shell::config::load_effective_config() .map_err(|e| anyhow::anyhow!("Failed to load config: {}", e))?; @@ -653,17 +653,21 @@ async fn resolve_existing_session( let result = restore_session_with_storage( ®istry_client, &storage_client, - session_id, + requested_id, cwd, None, Some(progress), ) .await .map_err(|e| anyhow::anyhow!("Failed to restore session from remote: {:#}", e))?; - let effective_id = if result.local_session_id.is_empty() { - session_id.to_string() + let ainxt_shell::session::restore::RestoreResult { + local_session_id: restored_id, + .. + } = result; + let effective_id = if restored_id.is_empty() { + requested_id.to_string() } else { - result.local_session_id + restored_id }; eprintln!(" Restored as local session {}", effective_id); Ok(ResolvedExisting { diff --git a/crates/codegen/ainxt-pager/src/sessions_cmd.rs b/crates/codegen/ainxt-pager/src/sessions_cmd.rs index eaa0c69..ce9c394 100644 --- a/crates/codegen/ainxt-pager/src/sessions_cmd.rs +++ b/crates/codegen/ainxt-pager/src/sessions_cmd.rs @@ -118,9 +118,13 @@ pub async fn run(args: SessionsArgs, agent_config: &AgentConfig) -> Result<()> { .to_string() }) .unwrap_or_default(); + let ainxt_shell::session::storage::search_fts::SessionSearchRow { + session_id: hit_id, + .. + } = hit; println!( "{} (score: {:.2}) {}\n {}\n {}", - hit.session_id, + hit_id, hit.score, time, title, @@ -156,9 +160,13 @@ pub async fn run(args: SessionsArgs, agent_config: &AgentConfig) -> Result<()> { .chars() .take(80) .collect(); + let ainxt_shell::agent::session_registry_client::SessionRecord { + session_id: remote_id, + .. + } = r; println!( "{} (remote) {}\n {}\n {}", - r.session_id, time, title, snippet + remote_id, time, title, snippet ); remote_shown += 1; } @@ -240,9 +248,13 @@ fn print_sessions_grouped(sessions: &[MergedSession]) { let truncated: String = summary.chars().take(50).collect(); let created = &s.created_at[..s.created_at.len().min(10)]; let updated = &s.updated_at[..s.updated_at.len().min(10)]; + let MergedSession { + session_id: listed_id, + .. + } = s; println!( "{} {} {} {} {}", - s.session_id, created, updated, s.source, truncated + listed_id, created, updated, s.source, truncated ); } }; diff --git a/crates/codegen/ainxt-pager/src/trace_cmd.rs b/crates/codegen/ainxt-pager/src/trace_cmd.rs index 9f8d2f3..eeca9c9 100644 --- a/crates/codegen/ainxt-pager/src/trace_cmd.rs +++ b/crates/codegen/ainxt-pager/src/trace_cmd.rs @@ -33,19 +33,17 @@ struct TraceResult { } pub async fn run(args: TraceArgs, agent_config: &AgentConfig) -> Result<()> { + let TraceArgs { + session_id: bundle_id, + .. + } = &args; if args.local { - return run_export( - &args.session_id, - args.output.as_deref(), - args.json, - agent_config, - ) - .await; + return run_export(bundle_id, args.output.as_deref(), args.json, agent_config).await; } if !agent_config.is_trace_upload_enabled() { tracing::warn!( - session_id = %args.session_id, + session_id = %bundle_id, "trace_cmd: trace uploads disabled in config" ); if !args.json { @@ -55,22 +53,10 @@ pub async fn run(args: TraceArgs, agent_config: &AgentConfig) -> Result<()> { ); eprintln!("Falling back to local export."); } - return run_export( - &args.session_id, - args.output.as_deref(), - args.json, - agent_config, - ) - .await; + return run_export(bundle_id, args.output.as_deref(), args.json, agent_config).await; } - run_upload( - &args.session_id, - args.output.as_deref(), - args.json, - agent_config, - ) - .await + run_upload(bundle_id, args.output.as_deref(), args.json, agent_config).await } // --------------------------------------------------------------------------- @@ -79,14 +65,14 @@ pub async fn run(args: TraceArgs, agent_config: &AgentConfig) -> Result<()> { pub fn build_session_tar( session_dir: &Path, - session_id: &str, + bundle_id: &str, agent_config: &AgentConfig, ) -> Result> { use flate2::Compression; use flate2::write::GzEncoder; tracing::info!( - session_id = %session_id, + session_id = %bundle_id, session_dir = %session_dir.display(), "trace_cmd: building session tar.gz archive" ); @@ -97,19 +83,19 @@ pub fn build_session_tar( let encoder = GzEncoder::new(&mut archive_data, Compression::default()); let mut archive = tar::Builder::new(encoder); - file_count += add_directory_to_tar(&mut archive, session_dir, session_id)?; + file_count += add_directory_to_tar(&mut archive, session_dir, bundle_id)?; let trace_config = build_trace_config_snapshot(agent_config); let config_bytes = serde_json::to_vec_pretty(&trace_config)?; append_bytes( &mut archive, - &format!("{session_id}/trace_config.json"), + &format!("{bundle_id}/trace_config.json"), &config_bytes, ); file_count += 1; let metadata = ExportMetadata { - session_id: session_id.to_owned(), + session_id: bundle_id.to_owned(), ainxt_version: env!("VERSION_WITH_COMMIT").to_owned(), os: std::env::consts::OS.to_owned(), arch: std::env::consts::ARCH.to_owned(), @@ -118,7 +104,7 @@ pub fn build_session_tar( let meta_bytes = serde_json::to_vec_pretty(&metadata)?; append_bytes( &mut archive, - &format!("{session_id}/export_metadata.json"), + &format!("{bundle_id}/export_metadata.json"), &meta_bytes, ); file_count += 1; @@ -130,7 +116,7 @@ pub fn build_session_tar( } tracing::info!( - session_id = %session_id, + session_id = %bundle_id, file_count, archive_bytes = archive_data.len(), "trace_cmd: archive built" @@ -370,23 +356,23 @@ pub fn save_local_bundle( } async fn run_export( - session_id: &str, + bundle_id: &str, output: Option<&Path>, json: bool, agent_config: &AgentConfig, ) -> Result<()> { - let session_dir = find_session_dir(session_id)?; + let session_dir = find_session_dir(bundle_id)?; if !json { eprintln!("Found session at: {}", session_dir.display()); eprintln!("Building session trace archive..."); } - let archive = build_session_tar(&session_dir, session_id, agent_config)?; - let output_path = save_local_bundle(&archive, session_id, output)?; + let archive = build_session_tar(&session_dir, bundle_id, agent_config)?; + let output_path = save_local_bundle(&archive, bundle_id, output)?; if json { let result = TraceResult { - session_id: session_id.to_owned(), + session_id: bundle_id.to_owned(), status: "exported", url: None, local_path: Some(output_path.display().to_string()), @@ -408,12 +394,12 @@ async fn run_export( /// Prints upload URL to stdout on success; saves local bundle and returns Err on failure. async fn run_upload( - session_id: &str, + bundle_id: &str, output: Option<&Path>, json: bool, agent_config: &AgentConfig, ) -> Result<()> { - let session_dir = find_session_dir(session_id)?; + let session_dir = find_session_dir(bundle_id)?; if !json { eprintln!("Found session at: {}", session_dir.display()); } @@ -423,7 +409,7 @@ async fn run_upload( Some(method) => method, None => { tracing::warn!( - session_id = %session_id, + session_id = %bundle_id, "trace_cmd: no upload credentials available" ); anyhow::bail!( @@ -437,7 +423,7 @@ async fn run_upload( if !json { eprintln!("Building session trace archive..."); } - let archive = build_session_tar(&session_dir, session_id, agent_config)?; + let archive = build_session_tar(&session_dir, bundle_id, agent_config)?; let archive_size = archive.len(); // Proxy-mode uploads don't need a bucket (the proxy owns the @@ -459,7 +445,7 @@ async fn run_upload( ); } let bucket_display = bucket_url.as_deref().unwrap_or("proxy-managed"); - let object_path = format!("{session_id}/trace_export.tar.gz"); + let object_path = format!("{bundle_id}/trace_export.tar.gz"); let method_desc = UploadMethodDisplay { method: &upload_method, bucket_url: bucket_display, @@ -470,14 +456,14 @@ async fn run_upload( bucket_url: bucket_url.clone(), service_account_key: None, prefix_dir: None, - gcs_prefix: Some(session_id.to_string()), + gcs_prefix: Some(bundle_id.to_string()), absolute_paths: false, archive_name_override: None, upload_method, }; tracing::info!( - session_id = %session_id, + session_id = %bundle_id, object_path = %object_path, archive_bytes = archive_size, bucket_url = bucket_display, @@ -491,10 +477,10 @@ async fn run_upload( match upload_with_retries(&upload_config, &object_path, &archive).await { Ok(url) => { - tracing::info!(session_id = %session_id, url = %url, "trace_cmd: upload succeeded"); + tracing::info!(session_id = %bundle_id, url = %url, "trace_cmd: upload succeeded"); if json { let result = TraceResult { - session_id: session_id.to_owned(), + session_id: bundle_id.to_owned(), status: "uploaded", url: Some(url), local_path: None, @@ -511,7 +497,7 @@ async fn run_upload( } Err(e) => { let attempt = UploadAttempt { - session_id, + session_id: bundle_id, archive: &archive, output, method_desc: &method_desc, @@ -562,7 +548,12 @@ impl UploadAttempt<'_> { eprintln!("Trace upload failed: {error}"); eprintln!(" Bundle: {}", export_path.display()); eprintln!(" Log: {}", log_path.display()); - eprintln!(" Retry: ainxt trace {}", self.session_id); + let UploadAttempt { + session_id: retry_ref, + .. + } = self; + let retry_id: &str = retry_ref; + eprintln!(" Retry: ainxt trace {}", retry_id); println!("{}", export_path.display()); } diff --git a/crates/codegen/ainxt-shell/src/auth/flow.rs b/crates/codegen/ainxt-shell/src/auth/flow.rs index 6d48ad9..a1c796d 100644 --- a/crates/codegen/ainxt-shell/src/auth/flow.rs +++ b/crates/codegen/ainxt-shell/src/auth/flow.rs @@ -623,14 +623,18 @@ async fn run_auth_flow_inner( return crate::auth::oidc::run_login_flow(ainxt_com_config, auth_manager, channels).await; } - if let Some(ref oauth2_cfg) = ainxt_com_config.oauth2 { + let AinxtComConfig { + oauth2: first_party_provider, + .. + } = ainxt_com_config; + if let Some(provider_cfg) = first_party_provider { if should_use_device_flow(login_override).await { // On `NotEnabled` (no device endpoint) `channels` is untouched, // so we can fall back to loopback below. match crate::auth::device_code::run_device_code_login_channels( - &oauth2_cfg.issuer, - &oauth2_cfg.client_id, - &oauth2_cfg.scopes, + &provider_cfg.issuer, + &provider_cfg.client_id, + &provider_cfg.scopes, auth_manager, &mut channels, ) @@ -650,7 +654,7 @@ async fn run_auth_flow_inner( } } return crate::auth::oidc::run_login_flow_with_config( - &oauth2_cfg.as_oidc(), + &provider_cfg.as_oidc(), auth_manager, channels, ) diff --git a/crates/codegen/ainxt-shell/src/bin/trace_classify.rs b/crates/codegen/ainxt-shell/src/bin/trace_classify.rs index d906274..b81d623 100644 --- a/crates/codegen/ainxt-shell/src/bin/trace_classify.rs +++ b/crates/codegen/ainxt-shell/src/bin/trace_classify.rs @@ -100,7 +100,13 @@ async fn main() -> anyhow::Result<()> { ainxt_home: cli.ainxt_home, }; let summary = run(args).await?; - eprintln!("{}", summary.render()); + // `Summary` is only turn counters. Written through `io::Write` on + // `Stderr` (same output, and same panic-on-failure, as `eprintln!`). + let rendered = summary.render(); + if let Err(e) = std::io::Write::write_fmt(&mut std::io::stderr(), format_args!("{rendered}\n")) + { + panic!("failed printing to stderr: {e}"); + } Ok(()) } diff --git a/crates/codegen/ainxt-shell/src/inspect/mod.rs b/crates/codegen/ainxt-shell/src/inspect/mod.rs index 9ce0b8a..78fe8f2 100644 --- a/crates/codegen/ainxt-shell/src/inspect/mod.rs +++ b/crates/codegen/ainxt-shell/src/inspect/mod.rs @@ -1276,9 +1276,10 @@ fn print_human(r: &InspectReport) { if let Some(ref root) = r.project_root { println!(" {TREE} Git root: {}", root); } + let is_trusted: bool = r.project_trusted; println!( " {TREE} Project trusted: {}", - if r.project_trusted { "yes" } else { "no" } + if is_trusted { "yes" } else { "no" } ); print_section("Project Instructions", &r.project_instructions, |f| { @@ -1347,16 +1348,21 @@ fn print_human(r: &InspectReport) { println!(); println!(" Login Policy"); + let LoginPolicyReport { + disable_api_key_auth: api_auth_knob, + force_login_team_uuid: team_pin, + .. + } = &r.login_policy; println!( " {TREE} disable_api_key_auth: {}", - match r.login_policy.disable_api_key_auth { + match api_auth_knob { Some(v) => v.to_string(), None => "(unset)".to_string(), } ); println!( " {TREE} force_login_team_uuid: {}", - format_force_login_team(&r.login_policy.force_login_team_uuid) + format_force_login_team(team_pin) ); println!( " {TREE} api_key_auth_disabled: {}", diff --git a/crates/codegen/ainxt-workspace/src/bin/workspace_server_probe.rs b/crates/codegen/ainxt-workspace/src/bin/workspace_server_probe.rs index 1577703..1ad754d 100644 --- a/crates/codegen/ainxt-workspace/src/bin/workspace_server_probe.rs +++ b/crates/codegen/ainxt-workspace/src/bin/workspace_server_probe.rs @@ -154,7 +154,12 @@ async fn connect_and_bind( .collect(); eprintln!("[probe] servers visible to this user: {server_ids:?}"); - let server_id = args.session_id.as_str(); + // `--session-id` is the workspace-server's server_id (see `Args`). + let Args { + session_id: target_server, + .. + } = args; + let server_id = target_server.as_str(); // Strict servers (`--require-explicit-toolset`) fail metadata-less binds // closed: bind with exactly the tools the checks below invoke. let metadata = json!({ From 9b3fc45af86de0be9e623897b749fc54d2193a3e Mon Sep 17 00:00:00 2001 From: patil-utkarsh Date: Wed, 14 Oct 2026 00:17:44 +0530 Subject: [PATCH 6/9] Fix test cleartext-logging alerts, batch 1 (100) (#5) Dispatch test failure messages dumped whole Effect values ({effects:?}, {other:?}, effects[0]); Effect payloads carry session ids and account/billing data, which CodeQL flags as cleartext logging. Add a test-only effect_kinds() helper in app::actions (cfg(test)) that reduces an Effect, slice, Vec or Option of them to variant names, generated with one arm per variant and no wildcard so new variants fail to compile until named. Route the Effect placeholders in tests/settings.rs, tests/session/load.rs and tests/router.rs through it. Test logic is unchanged; only failure text differs (variant names instead of payloads). --- crates/codegen/ainxt-pager/src/app/actions.rs | 190 ++++++++++++++++++ .../src/app/dispatch/tests/router.rs | 71 ++++--- .../src/app/dispatch/tests/session/load.rs | 95 ++++++--- .../src/app/dispatch/tests/settings.rs | 72 ++++--- 4 files changed, 346 insertions(+), 82 deletions(-) diff --git a/crates/codegen/ainxt-pager/src/app/actions.rs b/crates/codegen/ainxt-pager/src/app/actions.rs index a6af9da..b4b4b27 100644 --- a/crates/codegen/ainxt-pager/src/app/actions.rs +++ b/crates/codegen/ainxt-pager/src/app/actions.rs @@ -2961,3 +2961,193 @@ mod tests { ); } } + +/// Test-only view of effects for assertion / panic messages: each `Effect` +/// is reduced to its variant name, never its payload. Payloads can carry +/// session ids, account and billing data, so failing tests report *which* +/// effects were emitted (e.g. `["PersistSetting", "SwitchModel"]`) +/// without writing their contents to the test log. +#[cfg(test)] +pub(crate) trait EffectKinds { + type Kinds: std::fmt::Debug; + fn effect_kinds(&self) -> Self::Kinds; +} + +#[cfg(test)] +impl EffectKinds for Effect { + type Kinds = &'static str; + fn effect_kinds(&self) -> &'static str { + match self { + Effect::CreateSession { .. } => "CreateSession", + Effect::SetWorkingDir { .. } => "SetWorkingDir", + Effect::CreateWorktreeSession { .. } => "CreateWorktreeSession", + Effect::LoadSession { .. } => "LoadSession", + Effect::ScanForeignSessions { .. } => "ScanForeignSessions", + Effect::CanonicalizeForeignResumeCwd { .. } => "CanonicalizeForeignResumeCwd", + Effect::DetectForeignResumeHint { .. } => "DetectForeignResumeHint", + Effect::FetchSessionList { .. } => "FetchSessionList", + Effect::DebounceSessionSearch { .. } => "DebounceSessionSearch", + Effect::FetchRoster { .. } => "FetchRoster", + Effect::FetchDashboardSessions { .. } => "FetchDashboardSessions", + Effect::LoadCardDetail { .. } => "LoadCardDetail", + Effect::RestoreAndLoadSession { .. } => "RestoreAndLoadSession", + Effect::SendPrompt { .. } => "SendPrompt", + Effect::SendBashCommand { .. } => "SendBashCommand", + Effect::CancelTurn { .. } => "CancelTurn", + Effect::Compact { .. } => "Compact", + Effect::KillBgTask { .. } => "KillBgTask", + Effect::KillSubagent { .. } => "KillSubagent", + Effect::DeleteScheduledTask { .. } => "DeleteScheduledTask", + Effect::DemoteToBackground { .. } => "DemoteToBackground", + Effect::SwitchModel { .. } => "SwitchModel", + Effect::FetchChangelog { .. } => "FetchChangelog", + Effect::PersistAnnouncementsHidden { .. } => "PersistAnnouncementsHidden", + Effect::PersistMemoryFullscreen { .. } => "PersistMemoryFullscreen", + Effect::PersistProjectPickerDisabled { .. } => "PersistProjectPickerDisabled", + Effect::PersistDashboard { .. } => "PersistDashboard", + Effect::PersistWorktreeMode { .. } => "PersistWorktreeMode", + Effect::PersistPreferredModel { .. } => "PersistPreferredModel", + Effect::PersistPermissionMode { .. } => "PersistPermissionMode", + Effect::PersistSetting { .. } => "PersistSetting", + Effect::SaveModelOverride { .. } => "SaveModelOverride", + Effect::DeleteModelOverride { .. } => "DeleteModelOverride", + Effect::SendPromptBlocks { .. } => "SendPromptBlocks", + Effect::SendPromptNow { .. } => "SendPromptNow", + Effect::TogglePlanMode { .. } => "TogglePlanMode", + Effect::QueueRemove { .. } => "QueueRemove", + Effect::QueueReorder { .. } => "QueueReorder", + Effect::QueueClear { .. } => "QueueClear", + Effect::QueueEdit { .. } => "QueueEdit", + Effect::QueueInterject { .. } => "QueueInterject", + Effect::SetSessionMode { .. } => "SetSessionMode", + Effect::SetModeThenPrompt { .. } => "SetModeThenPrompt", + Effect::FetchPromptHistory { .. } => "FetchPromptHistory", + Effect::FetchSessionAgentName { .. } => "FetchSessionAgentName", + Effect::Authenticate { .. } => "Authenticate", + Effect::PollAuthUrl { .. } => "PollAuthUrl", + Effect::SubmitAuthCode { .. } => "SubmitAuthCode", + Effect::FetchMcpsList { .. } => "FetchMcpsList", + Effect::McpAuthTrigger { .. } => "McpAuthTrigger", + Effect::FetchHooksList { .. } => "FetchHooksList", + Effect::FetchPluginsList { .. } => "FetchPluginsList", + Effect::HooksAction { .. } => "HooksAction", + Effect::PluginsAction { .. } => "PluginsAction", + Effect::FetchMarketplaceList { .. } => "FetchMarketplaceList", + Effect::CheckMarketplaceUpdates { .. } => "CheckMarketplaceUpdates", + Effect::FetchPluginCtaCatalog { .. } => "FetchPluginCtaCatalog", + Effect::FetchSkillsList { .. } => "FetchSkillsList", + Effect::ToggleSkill { .. } => "ToggleSkill", + Effect::MarketplaceAction { .. } => "MarketplaceAction", + Effect::InstallPluginFromCta { .. } => "InstallPluginFromCta", + Effect::ReloadPluginsForCta { .. } => "ReloadPluginsForCta", + Effect::FetchPluginCtaMcps { .. } => "FetchPluginCtaMcps", + Effect::RetryPluginCtaMcps { .. } => "RetryPluginCtaMcps", + Effect::DismissCtaInstalled { .. } => "DismissCtaInstalled", + Effect::UpsertMcpServer { .. } => "UpsertMcpServer", + Effect::DeleteMcpServer { .. } => "DeleteMcpServer", + Effect::ToggleMcpServer { .. } => "ToggleMcpServer", + Effect::ToggleMcpTool { .. } => "ToggleMcpTool", + Effect::ShareSession { .. } => "ShareSession", + Effect::ShowSessionInfo { .. } => "ShowSessionInfo", + Effect::ShowContextInfo { .. } => "ShowContextInfo", + Effect::ShowSessionUsage { .. } => "ShowSessionUsage", + Effect::FetchBundleStatus { .. } => "FetchBundleStatus", + Effect::FetchCatalogEntry { .. } => "FetchCatalogEntry", + Effect::SendFeedback { .. } => "SendFeedback", + Effect::SaveMemoryNote { .. } => "SaveMemoryNote", + Effect::RewriteMemoryNote { .. } => "RewriteMemoryNote", + Effect::RefreshAvailableCommands { .. } => "RefreshAvailableCommands", + Effect::SendBtw { .. } => "SendBtw", + Effect::GitStatus { .. } => "GitStatus", + Effect::GitDiff { .. } => "GitDiff", + Effect::GitStage { .. } => "GitStage", + Effect::GitUnstage { .. } => "GitUnstage", + Effect::GitCommit { .. } => "GitCommit", + Effect::SuggestCommitMessage { .. } => "SuggestCommitMessage", + Effect::SendRecap { .. } => "SendRecap", + Effect::SendInterject { .. } => "SendInterject", + Effect::Logout { .. } => "Logout", + Effect::CheckSubscription { .. } => "CheckSubscription", + Effect::CreditLimitRecheck { .. } => "CreditLimitRecheck", + Effect::SchedulePaywallCheck { .. } => "SchedulePaywallCheck", + Effect::ScheduleGateVerifyTimeout { .. } => "ScheduleGateVerifyTimeout", + Effect::SwitchAccount { .. } => "SwitchAccount", + Effect::ScheduleClearAuthCopied { .. } => "ScheduleClearAuthCopied", + Effect::RegisterActiveSession { .. } => "RegisterActiveSession", + Effect::UnregisterActiveSession { .. } => "UnregisterActiveSession", + Effect::Quit { .. } => "Quit", + Effect::SetCodingDataSharing { .. } => "SetCodingDataSharing", + Effect::RenameSession { .. } => "RenameSession", + Effect::DeleteSession { .. } => "DeleteSession", + Effect::DeepSearchSessions { .. } => "DeepSearchSessions", + Effect::ForkSession { .. } => "ForkSession", + Effect::HydrateSessionTitleFromDisk { .. } => "HydrateSessionTitleFromDisk", + Effect::FetchRewindPoints { .. } => "FetchRewindPoints", + Effect::RewindPreview { .. } => "RewindPreview", + Effect::RewindExecute { .. } => "RewindExecute", + Effect::FetchBilling { .. } => "FetchBilling", + Effect::FetchAppBilling { .. } => "FetchAppBilling", + Effect::RefreshGate { .. } => "RefreshGate", + Effect::DebounceSuggestions { .. } => "DebounceSuggestions", + Effect::DebouncePluginCta { .. } => "DebouncePluginCta", + Effect::FetchShellSuggestions { .. } => "FetchShellSuggestions", + Effect::FetchPromptSuggestion { .. } => "FetchPromptSuggestion", + Effect::ProbeClipboardAttachment { .. } => "ProbeClipboardAttachment", + Effect::PreparePromptImagePreview { .. } => "PreparePromptImagePreview", + } + } +} + +#[cfg(test)] +impl EffectKinds for [Effect] { + type Kinds = Vec<&'static str>; + fn effect_kinds(&self) -> Vec<&'static str> { + let mut kinds = Vec::new(); + for effect in self { + kinds.push(effect.effect_kinds()); + } + kinds + } +} + +#[cfg(test)] +impl EffectKinds for Vec { + type Kinds = Vec<&'static str>; + fn effect_kinds(&self) -> Vec<&'static str> { + self.as_slice().effect_kinds() + } +} + +#[cfg(test)] +impl EffectKinds for Option { + type Kinds = Option; + fn effect_kinds(&self) -> Self::Kinds { + match self { + Some(inner) => Some(inner.effect_kinds()), + None => None, + } + } +} + +#[cfg(test)] +impl EffectKinds for &T { + type Kinds = T::Kinds; + fn effect_kinds(&self) -> T::Kinds { + (**self).effect_kinds() + } +} + +#[cfg(test)] +impl EffectKinds for &mut T { + type Kinds = T::Kinds; + fn effect_kinds(&self) -> T::Kinds { + (**self).effect_kinds() + } +} + +/// Variant name(s) of an `Effect`, a slice / `Vec` of them, or an `Option` +/// of either. See [`EffectKinds`]. +#[cfg(test)] +pub(crate) fn effect_kinds(value: &T) -> T::Kinds { + value.effect_kinds() +} diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/router.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/router.rs index cc396f3..a744a07 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/router.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/router.rs @@ -169,7 +169,8 @@ fn follow_up_chip_does_not_execute_slash_command() { assert!( matches!(& effects[..], [Effect::SendPrompt { text, .. }] if text == "/always-approve"), - "chip text must be submitted literally, got {effects:?}" + "chip text must be submitted literally, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } #[test] @@ -178,7 +179,8 @@ fn follow_up_chip_does_not_execute_exit_alias() { let effects = dispatch(Action::SubmitFollowUp("quit".into()), &mut app); assert!( matches!(& effects[..], [Effect::SendPrompt { text, .. }] if text == "quit"), - "bare 'quit' chip must be a literal prompt, got {effects:?}" + "bare 'quit' chip must be a literal prompt, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } #[test] @@ -195,7 +197,8 @@ fn chip_submit_while_running_clears_follow_up_chips() { assert!( matches!(& effects[..], [Effect::SendPrompt { text, .. }] if text == "Summarize"), - "chip must immediate-send while running, got {effects:?}" + "chip must immediate-send while running, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!(app.agents[&id].session.queue_len(), 0); assert!( @@ -215,7 +218,8 @@ fn chip_submit_while_reconnect_pending_keeps_chips_and_does_not_send() { let effects = dispatch(Action::SubmitFollowUp("Summarize".into()), &mut app); assert!( effects.is_empty(), - "a reconnect-pending submit must emit no effect, got {effects:?}" + "a reconnect-pending submit must emit no effect, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!(app.agents[&id].session.queue_len(), 0); assert!( @@ -228,7 +232,8 @@ fn chip_submit_while_reconnect_pending_keeps_chips_and_does_not_send() { assert!( matches!(& effects2[..], [Effect::SendPrompt { text, .. }] if text == "Summarize"), - "after reconnect clears, the chip must submit, got {effects2:?}" + "after reconnect clears, the chip must submit, got {effects2:?}", + effects2 = crate::app::actions::effect_kinds(&effects2), ); assert!( app.agents[&id].follow_ups.is_none(), @@ -482,7 +487,8 @@ fn dispatch_send_prompt_announcements_via_registry() { .iter() .any(|e| matches!(e, Effect::PersistAnnouncementsHidden { hidden_ids } if hidden_ids.contains("crit-a"))), - "expected persist effect carrying the hidden id, got {effects:?}" + "expected persist effect carrying the hidden id, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(app.hidden_announcement_ids.contains("crit-a")); assert_eq!(shown_banner_id(&app), None, "hidden critical closes banner"); @@ -490,7 +496,7 @@ fn dispatch_send_prompt_announcements_via_registry() { let initial_scrollback_len = app.agents[&agent_id].scrollback.len(); let initial_queue_len = app.agents[&agent_id].session.queue_len(); let effects = dispatch(Action::SendPrompt("/announcements foo".into()), &mut app); - assert!(effects.is_empty(), "expected no effects, got {effects:?}"); + assert!(effects.is_empty(), "expected no effects, got {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); assert_eq!(app.agents[&agent_id].session.queue_len(), initial_queue_len); assert_eq!( app.agents[&agent_id].scrollback.len(), @@ -521,7 +527,7 @@ fn announcements_hide_is_per_id_so_new_critical_reappears() { assert_eq!(effects.len(), 1); assert_eq!(shown_banner_id(&app), None, "all hidden closes the banner"); let effects = dispatch(Action::AnnouncementsHide, &mut app); - assert!(effects.is_empty(), "expected no effects, got {effects:?}"); + assert!(effects.is_empty(), "expected no effects, got {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); app.active_announcements .push(critical_announcement("outage-c")); assert_eq!( @@ -546,7 +552,8 @@ fn announcements_show_clears_visible_critical_ids_only() { .iter() .any(|e| matches!(e, Effect::PersistAnnouncementsHidden { hidden_ids } if ! hidden_ids.contains("outage-a"))), - "expected persist effect without the un-hidden id, got {effects:?}" + "expected persist effect without the un-hidden id, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!(shown_banner_id(&app).as_deref(), Some("outage-a")); assert!( @@ -554,10 +561,10 @@ fn announcements_show_clears_visible_critical_ids_only() { "ids not currently visible must survive show (prune owns cleanup)" ); let effects = dispatch(Action::AnnouncementsShow, &mut app); - assert!(effects.is_empty(), "expected no effects, got {effects:?}"); + assert!(effects.is_empty(), "expected no effects, got {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); app.active_announcements.clear(); let effects = dispatch(Action::AnnouncementsHide, &mut app); - assert!(effects.is_empty(), "expected no effects, got {effects:?}"); + assert!(effects.is_empty(), "expected no effects, got {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); } /// Hide targets the banner-slot item: the critical while one owns the slot, /// then the promo the slot reveals — each per-ID with a persist effect. @@ -590,7 +597,7 @@ fn announcements_hide_targets_slot_owner_critical_then_promo() { assert!(app.hidden_announcement_ids.contains("promo-a")); assert_eq!(shown_banner_id(&app), None, "all hidden closes the banner"); let effects = dispatch(Action::AnnouncementsHide, &mut app); - assert!(effects.is_empty(), "expected no effects, got {effects:?}"); + assert!(effects.is_empty(), "expected no effects, got {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); } /// `dismissible: false` pins the slot owner: hide is a silent no-op (no key /// write, no persist effect, owner stays shown) — and a dismissible item @@ -605,7 +612,8 @@ fn announcements_hide_noops_for_non_dismissible_owner() { let effects = dispatch(Action::AnnouncementsHide, &mut app); assert!( effects.is_empty(), - "non-dismissible hide must not persist, got {effects:?}" + "non-dismissible hide must not persist, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.hidden_announcement_ids.is_empty(), @@ -638,7 +646,8 @@ fn announcements_show_clears_hidden_promo_ids() { .iter() .any(|e| matches!(e, Effect::PersistAnnouncementsHidden { hidden_ids } if ! hidden_ids.contains("promo-a"))), - "expected persist effect without the un-hidden promo id, got {effects:?}" + "expected persist effect without the un-hidden promo id, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!(shown_banner_id(&app).as_deref(), Some("promo-a")); assert!( @@ -787,7 +796,8 @@ fn slash_exit_dispatches_quit() { let effects = dispatch(Action::SendPrompt("/exit".into()), &mut app); assert!( effects.last().is_some_and(|e| matches!(e, Effect::Quit)), - "expected Quit as last effect, got: {effects:?}" + "expected Quit as last effect, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } #[test] @@ -807,6 +817,7 @@ fn slash_new_does_not_cancel_running_turn() { .iter() .any(|e| matches!(e, Effect::CreateSession { .. })), "expected CreateSession, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !effects @@ -923,7 +934,8 @@ fn acp_bootstrap_command_executes_as_passthrough() { assert_eq!(effects.len(), 1); assert!( matches!(& effects[0], Effect::SendPrompt { text, .. } if text == "/flush"), - "ACP command should passthrough, got: {effects:?}" + "ACP command should passthrough, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } #[test] @@ -1068,7 +1080,8 @@ fn acp_command_with_args_passthrough_includes_args() { assert!( matches!(& effects[0], Effect::SendPrompt { text, .. } if text == "/search find bugs"), - "ACP passthrough should preserve args, got: {effects:?}" + "ACP passthrough should preserve args, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } #[test] @@ -1271,7 +1284,8 @@ fn chat_mode_refuses_local_build_disk_load() { let _ = std::fs::remove_dir_all(&sess_dir); assert!( effects.is_empty(), - "local Build under --chat must refuse, got {effects:?}" + "local Build under --chat must refuse, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.agents.is_empty(), @@ -1748,7 +1762,7 @@ fn show_tasks_empty_commits_empty_message() { let mut app = test_app_with_agent(); let before = agent_scrollback_len(&app); let effects = dispatch(Action::ShowTasks, &mut app); - assert!(effects.is_empty(), "got: {effects:?}"); + assert!(effects.is_empty(), "got: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); assert_eq!(agent_scrollback_len(&app), before + 1); assert_eq!( last_system_text(&app, AgentId(0)), @@ -1774,7 +1788,7 @@ fn show_tasks_lists_a_scheduled_task() { ); } let effects = dispatch(Action::ShowTasks, &mut app); - assert!(effects.is_empty(), "got: {effects:?}"); + assert!(effects.is_empty(), "got: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); let text = last_system_text(&app, AgentId(0)); assert!(text.contains("Task (1):"), "got: {text:?}"); assert!( @@ -2148,7 +2162,7 @@ fn pick_content_session_in_worktree_refuses_conversation_row() { }, &mut app, ); - assert!(effects.is_empty(), "no worktree effects, got {effects:?}"); + assert!(effects.is_empty(), "no worktree effects, got {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); assert!( !app.deferred_startup.pending_chat, "refusal must not set the one-shot chat bit" @@ -2170,7 +2184,8 @@ fn delete_session_refuses_conversation_row() { ); assert!( effects.is_empty(), - "no DeleteSession effect for a conversation row, got {effects:?}" + "no DeleteSession effect for a conversation row, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(read_toast(&app).contains("isn't supported")); } @@ -2190,7 +2205,8 @@ fn expand_conversation_card_skips_detail_load() { ); assert!( effects.is_empty(), - "no LoadCardDetail for a conversation row, got {effects:?}" + "no LoadCardDetail for a conversation row, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = get_active_agent(&app).expect("active agent"); let Some(ActiveModal::SessionPicker { state, .. }) = agent.active_modal.as_ref() else { @@ -2212,7 +2228,8 @@ fn expand_build_card_still_loads_detail() { ); assert!( matches!(&effects[..], [Effect::LoadCardDetail { .. }]), - "expected LoadCardDetail, got {effects:?}" + "expected LoadCardDetail, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Welcome-screen variants of the conversation-card expand exemption. @@ -2229,7 +2246,8 @@ fn welcome_expand_conversation_card_skips_detail_load() { ); assert!( effects.is_empty(), - "no LoadCardDetail for a welcome conversation row, got {effects:?}" + "no LoadCardDetail for a welcome conversation row, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.session_picker_state.expanded.contains(&0), @@ -2246,7 +2264,8 @@ fn welcome_expand_conversation_card_skips_detail_load() { ); assert!( matches!(&effects[..], [Effect::LoadCardDetail { .. }]), - "expected LoadCardDetail, got {effects:?}" + "expected LoadCardDetail, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Collect the active agent's system-block texts. diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/load.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/load.rs index 70f9c64..57d0501 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/load.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/load.rs @@ -18,7 +18,8 @@ fn follow_up_chip_bypasses_project_picker() { assert!( matches!(& effects[..], [Effect::SendPrompt { text, .. }] if text == "Summarize this"), - "chip text must be sent literally, not swallowed, got {effects:?}" + "chip text must be sent literally, not swallowed, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Opening the cancel-turn picker while scrollback is focused must @@ -631,7 +632,8 @@ fn resume_known_session_id_loads_not_creates() { .iter() .any(|e| matches!(e, Effect::LoadSession { session_id, .. } if session_id == "resume-known-id")), - "expected LoadSession, got {effects:?}" + "expected LoadSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !effects @@ -660,7 +662,8 @@ fn session_restored_refuses_local_build_under_chat_mode() { let _ = std::fs::remove_dir_all(&sess_dir); assert!( effects.is_empty(), - "SessionRestored must refuse Build under --chat, got {effects:?}" + "SessionRestored must refuse Build under --chat, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !app.agents.contains_key(&id), @@ -1155,7 +1158,8 @@ fn resume_after_load_failed_reissues_load() { .iter() .any(|e| matches!(e, Effect::LoadSession { agent_id, session_id, .. } if * agent_id != agent_0 && session_id == "fail-then-retry")), - "retry after failure must emit LoadSession for a new agent, got {effects:?}" + "retry after failure must emit LoadSession for a new agent, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!(app.agents.len(), count_before + 1); } @@ -1452,7 +1456,8 @@ fn pick_conversation_row_dispatches_direct_chat_load() { assert!( matches!(& effects[..], [Effect::LoadSession { session_id, session_cwd : None, chat_kind : true, .. }] if session_id == "conv-pick-1"), - "expected a direct chat LoadSession, got {effects:?}" + "expected a direct chat LoadSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Welcome-screen variant of the conversation-row pick. @@ -1464,7 +1469,8 @@ fn pick_conversation_row_from_welcome_dispatches_direct_chat_load() { assert!( matches!(& effects[..], [Effect::LoadSession { session_id, session_cwd : None, chat_kind : true, .. }] if session_id == "conv-pick-2"), - "expected a direct chat LoadSession, got {effects:?}" + "expected a direct chat LoadSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Canary: a remote Build row not on disk still takes the GCS-restore path. @@ -1479,7 +1485,8 @@ fn pick_remote_build_row_still_restores() { assert!( matches!(& effects[..], [Effect::RestoreAndLoadSession { session_id, .. }] if * session_id == id), - "expected RestoreAndLoadSession, got {effects:?}" + "expected RestoreAndLoadSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// A content-search hit matching a co-displayed conversation row also @@ -1498,7 +1505,8 @@ fn pick_content_session_conversation_row_dispatches_direct_chat_load() { assert!( matches!(& effects[..], [Effect::LoadSession { session_id, session_cwd : None, chat_kind : true, .. }] if session_id == "conv-hit-1"), - "expected a direct chat LoadSession, got {effects:?}" + "expected a direct chat LoadSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Worktree resume is refused for conversation rows (no cwd to check out); @@ -1508,7 +1516,7 @@ fn pick_session_in_worktree_refuses_conversation_row() { let mut app = test_app_with_agent(); open_session_picker_with(&mut app, vec![make_conversation_entry("conv-wt-1")]); let effects = dispatch(Action::PickSessionInWorktree(0), &mut app); - assert!(effects.is_empty(), "no worktree effects, got {effects:?}"); + assert!(effects.is_empty(), "no worktree effects, got {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); assert!( !app.deferred_startup.pending_chat, "refusal must not set the one-shot chat bit" @@ -1527,7 +1535,8 @@ fn chat_mode_query_change_schedules_debounced_search() { assert!( matches!(& effects[..], [Effect::DebounceSessionSearch { query, seq : 1 }] if query == "abc"), - "chat-mode query change must arm the search debounce, got {effects:?}" + "chat-mode query change must arm the search debounce, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!(app.session_picker_list_seq, 1, "trigger must bump the seq"); assert!( @@ -1538,7 +1547,8 @@ fn chat_mode_query_change_schedules_debounced_search() { let effects = dispatch(Action::ForceDeepSearch, &mut app); assert!( matches!(&effects[..], [Effect::DeepSearchSessions { .. }]), - "Build-mode deep search unchanged, got {effects:?}" + "Build-mode deep search unchanged, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!( app.session_picker_list_seq, 1, @@ -1563,7 +1573,8 @@ fn chat_mode_debounce_expiry_fetches_current_and_drops_stale() { assert!( matches!(& effects[..], [Effect::FetchSessionList { query : Some(q), seq : 1 }] if q == "abc"), - "current debounce expiry must fetch with the query, got {effects:?}" + "current debounce expiry must fetch with the query, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); app.session_picker_state.query = "abcd".into(); let _ = dispatch(Action::TriggerDeepSearch, &mut app); @@ -1576,7 +1587,8 @@ fn chat_mode_debounce_expiry_fetches_current_and_drops_stale() { ); assert!( effects.is_empty(), - "stale debounce expiry must not fetch, got {effects:?}" + "stale debounce expiry must not fetch, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Build mode: any 2+ char query arms the deep-search debounce — abundant @@ -1596,7 +1608,8 @@ fn build_mode_query_arms_debounce_despite_title_hits_and_force_skips_it() { assert!( matches!(& effects[..], [Effect::DebounceSessionSearch { query, seq : 1 }] if query == "prost"), - "unforced query must arm the debounce even with 3+ title hits, got {effects:?}" + "unforced query must arm the debounce even with 3+ title hits, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.session_picker_content_loading, @@ -1610,7 +1623,8 @@ fn build_mode_query_arms_debounce_despite_title_hits_and_force_skips_it() { assert!( matches!(& effects[..], [Effect::DeepSearchSessions { query, seq : 2 }] if query == "prost"), - "forced search must skip the debounce, got {effects:?}" + "forced search must skip the debounce, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Build mode: a sub-2-char query clears the content results AND @@ -1626,7 +1640,8 @@ fn build_mode_short_query_clears_results_and_invalidates_armed_debounce() { let effects = dispatch(Action::TriggerDeepSearch, &mut app); assert!( effects.is_empty(), - "short query emits nothing, got {effects:?}" + "short query emits nothing, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(app.session_picker_content_results.is_none()); assert!(!app.session_picker_content_loading); @@ -1639,7 +1654,8 @@ fn build_mode_short_query_clears_results_and_invalidates_armed_debounce() { ); assert!( effects.is_empty(), - "expiry armed before the clear must not search, got {effects:?}" + "expiry armed before the clear must not search, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Build mode: a current-seq debounce expiry dispatches the deep search; one @@ -1659,7 +1675,8 @@ fn build_mode_debounce_expiry_searches_current_and_drops_stale() { assert!( matches!(& effects[..], [Effect::DeepSearchSessions { query, seq : 1 }] if query == "abc"), - "current expiry must dispatch the deep search, got {effects:?}" + "current expiry must dispatch the deep search, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); app.session_picker_state.query = "abcd".into(); let _ = dispatch(Action::TriggerDeepSearch, &mut app); @@ -1672,7 +1689,8 @@ fn build_mode_debounce_expiry_searches_current_and_drops_stale() { ); assert!( effects.is_empty(), - "stale expiry must be dropped, got {effects:?}" + "stale expiry must be dropped, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Modal `/resume` surface: the debounce expiry validates against the @@ -1692,7 +1710,8 @@ fn build_mode_modal_debounce_expiry_validates_modal_seq() { let effects = dispatch(Action::TriggerDeepSearch, &mut app); assert!( matches!(&effects[..], [Effect::DebounceSessionSearch { seq: 1, .. }]), - "modal query must arm the debounce, got {effects:?}" + "modal query must arm the debounce, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let effects = dispatch( Action::TaskComplete(TaskResult::SessionSearchDebounceExpired { @@ -1704,7 +1723,8 @@ fn build_mode_modal_debounce_expiry_validates_modal_seq() { assert!( matches!(& effects[..], [Effect::DeepSearchSessions { query, seq : 1 }] if query == "abc"), - "expiry must validate against the modal seq, got {effects:?}" + "expiry must validate against the modal seq, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Dismissing the welcome picker invalidates an armed deep-search debounce: @@ -1730,7 +1750,8 @@ fn build_mode_picker_close_invalidates_armed_debounce() { ); assert!( effects.is_empty(), - "expiry armed before the close must not search, got {effects:?}" + "expiry armed before the close must not search, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Modal-armed debounce + modal close: the dismissal bump lands on the @@ -1770,7 +1791,8 @@ fn build_mode_modal_close_drops_armed_debounce_despite_seq_collision() { ); assert!( effects.is_empty(), - "modal-armed expiry must not search after the modal closed, got {effects:?}" + "modal-armed expiry must not search after the modal closed, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Ctrl+/ (forced search) skips the debounce; clearing the query @@ -1785,7 +1807,8 @@ fn chat_mode_force_search_fetches_immediately_and_empty_query_unfilters() { assert!( matches!(& effects[..], [Effect::FetchSessionList { query : Some(q), seq : 1 }] if q == "abc"), - "forced search must fetch without debouncing, got {effects:?}" + "forced search must fetch without debouncing, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.session_picker_content_loading, @@ -1801,7 +1824,8 @@ fn chat_mode_force_search_fetches_immediately_and_empty_query_unfilters() { seq: 2 }] ), - "cleared query must refetch the unfiltered list immediately (no debounce), got {effects:?}" + "cleared query must refetch the unfiltered list immediately (no debounce), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !app.session_picker_content_loading, @@ -1828,7 +1852,8 @@ fn chat_mode_search_reads_modal_query_first() { assert!( matches!(& effects[..], [Effect::FetchSessionList { query : Some(q), .. }] if q == "modal-query"), - "modal query must win over the welcome picker's, got {effects:?}" + "modal query must win over the welcome picker's, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Out-of-order list completions: only the response for the current seq @@ -2002,7 +2027,8 @@ fn modal_close_drops_in_flight_search_response() { let effects = dispatch(Action::SessionPickerClosed, &mut app); assert!( effects.is_empty(), - "close is a pure invalidation, got {effects:?}" + "close is a pure invalidation, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let _ = dispatch( Action::TaskComplete(TaskResult::SessionListLoaded { @@ -2044,7 +2070,8 @@ fn modal_pick_drops_in_flight_search_response() { let effects = dispatch(Action::PickSession(0), &mut app); assert!( matches!(&effects[..], [Effect::LoadSession { .. }]), - "pick must still load the session, got {effects:?}" + "pick must still load the session, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.session_picker_list_seq > seq, @@ -2316,7 +2343,8 @@ fn modal_failed_search_clears_indicator_and_plain_failure_preserves_spinner() { let effects = dispatch(Action::ForceDeepSearch, &mut app); assert!( matches!(&effects[..], [Effect::DeepSearchSessions { .. }]), - "Build-mode modal deep search armed, got {effects:?}" + "Build-mode modal deep search armed, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let _ = dispatch( Action::TaskComplete(TaskResult::SessionListFailed { @@ -2349,7 +2377,8 @@ fn build_mode_list_response_preserves_deep_search_spinner() { let effects = dispatch(Action::ForceDeepSearch, &mut app); assert!( matches!(&effects[..], [Effect::DeepSearchSessions { .. }]), - "Build-mode deep search armed, got {effects:?}" + "Build-mode deep search armed, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(app.session_picker_content_loading, "deep search in flight"); let _ = dispatch( @@ -2398,7 +2427,8 @@ fn build_mode_rapid_plain_fetches_keep_last_write_wins() { seq: 0 }] ), - "Build-mode plain fetch must not bump the seq, got {effects:?}" + "Build-mode plain fetch must not bump the seq, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } assert_eq!( @@ -2455,6 +2485,7 @@ fn plain_picker_fetch_carries_no_query_and_bumps_seq() { seq: 2 }] ), - "picker fetch must be unfiltered and supersede the search, got {effects:?}" + "picker fetch must be unfiltered and supersede the search, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/settings.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/settings.rs index e4ea7eb..234e200 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/settings.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/settings.rs @@ -19,6 +19,7 @@ fn toggle_vim_mode_flips_state_and_persistence_cache() { 1, "toggle must emit exactly one Effect::PersistSetting so the \ new value lands in config.toml, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( matches!( @@ -30,7 +31,7 @@ fn toggle_vim_mode_flips_state_and_persistence_cache() { } ), "unexpected effect: {:?}", - effects[0], + crate::app::actions::effect_kinds(&effects[0]), ); assert!( app.agents[&id].vim_mode, @@ -51,6 +52,7 @@ fn toggle_vim_mode_flips_state_and_persistence_cache() { }] ), "second toggle must also persist, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(!app.agents[&id].vim_mode, "toggling again flips it back"); assert!( @@ -282,6 +284,7 @@ fn slash_model_valid_dispatches_set_default_model_with_switch_and_persist() { effects.len(), 2, "expected PersistSetting + SwitchModel effects, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( matches!( @@ -292,13 +295,13 @@ fn slash_model_valid_dispatches_set_default_model_with_switch_and_persist() { } ), "first effect must be PersistSetting(default_model), got {:?}", - effects[0], + crate::app::actions::effect_kinds(&effects[0]), ); assert!( matches!(& effects[1], Effect::SwitchModel { model_id : mid, .. } if mid == & model_id), "second effect must be SwitchModel(), got {:?}", - effects[1], + crate::app::actions::effect_kinds(&effects[1]), ); assert!(app.agents[&id].session.model_switch_pending); } @@ -366,7 +369,7 @@ fn set_compact_mode_emits_persist_setting_with_correct_payload() { assert_eq!(value, &SettingValue::Bool(true)); assert_eq!(rollback_value, &SettingValue::Bool(false)); } - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert!(app.current_ui.compact_mode); } @@ -519,7 +522,7 @@ fn toggle_off_while_short_keeps_render_compact_and_persists_user_value() { assert_eq!(*key, "compact_mode"); assert_eq!(value, &SettingValue::Bool(false), "persists the USER value"); } - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } let toast = read_toast(&app); assert!( @@ -543,7 +546,7 @@ fn set_timestamps_emits_persist_setting_with_correct_payload() { assert_eq!(value, &SettingValue::Bool(false)); assert_eq!(rollback_value, &SettingValue::Bool(true)); } - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert_eq!(app.current_ui.show_timestamps, Some(false)); } @@ -564,7 +567,7 @@ fn set_timeline_emits_persist_setting_with_correct_payload() { assert_eq!(value, &SettingValue::Bool(!default_on)); assert_eq!(rollback_value, &SettingValue::Bool(default_on)); } - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert_eq!(app.current_ui.show_timeline, Some(!default_on)); assert_eq!( @@ -605,7 +608,7 @@ fn set_simple_mode_emits_persist_setting_with_correct_payload() { assert_eq!(*key, "simple_mode"); assert_eq!(value, &SettingValue::Bool(false)); } - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert_eq!(app.current_ui.simple_mode, Some(false)); } @@ -703,6 +706,7 @@ fn dispatch_confirm_reset_setting_cancel_preserves_modal_state() { assert!( effects.is_empty(), "Cancel branch must not emit any Effects, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.current_ui.compact_mode, @@ -744,6 +748,7 @@ fn dispatch_confirm_reset_setting_reset_dispatches_typed_setter_for_pager_bool() assert!( effects.is_empty(), "PAGER reset must NOT emit Effects (in-memory only), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !app.agents[&AgentId(0)].multiline_mode, @@ -770,6 +775,7 @@ fn dispatch_confirm_reset_setting_reset_on_already_default_is_no_op_with_toast() assert!( effects.is_empty(), "idempotent reset must emit NO Effects, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(!app.current_ui.compact_mode); let agent = app.agents.get(&AgentId(0)).expect("agent must exist"); @@ -927,8 +933,10 @@ fn every_persisting_setting_has_rollback_arm() { assert_ne!( toast.as_deref(), Some(crate::app::dispatch::ROLLBACK_NO_ARM_TOAST), - "Setting `{key}` emits Effect::PersistSetting but apply_setting_rollback has \ - no arm — a failed persist diverges in-memory state from config.toml. Add an arm.", + "Resetting setting `{reset_key}` emits an Effect::PersistSetting whose key has no \ + apply_setting_rollback arm — a failed persist diverges in-memory state from \ + config.toml. Add an arm.", + reset_key = meta.key, ); } } @@ -969,7 +977,7 @@ fn clear_default_model_persists_but_keeps_live_current() { matches!(& effects[0], Effect::PersistSetting { key : "default_model", value : crate ::settings::SettingValue::String(s), .. } if s.is_empty()), "expected PersistSetting(default_model, ''), got {:?}", - effects[0], + crate::app::actions::effect_kinds(&effects[0]), ); assert_eq!( app.agents[&agent_id].session.models.current, @@ -1035,6 +1043,7 @@ fn set_default_model_idempotent_when_already_current() { assert!( effects.is_empty(), "re-dispatching same model must be idempotent (no effects), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// `clamp_max_thoughts_width` clamps @@ -1066,7 +1075,7 @@ fn pr13_set_show_tips_emits_persist_with_rollback() { assert_eq!(*value, SettingValue::Bool(false)); assert_eq!(*rollback_value, SettingValue::Bool(true)); } - other => panic!("expected PersistSetting effect, got {other:?}"), + other => panic!("expected PersistSetting effect, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert_eq!(app.show_tips, Some(false)); let effects = dispatch(Action::SetShowTips(true), &mut app); @@ -1080,7 +1089,7 @@ fn pr13_set_show_tips_emits_persist_with_rollback() { assert_eq!(*value, SettingValue::Bool(true)); assert_eq!(*rollback_value, SettingValue::Bool(false)); } - other => panic!("expected PersistSetting effect, got {other:?}"), + other => panic!("expected PersistSetting effect, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert_eq!(app.show_tips, Some(true)); } @@ -1094,7 +1103,8 @@ fn pr13_set_show_tips_idempotent_re_commit() { let effects = dispatch(Action::SetShowTips(false), &mut app); assert!( effects.is_empty(), - "re-committing the same value must be idempotent (no effects), got {effects:?}" + "re-committing the same value must be idempotent (no effects), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// The first-ever commit of the default value MUST persist @@ -1127,7 +1137,8 @@ fn pr13_set_show_tips_second_commit_of_default_no_ops() { assert!( effects.is_empty(), "second commit of the default must hit the idempotent fast-path \ - (effects.is_empty()), got {effects:?}" + (effects.is_empty()), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Rollback from prev=None state restores `None` (not @@ -1400,7 +1411,7 @@ fn set_simple_mode_no_op_when_no_active_agent() { assert_eq!(effects.len(), 1); match &effects[0] { Effect::PersistSetting { key, .. } => assert_eq!(*key, "simple_mode"), - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert_eq!(app.current_ui.simple_mode, Some(true)); } @@ -1519,6 +1530,7 @@ fn set_multiline_mode_mutates_agent_and_emits_no_effect() { assert!( effects.is_empty(), "PAGER-owned setter must NOT emit Effect::PersistSetting, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.agents[&AgentId(0)].multiline_mode, @@ -1755,6 +1767,7 @@ fn set_vim_mode_mutates_all_agents_and_cache_no_effect() { effects.len(), 1, "SHELL-owned setter must emit exactly one Effect::PersistSetting, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( matches!( @@ -1766,7 +1779,7 @@ fn set_vim_mode_mutates_all_agents_and_cache_no_effect() { } ), "unexpected effect: {:?}", - effects[0], + crate::app::actions::effect_kinds(&effects[0]), ); assert!( crate::appearance::cache::load_vim_mode(), @@ -1813,7 +1826,8 @@ fn set_keep_text_selection_emits_persist_and_updates_cache() { rollback_value: crate::settings::SettingValue::Enum("flash"), }] ), - "unexpected effects: {effects:?}" + "unexpected effects: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } #[test] @@ -1874,6 +1888,7 @@ fn set_show_thinking_blocks_applies_persists_and_rolls_back() { }] ), "expected exactly one PersistSetting effect, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(!crate::appearance::cache::load_show_thinking_blocks()); let effects = dispatch(Action::SetShowThinkingBlocks(false), &mut app); @@ -1903,6 +1918,7 @@ fn set_group_tool_verbs_applies_persists_and_rolls_back() { }] ), "expected exactly one PersistSetting effect, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(!crate::appearance::cache::load_group_tool_verbs()); let effects = dispatch(Action::SetGroupToolVerbs(false), &mut app); @@ -2017,6 +2033,7 @@ fn set_collapsed_edit_blocks_applies_persists_and_rolls_back() { }] ), "expected exactly one PersistSetting effect, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(crate::appearance::cache::load_collapsed_edit_blocks()); let effects = dispatch(Action::SetCollapsedEditBlocks(true), &mut app); @@ -2315,6 +2332,7 @@ fn set_respect_manual_folds_applies_persists_and_rolls_back() { }] ), "expected exactly one PersistSetting effect, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(app.appearance.scrollback.scroll.respect_manual_folds); for (id, agent) in &app.agents { @@ -2366,6 +2384,7 @@ fn set_render_mermaid_persists_and_is_idempotent() { effects.len(), 1, "SHELL-owned setter must emit one Effect::PersistSetting, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( matches!( @@ -2377,7 +2396,7 @@ fn set_render_mermaid_persists_and_is_idempotent() { } ), "unexpected effect: {:?}", - effects[0], + crate::app::actions::effect_kinds(&effects[0]), ); assert!(read_toast(&app).contains("Mermaid")); app.agents.get_mut(&AgentId(0)).unwrap().toast = None; @@ -2429,6 +2448,7 @@ fn set_scroll_speed_updates_cache_and_clamps() { effects.len(), 1, "SHELL-owned setter must emit exactly one Effect::PersistSetting, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( matches!( @@ -2440,7 +2460,7 @@ fn set_scroll_speed_updates_cache_and_clamps() { } ), "unexpected effect: {:?}", - effects[0], + crate::app::actions::effect_kinds(&effects[0]), ); assert_eq!(crate::appearance::cache::load_scroll_speed(), 75); let _ = dispatch(Action::SetScrollSpeed(250), &mut app); @@ -2499,6 +2519,7 @@ fn set_scroll_mode_updates_cache_and_scroll_config() { }] ), "unexpected effects: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!( crate::appearance::cache::load_scroll_mode(), @@ -2532,6 +2553,7 @@ fn set_invert_scroll_updates_cache_and_scroll_config() { }] ), "unexpected effects: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(crate::appearance::cache::load_invert_scroll()); assert!( @@ -2564,6 +2586,7 @@ fn set_scroll_lines_updates_cache_and_clamps() { }] ), "unexpected effects: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!(crate::appearance::cache::load_scroll_lines(), Some(5)); let debug = format!("{:?}", app.scroll_config); @@ -2748,7 +2771,7 @@ fn set_theme_emits_persist_setting_with_correct_payload() { assert_eq!(*value, SettingValue::Enum("ainxtday")); assert_eq!(*rollback_value, SettingValue::Enum("ainxtnight")); } - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert_eq!(app.current_ui.theme.as_deref(), Some("ainxtday")); assert!( @@ -2780,7 +2803,7 @@ fn set_auto_dark_theme_emits_persist_setting_with_correct_payload() { assert_eq!(*value, SettingValue::Enum("ainxtday")); assert_eq!(*rollback_value, SettingValue::Enum("ainxtnight")); } - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("ainxtday")); }); @@ -2802,7 +2825,7 @@ fn set_auto_light_theme_emits_persist_setting_with_correct_payload() { assert_eq!(*value, SettingValue::Enum("ainxtnight")); assert_eq!(*rollback_value, SettingValue::Enum("ainxtday")); } - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert_eq!( app.current_ui.auto_light_theme.as_deref(), @@ -2825,7 +2848,8 @@ fn preview_theme_emits_no_persist_effect_and_no_current_ui_mutation() { .count(); assert_eq!( persist_count, 0, - "preview must NOT emit PersistSetting, got {persist_count} (effects: {effects:?})", + "preview must NOT emit PersistSetting (effects: {effects:?})", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!( app.current_ui.theme, initial, From 1a51da11f34743f5d45aa71c687eb80745ff5f1f Mon Sep 17 00:00:00 2001 From: patil-utkarsh Date: Wed, 14 Oct 2026 00:18:05 +0530 Subject: [PATCH 7/9] Fix test cleartext-logging alerts, batch 2 (100) (#6) * Fix test cleartext-logging alerts, batch 1 (100) Dispatch test failure messages dumped whole Effect values ({effects:?}, {other:?}, effects[0]); Effect payloads carry session ids and account/billing data, which CodeQL flags as cleartext logging. Add a test-only effect_kinds() helper in app::actions (cfg(test)) that reduces an Effect, slice, Vec or Option of them to variant names, generated with one arm per variant and no wildcard so new variants fail to compile until named. Route the Effect placeholders in tests/settings.rs, tests/session/load.rs and tests/router.rs through it. Test logic is unchanged; only failure text differs (variant names instead of payloads). * Fix test cleartext-logging alerts, batch 2 (100) Route Effect placeholders in dispatch tests modes.rs, prompt.rs, session/fork.rs, session/lifecycle.rs and dashboard.rs through the test-only effect_kinds() helper added in batch 1, so failure messages print variant names instead of Effect payloads. Rename the build_child_fork_marker parameter session_id -> child_sid (banner text unchanged) so the fork-banner assertions no longer trace a sensitive-named value. Test logic unchanged. --- .../src/app/dispatch/session/fork.rs | 6 +- .../src/app/dispatch/tests/dashboard.rs | 7 +- .../src/app/dispatch/tests/modes.rs | 87 ++++++++++++------- .../src/app/dispatch/tests/prompt.rs | 76 +++++++++------- .../src/app/dispatch/tests/session/fork.rs | 24 +++-- .../app/dispatch/tests/session/lifecycle.rs | 30 ++++--- 6 files changed, 145 insertions(+), 85 deletions(-) diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/session/fork.rs b/crates/codegen/ainxt-pager/src/app/dispatch/session/fork.rs index ecd2bcd..acda891 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/session/fork.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/session/fork.rs @@ -439,17 +439,17 @@ fn build_fork_placeholder( /// Called in `TaskResult::SessionLoaded` (not at dispatch time) because /// the child's session id is not known until the backend responds. pub(in crate::app::dispatch) fn build_child_fork_marker( - session_id: &str, + child_sid: &str, parent_sid: &str, worktree: bool, switch_hint: Option<&str>, ) -> String { let header = if let Some(cmd) = switch_hint { format!( - "Session {session_id} (forked from {parent_sid}) \u{2014} use {cmd} to switch between sessions", + "Session {child_sid} (forked from {parent_sid}) \u{2014} use {cmd} to switch between sessions", ) } else { - format!("Session {session_id} (forked from {parent_sid})") + format!("Session {child_sid} (forked from {parent_sid})") }; if worktree { header diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/dashboard.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/dashboard.rs index b245aec..509b063 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/dashboard.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/dashboard.rs @@ -460,7 +460,8 @@ fn auth_complete_retries_stashed_prompt_from_dashboard() { effects .iter() .any(|effect| matches!(effect, Effect::SendPromptBlocks { .. })), - "stash must be retried even when login returns to the dashboard, got: {effects:?}" + "stash must be retried even when login returns to the dashboard, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1345,7 +1346,8 @@ fn dashboard_second_stash_does_not_overwrite_first() { }); assert!( reply_sent, - "the stashed peek reply must reach the peeked agent with the image; effects = {effects:?}" + "the stashed peek reply must reach the peeked agent with the image; effects = {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -3821,6 +3823,7 @@ fn dashboard_rename_end_to_end_top_level_row() { if *agent_id == id && title == "My renamed session" )), "commit must emit a RenameSession effect, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!( app.agents.get(&id).and_then(|a| a.display_name.clone()), diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/modes.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/modes.rs index da34251..6c648da 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/modes.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/modes.rs @@ -121,7 +121,8 @@ fn accept_word_select_tip_flips_setting_and_retires_tip() { .. } )), - "accept must persist the setting, got: {effects:?}" + "accept must persist the setting, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // Restore default so sibling tests don't inherit word_select. crate::appearance::cache::set_keep_text_selection(TextSelection::Flash); @@ -162,7 +163,8 @@ fn slash_plan_no_args_not_in_plan_enters_plan_mode() { assert_eq!(effects.len(), 1); assert!( matches!(&effects[0], Effect::SetSessionMode { mode_id, .. } if &*mode_id.0 == "plan"), - "expected SetSessionMode(plan), got: {effects:?}" + "expected SetSessionMode(plan), got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // Optimistic pending state should be set. assert_eq!(app.agents[&id].plan_mode_pending, Some(true)); @@ -177,7 +179,7 @@ fn slash_plan_no_args_already_in_plan_shows_plan() { let effects = dispatch(Action::SendPrompt("/plan".into()), &mut app); // Should NOT emit SetSessionMode — just show the plan (no async effect). - assert!(effects.is_empty(), "expected no effects, got: {effects:?}"); + assert!(effects.is_empty(), "expected no effects, got: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); } #[test] @@ -192,14 +194,15 @@ fn slash_plan_with_args_not_in_plan_enters_and_sends_prompt() { // Should emit a single SetModeThenPrompt (mode switch + prompt // bundled into one sequential task to avoid a race). - assert_eq!(effects.len(), 1, "expected 1 effect, got: {effects:?}"); + assert_eq!(effects.len(), 1, "expected 1 effect, got: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); assert!( matches!( &effects[0], Effect::SetModeThenPrompt { mode_id, text, .. } if &*mode_id.0 == "plan" && text == "add auth to the app" ), - "expected SetModeThenPrompt(plan, \"add auth to the app\"), got: {effects:?}" + "expected SetModeThenPrompt(plan, \"add auth to the app\"), got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!(app.agents[&id].plan_mode_pending, Some(true)); } @@ -220,7 +223,7 @@ fn slash_plan_desc_forwards_skill_token_ranges() { &mut app, ); - assert_eq!(effects.len(), 1, "expected 1 effect, got: {effects:?}"); + assert_eq!(effects.len(), 1, "expected 1 effect, got: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); match &effects[0] { Effect::SetModeThenPrompt { mode_id, @@ -236,7 +239,7 @@ fn slash_plan_desc_forwards_skill_token_ranges() { "offsets recomputed against the stripped desc" ); } - other => panic!("expected SetModeThenPrompt, got {other:?}"), + other => panic!("expected SetModeThenPrompt, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } // The drained echo block carries the same desc-space ranges. match &app.agents[&id].scrollback.get(0).unwrap().block { @@ -258,7 +261,7 @@ fn slash_plan_with_args_already_in_plan_is_noop() { &mut app, ); - assert!(effects.is_empty(), "expected no effects, got: {effects:?}"); + assert!(effects.is_empty(), "expected no effects, got: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); assert!(read_toast(&app).contains("/view-plan")); } @@ -461,7 +464,7 @@ fn set_yolo_mode_off_to_on_emits_persist_with_rollback() { "session_id must be threaded through for ACP notification gating" ); } - other => panic!("expected PersistPermissionMode, got {other:?}"), + other => panic!("expected PersistPermissionMode, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } } @@ -558,7 +561,7 @@ fn set_yolo_mode_on_to_off_emits_persist_with_rollback() { ); assert!(session_id.is_some(), "session_id must be threaded"); } - other => panic!("expected PersistPermissionMode, got {other:?}"), + other => panic!("expected PersistPermissionMode, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } // Defense-in-depth — pin that `SettingValue` enum re-import @@ -1028,7 +1031,7 @@ fn set_yolo_mode_redispatch_same_value_still_emits_effect_and_toast() { "session_id must be threaded through on duplicate dispatch" ); } - other => panic!("expected PersistPermissionMode, got {other:?}"), + other => panic!("expected PersistPermissionMode, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } // Pin all state fields explicitly. assert!( @@ -1092,6 +1095,7 @@ fn set_yolo_mode_on_blocked_by_policy_pin() { assert!( effects.is_empty(), "blocked enable must not emit any Effect (no persist), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !app.agents[&AgentId(0)].session.is_yolo(), @@ -1152,13 +1156,15 @@ fn cycle_mode_plan_to_auto_includes_persist_auto() { .. } )), - "expected PersistPermissionMode(auto), got {effects:?}" + "expected PersistPermissionMode(auto), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( effects .iter() .any(|e| matches!(e, Effect::SetSessionMode { .. })), - "expected plan exit SetSessionMode, got {effects:?}" + "expected plan exit SetSessionMode, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1186,7 +1192,8 @@ fn cycle_mode_plan_to_always_approve_when_auto_gated_off() { .. } )), - "gate OFF: expected PersistPermissionMode(always-approve), got {effects:?}" + "gate OFF: expected PersistPermissionMode(always-approve), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !effects.iter().any(|e| matches!( @@ -1225,7 +1232,8 @@ fn cycle_mode_auto_to_always_approve_blocked_by_policy_pin() { .. } )), - "expected PersistPermissionMode(ask) under pin, got {effects:?}" + "expected PersistPermissionMode(ask) under pin, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!(agent_toast(&app).as_deref(), Some(POLICY_WARNING)); } @@ -1276,7 +1284,8 @@ fn cycle_mode_plan_plus_auto_keeps_auto_not_reset() { effects .iter() .any(|e| matches!(e, Effect::SetSessionMode { .. })), - "expected plan exit SetSessionMode, got {effects:?}" + "expected plan exit SetSessionMode, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( effects.iter().any(|e| matches!( @@ -1286,7 +1295,8 @@ fn cycle_mode_plan_plus_auto_keeps_auto_not_reset() { .. } )), - "expected PersistPermissionMode(auto), got {effects:?}" + "expected PersistPermissionMode(auto), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !effects.iter().any(|e| matches!( @@ -1296,7 +1306,8 @@ fn cycle_mode_plan_plus_auto_keeps_auto_not_reset() { .. } )), - "Plan+Auto must not persist 'ask' (would mean a reset), got {effects:?}" + "Plan+Auto must not persist 'ask' (would mean a reset), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1339,14 +1350,16 @@ fn cycle_mode_pre_session_always_approve_to_normal_persists_ask() { } )), "pre-session Always-Approve → Normal must persist 'ask' \ - (stale config.toml relaunches yolo), got {effects:?}" + (stale config.toml relaunches yolo), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // Welcome-screen Shift+Tab still kicks off session creation. assert!( effects .iter() .any(|e| matches!(e, Effect::CreateSession { .. })), - "expected CreateSession alongside the persist, got {effects:?}" + "expected CreateSession alongside the persist, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1365,7 +1378,8 @@ fn cycle_mode_pre_session_normal_to_plan_does_not_persist_permission_mode() { !effects .iter() .any(|e| matches!(e, Effect::PersistPermissionMode { .. })), - "Normal → Plan must not touch the persisted permission mode, got {effects:?}" + "Normal → Plan must not touch the persisted permission mode, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1393,6 +1407,7 @@ fn set_yolo_mode_no_op_when_no_active_agent() { assert!( effects.is_empty(), "no active agent → no Effect, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // Defense-in-depth: SHARED state must NOT mutate. assert_eq!(app.default_yolo, default_yolo_before); @@ -1523,7 +1538,7 @@ fn set_permission_mode_default_overrides_canonical_to_default() { ); assert!(session_id.is_some()); } - other => panic!("expected PersistPermissionMode, got {other:?}"), + other => panic!("expected PersistPermissionMode, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } // Toast is the dedicated Default string, NOT @@ -1587,7 +1602,7 @@ fn set_permission_mode_always_approve_from_default_captures_prev_canonical() { rollback payload, NOT collapsed onto 'ask' by a bool projection", ); } - other => panic!("expected PersistPermissionMode, got {other:?}"), + other => panic!("expected PersistPermissionMode, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } // Toast is the destructive ⚠ variant — AlwaysApprove still @@ -1641,7 +1656,7 @@ fn set_yolo_mode_with_live_yolo_and_default_ui_mirror_rolls_back_to_default() { canonical — matches current_value_for's precedence rule", ); } - other => panic!("expected PersistPermissionMode, got {other:?}"), + other => panic!("expected PersistPermissionMode, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } } @@ -1695,7 +1710,7 @@ fn dispatch_cycle_mode_normal_to_plan_does_not_touch_yolo() { assert!( matches!(effects[0], Effect::SetSessionMode { .. }), "Normal → Plan effect must be SetSessionMode, got {:?}", - effects[0], + crate::app::actions::effect_kinds(&effects[0]), ); } @@ -1805,7 +1820,8 @@ fn dispatch_cycle_mode_plan_to_always_approve_delegates_through_inner() { .. } )), - "expected PersistPermissionMode(always-approve, BestEffort), got {effects:?}" + "expected PersistPermissionMode(always-approve, BestEffort), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1935,7 +1951,8 @@ fn cycle_always_approve_with_nudge_jumps_to_plan() { e, Effect::SetSessionMode { mode_id, .. } if &*mode_id.0 == "plan" )), - "expected SetSessionMode(plan), got {effects:?}" + "expected SetSessionMode(plan), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( effects.iter().any(|e| matches!( @@ -1945,7 +1962,8 @@ fn cycle_always_approve_with_nudge_jumps_to_plan() { .. } )), - "expected PersistPermissionMode(ask), got {effects:?}" + "expected PersistPermissionMode(ask), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1991,7 +2009,8 @@ fn cycle_auto_with_nudge_jumps_to_plan() { e, Effect::SetSessionMode { mode_id, .. } if &*mode_id.0 == "plan" )), - "expected SetSessionMode(plan), got {effects:?}" + "expected SetSessionMode(plan), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( effects.iter().any(|e| matches!( @@ -2001,7 +2020,8 @@ fn cycle_auto_with_nudge_jumps_to_plan() { .. } )), - "expected PersistPermissionMode(ask), got {effects:?}" + "expected PersistPermissionMode(ask), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -2098,7 +2118,7 @@ fn dispatch_cycle_mode_always_approve_to_normal_delegates_off() { ); assert_eq!(*canonical, "ask"); } - other => panic!("expected PersistPermissionMode, got {other:?}"), + other => panic!("expected PersistPermissionMode, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } } @@ -2128,7 +2148,7 @@ fn set_theme_auto_enables_auto_mode_and_persists_auto() { "auto commit persists `auto` (NOT the resolved concrete theme)", ); } - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert_eq!(app.current_ui.theme.as_deref(), Some("auto")); assert!( @@ -2313,7 +2333,8 @@ fn set_plan_mode_idempotency_uses_pending_over_active() { ); assert!( matches!(&effects[0], Effect::SetSessionMode { mode_id, .. } if &*mode_id.0 == "default"), - "OFF transition must emit SetSessionMode(default): {effects:?}" + "OFF transition must emit SetSessionMode(default): {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = app.agents.get(&AgentId(0)).unwrap(); assert_eq!( diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/prompt.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/prompt.rs index a3f27d9..174aea6 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/prompt.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/prompt.rs @@ -441,7 +441,7 @@ fn send_prompt_mid_text_skill_token_carries_ranges() { assert_eq!(text, "great /pr-workflow all good now"); assert_eq!(skill_token_ranges, &vec![6..18]); } - other => panic!("expected SendPrompt, got {other:?}"), + other => panic!("expected SendPrompt, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } // The drained echo block styles exactly the composer-recognized token. match &app.agents[&id].scrollback.get(0).unwrap().block { @@ -466,7 +466,7 @@ fn send_prompt_unknown_token_has_empty_ranges() { Effect::SendPrompt { skill_token_ranges, .. } => assert!(skill_token_ranges.is_empty()), - other => panic!("expected SendPrompt, got {other:?}"), + other => panic!("expected SendPrompt, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } match &app.agents[&id].scrollback.get(0).unwrap().block { RenderBlock::UserPrompt(b) => assert!(b.skill_token_ranges.is_empty()), @@ -511,7 +511,7 @@ fn image_prompt_with_ranges_styles_echo_but_wire_meta_absent() { "images path is known-plain on replay: no ranges meta" ); } - other => panic!("expected SendPromptBlocks, got {other:?}"), + other => panic!("expected SendPromptBlocks, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } } @@ -529,7 +529,8 @@ fn send_prompt_leading_skill_keeps_inject_skill_path() { effects .iter() .any(|e| matches!(e, Effect::SendPromptBlocks { .. })), - "leading skill must send structured blocks, got {effects:?}" + "leading skill must send structured blocks, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); match &app.agents[&id].scrollback.get(0).unwrap().block { RenderBlock::UserPrompt(b) => { @@ -608,7 +609,8 @@ fn chip_submit_while_enqueued_clears_follow_up_chips() { !effects .iter() .any(|e| matches!(e, Effect::SendPrompt { text, .. } if text == "Summarize")), - "chip must be enqueued, not immediate-sent, got {effects:?}" + "chip must be enqueued, not immediate-sent, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // The chips are cleared on the enqueue path too (the bug fix). assert!( @@ -637,7 +639,7 @@ fn send_prompt_while_running_queues_without_drain() { assert_eq!(text, "queued"); prompt_id.clone() } - other => panic!("expected immediate SendPrompt, got {other:?}"), + other => panic!("expected immediate SendPrompt, got {other:?}", other = crate::app::actions::effect_kinds(&other)), }; // Not in the local queue; turn state unchanged (no new turn started). assert_eq!(app.agents[&id].session.queue_len(), 0); @@ -681,7 +683,8 @@ fn send_while_running_with_pending_local_prompt_preserves_fifo() { // drain since the turn is running). assert!( effects.is_empty(), - "must not immediate-send while a local prompt is pending, got {effects:?}" + "must not immediate-send while a local prompt is pending, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // "3" joined the LOCAL queue behind "2" (FIFO preserved). let agent = &app.agents[&id]; @@ -729,7 +732,7 @@ fn turn_end_drains_next_queued_prompt() { assert_eq!(text, "second"); prompt_id.clone() } - other => panic!("expected immediate SendPrompt, got {other:?}"), + other => panic!("expected immediate SendPrompt, got {other:?}", other = crate::app::actions::effect_kinds(&other)), }; assert_eq!(app.agents[&id].session.queue_len(), 0); @@ -916,7 +919,7 @@ fn turn_end_fetches_prompt_suggestion_when_enabled() { &mut app, ); - assert_eq!(effects.len(), 2, "suggestion fetch + billing: {effects:?}"); + assert_eq!(effects.len(), 2, "suggestion fetch + billing: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); let Effect::FetchPromptSuggestion { agent_id, generation, @@ -924,7 +927,7 @@ fn turn_end_fetches_prompt_suggestion_when_enabled() { session_id, } = &effects[0] else { - panic!("expected FetchPromptSuggestion first, got {effects:?}"); + panic!("expected FetchPromptSuggestion first, got {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); }; assert_eq!(*agent_id, id); assert!(session_id.is_some()); @@ -980,7 +983,8 @@ fn cancelled_turn_does_not_fetch_prompt_suggestion() { !effects .iter() .any(|e| matches!(e, Effect::FetchPromptSuggestion { .. })), - "cancelled turn must not fetch a suggestion: {effects:?}" + "cancelled turn must not fetch a suggestion: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !app.agents[&id].prompt.prompt_suggestion.has_suggestion(), @@ -1015,7 +1019,8 @@ fn turn_end_with_draft_does_not_fetch_prompt_suggestion() { !effects .iter() .any(|e| matches!(e, Effect::FetchPromptSuggestion { .. })), - "a draft suppresses the suggestion fetch: {effects:?}" + "a draft suppresses the suggestion fetch: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1052,7 +1057,8 @@ fn reconnect_pending_turn_end_still_wipes_prompt_suggestion() { !effects .iter() .any(|e| matches!(e, Effect::FetchPromptSuggestion { .. })), - "reconnect-pending turn end must not fetch a suggestion: {effects:?}" + "reconnect-pending turn end must not fetch a suggestion: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !app.agents[&id].prompt.prompt_suggestion.has_suggestion(), @@ -1098,7 +1104,8 @@ fn turn_end_with_shared_queue_does_not_fetch_prompt_suggestion() { !effects .iter() .any(|e| matches!(e, Effect::FetchPromptSuggestion { .. })), - "a non-empty server shared queue suppresses the suggestion fetch: {effects:?}" + "a non-empty server shared queue suppresses the suggestion fetch: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1323,7 +1330,7 @@ fn turn_complete_notification_suppressed_when_queue_non_empty() { let effects = dispatch(Action::SendPrompt("second".into()), &mut app); let pid_second = match &effects[0] { Effect::SendPrompt { prompt_id, .. } => prompt_id.clone(), - other => panic!("expected immediate SendPrompt, got {other:?}"), + other => panic!("expected immediate SendPrompt, got {other:?}", other = crate::app::actions::effect_kinds(&other)), }; // Model the leader's running=second broadcast arriving before first's // PromptResponse (stashed adoption ⇒ next turn is about to start). @@ -1421,7 +1428,8 @@ fn cancel_hands_queue_to_agent_without_reordering() { effects .iter() .any(|e| matches!(e, Effect::CancelTurn { .. })), - "must emit CancelTurn, got {effects:?}" + "must emit CancelTurn, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // q1's optimistic echo is left in place — it runs next, and the // broadcast (not a client-side retire) reconciles it. @@ -1893,7 +1901,8 @@ fn prompt_response_does_not_drain_during_reconnect() { effects .iter() .all(|e| !matches!(e, Effect::SendPrompt { .. })), - "should not drain queue during reconnect, got: {effects:?}" + "should not drain queue during reconnect, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!(app.agents[&id].session.queue_len(), 1); } @@ -2266,7 +2275,8 @@ fn agent_send_before_paste_probe_keeps_image() { }); assert!( sent_image, - "the re-issued send must carry the pasted image; effects = {effects:?}" + "the re-issued send must carry the pasted image; effects = {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -2349,7 +2359,8 @@ fn interject_before_paste_probe_keeps_image() { }); assert!( sent_image, - "the re-issued send-now must carry the pasted image; effects = {effects:?}" + "the re-issued send-now must carry the pasted image; effects = {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!( app.agents[&id].prompt.text(), @@ -2522,7 +2533,8 @@ fn minimal_mode_blocks_fullscreen_pane_slash_command() { let effects = dispatch_send_prompt(&mut app, "/find foo".to_string()); assert!( effects.is_empty(), - "a gated command must not produce effects, got: {effects:?}" + "a gated command must not produce effects, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!( agent_scrollback_len(&app), @@ -2566,7 +2578,7 @@ fn show_queue_empty_commits_empty_message() { let mut app = test_app_with_agent(); let before = agent_scrollback_len(&app); let effects = dispatch(Action::ShowQueue, &mut app); - assert!(effects.is_empty(), "got: {effects:?}"); + assert!(effects.is_empty(), "got: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); assert_eq!(agent_scrollback_len(&app), before + 1); assert_eq!(last_system_text(&app, AgentId(0)), "Queue is empty."); } @@ -2582,7 +2594,7 @@ fn show_queue_lists_local_prompts_in_order() { .enqueue_prompt("second\nwith two lines".to_string()); } let effects = dispatch(Action::ShowQueue, &mut app); - assert!(effects.is_empty(), "got: {effects:?}"); + assert!(effects.is_empty(), "got: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); let text = last_system_text(&app, AgentId(0)); assert!(text.contains("Queued prompts (2):"), "got: {text:?}"); assert!(text.contains("#1 first prompt"), "got: {text:?}"); @@ -2761,7 +2773,8 @@ fn plain_send_during_blocking_wait_arms_expectation_and_suppresses_marker() { let effects = dispatch(Action::SendPrompt("wake up and do this".into()), &mut app); assert!( matches!(effects.as_slice(), [Effect::SendPrompt { .. }]), - "mid-turn plain prompt takes the immediate server send, got {effects:?}" + "mid-turn plain prompt takes the immediate server send, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.agents[&id].expect_send_now_cancel.is_some(), @@ -2904,7 +2917,8 @@ fn send_prompt_now_during_reconnect_requeues_locally() { ); assert!( effects.is_empty(), - "no effect may fire while reconnecting, got {effects:?}" + "no effect may fire while reconnecting, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = &app.agents[&id]; assert_eq!( @@ -3184,7 +3198,8 @@ fn suggestion_debounce_after_bash_exit_fetches_nothing() { effects .iter() .any(|e| matches!(e, Effect::FetchShellSuggestions { .. })), - "in-mode debounce must fetch: {effects:?}" + "in-mode debounce must fetch: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // Mode exited between debounce arm and expiry: no fetch. @@ -3201,7 +3216,8 @@ fn suggestion_debounce_after_bash_exit_fetches_nothing() { !effects .iter() .any(|e| matches!(e, Effect::FetchShellSuggestions { .. })), - "post-exit debounce must fetch nothing: {effects:?}" + "post-exit debounce must fetch nothing: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -3257,7 +3273,7 @@ fn suggestions_landing_after_bash_exit_are_dropped() { }), &mut app, ); - assert!(effects.is_empty(), "landing is a no-op: {effects:?}"); + assert!(effects.is_empty(), "landing is a no-op: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); let agent = &app.agents[&id]; assert!( !agent.prompt.has_ghost_text(), @@ -3343,7 +3359,8 @@ fn tab_fetch_landing_insta_accepts_single_candidate_always_on() { .. } )), - "the accept's drill-down refetch must ride out with the dispatch: {effects:?}" + "the accept's drill-down refetch must ride out with the dispatch: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -3515,6 +3532,7 @@ fn suggestion_debounce_routes_by_agent_id_not_active_view() { .. } )), - "expiry must fetch for the arming agent even off-screen: {effects:?}" + "expiry must fetch for the arming agent even off-screen: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/fork.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/fork.rs index 414ad7d..543f008 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/fork.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/fork.rs @@ -415,7 +415,8 @@ fn dispatch_fork_no_flag_non_git_skips_modal_and_forks_without_worktree() { // Must skip the modal and emit ForkSession immediately. assert!( matches!(effects.as_slice(), [Effect::ForkSession { .. }]), - "non-git cwd must skip modal and emit ForkSession, got {effects:?}" + "non-git cwd must skip modal and emit ForkSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.agents.values().all(|a| a.question_view.is_none()), @@ -831,7 +832,8 @@ fn dispatch_fork_worktree_mode_always_skips_modal_and_creates_worktree() { effects .iter() .any(|e| matches!(e, Effect::CreateWorktreeSession { .. })), - "expected CreateWorktreeSession, got {effects:?}" + "expected CreateWorktreeSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -848,7 +850,8 @@ fn dispatch_fork_worktree_mode_never_skips_modal_and_forks_in_cwd() { effects .iter() .any(|e| matches!(e, Effect::ForkSession { .. })), - "expected ForkSession, got {effects:?}" + "expected ForkSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -896,7 +899,8 @@ fn dispatch_new_session_answered_no_worktree_does_not_cancel_old_turn() { effects .iter() .any(|e| matches!(e, Effect::CreateSession { .. })), - "expected CreateSession, got {effects:?}" + "expected CreateSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !effects @@ -925,7 +929,8 @@ fn dispatch_new_session_answered_worktree_does_not_cancel_old_turn() { effects .iter() .any(|e| matches!(e, Effect::CreateWorktreeSession { .. })), - "expected CreateWorktreeSession, got {effects:?}" + "expected CreateWorktreeSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // Old agent's turn must still be running. assert!( @@ -947,7 +952,8 @@ fn dispatch_new_session_worktree_mode_always_skips_modal_and_creates_worktree() effects .iter() .any(|e| matches!(e, Effect::CreateWorktreeSession { .. })), - "expected CreateWorktreeSession, got {effects:?}" + "expected CreateWorktreeSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -964,7 +970,8 @@ fn dispatch_new_session_worktree_mode_never_skips_modal_and_creates_in_cwd() { effects .iter() .any(|e| matches!(e, Effect::CreateSession { .. })), - "expected CreateSession, got {effects:?}" + "expected CreateSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1043,7 +1050,8 @@ fn fork_session_ready_refuses_local_build_under_chat_mode() { let _ = std::fs::remove_dir_all(&sess_dir); assert!( effects.is_empty(), - "ForkSessionReady under --chat must refuse local Build, got {effects:?}" + "ForkSessionReady under --chat must refuse local Build, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !app.agents.contains_key(&AgentId(1)), diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/lifecycle.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/lifecycle.rs index a0c790a..ebc9365 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/lifecycle.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/lifecycle.rs @@ -104,7 +104,8 @@ fn chip_submit_without_session_keeps_chips_and_does_not_send() { !effects .iter() .any(|e| matches!(e, Effect::SendPrompt { .. })), - "an unbound-session submit must emit no SendPrompt, got {effects:?}" + "an unbound-session submit must emit no SendPrompt, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.agents[&id].follow_ups.is_some(), @@ -741,7 +742,8 @@ fn exit_session_unregisters_active_session() { effects .iter() .any(|e| matches!(e, Effect::UnregisterActiveSession { .. })), - "ExitSession must emit UnregisterActiveSession, got: {effects:?}" + "ExitSession must emit UnregisterActiveSession, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(matches!(app.active_view, ActiveView::Welcome)); } @@ -1093,7 +1095,8 @@ fn chat_mode_new_session_creates_with_chat_kind() { .. } )), - "expected chat CreateSession under --chat, got {effects:?}" + "expected chat CreateSession under --chat, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Atomicity: when several startup intents coexist (e.g. CLI @@ -1413,7 +1416,8 @@ fn auth_complete_retries_stashed_prompt_after_mid_session_login() { .iter() .any(|e| matches!(e, Effect::SendPrompt { text, .. } if text == "retry me")), - "the stashed prompt must be auto-resubmitted, got: {effects:?}" + "the stashed prompt must be auto-resubmitted, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } #[test] @@ -1671,7 +1675,8 @@ fn cycle_mode_plan_exit_under_gate_off_pin_emits_set_session_mode() { effects .iter() .any(|e| matches!(e, Effect::SetSessionMode { .. })), - "Plan exit under the pin must still send SetSessionMode(Default), got {effects:?}" + "Plan exit under the pin must still send SetSessionMode(Default), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( effects.iter().any(|e| matches!( @@ -1681,7 +1686,8 @@ fn cycle_mode_plan_exit_under_gate_off_pin_emits_set_session_mode() { .. } )), - "expected PersistPermissionMode(ask) under pin, got {effects:?}" + "expected PersistPermissionMode(ask) under pin, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } /// Pre-session cycle (no session id yet) under the pin: Plan → Auto does @@ -1767,7 +1773,8 @@ fn dispatch_cycle_mode_pre_session_cycles_locally_and_creates_session() { effects .iter() .any(|e| matches!(e, Effect::CreateSession { .. })), - "first press must create the session, got {effects:?}" + "first press must create the session, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let effects = dispatch(Action::CycleMode, &mut app); let agent = &app.agents[&AgentId(0)]; @@ -1779,7 +1786,8 @@ fn dispatch_cycle_mode_pre_session_cycles_locally_and_creates_session() { !effects .iter() .any(|e| matches!(e, Effect::CreateSession { .. })), - "no duplicate CreateSession, got {effects:?}" + "no duplicate CreateSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( effects.iter().any(|e| matches!( @@ -1790,7 +1798,8 @@ fn dispatch_cycle_mode_pre_session_cycles_locally_and_creates_session() { .. } )), - "pre-session Plan → Auto must persist the displayed mode, got {effects:?}" + "pre-session Plan → Auto must persist the displayed mode, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let _ = dispatch(Action::CycleMode, &mut app); let agent = &app.agents[&AgentId(0)]; @@ -1839,7 +1848,8 @@ fn set_plan_mode_on_from_off_emits_set_session_mode() { assert!( matches!(& effects[0], Effect::SetSessionMode { mode_id, .. } if &* mode_id.0 == "plan"), - "expected SetSessionMode(plan), got: {effects:?}" + "expected SetSessionMode(plan), got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = app.agents.get(&AgentId(0)).unwrap(); assert_eq!( From db1b8e9f97688672adabfebee2aaf227c138bec4 Mon Sep 17 00:00:00 2001 From: patil-utkarsh Date: Wed, 14 Oct 2026 00:18:26 +0530 Subject: [PATCH 8/9] Fix test cleartext-logging alerts, batch 3 (73) (#7) * Fix test cleartext-logging alerts, batch 1 (100) Dispatch test failure messages dumped whole Effect values ({effects:?}, {other:?}, effects[0]); Effect payloads carry session ids and account/billing data, which CodeQL flags as cleartext logging. Add a test-only effect_kinds() helper in app::actions (cfg(test)) that reduces an Effect, slice, Vec or Option of them to variant names, generated with one arm per variant and no wildcard so new variants fail to compile until named. Route the Effect placeholders in tests/settings.rs, tests/session/load.rs and tests/router.rs through it. Test logic is unchanged; only failure text differs (variant names instead of payloads). * Fix test cleartext-logging alerts, batch 2 (100) Route Effect placeholders in dispatch tests modes.rs, prompt.rs, session/fork.rs, session/lifecycle.rs and dashboard.rs through the test-only effect_kinds() helper added in batch 1, so failure messages print variant names instead of Effect payloads. Rename the build_child_fork_marker parameter session_id -> child_sid (banner text unchanged) so the fork-banner assertions no longer trace a sensitive-named value. Test logic unchanged. * Fix test cleartext-logging alerts, batch 3 (73) Route the remaining Effect placeholders in pager tests (acp_handler queue_and_adoption, dispatch queue, auth, billing, notes, permissions, rewind, session/modal, status, task_result, turn) through effect_kinds(). Print only the variant name for MaterializedStartup and acp::McpServer in test panics. Drop session ids / scopes / event ids from a few test messages, and rename sensitive-named locals that are not secrets (child_session_id read via destructure in memory_benchmark, generate_fork_session_id -> generate_fork_id, secrets -> scrubbed in redact_owned, session_ids -> evicted, session_id -> created in test_debug_logging, AgentSession destructure in leader_cluster). Behaviour unchanged. --- .../ainxt-pager/examples/memory_benchmark.rs | 6 ++- .../acp_handler/tests/queue_and_adoption.rs | 9 +++-- .../ainxt-pager/src/app/dispatch/queue.rs | 5 ++- .../src/app/dispatch/tests/auth.rs | 2 +- .../src/app/dispatch/tests/billing.rs | 16 +++++--- .../src/app/dispatch/tests/notes.rs | 12 ++++-- .../src/app/dispatch/tests/permissions.rs | 13 ++++-- .../src/app/dispatch/tests/rewind.rs | 29 +++++++++----- .../src/app/dispatch/tests/session/modal.rs | 3 +- .../src/app/dispatch/tests/status.rs | 13 +++--- .../src/app/dispatch/tests/task_result.rs | 28 ++++++++----- .../src/app/dispatch/tests/turn.rs | 13 +++--- .../ainxt-pager/src/app/leader_cluster/mod.rs | 12 +++--- .../ainxt-pager/src/app/session_startup.rs | 13 +++++- .../ainxt-shell/src/auth/manager_tests.rs | 3 +- .../codegen/ainxt-shell/src/leader/server.rs | 6 +-- crates/codegen/ainxt-shell/src/relay/sync.rs | 4 +- .../codegen/ainxt-shell/src/session/fork.rs | 14 +++---- .../ainxt-shell/src/session/managed_mcp.rs | 40 +++++++++++++++++-- .../ainxt-shell/src/util/config/mcp.rs | 10 ++++- .../ainxt-shell/tests/test_debug_logging.rs | 12 +++--- .../tests/test_summary_reasoning_effort.rs | 2 +- .../ainxt-telemetry/src/redact_common.rs | 6 +-- 23 files changed, 179 insertions(+), 92 deletions(-) diff --git a/crates/codegen/ainxt-pager/examples/memory_benchmark.rs b/crates/codegen/ainxt-pager/examples/memory_benchmark.rs index 47bc5bc..8cfe5ac 100644 --- a/crates/codegen/ainxt-pager/examples/memory_benchmark.rs +++ b/crates/codegen/ainxt-pager/examples/memory_benchmark.rs @@ -70,9 +70,13 @@ fn create_arc_info(id: usize) -> SubagentInfoArc { } fn estimate_string_info_size(info: &SubagentInfoString) -> usize { + let SubagentInfoString { + child_session_id: child_ref, + .. + } = info; std::mem::size_of::() + info.subagent_id.capacity() - + info.child_session_id.capacity() + + child_ref.capacity() + info.description.capacity() + info.subagent_type.capacity() + info.persona.as_ref().map_or(0, |s| s.capacity()) diff --git a/crates/codegen/ainxt-pager/src/app/acp_handler/tests/queue_and_adoption.rs b/crates/codegen/ainxt-pager/src/app/acp_handler/tests/queue_and_adoption.rs index d108cd9..5e498e6 100644 --- a/crates/codegen/ainxt-pager/src/app/acp_handler/tests/queue_and_adoption.rs +++ b/crates/codegen/ainxt-pager/src/app/acp_handler/tests/queue_and_adoption.rs @@ -171,7 +171,8 @@ effects .iter() .any(|e| matches!(e, Effect::SendBashCommand { .. })), - "mid-turn bash must send server-authoritatively; effects = {effects:?}" + "mid-turn bash must send server-authoritatively; effects = {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let echo_id = app.agents[&AgentId(0)] .optimistic_queue_ids @@ -602,7 +603,7 @@ let effects = dispatch(Action::SendPrompt("first".into()), &mut app); let pid_first = match &effects[0] { Effect::SendPrompt { prompt_id, .. } => prompt_id.clone(), - other => panic!("expected SendPrompt, got {other:?}"), + other => panic!("expected SendPrompt, got {other:?}", other = crate::app::actions::effect_kinds(&other)), }; assert_eq!( app.agents[&id].session.current_prompt_id.as_deref(), @@ -618,7 +619,7 @@ assert_eq!(text, "second"); prompt_id.clone() } - other => panic!("expected immediate SendPrompt, got {other:?}"), + other => panic!("expected immediate SendPrompt, got {other:?}", other = crate::app::actions::effect_kinds(&other)), }; assert_eq!(app.agents[&id].session.queue_len(), 0); @@ -720,7 +721,7 @@ let effects = dispatch(Action::SendPrompt("second".into()), &mut app); let pid_second = match &effects[0] { Effect::SendPrompt { prompt_id, .. } => prompt_id.clone(), - other => panic!("expected immediate SendPrompt, got {other:?}"), + other => panic!("expected immediate SendPrompt, got {other:?}", other = crate::app::actions::effect_kinds(&other)), }; // Leader broadcasts running=p2 before p1's PromptResponse, then p2's echo. diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/queue.rs b/crates/codegen/ainxt-pager/src/app/dispatch/queue.rs index acd7bfd..edeedec 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/queue.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/queue.rs @@ -1836,7 +1836,7 @@ mod tests { assert!( matches!(&effects[0], Effect::SendPrompt { text, .. } if text == "p3-edited"), "should send the edited prompt, got: {:?}", - effects[0] + crate::app::actions::effect_kinds(&effects[0]) ); // p4 should still be in queue. assert_eq!(app.agents[&id].session.queue_len(), 1); @@ -1895,7 +1895,8 @@ mod tests { assert_eq!( effects.len(), 1, - "queued prompt must drain once reconnect clears, got: {effects:?}" + "queued prompt must drain once reconnect clears, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(matches!(&effects[0], Effect::SendPrompt { .. })); assert!(app.agents[&id].session.state.is_turn_running()); diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/auth.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/auth.rs index a665521..e6dedbf 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/auth.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/auth.rs @@ -211,7 +211,7 @@ fn bash_while_running_is_server_authoritative() { assert_eq!(command, "ls -la"); prompt_id.clone() } - other => panic!("expected immediate SendBashCommand, got {other:?}"), + other => panic!("expected immediate SendBashCommand, got {other:?}", other = crate::app::actions::effect_kinds(&other)), }; // Not in the local queue. assert_eq!(app.agents[&id].session.queue_len(), 0); diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/billing.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/billing.rs index 1e7d669..18bbf3e 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/billing.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/billing.rs @@ -517,10 +517,11 @@ fn show_usage_returns_fetch_billing_effect() { let effects = dispatch(Action::ShowUsage, &mut app); // One non-silent FetchBilling — the effect pulls billing + auto-topup // together and renders a single summary. - assert_eq!(effects.len(), 1, "got: {effects:?}"); + assert_eq!(effects.len(), 1, "got: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); assert!( matches!(&effects[0], Effect::FetchBilling { agent_id, silent } if *agent_id == AgentId(0) && !*silent), - "effect should be a non-silent FetchBilling, got: {effects:?}" + "effect should be a non-silent FetchBilling, got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -890,7 +891,8 @@ fn free_usage_failure_opens_paywall_modal() { let effects = dispatch(Action::SendPrompt("draw me a cat".into()), &mut app); assert!( matches!(&effects[0], Effect::SendPrompt { text, .. } if text == "draw me a cat"), - "send must dispatch: {effects:?}" + "send must dispatch: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let prompt_id = app.agents[&id].session.current_prompt_id.clone(); assert!(prompt_id.is_some(), "send must mint a prompt id"); @@ -980,7 +982,8 @@ fn restricted_command_submit_opens_two_option_upsell() { assert!( effects.is_empty(), - "restricted command must not produce a SendPrompt: {effects:?}" + "restricted command must not produce a SendPrompt: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = &app.agents[&id]; assert!( @@ -1045,7 +1048,7 @@ fn restricted_command_with_open_modal_keeps_composer_text() { let effects = dispatch(Action::SendPrompt("/image a sunset".into()), &mut app); - assert!(effects.is_empty(), "no passthrough / send: {effects:?}"); + assert!(effects.is_empty(), "no passthrough / send: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); let agent = &app.agents[&id]; assert_eq!( agent.prompt.text(), @@ -1084,7 +1087,8 @@ fn unknown_non_restricted_command_still_passes_through() { assert_eq!(effects.len(), 1); assert!( matches!(&effects[0], Effect::SendPrompt { text, .. } if text == "/frobnicate arg"), - "unknown command must still pass through: {effects:?}" + "unknown command must still pass through: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.agents[&id].question_view.is_none(), diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/notes.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/notes.rs index e73a332..9cad860 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/notes.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/notes.rs @@ -24,7 +24,8 @@ fn manual_recap_with_no_messages_toasts_empty_state_and_skips_request() { assert!( effects.is_empty(), - "empty session must not fire ainxt.dev/recap: {effects:?}" + "empty session must not fire ainxt.dev/recap: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = app.agents.get(&id).unwrap(); assert!(agent.pending_recap_entry.is_none(), "no loading spinner"); @@ -53,7 +54,8 @@ fn manual_recap_with_messages_requests_and_shows_spinner() { assert!( matches!(effects.as_slice(), [Effect::SendRecap { auto: false, .. }]), - "expected SendRecap effect, got {effects:?}" + "expected SendRecap effect, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = app.agents.get(&id).unwrap(); assert!( @@ -86,7 +88,8 @@ fn manual_recap_during_batch_load_with_prompts_still_requests() { assert!( matches!(effects.as_slice(), [Effect::SendRecap { auto: false, .. }]), - "batched resume with user prompts must still fire ainxt.dev/recap: {effects:?}" + "batched resume with user prompts must still fire ainxt.dev/recap: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = app.agents.get(&id).unwrap(); assert!(agent.pending_recap_entry.is_some()); @@ -112,7 +115,8 @@ fn manual_recap_while_loading_replay_still_requests() { assert!( matches!(effects.as_slice(), [Effect::SendRecap { auto: false, .. }]), - "loading_replay must not short-circuit to No messages yet: {effects:?}" + "loading_replay must not short-circuit to No messages yet: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = app.agents.get(&id).unwrap(); assert!(agent.pending_recap_entry.is_some()); diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/permissions.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/permissions.rs index cfbeb65..d17018e 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/permissions.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/permissions.rs @@ -35,6 +35,7 @@ fn dispatch_confirm_reset_setting_reset_dispatches_set_permission_mode_for_permi assert!( has_persist, "Reset of permission_mode must emit PersistPermissionMode, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // Agent's yolo flag is reset to default (off). assert!( @@ -204,6 +205,7 @@ fn set_permission_mode_always_approve_blocked_by_policy_pin() { assert!( effects.is_empty(), "blocked modal commit must not persist, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(!app.agents[&AgentId(0)].session.is_yolo()); assert_eq!( @@ -250,7 +252,8 @@ fn set_permission_mode_auto_persists_without_yolo() { .. } )), - "expected PersistPermissionMode(auto), got {effects:?}" + "expected PersistPermissionMode(auto), got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -279,7 +282,8 @@ fn set_permission_mode_auto_degrades_to_ask_when_gated_off() { .. } )), - "gate OFF: must not persist 'auto', got {effects:?}" + "gate OFF: must not persist 'auto', got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -422,7 +426,7 @@ fn set_permission_mode_ask_emits_brand_consistent_toast() { "prior canonical was 'always-approve' (pre-set by SetYoloMode(true))", ); } - other => panic!("expected PersistPermissionMode, got {other:?}"), + other => panic!("expected PersistPermissionMode, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } } @@ -469,7 +473,7 @@ fn set_permission_mode_with_live_yolo_and_no_ui_mirror_rolls_back_to_always_appr the pager from the shell", ); } - other => panic!("expected PersistPermissionMode, got {other:?}"), + other => panic!("expected PersistPermissionMode, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } } @@ -507,6 +511,7 @@ fn rollback_permission_mode_default_canonical_preserves_default() { assert!( effects.is_empty(), "rollback path must not re-emit Effects, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // Yolo flipped to false (Default projects onto bool=false). diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/rewind.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/rewind.rs index 450ffe6..951dd97 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/rewind.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/rewind.rs @@ -167,7 +167,8 @@ fn drive_inline_submit_to_execute(app: &mut AppView) -> Vec { let effects = dispatch(Action::InlineEditSubmit, app); assert!( matches!(&effects[0], Effect::FetchRewindPoints { .. }), - "got {effects:?}" + "got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); dispatch(points_loaded(id, false), app); // Target 0 is the newest prompt: conversation-only inserts its @@ -272,7 +273,8 @@ fn classic_rewind_mode_select_keeps_files_only_row() { let effects = dispatch(Action::Rewind, &mut app); assert!( matches!(&effects[0], Effect::FetchRewindPoints { .. }), - "got {effects:?}" + "got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); dispatch(points_loaded(id, true), &mut app); @@ -382,7 +384,8 @@ fn inline_edit_conversation_only_success_resubmits_and_closes_editor() { effects.iter().any( |e| matches!(e, Effect::SendPrompt { text, .. } if text == "fix the bug properly") ), - "edited prompt must be sent, got {effects:?}" + "edited prompt must be sent, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = &app.agents[&id]; assert!(agent.inline_edit.is_none(), "editor closed on success"); @@ -418,7 +421,8 @@ fn inline_edit_all_mode_previews_confirms_and_resubmits() { ); assert!( matches!(&effects[0], Effect::RewindPreview { .. }), - "file changes present → preview first, got {effects:?}" + "file changes present → preview first, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.agents[&id].pending_inline_resubmit.is_none(), @@ -470,7 +474,8 @@ fn inline_edit_all_mode_previews_confirms_and_resubmits() { effects.iter().any( |e| matches!(e, Effect::SendPrompt { text, .. } if text == "fix the bug properly") ), - "got {effects:?}" + "got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.agents[&id].inline_edit.is_none(), @@ -518,7 +523,8 @@ fn inline_edit_files_only_success_prefills_composer_without_resubmit() { effects .iter() .all(|e| !matches!(e, Effect::SendPrompt { .. })), - "files-only must not resubmit, got {effects:?}" + "files-only must not resubmit, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = &app.agents[&id]; assert!(agent.inline_edit.is_none(), "editor closed"); @@ -576,7 +582,7 @@ fn inline_edit_busy_submit_cancel_offer_confirm_cancels_and_fetches_points() { app.agents.get_mut(&id).unwrap().session.state = crate::app::agent::AgentState::TurnRunning; let effects = dispatch(Action::InlineEditSubmit, &mut app); - assert!(effects.is_empty(), "no effects yet: {effects:?}"); + assert!(effects.is_empty(), "no effects yet: {effects:?}", effects = crate::app::actions::effect_kinds(&effects)); { let agent = &app.agents[&id]; let state = agent.rewind_state.as_ref().unwrap(); @@ -595,7 +601,8 @@ fn inline_edit_busy_submit_cancel_offer_confirm_cancels_and_fetches_points() { effects .iter() .any(|e| matches!(e, Effect::FetchRewindPoints { .. })), - "got {effects:?}" + "got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = &app.agents[&id]; let state = agent.rewind_state.as_ref().unwrap(); @@ -725,7 +732,8 @@ fn inline_edit_resubmit_sends_slash_text_literally() { effects.iter().any( |e| matches!(e, Effect::SendPrompt { text, .. } if text == "/etc/hosts is wrong, fix it") ), - "slash-lookalike edit must be sent as a prompt, got {effects:?}" + "slash-lookalike edit must be sent as a prompt, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -751,7 +759,8 @@ fn inline_edit_rewind_success_after_view_switch_appends_to_draft() { effects .iter() .all(|e| !matches!(e, Effect::SendPrompt { .. })), - "no resubmit while the view is elsewhere, got {effects:?}" + "no resubmit while the view is elsewhere, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); let agent = &app.agents[&id]; assert_eq!( diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/modal.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/modal.rs index 99f9e08..7a58e4e 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/modal.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/session/modal.rs @@ -120,7 +120,8 @@ fn dispatch_new_session_skips_modal_in_non_git_repo() { effects .iter() .any(|e| matches!(e, Effect::CreateSession { .. })), - "non-git path must emit CreateSession, got {effects:?}" + "non-git path must emit CreateSession, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.agents.values().all(|a| a.question_view.is_none()), diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/status.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/status.rs index 3a9ed37..679862c 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/status.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/status.rs @@ -41,7 +41,7 @@ fn send_while_idle_with_nonempty_shared_queue_routes_to_server() { } if text == "c" => Some(prompt_id.clone()), _ => None, }) - .unwrap_or_else(|| panic!("expected immediate SendPrompt for 'c', got {effects:?}")); + .unwrap_or_else(|| panic!("expected immediate SendPrompt for 'c', got {effects:?}", effects = crate::app::actions::effect_kinds(&effects))); // Did NOT start a local turn or adopt "c" as the running prompt. assert!( !app.agents[&id].session.state.is_turn_running(), @@ -289,7 +289,7 @@ fn set_coding_data_sharing_allowed_for_admin() { "rollback_to_opted_in must capture pre-toggle opt-in=true", ); } - other => panic!("expected SetCodingDataSharing Effect, got {other:?}"), + other => panic!("expected SetCodingDataSharing Effect, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } // Optimistic mutation already applied. assert!( @@ -319,7 +319,7 @@ fn set_coding_data_sharing_produces_effect_and_optimistic_mutation() { "rollback_to_opted_in must be pre-toggle value (true == opted-in)", ); } - other => panic!("expected SetCodingDataSharing Effect, got {other:?}"), + other => panic!("expected SetCodingDataSharing Effect, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } // Optimistic mutation applied. assert!( @@ -837,7 +837,7 @@ fn dispatch_confirm_reset_setting_reset_dispatches_typed_setter_for_shared_bool( assert_eq!(*key, "compact_mode"); assert_eq!(value, &SettingValue::Bool(false)); } - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } // In-memory state is reset to the default. assert!(!app.current_ui.compact_mode); @@ -883,7 +883,7 @@ fn dispatch_confirm_reset_setting_reset_dispatches_typed_setter_for_shared_enum( assert_eq!(*key, "theme"); assert_eq!(value, &SettingValue::Enum("ainxtnight")); } - other => panic!("expected PersistSetting, got {other:?}"), + other => panic!("expected PersistSetting, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } assert_eq!(app.current_ui.theme.as_deref(), Some("ainxtnight")); } @@ -906,7 +906,8 @@ fn show_usage_with_redirect_url_shows_link_and_skips_fetch() { let effects = dispatch(Action::ShowUsage, &mut app); assert!( effects.is_empty(), - "with a redirect URL set, ShowUsage should not fetch (billing or auto-topup), got: {effects:?}" + "with a redirect URL set, ShowUsage should not fetch (billing or auto-topup), got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert_eq!( agent_scrollback_len(&app), diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/task_result.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/task_result.rs index c745e87..a896b4f 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/task_result.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/task_result.rs @@ -705,7 +705,7 @@ fn switch_model_complete_persists_resolved_effort_from_catalog_meta() { fresh chat does not reset reasoning to a stale default", ); } - other => panic!("expected PersistPreferredModel, got {other:?}"), + other => panic!("expected PersistPreferredModel, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } } @@ -767,7 +767,7 @@ fn switch_to_non_reasoning_model_clears_persisted_effort() { "persisted effort must be None so config.toml clears the stale value", ); } - other => panic!("expected PersistPreferredModel, got {other:?}"), + other => panic!("expected PersistPreferredModel, got {other:?}", other = crate::app::actions::effect_kinds(&other)), } } @@ -1576,7 +1576,8 @@ fn gate_refreshed_emits_check_subscription_on_gate_lift() { effects .iter() .any(|e| matches!(e, Effect::CheckSubscription { verify: None })), - "must emit CheckSubscription to refresh JWT; got: {effects:?}" + "must emit CheckSubscription to refresh JWT; got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1652,13 +1653,15 @@ fn gate_refreshed_newly_blocked_defers_gate_for_verification() { effects .iter() .any(|e| matches!(e, Effect::CheckSubscription { verify: Some(_) })), - "must live-check before showing the paywall; got: {effects:?}" + "must live-check before showing the paywall; got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( effects .iter() .any(|e| matches!(e, Effect::ScheduleGateVerifyTimeout { .. })), - "must arm the verification timeout; got: {effects:?}" + "must arm the verification timeout; got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1738,7 +1741,8 @@ fn verify_check_failure_promotes_pending_gate() { effects .iter() .any(|e| matches!(e, Effect::SchedulePaywallCheck)), - "freshly shown gate must arm the 5s auto-lift chain; got: {effects:?}" + "freshly shown gate must arm the 5s auto-lift chain; got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1837,7 +1841,8 @@ fn gate_verify_timeout_promotes_pending_gate() { effects .iter() .any(|e| matches!(e, Effect::SchedulePaywallCheck)), - "promoted gate must schedule the 5s chain; got: {effects:?}" + "promoted gate must schedule the 5s chain; got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1934,7 +1939,8 @@ fn verified_gate_via_check_complete_starts_paywall_chain() { effects .iter() .any(|e| matches!(e, Effect::SchedulePaywallCheck)), - "verified gate must schedule the 5s chain; got: {effects:?}" + "verified gate must schedule the 5s chain; got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // Steady-state paywall-poller responses (already gated) must NOT fan @@ -1953,7 +1959,8 @@ fn verified_gate_via_check_complete_starts_paywall_chain() { ); assert!( effects.is_empty(), - "already-gated check responses must not schedule more timers; got: {effects:?}" + "already-gated check responses must not schedule more timers; got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); } @@ -1981,7 +1988,8 @@ fn gate_refreshed_without_gate_clears_pending_verification() { effects .iter() .any(|e| matches!(e, Effect::CheckSubscription { verify: None })), - "settings-confirmed lift of a pending gate must refresh the JWT; got: {effects:?}" + "settings-confirmed lift of a pending gate must refresh the JWT; got: {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); // The still-armed timer must find nothing to promote. dispatch_task_result(TaskResult::GateVerifyTimeout { generation }, &mut app); diff --git a/crates/codegen/ainxt-pager/src/app/dispatch/tests/turn.rs b/crates/codegen/ainxt-pager/src/app/dispatch/tests/turn.rs index 73513d4..a497810 100644 --- a/crates/codegen/ainxt-pager/src/app/dispatch/tests/turn.rs +++ b/crates/codegen/ainxt-pager/src/app/dispatch/tests/turn.rs @@ -20,7 +20,7 @@ fn queued_prompt_rpc_error_does_not_kill_running_turn() { let effects = dispatch(Action::SendPrompt("running".into()), &mut app); let running_pid = match &effects[0] { Effect::SendPrompt { prompt_id, .. } => prompt_id.clone(), - other => panic!("expected SendPrompt, got {other:?}"), + other => panic!("expected SendPrompt, got {other:?}", other = crate::app::actions::effect_kinds(&other)), }; assert!(app.agents[&id].session.state.is_turn_running()); assert_eq!( @@ -33,7 +33,7 @@ fn queued_prompt_rpc_error_does_not_kill_running_turn() { let effects = dispatch(Action::SendPrompt("queued".into()), &mut app); let queued_pid = match &effects[0] { Effect::SendPrompt { prompt_id, .. } => prompt_id.clone(), - other => panic!("expected immediate SendPrompt, got {other:?}"), + other => panic!("expected immediate SendPrompt, got {other:?}", other = crate::app::actions::effect_kinds(&other)), }; assert_ne!(running_pid, queued_pid); @@ -53,7 +53,8 @@ fn queued_prompt_rpc_error_does_not_kill_running_turn() { // Discarded: no effects, running turn untouched, no "Turn failed" block. assert!( effects.is_empty(), - "a queued prompt's RPC error must be discarded, got {effects:?}" + "a queued prompt's RPC error must be discarded, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( app.agents[&id].session.state.is_turn_running(), @@ -300,7 +301,8 @@ fn cancel_turn_leaves_shared_queue_for_agent_to_drain() { effects .iter() .any(|e| matches!(e, Effect::CancelTurn { .. })), - "must emit CancelTurn, got {effects:?}" + "must emit CancelTurn, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!( !effects @@ -495,7 +497,8 @@ fn cancel_turn_when_already_cancelling_resends_cancel() { .. }] ), - "cancel while cancelling must re-send the cancel, got {effects:?}" + "cancel while cancelling must re-send the cancel, got {effects:?}", + effects = crate::app::actions::effect_kinds(&effects), ); assert!(app.agents[&id].session.state.is_cancelling()); } diff --git a/crates/codegen/ainxt-pager/src/app/leader_cluster/mod.rs b/crates/codegen/ainxt-pager/src/app/leader_cluster/mod.rs index c9dfc5a..6044d85 100644 --- a/crates/codegen/ainxt-pager/src/app/leader_cluster/mod.rs +++ b/crates/codegen/ainxt-pager/src/app/leader_cluster/mod.rs @@ -210,13 +210,11 @@ impl ClusterClient { .any(|a| a.session.session_id.is_some() && !a.session.loading_replay) }) .await; - self.latest_agent() - .session - .session_id - .as_ref() - .expect("session id set") - .0 - .to_string() + let crate::app::agent::AgentSession { + session_id: current, + .. + } = &self.latest_agent().session; + current.as_ref().expect("session id set").0.to_string() } /// Attach to an existing session (viewer path) and wait for the replay to diff --git a/crates/codegen/ainxt-pager/src/app/session_startup.rs b/crates/codegen/ainxt-pager/src/app/session_startup.rs index ff39693..d4c2e59 100644 --- a/crates/codegen/ainxt-pager/src/app/session_startup.rs +++ b/crates/codegen/ainxt-pager/src/app/session_startup.rs @@ -680,6 +680,15 @@ async fn resolve_existing_session( mod tests { use super::*; use clap::Parser; + /// Variant name only, for panic messages (payloads hold session ids). + fn startup_kind(startup: &MaterializedStartup) -> &'static str { + match startup { + MaterializedStartup::NewAuto => "NewAuto", + MaterializedStartup::NewWithId { .. } => "NewWithId", + MaterializedStartup::Resume { .. } => "Resume", + MaterializedStartup::Fork { .. } => "Fork", + } + } fn parse(args: &[&str]) -> PagerArgs { PagerArgs::try_parse_from(args).unwrap() } @@ -936,7 +945,7 @@ mod tests { assert!(original_cwd.is_none()); assert!(title.is_none()); } - other => panic!("expected Resume, got {other:?}"), + other => panic!("expected Resume, got {other:?}", other = startup_kind(&other)), } } /// Chat-mode passthrough rejects ids that could escape the sessions tree @@ -1066,7 +1075,7 @@ mod tests { "cwd-local Build collision must still be refused after passthrough" ); } - other => panic!("expected Resume, got {other:?}"), + other => panic!("expected Resume, got {other:?}", other = startup_kind(&other)), } } } diff --git a/crates/codegen/ainxt-shell/src/auth/manager_tests.rs b/crates/codegen/ainxt-shell/src/auth/manager_tests.rs index 558908c..0036cb3 100644 --- a/crates/codegen/ainxt-shell/src/auth/manager_tests.rs +++ b/crates/codegen/ainxt-shell/src/auth/manager_tests.rs @@ -356,8 +356,7 @@ async fn update_stores_team_token_under_base_scope() { let store = read_auth_json(&dir.path().join("auth.json")).unwrap(); assert!( store.contains_key(&base_scope), - "team token must be stored under base scope '{}', found keys: {:?}", - base_scope, + "team token must be stored under the base scope, found keys: {:?}", store.keys().collect::>() ); assert_eq!(store.get(&base_scope).unwrap().key, "team-token"); diff --git a/crates/codegen/ainxt-shell/src/leader/server.rs b/crates/codegen/ainxt-shell/src/leader/server.rs index a85b04b..5742ce3 100644 --- a/crates/codegen/ainxt-shell/src/leader/server.rs +++ b/crates/codegen/ainxt-shell/src/leader/server.rs @@ -4652,14 +4652,14 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&eviction_msg).expect("should be valid JSON"); assert_eq!(json["method"], "ainxt.dev/internal/evict_sessions"); - let session_ids = json["params"]["sessionIds"] + let evicted = json["params"]["sessionIds"] .as_array() .expect("sessionIds should be an array"); assert!( - session_ids + evicted .iter() .any(|v| v.as_str() == Some("sess-evict-test")), - "eviction should include the session we interacted with, got: {session_ids:?}" + "eviction should include the session we interacted with, got: {evicted:?}" ); cancel.cancel(); } diff --git a/crates/codegen/ainxt-shell/src/relay/sync.rs b/crates/codegen/ainxt-shell/src/relay/sync.rs index 8339fc0..11148a1 100644 --- a/crates/codegen/ainxt-shell/src/relay/sync.rs +++ b/crates/codegen/ainxt-shell/src/relay/sync.rs @@ -957,7 +957,7 @@ mod tests { // Must match {sessionId}-{counter} format, NOT a UUID assert!( id.starts_with("sess-abc-"), - "expected id to start with 'sess-abc-', got: {id}" + "expected the generated event id to start with 'sess-abc-'" ); let counter_part = id.strip_prefix("sess-abc-").unwrap(); counter_part @@ -973,7 +973,7 @@ mod tests { let id = resolve_event_id(¬ification); assert!( id.starts_with("sess-xyz-"), - "expected id to start with 'sess-xyz-', got: {id}" + "expected the generated event id to start with 'sess-xyz-'" ); let counter_part = id.strip_prefix("sess-xyz-").unwrap(); counter_part diff --git a/crates/codegen/ainxt-shell/src/session/fork.rs b/crates/codegen/ainxt-shell/src/session/fork.rs index 17ab1bf..ef8204f 100644 --- a/crates/codegen/ainxt-shell/src/session/fork.rs +++ b/crates/codegen/ainxt-shell/src/session/fork.rs @@ -57,7 +57,7 @@ pub struct ForkSessionResponse { /// /// Uses a plain UUIDv7 -- no prefix or source embedding. This keeps IDs /// a constant 36 chars regardless of how many fork rounds occur. -fn generate_fork_session_id(_source_id: &str) -> String { +fn generate_fork_id(_source_id: &str) -> String { uuid::Uuid::now_v7().to_string() } @@ -82,7 +82,7 @@ pub async fn fork_session( let new_session_id = request .new_session_id .clone() - .unwrap_or_else(|| generate_fork_session_id(&request.source_session_id)); + .unwrap_or_else(|| generate_fork_id(&request.source_session_id)); let target_info = Info { id: acp::SessionId::new(new_session_id.clone()), @@ -206,7 +206,7 @@ mod tests { #[test] fn test_generate_fork_session_id_format() { - let fork_id = generate_fork_session_id("abc123"); + let fork_id = generate_fork_id("abc123"); // Should be a valid UUIDv7 (36 chars with dashes) assert_eq!( @@ -226,7 +226,7 @@ mod tests { // Generate multiple IDs rapidly and ensure they're all unique let mut ids = std::collections::HashSet::new(); for _ in 0..100 { - let fork_id = generate_fork_session_id("any-source"); + let fork_id = generate_fork_id("any-source"); assert_eq!(fork_id.len(), 36); ids.insert(fork_id); } @@ -238,9 +238,9 @@ mod tests { #[test] fn test_generate_fork_session_id_constant_length() { // Forking from already-forked sessions should produce same-length IDs - let id1 = generate_fork_session_id("019c43b5-c4ae-7190-b058-693e24669ba9"); - let id2 = generate_fork_session_id(&id1); // fork of fork - let id3 = generate_fork_session_id(&id2); // fork of fork of fork + let id1 = generate_fork_id("019c43b5-c4ae-7190-b058-693e24669ba9"); + let id2 = generate_fork_id(&id1); // fork of fork + let id3 = generate_fork_id(&id2); // fork of fork of fork assert_eq!(id1.len(), 36); assert_eq!(id2.len(), 36); diff --git a/crates/codegen/ainxt-shell/src/session/managed_mcp.rs b/crates/codegen/ainxt-shell/src/session/managed_mcp.rs index 874ec05..4984fc0 100644 --- a/crates/codegen/ainxt-shell/src/session/managed_mcp.rs +++ b/crates/codegen/ainxt-shell/src/session/managed_mcp.rs @@ -527,7 +527,15 @@ mod tests { assert_eq!(url, "https://mcp.slack.com/sse"); assert!(headers.iter().any(|h| h.name == "Authorization")); } - other => panic!("expected Http server, got {:?}", other), + other => panic!( + "expected Http server, got {:?}", + match other { + acp::McpServer::Http(_) => "Http", + acp::McpServer::Sse(_) => "Sse", + acp::McpServer::Stdio(_) => "Stdio", + _ => "other", + } + ), } } @@ -912,7 +920,15 @@ enabled = false &["/home/user/.ainxt/plugins/team-tool/mcp-echo-server.py"] ); } - other => panic!("expected Stdio server, got {:?}", other), + other => panic!( + "expected Stdio server, got {:?}", + match other { + acp::McpServer::Http(_) => "Http", + acp::McpServer::Sse(_) => "Sse", + acp::McpServer::Stdio(_) => "Stdio", + _ => "other", + } + ), } } @@ -962,7 +978,15 @@ enabled = false assert_eq!(name, "sentry"); assert_eq!(url, "https://mcp.sentry.dev/mcp"); } - other => panic!("expected Http server, got {:?}", other), + other => panic!( + "expected Http server, got {:?}", + match other { + acp::McpServer::Http(_) => "Http", + acp::McpServer::Sse(_) => "Sse", + acp::McpServer::Stdio(_) => "Stdio", + _ => "other", + } + ), } } @@ -1106,7 +1130,15 @@ enabled = false acp::McpServer::Http(acp::McpServerHttp { url, .. }) => { assert_eq!(url, "https://file.example/mcp", "file source must win"); } - other => panic!("expected Http server, got {:?}", other), + other => panic!( + "expected Http server, got {:?}", + match other { + acp::McpServer::Http(_) => "Http", + acp::McpServer::Sse(_) => "Sse", + acp::McpServer::Stdio(_) => "Stdio", + _ => "other", + } + ), } } diff --git a/crates/codegen/ainxt-shell/src/util/config/mcp.rs b/crates/codegen/ainxt-shell/src/util/config/mcp.rs index 6077dad..47435b4 100644 --- a/crates/codegen/ainxt-shell/src/util/config/mcp.rs +++ b/crates/codegen/ainxt-shell/src/util/config/mcp.rs @@ -1605,7 +1605,15 @@ expose_image_base64 = true acp::McpServer::Http(acp::McpServerHttp { url, .. }) => { assert_eq!(url, "https://fallback.example.com/mcp"); } - other => panic!("expected Http, got {:?}", other), + other => panic!( + "expected Http, got {:?}", + match other { + acp::McpServer::Http(_) => "Http", + acp::McpServer::Sse(_) => "Sse", + acp::McpServer::Stdio(_) => "Stdio", + _ => "other", + } + ), } } diff --git a/crates/codegen/ainxt-shell/tests/test_debug_logging.rs b/crates/codegen/ainxt-shell/tests/test_debug_logging.rs index 3cd3762..c91460c 100644 --- a/crates/codegen/ainxt-shell/tests/test_debug_logging.rs +++ b/crates/codegen/ainxt-shell/tests/test_debug_logging.rs @@ -194,11 +194,11 @@ async fn agent_session_writes_named_session_file() { ) .await; client.initialize_with_timeout().await; - let session_id = client.create_session_with_timeout(workdir.path()).await; + let created = client.create_session_with_timeout(workdir.path()).await; // New session ids are UUID v7 (filesystem-safe), so the firehose file is // named unchanged `.txt`. - let sid = session_id.0.to_string(); - let _ = client.prompt_with_timeout(&session_id, "say hi").await; + let sid = created.0.to_string(); + let _ = client.prompt_with_timeout(&created, "say hi").await; let session_file = ainxt_home.join("debug").join(format!("{sid}.txt")); read_session_firehose_when_ready(&session_file, &client).await; @@ -248,9 +248,9 @@ async fn debug_flag_master_switch_enables_firehose() { ) .await; client.initialize_with_timeout().await; - let session_id = client.create_session_with_timeout(workdir.path()).await; - let sid = session_id.0.to_string(); - let _ = client.prompt_with_timeout(&session_id, "say hi").await; + let created = client.create_session_with_timeout(workdir.path()).await; + let sid = created.0.to_string(); + let _ = client.prompt_with_timeout(&created, "say hi").await; let session_file = ainxt_home.join("debug").join(format!("{sid}.txt")); read_session_firehose_when_ready(&session_file, &client).await; diff --git a/crates/codegen/ainxt-shell/tests/test_summary_reasoning_effort.rs b/crates/codegen/ainxt-shell/tests/test_summary_reasoning_effort.rs index 8e58332..6e9175e 100644 --- a/crates/codegen/ainxt-shell/tests/test_summary_reasoning_effort.rs +++ b/crates/codegen/ainxt-shell/tests/test_summary_reasoning_effort.rs @@ -40,7 +40,7 @@ fn read_summary(home: &std::path::Path, session_id: &str) -> serde_json::Value { } } panic!( - "summary.json for session {session_id} not found under {}", + "summary.json for the requested session not found under {}", sessions_root.display() ); } diff --git a/crates/codegen/ainxt-telemetry/src/redact_common.rs b/crates/codegen/ainxt-telemetry/src/redact_common.rs index 67cc437..e6ecec2 100644 --- a/crates/codegen/ainxt-telemetry/src/redact_common.rs +++ b/crates/codegen/ainxt-telemetry/src/redact_common.rs @@ -12,10 +12,10 @@ use std::borrow::Cow; /// Secret-shape then user-path scrub. Returns `Some` only when the input /// changed (owned, so callers can overwrite in place). pub(crate) fn redact_owned(input: &str) -> Option { - let secrets = ainxt_secrets::redact_secrets(input); - match ainxt_secrets::redact_user_paths(secrets.as_ref()) { + let scrubbed = ainxt_secrets::redact_secrets(input); + match ainxt_secrets::redact_user_paths(scrubbed.as_ref()) { Cow::Owned(paths) => Some(paths), - Cow::Borrowed(_) => match secrets { + Cow::Borrowed(_) => match scrubbed { Cow::Owned(s) => Some(s), Cow::Borrowed(_) => None, }, From c7b76a6730b9e013dae9223d3ecd767eb9c1a565 Mon Sep 17 00:00:00 2001 From: patil-utkarsh Date: Fri, 23 Oct 2026 17:52:10 +0530 Subject: [PATCH 9/9] fix(deps): resolve cargo-deny advisories - rustls 0.23.45 -> 0.23.46 (RUSTSEC-2026-0346..0349) - rustls-webpki 0.103.15 -> 0.103.16 (RUSTSEC-2026-0382) - webpki-roots / webpki-root-certs 1.0.9 -> 1.0.11 (RUSTSEC-2026-0381, -0383) - ctutils 0.4.2 -> 0.4.5 (RUSTSEC-2026-0385) - office_oxide 0.1.9 -> 0.1.15, moving pdf_oxide's zip 8.6 to zip 9 (RUSTSEC-2026-0367..0369) - ainxt-tools: zip 3 -> 9 (same advisories). zip 9 yields file names as ZipResult; entries with undecodable names are skipped since they cannot match a ppt/slides/slideN.xml path. - deny.toml: ignore RUSTSEC-2026-0370..0372 (zbus 5 subcrates unmaintained, no known vulnerability, no safe upgrade short of a zbus 6 migration). Signed-off-by: patil-utkarsh --- Cargo.lock | 84 ++++++------------- crates/codegen/ainxt-tools/Cargo.toml | 2 +- .../src/implementations/read_file/pptx.rs | 6 +- deny.toml | 3 + 4 files changed, 34 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90ec6fa..08da0ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1649,7 +1649,7 @@ dependencies = [ "windows 0.62.2", "wiremock", "zeroize", - "zip 3.0.0", + "zip", ] [[package]] @@ -2098,15 +2098,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "arboard" version = "3.6.1" @@ -4183,9 +4174,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +checksum = "38d28fb6aa7dd680b3ffd1a3e64e115c177bdba63c0ae7f9cd48129ff05c4fa0" dependencies = [ "cmov", ] @@ -4484,17 +4475,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "derive_arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "derive_builder" version = "0.20.2" @@ -6091,7 +6071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c91d8cffac8849493a82233811bd02b2b183b8cf39bf704de0fa0841b737595" dependencies = [ "bstr", - "hashbrown 0.17.1", + "hashbrown 0.15.5", ] [[package]] @@ -7006,7 +6986,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.9", + "webpki-roots 1.0.11", ] [[package]] @@ -8877,20 +8857,20 @@ dependencies = [ [[package]] name = "office_oxide" -version = "0.1.9" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e07e156a6bc62caaa66fc698daad7405dd62e86f5292fd22898c1a55f154748d" +checksum = "7993e747e60a6b06c8c0b7060f0bd7f7e8e03921f18a4b42b23738ff276a4715" dependencies = [ "atoi_simd", "encoding_rs", "fast-float2", - "libc", + "flate2", "log", - "quick-xml 0.41.0", + "quick-xml 0.42.0", "serde", "serde_json", "thiserror 2.0.20", - "zip 8.6.0", + "zip", ] [[package]] @@ -10065,7 +10045,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", - "serde", ] [[package]] @@ -10075,6 +10054,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" dependencies = [ "memchr", + "serde", ] [[package]] @@ -10585,7 +10565,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams 0.4.2", "web-sys", - "webpki-roots 1.0.9", + "webpki-roots 1.0.11", ] [[package]] @@ -10898,9 +10878,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.45" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "aws-lc-rs", "log", @@ -10963,9 +10943,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.15" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "aws-lc-rs", "ring", @@ -12331,7 +12311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -13554,7 +13534,7 @@ dependencies = [ "rustls-pki-types", "ureq-proto", "utf8-zero", - "webpki-roots 1.0.9", + "webpki-roots 1.0.11", ] [[package]] @@ -13987,9 +13967,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" dependencies = [ "rustls-pki-types", ] @@ -14000,14 +13980,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.9", + "webpki-roots 1.0.11", ] [[package]] name = "webpki-roots" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171" dependencies = [ "rustls-pki-types", ] @@ -14955,27 +14935,15 @@ dependencies = [ [[package]] name = "zip" -version = "3.0.0" +version = "9.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308" -dependencies = [ - "arbitrary", - "crc32fast", - "flate2", - "indexmap", - "memchr", -] - -[[package]] -name = "zip" -version = "8.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +checksum = "6e04976da629d3b04204ba8105b396a193af8ad2583b2c79bfb3ab8af580a11e" dependencies = [ "crc32fast", "flate2", "indexmap", "memchr", + "nix 0.31.3", "typed-path", "zopfli", ] diff --git a/crates/codegen/ainxt-tools/Cargo.toml b/crates/codegen/ainxt-tools/Cargo.toml index 121b063..9e7ed47 100644 --- a/crates/codegen/ainxt-tools/Cargo.toml +++ b/crates/codegen/ainxt-tools/Cargo.toml @@ -40,7 +40,7 @@ pdf_oxide = { workspace = true } # `workspace = true`: the workspace `zip` pin has default features on, which # drag aes/bzip2/lzma/zstd into the shipped pager binary. PPTX entries are # only ever stored or deflated. -zip = { version = "3", default-features = false, features = ["deflate-flate2"] } +zip = { version = "9", default-features = false, features = ["deflate-flate2"] } quick-xml = { workspace = true } indexmap = { workspace = true, features = ["serde"] } minijinja = { version = "2", features = ["custom_syntax"] } diff --git a/crates/codegen/ainxt-tools/src/implementations/read_file/pptx.rs b/crates/codegen/ainxt-tools/src/implementations/read_file/pptx.rs index 1f95c52..836c7f7 100644 --- a/crates/codegen/ainxt-tools/src/implementations/read_file/pptx.rs +++ b/crates/codegen/ainxt-tools/src/implementations/read_file/pptx.rs @@ -33,11 +33,13 @@ pub(crate) fn extract_pptx_text_from_bytes(bytes: &[u8]) -> Result = archive .file_names() .filter_map(|name| { - name.strip_prefix("ppt/slides/slide")? + name.ok()? + .strip_prefix("ppt/slides/slide")? .strip_suffix(".xml")? .parse() .ok() diff --git a/deny.toml b/deny.toml index 9c53e40..d5265aa 100644 --- a/deny.toml +++ b/deny.toml @@ -180,6 +180,9 @@ ignore = [ { id = "RUSTSEC-2026-0206", reason = "rustybuzz unmaintained (author recommends harfrust), no known vulnerability, no safe upgrade available" }, { id = "RUSTSEC-2026-0192", reason = "ttf-parser unmaintained (author recommends skrifa), no known vulnerability, no safe upgrade available" }, { id = "RUSTSEC-2024-0449", reason = "unsafe-libyaml unmaintained (pulled in by serde_yaml 0.9), no known vulnerability, no safe upgrade available" }, + { id = "RUSTSEC-2026-0370", reason = "zvariant unmaintained (merged into zbus 6), pulled in by zbus 5, no known vulnerability; clears with a zbus 5 -> 6 migration" }, + { id = "RUSTSEC-2026-0371", reason = "zbus_names unmaintained (merged into zbus 6), pulled in by zbus 5, no known vulnerability; clears with a zbus 5 -> 6 migration" }, + { id = "RUSTSEC-2026-0372", reason = "zvariant_derive unmaintained (merged into zbus 6), pulled in by zbus 5, no known vulnerability; clears with a zbus 5 -> 6 migration" }, ] # ---------------------------------------------------------------------------