From 47ea9b88107a2f3e96a32214a1f5697be3d87f36 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 10:45:08 -0700 Subject: [PATCH 1/9] test(network): require bounded BiDi pointer click transport --- .../webdriver_bidi_pointer_click_send.rs | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 crates/originweave-network/tests/webdriver_bidi_pointer_click_send.rs diff --git a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send.rs b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send.rs new file mode 100644 index 000000000..ef5240466 --- /dev/null +++ b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send.rs @@ -0,0 +1,143 @@ +use std::{ + error::Error, + io::{self, Read, Write}, + net::{TcpListener, TcpStream}, + thread, + time::Duration, +}; + +use originweave_core::{WebDriverBiDiPointerClickCommand, WebDriverBiDiRemoteNodeReference, WebDriverBiDiWebSocketEndpoint}; +use originweave_network::{ + WebDriverBiDiCommandCorrelation, WebDriverBiDiTcpConnectionPlan, + WebDriverBiDiWebSocketClientKey, WebDriverBiDiWebSocketHandshakePlan, + WebDriverBiDiWebSocketMaskKey, send_webdriver_bidi_pointer_click, +}; + +const SESSION_ID: &str = "01234567-89ab-cdef-0123-456789abcdef"; +const RFC6455_SAMPLE_KEY: &str = "dGhlIHNhbXBsZSBub25jZQ=="; +const OPENING_RESPONSE: &[u8] = b"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n"; + +fn read_opening_request(stream: &mut TcpStream) -> io::Result<()> { + stream.set_read_timeout(Some(Duration::from_secs(2)))?; + let mut request = Vec::new(); + let mut buffer = [0_u8; 512]; + while !request.ends_with(b"\r\n\r\n") { + let count = stream.read(&mut buffer)?; + if count == 0 { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "client opening request ended before the header terminator", + )); + } + request.extend_from_slice(&buffer[..count]); + } + Ok(()) +} + +fn read_masked_text_frame(stream: &mut TcpStream) -> io::Result> { + let mut header = [0_u8; 2]; + stream.read_exact(&mut header)?; + if header[0] != 0x81 || header[1] & 0x80 == 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "expected one final masked client text frame", + )); + } + + let marker = header[1] & 0x7f; + let length = match marker { + 0..=125 => usize::from(marker), + 126 => { + let mut extended = [0_u8; 2]; + stream.read_exact(&mut extended)?; + let length = usize::from(u16::from_be_bytes(extended)); + if length <= 125 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "client text frame used non-minimal 16-bit length encoding", + )); + } + length + } + 127 => { + let mut extended = [0_u8; 8]; + stream.read_exact(&mut extended)?; + let length = u64::from_be_bytes(extended); + if length <= u64::from(u16::MAX) || length > usize::MAX as u64 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "client text frame used invalid 64-bit length encoding", + )); + } + length as usize + } + _ => unreachable!(), + }; + + let mut mask = [0_u8; 4]; + stream.read_exact(&mut mask)?; + let mut payload = vec![0_u8; length]; + stream.read_exact(&mut payload)?; + for (index, byte) in payload.iter_mut().enumerate() { + *byte ^= mask[index % mask.len()]; + } + Ok(payload) +} + +#[test] +fn pointer_click_command_writes_exact_masked_bidi_frame_and_stays_outstanding() +-> Result<(), Box> { + let listener = TcpListener::bind(("127.0.0.1", 0))?; + let local_addr = listener.local_addr()?; + let expected = WebDriverBiDiPointerClickCommand::new( + 42, + "context-a", + &WebDriverBiDiRemoteNodeReference::new("node", Some("shared-node-42"))?, + )?; + let expected_json = expected.as_json().as_bytes().to_vec(); + + let server = thread::spawn(move || -> io::Result<()> { + let (mut stream, _) = listener.accept()?; + read_opening_request(&mut stream)?; + stream.write_all(OPENING_RESPONSE)?; + let command = read_masked_text_frame(&mut stream)?; + if command != expected_json { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "unexpected input.performActions pointer-click command", + )); + } + Ok(()) + }); + + let endpoint = format!("ws://{local_addr}/session/{SESSION_ID}"); + let target = WebDriverBiDiWebSocketEndpoint::new(&endpoint)? + .correlate_session_id(SESSION_ID)? + .into_explicit_connect_target()?; + let connection = + WebDriverBiDiTcpConnectionPlan::new(target, Duration::from_secs(1), 1)?.connect()?; + let key = WebDriverBiDiWebSocketClientKey::new(RFC6455_SAMPLE_KEY)?; + let established = WebDriverBiDiWebSocketHandshakePlan::new(connection, key)? + .write_opening_request(Duration::from_millis(500))? + .read_opening_response(Duration::from_millis(500))?; + + let command = WebDriverBiDiPointerClickCommand::new( + 42, + "context-a", + &WebDriverBiDiRemoteNodeReference::new("node", Some("shared-node-42"))?, + )?; + let mut correlation = WebDriverBiDiCommandCorrelation::new(); + let _established = send_webdriver_bidi_pointer_click( + &command, + established, + &mut correlation, + WebDriverBiDiWebSocketMaskKey::new([1, 2, 3, 4]), + Duration::from_millis(500), + )?; + assert_eq!(correlation.outstanding_count(), 1); + + server + .join() + .map_err(|_| io::Error::other("pointer-click transport test server panicked"))??; + Ok(()) +} From 7f84a621950be8f1c918c584cc9443f351c16612 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 10:47:38 -0700 Subject: [PATCH 2/9] style(network): apply canonical pointer transport formatting --- .../tests/webdriver_bidi_pointer_click_send.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send.rs b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send.rs index ef5240466..379b606f5 100644 --- a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send.rs +++ b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send.rs @@ -6,7 +6,10 @@ use std::{ time::Duration, }; -use originweave_core::{WebDriverBiDiPointerClickCommand, WebDriverBiDiRemoteNodeReference, WebDriverBiDiWebSocketEndpoint}; +use originweave_core::{ + WebDriverBiDiPointerClickCommand, WebDriverBiDiRemoteNodeReference, + WebDriverBiDiWebSocketEndpoint, +}; use originweave_network::{ WebDriverBiDiCommandCorrelation, WebDriverBiDiTcpConnectionPlan, WebDriverBiDiWebSocketClientKey, WebDriverBiDiWebSocketHandshakePlan, From 9f90d4b5d82a1ea0f88867ecbb81f4b9e0908518 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 10:49:20 -0700 Subject: [PATCH 3/9] feat(network): send bounded BiDi pointer click frame --- .../webdriver_bidi_pointer_click_transport.rs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs diff --git a/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs b/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs new file mode 100644 index 000000000..f22ba6c6d --- /dev/null +++ b/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs @@ -0,0 +1,72 @@ +use std::{error::Error, fmt, time::Duration}; + +use originweave_core::WebDriverBiDiPointerClickCommand; + +use crate::{ + WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandCorrelationError, + WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketFrameError, + WebDriverBiDiWebSocketMaskKey, +}; + +/// Fail-closed errors while transporting one already validated pointer-click command. +#[derive(Debug)] +pub enum WebDriverBiDiPointerClickSendError { + /// The bounded correlation registry rejected the command before network I/O. + Correlation { + /// Exact typed correlation failure. + source: WebDriverBiDiCommandCorrelationError, + }, + /// Writing the already-registered command frame failed and the transport is not reusable. + FrameWrite { + /// Exact typed bounded WebSocket frame-write failure. + source: WebDriverBiDiWebSocketFrameError, + }, +} + +impl fmt::Display for WebDriverBiDiPointerClickSendError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(match self { + Self::Correlation { .. } => { + "WebDriver BiDi pointer-click command correlation was rejected" + } + Self::FrameWrite { .. } => "WebDriver BiDi pointer-click command frame write failed", + }) + } +} + +impl Error for WebDriverBiDiPointerClickSendError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::Correlation { source } => Some(source), + Self::FrameWrite { source } => Some(source), + } + } +} + +/// Register and write one already validated `input.performActions` pointer-click command. +/// +/// Registration occurs before the first possible remote side effect. A correlation failure therefore +/// writes nothing. Once registration succeeds, a frame-write failure leaves the identifier +/// outstanding because a partial or complete remote side effect is ambiguous and the identifier +/// must not be silently reused. +/// +/// This boundary accepts only [`WebDriverBiDiPointerClickCommand`], not arbitrary JSON or method +/// names. It does not authenticate the browser, grant session/context/origin/document-epoch +/// authority, authorize policy or TypedInput capability, admit nodes, correlate a response, prove an +/// observed post-condition, retry, reconnect, or choose another destination. A trusted caller must +/// establish those independent authorities before transport and retain response/post-condition +/// evidence afterward. +pub fn send_webdriver_bidi_pointer_click( + command: &WebDriverBiDiPointerClickCommand, + established: WebDriverBiDiWebSocketEstablished, + correlation: &mut WebDriverBiDiCommandCorrelation, + masking_key: WebDriverBiDiWebSocketMaskKey, + frame_timeout: Duration, +) -> Result { + correlation + .register_command(command.command_id()) + .map_err(|source| WebDriverBiDiPointerClickSendError::Correlation { source })?; + established + .write_text_frame(command.as_json(), masking_key, frame_timeout) + .map_err(|source| WebDriverBiDiPointerClickSendError::FrameWrite { source }) +} From ff5a00f57e4d2877b1bdd001f037b6f23e814ef3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 10:49:45 -0700 Subject: [PATCH 4/9] feat(network): export bounded BiDi pointer transport --- crates/originweave-network/src/lib.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/originweave-network/src/lib.rs b/crates/originweave-network/src/lib.rs index 7400cc4d0..6aebd9c53 100644 --- a/crates/originweave-network/src/lib.rs +++ b/crates/originweave-network/src/lib.rs @@ -8,11 +8,12 @@ //! the RFC 6455 opening exchange, provides bounded masked client writes and //! unmasked server-frame reads, assembles bounded WebDriver BiDi text messages, //! classifies complete local-end JSON envelopes, tracks bounded command-response -//! correlation, sends narrowly typed `session.status` and `session.end` commands, -//! admits typed correlated status and end responses, observes bounded peer Close -//! or clean-EOF transport cessation, and keeps protocol/transport evidence separate -//! from explicit operational teardown observations without exposing generic JSON -//! bodies or granting browser, TLS, policy, secret, process, profile, or Agent authority. +//! correlation, transports a narrowly typed pointer click, sends narrowly typed +//! `session.status` and `session.end` commands, admits typed correlated status and +//! end responses, observes bounded peer Close or clean-EOF transport cessation, +//! and keeps protocol/transport evidence separate from explicit operational +//! teardown observations without exposing generic JSON bodies or granting browser, +//! TLS, policy, secret, process, profile, or Agent authority. #![forbid(unsafe_code)] #![deny(missing_docs)] @@ -21,6 +22,7 @@ mod connection; mod webdriver_bidi_command_correlation; mod webdriver_bidi_connection; mod webdriver_bidi_json_envelope; +mod webdriver_bidi_pointer_click_transport; mod webdriver_bidi_session_end_command; mod webdriver_bidi_session_end_response; mod webdriver_bidi_session_status_command; @@ -52,6 +54,9 @@ pub use webdriver_bidi_json_envelope::{ MAX_WEBDRIVER_BIDI_JS_UINT, MAX_WEBDRIVER_BIDI_JSON_DEPTH, WebDriverBiDiJsonEnvelope, WebDriverBiDiJsonEnvelopeError, WebDriverBiDiJsonEnvelopeKind, }; +pub use webdriver_bidi_pointer_click_transport::{ + WebDriverBiDiPointerClickSendError, send_webdriver_bidi_pointer_click, +}; pub use webdriver_bidi_session_end_command::{ WebDriverBiDiSessionEndCommand, WebDriverBiDiSessionEndCommandError, }; From 61535c54502873c968340187052506c6a3a59c00 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 10:50:07 -0700 Subject: [PATCH 5/9] test(network): cover fail-closed pointer transport errors --- ...driver_bidi_pointer_click_send_failures.rs | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs diff --git a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs new file mode 100644 index 000000000..58a6ac0d3 --- /dev/null +++ b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs @@ -0,0 +1,141 @@ +use std::{ + error::Error, + io::{self, Read, Write}, + net::{TcpListener, TcpStream}, + thread, + time::Duration, +}; + +use originweave_core::{ + WebDriverBiDiPointerClickCommand, WebDriverBiDiRemoteNodeReference, + WebDriverBiDiWebSocketEndpoint, +}; +use originweave_network::{ + WebDriverBiDiCommandCorrelation, WebDriverBiDiPointerClickSendError, + WebDriverBiDiTcpConnectionPlan, WebDriverBiDiWebSocketClientKey, + WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketHandshakePlan, + WebDriverBiDiWebSocketMaskKey, send_webdriver_bidi_pointer_click, +}; + +const SESSION_ID: &str = "01234567-89ab-cdef-0123-456789abcdef"; +const RFC6455_SAMPLE_KEY: &str = "dGhlIHNhbXBsZSBub25jZQ=="; +const OPENING_RESPONSE: &[u8] = b"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n"; +type HandshakeOnlyServer = ( + WebDriverBiDiWebSocketEstablished, + thread::JoinHandle>, +); + +fn read_opening_request(stream: &mut TcpStream) -> io::Result<()> { + stream.set_read_timeout(Some(Duration::from_secs(2)))?; + let mut request = Vec::new(); + let mut buffer = [0_u8; 512]; + while !request.ends_with(b"\r\n\r\n") { + let count = stream.read(&mut buffer)?; + if count == 0 { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "client opening request ended before the header terminator", + )); + } + request.extend_from_slice(&buffer[..count]); + } + Ok(()) +} + +fn establish_with_handshake_only_server() -> Result> { + let listener = TcpListener::bind(("127.0.0.1", 0))?; + let local_addr = listener.local_addr()?; + let server = thread::spawn(move || -> io::Result<()> { + let (mut stream, _) = listener.accept()?; + read_opening_request(&mut stream)?; + stream.write_all(OPENING_RESPONSE) + }); + + let endpoint = format!("ws://{local_addr}/session/{SESSION_ID}"); + let target = WebDriverBiDiWebSocketEndpoint::new(&endpoint)? + .correlate_session_id(SESSION_ID)? + .into_explicit_connect_target()?; + let connection = + WebDriverBiDiTcpConnectionPlan::new(target, Duration::from_secs(1), 1)?.connect()?; + let key = WebDriverBiDiWebSocketClientKey::new(RFC6455_SAMPLE_KEY)?; + let established = WebDriverBiDiWebSocketHandshakePlan::new(connection, key)? + .write_opening_request(Duration::from_millis(500))? + .read_opening_response(Duration::from_millis(500))?; + Ok((established, server)) +} + +fn pointer_click(command_id: u64) -> Result> { + let node = WebDriverBiDiRemoteNodeReference::new("node", Some("shared-node-42"))?; + Ok(WebDriverBiDiPointerClickCommand::new( + command_id, + "context-a", + &node, + )?) +} + +#[test] +fn pointer_click_rejects_duplicate_correlation_before_frame_write() +-> Result<(), Box> { + let (established, server) = establish_with_handshake_only_server()?; + let mut correlation = WebDriverBiDiCommandCorrelation::new(); + correlation.register_command(7)?; + let command = pointer_click(7)?; + + let error = send_webdriver_bidi_pointer_click( + &command, + established, + &mut correlation, + WebDriverBiDiWebSocketMaskKey::new([1, 2, 3, 4]), + Duration::from_millis(500), + ) + .err() + .ok_or_else(|| io::Error::other("duplicate correlation unexpectedly sent a pointer click"))?; + assert!(matches!( + error, + WebDriverBiDiPointerClickSendError::Correlation { .. } + )); + assert_eq!( + error.to_string(), + "WebDriver BiDi pointer-click command correlation was rejected" + ); + assert!(error.source().is_some()); + assert_eq!(correlation.outstanding_count(), 1); + + server + .join() + .map_err(|_| io::Error::other("duplicate-correlation pointer server panicked"))??; + Ok(()) +} + +#[test] +fn pointer_click_preserves_registration_when_frame_timeout_is_invalid() +-> Result<(), Box> { + let (established, server) = establish_with_handshake_only_server()?; + let mut correlation = WebDriverBiDiCommandCorrelation::new(); + let command = pointer_click(11)?; + + let error = send_webdriver_bidi_pointer_click( + &command, + established, + &mut correlation, + WebDriverBiDiWebSocketMaskKey::new([5, 6, 7, 8]), + Duration::ZERO, + ) + .err() + .ok_or_else(|| io::Error::other("zero frame timeout unexpectedly sent a pointer click"))?; + assert!(matches!( + error, + WebDriverBiDiPointerClickSendError::FrameWrite { .. } + )); + assert_eq!( + error.to_string(), + "WebDriver BiDi pointer-click command frame write failed" + ); + assert!(error.source().is_some()); + assert_eq!(correlation.outstanding_count(), 1); + + server + .join() + .map_err(|_| io::Error::other("invalid-timeout pointer server panicked"))??; + Ok(()) +} From 7da5e2d2cfcacc891d44b7fb620b1b19f1677e0c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 10:51:31 -0700 Subject: [PATCH 6/9] style(network): apply canonical pointer failure formatting --- .../tests/webdriver_bidi_pointer_click_send_failures.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs index 58a6ac0d3..d2abd97b5 100644 --- a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs +++ b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs @@ -74,8 +74,7 @@ fn pointer_click(command_id: u64) -> Result Result<(), Box> { +fn pointer_click_rejects_duplicate_correlation_before_frame_write() -> Result<(), Box> { let (established, server) = establish_with_handshake_only_server()?; let mut correlation = WebDriverBiDiCommandCorrelation::new(); correlation.register_command(7)?; @@ -108,8 +107,8 @@ fn pointer_click_rejects_duplicate_correlation_before_frame_write() } #[test] -fn pointer_click_preserves_registration_when_frame_timeout_is_invalid() --> Result<(), Box> { +fn pointer_click_preserves_registration_when_frame_timeout_is_invalid() -> Result<(), Box> +{ let (established, server) = establish_with_handshake_only_server()?; let mut correlation = WebDriverBiDiCommandCorrelation::new(); let command = pointer_click(11)?; From 1427c7ca31f39d0a293071c3dd46aaa233dfc3c6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 02:37:28 +0900 Subject: [PATCH 7/9] fix(network): repair typed pointer-click exact-head regression --- ...ver_bidi_json_envelope_public_boundary_tests.rs | 14 +++++++------- .../src/webdriver_bidi_pointer_click_transport.rs | 4 ++-- .../src/webdriver_bidi_session_status_response.rs | 8 +++++--- .../webdriver_bidi_pointer_click_send_failures.rs | 8 ++++---- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs b/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs index 6b6487cb0..879fb32bd 100644 --- a/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs +++ b/crates/originweave-network/src/webdriver_bidi_json_envelope_public_boundary_tests.rs @@ -9,12 +9,12 @@ use std::{ use originweave_core::WebDriverBiDiWebSocketEndpoint; use crate::{ - WebDriverBiDiCommandCorrelation, WebDriverBiDiJsonEnvelope, WebDriverBiDiJsonEnvelopeError, - WebDriverBiDiJsonEnvelopeKind, WebDriverBiDiSessionStatusResponseError, - WebDriverBiDiSessionStatusResult, WebDriverBiDiTcpConnectionPlan, - WebDriverBiDiWebSocketClientKey, WebDriverBiDiWebSocketHandshakePlan, - WebDriverBiDiWebSocketMessageAssembler, WebDriverBiDiWebSocketMessageAssembly, - WebDriverBiDiWebSocketTextMessage, + WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandKind, WebDriverBiDiJsonEnvelope, + WebDriverBiDiJsonEnvelopeError, WebDriverBiDiJsonEnvelopeKind, + WebDriverBiDiSessionStatusResponseError, WebDriverBiDiSessionStatusResult, + WebDriverBiDiTcpConnectionPlan, WebDriverBiDiWebSocketClientKey, + WebDriverBiDiWebSocketHandshakePlan, WebDriverBiDiWebSocketMessageAssembler, + WebDriverBiDiWebSocketMessageAssembly, WebDriverBiDiWebSocketTextMessage, }; const SESSION_ID: &str = "01234567-89ab-cdef-0123-456789abcdef"; @@ -134,7 +134,7 @@ fn public_json_envelope_unit_build_covers_fail_closed_json_edges() -> Result<(), fn public_session_status_empty_result_fails_closed_from_unit_build() -> Result<(), Box> { let text = read_text_over_loopback(EMPTY_STATUS_RESULT)?; let mut correlation = WebDriverBiDiCommandCorrelation::new(); - correlation.register_command(7)?; + correlation.register_command_for(7, WebDriverBiDiCommandKind::SessionStatus)?; let parsed = WebDriverBiDiSessionStatusResult::parse_and_correlate(&text, &mut correlation); assert!(matches!( diff --git a/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs b/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs index 429e76127..1c74717af 100644 --- a/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs +++ b/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs @@ -4,8 +4,8 @@ use originweave_core::WebDriverBiDiPointerClickCommand; use crate::{ WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandCorrelationError, - WebDriverBiDiCommandKind, WebDriverBiDiWebSocketEstablished, - WebDriverBiDiWebSocketFrameError, WebDriverBiDiWebSocketMaskKey, + WebDriverBiDiCommandKind, WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketFrameError, + WebDriverBiDiWebSocketMaskKey, }; /// Fail-closed errors while transporting one already validated pointer-click command. diff --git a/crates/originweave-network/src/webdriver_bidi_session_status_response.rs b/crates/originweave-network/src/webdriver_bidi_session_status_response.rs index e08a7efbb..b1e5596ba 100644 --- a/crates/originweave-network/src/webdriver_bidi_session_status_response.rs +++ b/crates/originweave-network/src/webdriver_bidi_session_status_response.rs @@ -71,9 +71,11 @@ impl WebDriverBiDiSessionStatusResult { retain_validated_error_code(envelope.error_code()).and_then(|error_code| { let completed = correlation .correlate_response_for(&envelope, WebDriverBiDiCommandKind::SessionStatus) - .map_err(|source| { - WebDriverBiDiSessionStatusResponseError::Correlation { source } - })?; + .map_err( + |source| WebDriverBiDiSessionStatusResponseError::Correlation { + source, + }, + )?; Err( WebDriverBiDiSessionStatusResponseError::RemoteProtocolError { command_id: completed.command_id(), diff --git a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs index 9d4a0ee38..c2d9cffcc 100644 --- a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs +++ b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs @@ -12,10 +12,10 @@ use originweave_core::{ }; use originweave_network::{ WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandCorrelationError, - WebDriverBiDiCommandKind, WebDriverBiDiPointerClickSendError, - WebDriverBiDiTcpConnectionPlan, WebDriverBiDiWebSocketClientKey, - WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketHandshakePlan, - WebDriverBiDiWebSocketMaskKey, send_webdriver_bidi_pointer_click, + WebDriverBiDiCommandKind, WebDriverBiDiPointerClickSendError, WebDriverBiDiTcpConnectionPlan, + WebDriverBiDiWebSocketClientKey, WebDriverBiDiWebSocketEstablished, + WebDriverBiDiWebSocketHandshakePlan, WebDriverBiDiWebSocketMaskKey, + send_webdriver_bidi_pointer_click, }; const SESSION_ID: &str = "01234567-89ab-cdef-0123-456789abcdef"; From a73ee0c54cbf131f4866e2facb711216662c61ee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:02:36 +0900 Subject: [PATCH 8/9] fix(network): repair pointer-click exact-head CI failures --- .../src/webdriver_bidi_command_correlation.rs | 41 ++++++++-------- .../src/webdriver_bidi_json_envelope.rs | 48 ++++++++++++++----- ...driver_bidi_pointer_click_send_failures.rs | 5 +- 3 files changed, 59 insertions(+), 35 deletions(-) diff --git a/crates/originweave-network/src/webdriver_bidi_command_correlation.rs b/crates/originweave-network/src/webdriver_bidi_command_correlation.rs index 18a408922..6dfdc6ea3 100644 --- a/crates/originweave-network/src/webdriver_bidi_command_correlation.rs +++ b/crates/originweave-network/src/webdriver_bidi_command_correlation.rs @@ -1,6 +1,8 @@ use std::{collections::BTreeMap, error::Error, fmt}; -use crate::{MAX_WEBDRIVER_BIDI_JS_UINT, WebDriverBiDiJsonEnvelope, WebDriverBiDiJsonEnvelopeKind}; +use crate::{ + MAX_WEBDRIVER_BIDI_JS_UINT, WebDriverBiDiJsonEnvelope, WebDriverBiDiJsonEnvelopeRouting, +}; /// Maximum number of local WebDriver BiDi commands retained as outstanding at once. /// @@ -185,30 +187,25 @@ impl WebDriverBiDiCommandCorrelation { envelope: &WebDriverBiDiJsonEnvelope, expected_kind: WebDriverBiDiCommandKind, ) -> Result { - match envelope.kind() { - WebDriverBiDiJsonEnvelopeKind::Event => { + match envelope.routing() { + WebDriverBiDiJsonEnvelopeRouting::Event => { Err(WebDriverBiDiCommandCorrelationError::EventIsNotResponse) } - WebDriverBiDiJsonEnvelopeKind::Error => { - let Some(command_id) = envelope.command_id() else { - return Err(WebDriverBiDiCommandCorrelationError::UncorrelatableErrorResponse); - }; - self.complete( - command_id, - expected_kind, - WebDriverBiDiCorrelatedResponseOutcome::Error, - ) - } - WebDriverBiDiJsonEnvelopeKind::Success => { - let Some(command_id) = envelope.command_id() else { - return Err(WebDriverBiDiCommandCorrelationError::CommandNotOutstanding); - }; - self.complete( - command_id, - expected_kind, - WebDriverBiDiCorrelatedResponseOutcome::Success, - ) + WebDriverBiDiJsonEnvelopeRouting::CommandError { command_id: None } => { + Err(WebDriverBiDiCommandCorrelationError::UncorrelatableErrorResponse) } + WebDriverBiDiJsonEnvelopeRouting::CommandError { + command_id: Some(command_id), + } => self.complete( + command_id, + expected_kind, + WebDriverBiDiCorrelatedResponseOutcome::Error, + ), + WebDriverBiDiJsonEnvelopeRouting::CommandSuccess { command_id } => self.complete( + command_id, + expected_kind, + WebDriverBiDiCorrelatedResponseOutcome::Success, + ), } } diff --git a/crates/originweave-network/src/webdriver_bidi_json_envelope.rs b/crates/originweave-network/src/webdriver_bidi_json_envelope.rs index e99adf2c9..7e878980f 100644 --- a/crates/originweave-network/src/webdriver_bidi_json_envelope.rs +++ b/crates/originweave-network/src/webdriver_bidi_json_envelope.rs @@ -23,6 +23,18 @@ pub enum WebDriverBiDiJsonEnvelopeKind { Event, } +/// Structurally valid command/event routing retained after common-envelope validation. +/// +/// Keeping success ids inside the success variant prevents an impossible `success` + missing-id +/// state from leaking into downstream command correlation. Error ids remain optional because the +/// WebDriver BiDi protocol explicitly permits `null` there, while events carry no command id. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum WebDriverBiDiJsonEnvelopeRouting { + CommandSuccess { command_id: u64 }, + CommandError { command_id: Option }, + Event, +} + /// Credential-minimal classification of one complete WebDriver BiDi local-end JSON envelope. /// /// Result and parameter bodies are deliberately validated and discarded at this boundary. They @@ -30,8 +42,7 @@ pub enum WebDriverBiDiJsonEnvelopeKind { /// as generic JSON values that could become ambient browser or Agent authority. #[derive(Eq, PartialEq)] pub struct WebDriverBiDiJsonEnvelope { - kind: WebDriverBiDiJsonEnvelopeKind, - command_id: Option, + routing: WebDriverBiDiJsonEnvelopeRouting, method: Option, error_code: Option, } @@ -40,8 +51,8 @@ impl fmt::Debug for WebDriverBiDiJsonEnvelope { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("WebDriverBiDiJsonEnvelope") - .field("kind", &self.kind) - .field("command_id", &self.command_id) + .field("kind", &self.kind()) + .field("command_id", &self.command_id()) .field("has_method", &self.method.is_some()) .field("has_error_code", &self.error_code.is_some()) .finish() @@ -73,7 +84,15 @@ impl WebDriverBiDiJsonEnvelope { /// Return the classified local-end envelope kind. #[must_use] pub const fn kind(&self) -> WebDriverBiDiJsonEnvelopeKind { - self.kind + match self.routing { + WebDriverBiDiJsonEnvelopeRouting::CommandSuccess { .. } => { + WebDriverBiDiJsonEnvelopeKind::Success + } + WebDriverBiDiJsonEnvelopeRouting::CommandError { .. } => { + WebDriverBiDiJsonEnvelopeKind::Error + } + WebDriverBiDiJsonEnvelopeRouting::Event => WebDriverBiDiJsonEnvelopeKind::Event, + } } /// Return the command identifier for success and correlatable error responses. @@ -81,7 +100,15 @@ impl WebDriverBiDiJsonEnvelope { /// Events and error responses whose protocol `id` is `null` return `None`. #[must_use] pub const fn command_id(&self) -> Option { - self.command_id + match self.routing { + WebDriverBiDiJsonEnvelopeRouting::CommandSuccess { command_id } => Some(command_id), + WebDriverBiDiJsonEnvelopeRouting::CommandError { command_id } => command_id, + WebDriverBiDiJsonEnvelopeRouting::Event => None, + } + } + + pub(crate) const fn routing(&self) -> WebDriverBiDiJsonEnvelopeRouting { + self.routing } /// Borrow the event method when this is an event envelope. @@ -208,8 +235,7 @@ impl TopLevelFields { let command_id = required_js_uint(self.id, "id")?; require_object(self.result, "result")?; Ok(WebDriverBiDiJsonEnvelope { - kind: WebDriverBiDiJsonEnvelopeKind::Success, - command_id: Some(command_id), + routing: WebDriverBiDiJsonEnvelopeRouting::CommandSuccess { command_id }, method: None, error_code: None, }) @@ -223,8 +249,7 @@ impl TopLevelFields { require_text_value(stacktrace, "stacktrace")?; } Ok(WebDriverBiDiJsonEnvelope { - kind: WebDriverBiDiJsonEnvelopeKind::Error, - command_id, + routing: WebDriverBiDiJsonEnvelopeRouting::CommandError { command_id }, method: None, error_code: Some(error_code), }) @@ -234,8 +259,7 @@ impl TopLevelFields { let method = required_text(self.method, "method")?; require_object(self.params, "params")?; Ok(WebDriverBiDiJsonEnvelope { - kind: WebDriverBiDiJsonEnvelopeKind::Event, - command_id: None, + routing: WebDriverBiDiJsonEnvelopeRouting::Event, method: Some(method), error_code: None, }) diff --git a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs index c2d9cffcc..f4f47cdb8 100644 --- a/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs +++ b/crates/originweave-network/tests/webdriver_bidi_pointer_click_send_failures.rs @@ -136,7 +136,10 @@ fn pointer_click_preserves_typed_registration_when_frame_timeout_is_invalid() let kind_error = correlation .retire_command_for(11, WebDriverBiDiCommandKind::SessionEnd) - .expect_err("pointer-click correlation must not retire as session.end"); + .err() + .ok_or_else(|| { + io::Error::other("pointer-click correlation unexpectedly retired as session.end") + })?; assert_eq!( kind_error, WebDriverBiDiCommandCorrelationError::CommandKindMismatch { From 9451fd8a23dec95b31749376bc78c2eaca977fe8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 00:13:33 +0900 Subject: [PATCH 9/9] fix(network): retain pointer sender connection provenance Commit-Message-Assisted-by: Codex (via Codex) Signed-off-by: Seongho Bae --- CHANGELOG.md | 1 + .../webdriver_bidi_pointer_click_transport.rs | 11 +++++++---- .../action-postcondition-evidence.md | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44515076b..277fefbf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Changed +- Retained the originating connection when sending a pointer click so later response validation can reject acknowledgments received through a replacement connection; sending still does not prove that the click completed. - Integrated the current connection-provenance and pointer-click prerequisites into bounded click transport, retaining both public adapters and keeping frame-write evidence separate from browser-action completion. - Integrated the current connection-provenance prerequisite into bounded pointer-click serialization, preserving its command validation and inert authority boundary while restoring the inherited executable release contract. - Removed an unused private correlated-response accessor while retaining connection-generation validation at the receiving-message boundary, and corrected the Rust `AtomicU64` standard-library reference to its canonical type-alias page. diff --git a/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs b/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs index 1c5b399fc..57c3d5112 100644 --- a/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs +++ b/crates/originweave-network/src/webdriver_bidi_pointer_click_transport.rs @@ -47,7 +47,8 @@ impl Error for WebDriverBiDiPointerClickSendError { /// Register and write one already validated `input.performActions` pointer-click command. /// /// Invalid local frame deadlines fail before registration. Correlation then occurs before the first -/// possible remote side effect. A frame preflight rejection that proves no write began retires the +/// possible remote side effect and retains the exact connection's private generation for later +/// connection-bound response admission. A frame preflight rejection that proves no write began retires the /// exact id; a partial or complete remote side effect remains ambiguous and leaves it outstanding. /// /// This boundary accepts only [`WebDriverBiDiPointerClickCommand`], not arbitrary JSON or method @@ -69,9 +70,11 @@ pub fn send_webdriver_bidi_pointer_click( if frame_timeout > MAX_WEBSOCKET_FRAME_TIMEOUT { return Err(invalid_frame_timeout(frame_timeout)); } - match correlation - .register_command_for(command.command_id(), WebDriverBiDiCommandKind::PointerClick) - { + match correlation.register_command_for_connection( + command.command_id(), + WebDriverBiDiCommandKind::PointerClick, + established.transport_evidence().connection_generation(), + ) { Ok(()) => {} Err(source) => { return Err(WebDriverBiDiPointerClickSendError::Correlation { source }); diff --git a/docs/traceability/action-postcondition-evidence.md b/docs/traceability/action-postcondition-evidence.md index 23a6e764d..9c47f208a 100644 --- a/docs/traceability/action-postcondition-evidence.md +++ b/docs/traceability/action-postcondition-evidence.md @@ -26,6 +26,23 @@ The generic value primitives are **IMPLEMENTED_ON_PROTECTED_MAIN**. The complete ## 3. Active executable evidence +### Pointer-click originating-connection prerequisite + +PR #258 test-only head `8193fcd50125d9e9a43b4755e0f7626801b74374`, on +PR #257 `8f1507346f65798a6bf4eaf370d65a2d406a6f44`, reproduced a replacement +connection consuming the original connection's pointer command. The Rust 1.97.1 +loopback regression failed at its rejection assertion (zero passed, one failed); +the four predecessor response tests passed separately. + +The #257 sender now registers the existing private transport generation before +writing, reusing the shared correlation owner without changing local deadline +rejection, preflight retirement, or ambiguous-write retention. This prerequisite +alone does not reject foreign responses: #258 must consume the existing sealed +received-message type and require connection-bound correlation. Its regression +must also retain unrelated requests and allow the original connection's response. +Outbound session authority, browser authentication, observed click effects, +protected-main acceptance, and release evidence remain separate and unproven. + ### PR #64 — verified, temporally ordered post-condition becomes typed action-outcome evidence **Capability maturity:** `IMPLEMENTED_ON_ACTIVE_PR`