Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ All notable changes to OriginWeave are documented in this file. The format follo

### Changed

- 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.
- Integrated the current teardown prerequisites into transport-closure observation, including the previously uncollected release-record check, while retaining the unresolved connection-provenance finding and its downstream repair ownership.
- Integrated the verified opening-exchange and closure prerequisites into the connection-bound response repair, preserving its sender, receiver and closure provenance checks while restoring the inherited executable release contract; process and profile cleanup remain unproven.

### Added

- Deterministic WebDriver BiDi primary-button click serialization for an already admitted remote node: it emits one fixed `input.performActions` mouse sequence from bounded command/context/node identifiers and remains inert until a trusted adapter binds it to current session, origin, document, policy, and approval authority.
- Typed outbound WebDriver BiDi `session.end` over the bounded client WebSocket stream: it serializes only the standards-defined method with empty params, rejects invalid frame deadlines before correlation registration, retires only the just-registered id when frame preflight proves no command bytes were emitted, preserves exact command-kind correlation across ambiguous writes, and does not treat frame-write success as proof that the browser session ended.
- Typed `session.end` response admission that consumes only the exact outstanding command-kind correlation after complete envelope validation, preserves remote protocol errors as failures, and does not claim browser-process exit or resource cleanup from a protocol acknowledgment.
- Fail-closed `session.end` teardown assessment that binds only the typed observation produced by consuming the exact transport, keeps browser-process-exit and task-profile-removal evidence unavailable until their runtime owners exist, and therefore cannot report operational completion from caller-supplied booleans.
Expand Down
9 changes: 5 additions & 4 deletions crates/originweave-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,11 @@ pub use browser_registry::{
pub use contracts::*;
pub use webdriver_bidi_command::{
CorrelatedWebDriverBiDiLocateNodesResponse, MAX_WEBDRIVER_BIDI_COMMAND_ID,
ValidatedWebDriverBiDiLocateNodesResponse, WebDriverBiDiCommandResponseKind,
WebDriverBiDiLocateNodesCommand, WebDriverBiDiLocateNodesCommandError,
WebDriverBiDiLocateNodesResponseCorrelationError,
WebDriverBiDiLocateNodesResponseEnvelopeError,
ValidatedWebDriverBiDiLocateNodesResponse, WEBDRIVER_BIDI_PERFORM_ACTIONS_METHOD,
WebDriverBiDiCommandResponseKind, WebDriverBiDiLocateNodesCommand,
WebDriverBiDiLocateNodesCommandError, WebDriverBiDiLocateNodesResponseCorrelationError,
WebDriverBiDiLocateNodesResponseEnvelopeError, WebDriverBiDiPointerClickCommand,
WebDriverBiDiPointerClickCommandError,
};
pub use webdriver_bidi_error_code::WebDriverBiDiErrorCode;
pub use webdriver_bidi_response_document::{
Expand Down
97 changes: 97 additions & 0 deletions crates/originweave-core/src/webdriver_bidi_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,103 @@ use crate::{
/// Maximum WebDriver BiDi command identifier representable by the protocol `js-uint` type.
pub const MAX_WEBDRIVER_BIDI_COMMAND_ID: u64 = 9_007_199_254_740_991;

/// WebDriver BiDi method used for one bounded typed pointer action sequence.
pub const WEBDRIVER_BIDI_PERFORM_ACTIONS_METHOD: &str = "input.performActions";

/// Fail-closed validation errors for one serialized WebDriver BiDi pointer click command.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WebDriverBiDiPointerClickCommandError {
/// The command identifier exceeds WebDriver BiDi's unsigned safe-integer range.
InvalidCommandId,
/// The browsing-context identifier is empty, over budget, or contains disallowed text.
InvalidBrowsingContext,
}

impl Display for WebDriverBiDiPointerClickCommandError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
Self::InvalidCommandId => "WebDriver BiDi command id is outside the js-uint range",
Self::InvalidBrowsingContext => {
"WebDriver BiDi browsing context is empty, over budget, or contains disallowed text"
}
})
}
}

impl Error for WebDriverBiDiPointerClickCommandError {}

/// Deterministic command for one primary-button click on an admitted remote node.
///
/// The fixed mouse action sequence moves to the element origin, presses button zero, and releases
/// button zero. Construction accepts an already admitted remote node reference and does not grant
/// browser-session, context, origin, document-epoch, policy, approval, or Agent authority. A trusted
/// adapter must bind this inert command to current authority before transport.
#[derive(Debug, PartialEq, Eq)]
pub struct WebDriverBiDiPointerClickCommand {
command_id: u64,
browsing_context: String,
json: String,
}

impl WebDriverBiDiPointerClickCommand {
/// Validate and serialize one bounded `input.performActions` pointer click command.
pub fn new(
command_id: u64,
browsing_context: &str,
node: &crate::WebDriverBiDiRemoteNodeReference,
) -> Result<Self, WebDriverBiDiPointerClickCommandError> {
if command_id > MAX_WEBDRIVER_BIDI_COMMAND_ID {
return Err(WebDriverBiDiPointerClickCommandError::InvalidCommandId);
}
if browsing_context.is_empty()
|| browsing_context.len() > MAX_EXTERNAL_BROWSER_IDENTIFIER_BYTES
|| contains_disallowed_protocol_text(browsing_context, false)
{
return Err(WebDriverBiDiPointerClickCommandError::InvalidBrowsingContext);
}

let mut json = String::from("{\"id\":");
json.push_str(&command_id.to_string());
json.push_str(",\"method\":\"");
json.push_str(WEBDRIVER_BIDI_PERFORM_ACTIONS_METHOD);
json.push_str("\",\"params\":{\"context\":");
push_json_string(&mut json, browsing_context);
json.push_str(",\"actions\":[{\"type\":\"pointer\",\"id\":\"originweave-mouse\",\"parameters\":{\"pointerType\":\"mouse\"},\"actions\":[{\"type\":\"pointerMove\",\"x\":0,\"y\":0,\"origin\":{\"type\":\"element\",\"element\":{\"sharedId\":");
push_json_string(&mut json, node.shared_id());
json.push_str("}}},{\"type\":\"pointerDown\",\"button\":0},{\"type\":\"pointerUp\",\"button\":0}]}]}}");

Ok(Self {
command_id,
browsing_context: browsing_context.to_owned(),
json,
})
}

/// Return the validated command identifier.
#[must_use]
pub const fn command_id(&self) -> u64 {
self.command_id
}

/// Return the exact WebDriver BiDi method serialized by this command.
#[must_use]
pub const fn method(&self) -> &'static str {
WEBDRIVER_BIDI_PERFORM_ACTIONS_METHOD
}

/// Return the exact validated browsing-context identifier.
#[must_use]
pub fn browsing_context(&self) -> &str {
&self.browsing_context
}

/// Return the deterministic JSON command envelope.
#[must_use]
pub fn as_json(&self) -> &str {
&self.json
}
}

/// Fail-closed validation errors for one serialized WebDriver BiDi `locateNodes` command.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WebDriverBiDiLocateNodesCommandError {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use std::error::Error;

use originweave_core::{
MAX_EXTERNAL_BROWSER_IDENTIFIER_BYTES, MAX_WEBDRIVER_BIDI_COMMAND_ID,
UNICODE_PROTOCOL_FORMAT_INJECTION_CHARS, WEBDRIVER_BIDI_PERFORM_ACTIONS_METHOD,
WebDriverBiDiPointerClickCommand, WebDriverBiDiPointerClickCommandError,
WebDriverBiDiRemoteNodeReference,
};

#[test]
fn pointer_click_command_serializes_exact_bidi_envelope() -> Result<(), Box<dyn Error>> {
let node = WebDriverBiDiRemoteNodeReference::new("node", Some("shared-node-42"))?;
let command = WebDriverBiDiPointerClickCommand::new(42, "context-a", &node)?;

assert_eq!(command.command_id(), 42);
assert_eq!(command.method(), WEBDRIVER_BIDI_PERFORM_ACTIONS_METHOD);
assert_eq!(command.browsing_context(), "context-a");
assert_eq!(
command.as_json(),
r#"{"id":42,"method":"input.performActions","params":{"context":"context-a","actions":[{"type":"pointer","id":"originweave-mouse","parameters":{"pointerType":"mouse"},"actions":[{"type":"pointerMove","x":0,"y":0,"origin":{"type":"element","element":{"sharedId":"shared-node-42"}}},{"type":"pointerDown","button":0},{"type":"pointerUp","button":0}]}]}}"#
);
Ok(())
}

#[test]
fn pointer_click_command_rejects_invalid_command_and_context() -> Result<(), Box<dyn Error>> {
let node = WebDriverBiDiRemoteNodeReference::new("node", Some("shared-node-42"))?;

assert_eq!(
WebDriverBiDiPointerClickCommand::new(
MAX_WEBDRIVER_BIDI_COMMAND_ID + 1,
"context-a",
&node,
),
Err(WebDriverBiDiPointerClickCommandError::InvalidCommandId)
);

for invalid in ["", "context with space", "context\nline"] {
assert_eq!(
WebDriverBiDiPointerClickCommand::new(1, invalid, &node),
Err(WebDriverBiDiPointerClickCommandError::InvalidBrowsingContext)
);
}

let overlong = "c".repeat(MAX_EXTERNAL_BROWSER_IDENTIFIER_BYTES + 1);
assert_eq!(
WebDriverBiDiPointerClickCommand::new(1, &overlong, &node),
Err(WebDriverBiDiPointerClickCommandError::InvalidBrowsingContext)
);
for character in UNICODE_PROTOCOL_FORMAT_INJECTION_CHARS {
let context = format!("context{character}");
assert_eq!(
WebDriverBiDiPointerClickCommand::new(1, &context, &node),
Err(WebDriverBiDiPointerClickCommandError::InvalidBrowsingContext)
);
}
Ok(())
}

#[test]
fn pointer_click_command_accepts_maximum_context_and_escaped_shared_id()
-> Result<(), Box<dyn Error>> {
let context = "c".repeat(MAX_EXTERNAL_BROWSER_IDENTIFIER_BYTES);
let node = WebDriverBiDiRemoteNodeReference::new("node", Some(r#"node-"quoted"\path"#))?;
let command =
WebDriverBiDiPointerClickCommand::new(MAX_WEBDRIVER_BIDI_COMMAND_ID, &context, &node)?;

assert!(command.as_json().contains(&context));
assert!(command.as_json().contains(r#"node-\"quoted\"\\path"#));
Ok(())
}

#[test]
fn pointer_click_command_error_contract_is_source_free() {
for error in [
WebDriverBiDiPointerClickCommandError::InvalidCommandId,
WebDriverBiDiPointerClickCommandError::InvalidBrowsingContext,
] {
assert!(error.source().is_none());
assert!(!error.to_string().is_empty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub enum WebDriverBiDiCommandKind {
SessionStatus,
/// WebDriver BiDi `session.end`.
SessionEnd,
/// WebDriver BiDi `input.performActions` pointer click.
PointerClick,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ Fresh integration verification executes 13 focused received-message, response an

## Evidence and remaining risk

### Pointer-click child integration

PR #256 predecessor `9f2e6f29be46371762e3031a97c1cac04720694f` lacked the current connection-provenance implementation and collected zero command-correlation release tests under native discovery. The expected-one assertion failed before ordinary integration of parent `3e7057443d7c9532ff526acb5eefe8cd4778c767`; the inherited contract then collected and passed. Its core command implementation, exports and four pointer-click tests are byte-identical to the predecessor. The sole child delta in the parent's correlation module remains the documented `PointerClick` command kind. Serialization does not prove a browser click, authorize input, or establish an observed page post-condition.

Fresh child verification passed all four pointer-click command tests, 13 received-message/response/teardown tests, 142 Python contracts, compileall, and the complete Rust 1.97.1 format/check/workspace-test/strict-Clippy/rustdoc gates. Pinned coverage measured 1088 functions, 11077 lines, 14146 regions and 1210 branches at 100%, with the unstable branch-option warning retained. These are local integrated-tree results; hosted checks, independent approval where required and protected-main delivery remain unproven.

The repair includes a realistic two-connection regression for a foreign `session.end` success response and focused reader coverage for fragmented text, an interleaved control frame, malformed server framing, binary-message rejection, event/null-id correlation, and connection-generation exhaustion after verified peer admission. The original inline response-substitution and closure-substitution findings are resolved by the current source boundary, but exact-head CI and security workflows remain authoritative before any integration claim.

The current scope deliberately does not infer browser-process ownership from the WebSocket connection and does not make protocol success an operational teardown post-condition. Process-exit and profile-removal evidence remain unavailable until their canonical runtime owners provide non-forgeable contracts.
Expand Down
Loading