From a928037a89314bf9002762964b436f47fb3470ae Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 10:15:36 +0900 Subject: [PATCH 1/9] test(core): require sandboxed text-value postcondition command --- ...er_bidi_type_text_postcondition_command.rs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs diff --git a/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs b/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs new file mode 100644 index 000000000..b2df409b7 --- /dev/null +++ b/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs @@ -0,0 +1,92 @@ +use std::{error::Error, io}; + +use originweave_core::{ + BoundedWebDriverBiDiResponseDocument, BrowserAuthorityRegistry, BrowserContextDispatchTarget, + BrowserContextOriginDispatchTarget, BrowserContextOriginEpochDispatchTarget, + BrowserProtocolAdapterDescriptor, BrowserProtocolCapability, BrowserProtocolKind, Origin, + OriginWeaveProtocolVersion, ValidatedBrowserProtocolUse, WEBDRIVER_BIDI_SCRIPT_CALL_FUNCTION_METHOD, + WEBDRIVER_BIDI_TEXT_VALUE_FUNCTION_DECLARATION, WEBDRIVER_BIDI_TEXT_VALUE_SANDBOX, + WebDriverBiDiAccessibilityQuery, WebDriverBiDiLocateNodesCommand, + WebDriverBiDiRemoteNodeReference, WebDriverBiDiTextValueObservationCommand, +}; + +const ORIGINWEAVE_PROTOCOL_VERSION: OriginWeaveProtocolVersion = + OriginWeaveProtocolVersion::new(0, 1); +const ADAPTER_VERSION: &str = "originweave-bidi-v1"; +const PROTOCOL_REVISION: &str = "webdriver-bidi-wd-2026-06-01"; +const BROWSER_REVISION: &str = "chromium-r1639810"; + +fn semantic_observation_proof() -> Result> { + let descriptor = BrowserProtocolAdapterDescriptor::new( + BrowserProtocolKind::WebDriverBiDi, + ORIGINWEAVE_PROTOCOL_VERSION, + ADAPTER_VERSION, + PROTOCOL_REVISION, + BROWSER_REVISION, + &[BrowserProtocolCapability::SemanticObservation], + )?; + Ok(descriptor.validate_use( + ORIGINWEAVE_PROTOCOL_VERSION, + BrowserProtocolKind::WebDriverBiDi, + ADAPTER_VERSION, + PROTOCOL_REVISION, + BROWSER_REVISION, + BrowserProtocolCapability::SemanticObservation, + )?) +} + +#[test] +fn text_value_postcondition_is_a_fixed_sandboxed_node_observation() -> Result<(), Box> { + let mut registry = BrowserAuthorityRegistry::new(); + let browser_session = registry.register_session("webdriver-session")?; + let browsing_context = registry.register_context(browser_session, "context-a")?; + let origin = Origin::parse("https://app.example").map_err(|error| { + io::Error::other(format!("fixture origin rejected unexpectedly: {error:?}")) + })?; + let epoch = registry.bind_context_origin(browser_session, browsing_context, &origin)?; + let target = BrowserContextOriginEpochDispatchTarget::new( + BrowserContextOriginDispatchTarget::new( + BrowserContextDispatchTarget::new(browser_session, browsing_context), + &origin, + ), + epoch, + ); + let query = WebDriverBiDiAccessibilityQuery::new(Some("textbox"), Some("Task name"), 1)?; + let locate = WebDriverBiDiLocateNodesCommand::new(41, "context-a", &query)?; + let document = BoundedWebDriverBiDiResponseDocument::new( + r#"{"type":"success","id":41,"result":{"nodes":[{"type":"node","sharedId":"shared-input-42"}]}}"#, + )?; + let handle = locate + .bind_response_document_nodes( + document, + semantic_observation_proof()?, + &mut registry, + target, + )? + .into_iter() + .next() + .ok_or_else(|| io::Error::other("locateNodes fixture did not bind its node"))?; + let remote = WebDriverBiDiRemoteNodeReference::new("node", Some("shared-input-42"))?; + + let command = WebDriverBiDiTextValueObservationCommand::new_for_current_node( + 43, + "context-a", + &handle, + &remote, + ®istry, + )?; + + assert_eq!(command.command_id(), 43); + assert_eq!(command.method(), WEBDRIVER_BIDI_SCRIPT_CALL_FUNCTION_METHOD); + assert_eq!(command.browsing_context(), "context-a"); + assert_eq!(command.sandbox(), WEBDRIVER_BIDI_TEXT_VALUE_SANDBOX); + assert_eq!( + command.function_declaration(), + WEBDRIVER_BIDI_TEXT_VALUE_FUNCTION_DECLARATION + ); + assert_eq!( + command.as_json(), + r#"{"id":43,"method":"script.callFunction","params":{"functionDeclaration":"node => node.value","awaitPromise":false,"target":{"context":"context-a","sandbox":"originweave-postcondition-v1"},"arguments":[{"sharedId":"shared-input-42"}],"resultOwnership":"none"}}"# + ); + Ok(()) +} From e7ad5a855129a36ae638fa941f7822f6a54daa05 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 10:17:16 +0900 Subject: [PATCH 2/9] style(core): apply canonical rustfmt to postcondition regression --- .../webdriver_bidi_type_text_postcondition_command.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs b/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs index b2df409b7..0582d21c8 100644 --- a/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs +++ b/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs @@ -4,10 +4,11 @@ use originweave_core::{ BoundedWebDriverBiDiResponseDocument, BrowserAuthorityRegistry, BrowserContextDispatchTarget, BrowserContextOriginDispatchTarget, BrowserContextOriginEpochDispatchTarget, BrowserProtocolAdapterDescriptor, BrowserProtocolCapability, BrowserProtocolKind, Origin, - OriginWeaveProtocolVersion, ValidatedBrowserProtocolUse, WEBDRIVER_BIDI_SCRIPT_CALL_FUNCTION_METHOD, - WEBDRIVER_BIDI_TEXT_VALUE_FUNCTION_DECLARATION, WEBDRIVER_BIDI_TEXT_VALUE_SANDBOX, - WebDriverBiDiAccessibilityQuery, WebDriverBiDiLocateNodesCommand, - WebDriverBiDiRemoteNodeReference, WebDriverBiDiTextValueObservationCommand, + OriginWeaveProtocolVersion, ValidatedBrowserProtocolUse, + WEBDRIVER_BIDI_SCRIPT_CALL_FUNCTION_METHOD, WEBDRIVER_BIDI_TEXT_VALUE_FUNCTION_DECLARATION, + WEBDRIVER_BIDI_TEXT_VALUE_SANDBOX, WebDriverBiDiAccessibilityQuery, + WebDriverBiDiLocateNodesCommand, WebDriverBiDiRemoteNodeReference, + WebDriverBiDiTextValueObservationCommand, }; const ORIGINWEAVE_PROTOCOL_VERSION: OriginWeaveProtocolVersion = From edc5fe782433190aafc18b33b4f28cdce9234d6c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 10:19:37 +0900 Subject: [PATCH 3/9] feat(core): add fixed sandboxed text-value observation command --- .../webdriver_bidi_text_value_observation.rs | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 crates/originweave-core/src/webdriver_bidi_text_value_observation.rs diff --git a/crates/originweave-core/src/webdriver_bidi_text_value_observation.rs b/crates/originweave-core/src/webdriver_bidi_text_value_observation.rs new file mode 100644 index 000000000..ea69875ad --- /dev/null +++ b/crates/originweave-core/src/webdriver_bidi_text_value_observation.rs @@ -0,0 +1,228 @@ +use std::error::Error; +use std::fmt::{Display, Formatter}; + +use crate::{ + AdmittedNodeHandle, BrowserAuthorityRegistry, BrowserRegistryError, + MAX_WEBDRIVER_BIDI_COMMAND_ID, NodeHandleError, WebDriverBiDiRemoteNodeReference, +}; + +/// Exact WebDriver BiDi method used for the fixed text-value post-condition observation. +pub const WEBDRIVER_BIDI_SCRIPT_CALL_FUNCTION_METHOD: &str = "script.callFunction"; + +/// Product-owned function declaration used to read one admitted form control's current value. +/// +/// Callers cannot replace this source string. It exists only as a reviewed adapter implementation +/// detail for a typed semantic observation and is not an arbitrary JavaScript capability. +pub const WEBDRIVER_BIDI_TEXT_VALUE_FUNCTION_DECLARATION: &str = "node => node.value"; + +/// Isolated WebDriver BiDi sandbox used for typed text-value post-condition observations. +pub const WEBDRIVER_BIDI_TEXT_VALUE_SANDBOX: &str = "originweave-postcondition-v1"; + +/// Fail-closed validation errors for one serialized text-value observation command. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WebDriverBiDiTextValueObservationCommandError { + /// The command identifier exceeds WebDriver BiDi's unsigned safe-integer range. + InvalidCommandId, +} + +impl Display for WebDriverBiDiTextValueObservationCommandError { + 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", + }) + } +} + +impl Error for WebDriverBiDiTextValueObservationCommandError {} + +/// Fail-closed authority errors while binding a text-value observation to an admitted current node. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WebDriverBiDiTextValueObservationAuthorityError { + /// The final deterministic observation command failed its bounded serialization contract. + Command(WebDriverBiDiTextValueObservationCommandError), + /// Current browser session, context, or origin authority could not be revalidated. + BrowserAuthority(BrowserRegistryError), + /// The observed node belongs to a stale or otherwise mismatched browser document lifetime. + NodeHandle(NodeHandleError), + /// The supplied wire node identifier is not the identifier admitted for this exact node handle. + NodeExternalIdentifierMismatch, +} + +impl Display for WebDriverBiDiTextValueObservationAuthorityError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Command(error) => write!(formatter, "text-value observation rejected input: {error}"), + Self::BrowserAuthority(error) => write!( + formatter, + "text-value observation browser authority rejected input: {error}" + ), + Self::NodeHandle(error) => write!( + formatter, + "text-value observation node authority rejected input: {error}" + ), + Self::NodeExternalIdentifierMismatch => formatter.write_str( + "text-value observation wire node identifier does not match the admitted current node", + ), + } + } +} + +impl Error for WebDriverBiDiTextValueObservationAuthorityError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::Command(error) => Some(error), + Self::BrowserAuthority(error) => Some(error), + Self::NodeHandle(error) => Some(error), + Self::NodeExternalIdentifierMismatch => None, + } + } +} + +/// Deterministic sandboxed observation of one admitted text field's current value. +/// +/// This is a typed semantic-observation adapter primitive, not a generic scripting surface. The +/// function declaration and sandbox are fixed product-owned constants, the caller can supply only +/// the command id plus already admitted browser/node authority, and construction revalidates the +/// current session, browsing context, origin, document epoch, and exact WebDriver BiDi `sharedId`. +/// The command performs no I/O and grants no new browser, policy, destination, secret, or Agent +/// authority. A matching protocol response must still be parsed and compared with the intended +/// non-secret text before the preceding text-input action can be treated as observed success. +#[derive(PartialEq, Eq)] +pub struct WebDriverBiDiTextValueObservationCommand { + command_id: u64, + browsing_context: String, + json: String, +} + +impl std::fmt::Debug for WebDriverBiDiTextValueObservationCommand { + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("WebDriverBiDiTextValueObservationCommand") + .field("command_id", &self.command_id) + .field("method", &WEBDRIVER_BIDI_SCRIPT_CALL_FUNCTION_METHOD) + .field("sandbox", &WEBDRIVER_BIDI_TEXT_VALUE_SANDBOX) + .finish_non_exhaustive() + } +} + +impl WebDriverBiDiTextValueObservationCommand { + /// Bind one fixed text-value observation to the exact current semantic node admitted here. + pub fn new_for_current_node( + command_id: u64, + browsing_context: &str, + handle: &AdmittedNodeHandle, + node: &WebDriverBiDiRemoteNodeReference, + registry: &BrowserAuthorityRegistry, + ) -> Result { + registry + .require_context_external_identifier( + handle.browser_session(), + handle.browsing_context(), + browsing_context, + ) + .map_err(WebDriverBiDiTextValueObservationAuthorityError::BrowserAuthority)?; + + let current_epoch = registry + .require_context_origin( + handle.browser_session(), + handle.browsing_context(), + handle.origin(), + ) + .map_err(WebDriverBiDiTextValueObservationAuthorityError::BrowserAuthority)?; + handle + .validate_current( + handle.browser_session(), + handle.browsing_context(), + handle.origin(), + current_epoch, + ) + .map_err(WebDriverBiDiTextValueObservationAuthorityError::NodeHandle)?; + + if !registry.node_external_identifier_matches(handle, node.shared_id()) { + return Err( + WebDriverBiDiTextValueObservationAuthorityError::NodeExternalIdentifierMismatch, + ); + } + + Self::new(command_id, browsing_context, node) + .map_err(WebDriverBiDiTextValueObservationAuthorityError::Command) + } + + fn new( + command_id: u64, + browsing_context: &str, + node: &WebDriverBiDiRemoteNodeReference, + ) -> Result { + if command_id > MAX_WEBDRIVER_BIDI_COMMAND_ID { + return Err(WebDriverBiDiTextValueObservationCommandError::InvalidCommandId); + } + + let mut json = String::from("{\"id\":"); + json.push_str(&command_id.to_string()); + json.push_str(",\"method\":\""); + json.push_str(WEBDRIVER_BIDI_SCRIPT_CALL_FUNCTION_METHOD); + json.push_str("\",\"params\":{\"functionDeclaration\":\""); + json.push_str(WEBDRIVER_BIDI_TEXT_VALUE_FUNCTION_DECLARATION); + json.push_str("\",\"awaitPromise\":false,\"target\":{\"context\":"); + push_json_string(&mut json, browsing_context); + json.push_str(",\"sandbox\":\""); + json.push_str(WEBDRIVER_BIDI_TEXT_VALUE_SANDBOX); + json.push_str("\"},\"arguments\":[{\"sharedId\":"); + push_json_string(&mut json, node.shared_id()); + json.push_str("}],\"resultOwnership\":\"none\"}}"); + + 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_SCRIPT_CALL_FUNCTION_METHOD + } + + /// Return the exact validated browsing-context identifier. + #[must_use] + pub fn browsing_context(&self) -> &str { + &self.browsing_context + } + + /// Return the fixed isolated sandbox used for this typed observation. + #[must_use] + pub const fn sandbox(&self) -> &'static str { + WEBDRIVER_BIDI_TEXT_VALUE_SANDBOX + } + + /// Return the fixed product-owned function declaration used by this typed observation. + #[must_use] + pub const fn function_declaration(&self) -> &'static str { + WEBDRIVER_BIDI_TEXT_VALUE_FUNCTION_DECLARATION + } + + /// Return the deterministic JSON command envelope. + #[must_use] + pub fn as_json(&self) -> &str { + &self.json + } +} + +fn push_json_string(output: &mut String, value: &str) { + output.push('"'); + for character in value.chars() { + match character { + '"' => output.push_str("\\\""), + '\\' => output.push_str("\\\\"), + character => output.push(character), + } + } + output.push('"'); +} From 6007c9cf86504e553d68daa965b0c73b4bb16ca3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 10:20:21 +0900 Subject: [PATCH 4/9] feat(core): export typed text-value observation boundary --- crates/originweave-core/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index 0ebe416ca..070263bc2 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -52,6 +52,7 @@ mod webdriver_bidi_response_document; mod webdriver_bidi_response_document_correlation; mod webdriver_bidi_response_envelope; mod webdriver_bidi_result; +mod webdriver_bidi_text_value_observation; mod webdriver_bidi_type_text; mod webdriver_bidi_websocket_connect_target; mod webdriver_bidi_websocket_endpoint; @@ -107,6 +108,11 @@ pub use webdriver_bidi_response_envelope::{ pub use webdriver_bidi_result::{ ValidatedWebDriverBiDiLocateNodesResult, WebDriverBiDiLocateNodesResultAdmissionError, }; +pub use webdriver_bidi_text_value_observation::{ + WEBDRIVER_BIDI_SCRIPT_CALL_FUNCTION_METHOD, WEBDRIVER_BIDI_TEXT_VALUE_FUNCTION_DECLARATION, + WEBDRIVER_BIDI_TEXT_VALUE_SANDBOX, WebDriverBiDiTextValueObservationAuthorityError, + WebDriverBiDiTextValueObservationCommand, WebDriverBiDiTextValueObservationCommandError, +}; pub use webdriver_bidi_type_text::{ MAX_WEBDRIVER_BIDI_TYPE_TEXT_BYTES, WebDriverBiDiTypeTextAuthorityError, WebDriverBiDiTypeTextCommand, WebDriverBiDiTypeTextCommandError, From 3ebeaba9e6437b3289755e96f9fe8967e4f29615 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 10:21:22 +0900 Subject: [PATCH 5/9] test(core): cover text-value observation authority boundaries --- ...er_bidi_type_text_postcondition_command.rs | 255 ++++++++++++++++-- 1 file changed, 239 insertions(+), 16 deletions(-) diff --git a/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs b/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs index 0582d21c8..8931513b6 100644 --- a/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs +++ b/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs @@ -1,14 +1,17 @@ use std::{error::Error, io}; use originweave_core::{ - BoundedWebDriverBiDiResponseDocument, BrowserAuthorityRegistry, BrowserContextDispatchTarget, - BrowserContextOriginDispatchTarget, BrowserContextOriginEpochDispatchTarget, - BrowserProtocolAdapterDescriptor, BrowserProtocolCapability, BrowserProtocolKind, Origin, + AdmittedNodeHandle, BoundedWebDriverBiDiResponseDocument, BrowserAuthorityRegistry, + BrowserContextDispatchTarget, BrowserContextOriginDispatchTarget, + BrowserContextOriginEpochDispatchTarget, BrowserProtocolAdapterDescriptor, + BrowserProtocolCapability, BrowserProtocolKind, BrowserRegistryError, BrowserSessionId, + BrowsingContextId, MAX_WEBDRIVER_BIDI_COMMAND_ID, NodeHandleError, Origin, OriginWeaveProtocolVersion, ValidatedBrowserProtocolUse, WEBDRIVER_BIDI_SCRIPT_CALL_FUNCTION_METHOD, WEBDRIVER_BIDI_TEXT_VALUE_FUNCTION_DECLARATION, WEBDRIVER_BIDI_TEXT_VALUE_SANDBOX, WebDriverBiDiAccessibilityQuery, WebDriverBiDiLocateNodesCommand, WebDriverBiDiRemoteNodeReference, - WebDriverBiDiTextValueObservationCommand, + WebDriverBiDiTextValueObservationAuthorityError, WebDriverBiDiTextValueObservationCommand, + WebDriverBiDiTextValueObservationCommandError, }; const ORIGINWEAVE_PROTOCOL_VERSION: OriginWeaveProtocolVersion = @@ -17,6 +20,15 @@ const ADAPTER_VERSION: &str = "originweave-bidi-v1"; const PROTOCOL_REVISION: &str = "webdriver-bidi-wd-2026-06-01"; const BROWSER_REVISION: &str = "chromium-r1639810"; +struct AdmittedTextField { + registry: BrowserAuthorityRegistry, + browser_session: BrowserSessionId, + browsing_context: BrowsingContextId, + external_context: String, + handle: AdmittedNodeHandle, + remote: WebDriverBiDiRemoteNodeReference, +} + fn semantic_observation_proof() -> Result> { let descriptor = BrowserProtocolAdapterDescriptor::new( BrowserProtocolKind::WebDriverBiDi, @@ -36,11 +48,13 @@ fn semantic_observation_proof() -> Result Result<(), Box> { +fn admitted_text_field( + external_context: &str, + shared_id: &str, +) -> Result> { let mut registry = BrowserAuthorityRegistry::new(); let browser_session = registry.register_session("webdriver-session")?; - let browsing_context = registry.register_context(browser_session, "context-a")?; + let browsing_context = registry.register_context(browser_session, external_context)?; let origin = Origin::parse("https://app.example").map_err(|error| { io::Error::other(format!("fixture origin rejected unexpectedly: {error:?}")) })?; @@ -53,10 +67,11 @@ fn text_value_postcondition_is_a_fixed_sandboxed_node_observation() -> Result<() epoch, ); let query = WebDriverBiDiAccessibilityQuery::new(Some("textbox"), Some("Task name"), 1)?; - let locate = WebDriverBiDiLocateNodesCommand::new(41, "context-a", &query)?; - let document = BoundedWebDriverBiDiResponseDocument::new( - r#"{"type":"success","id":41,"result":{"nodes":[{"type":"node","sharedId":"shared-input-42"}]}}"#, - )?; + let locate = WebDriverBiDiLocateNodesCommand::new(41, external_context, &query)?; + let escaped_shared_id = shared_id.replace('\\', "\\\\").replace('"', "\\\""); + let document = BoundedWebDriverBiDiResponseDocument::new(&format!( + r#"{{"type":"success","id":41,"result":{{"nodes":[{{"type":"node","sharedId":"{escaped_shared_id}"}}]}}}}"#, + ))?; let handle = locate .bind_response_document_nodes( document, @@ -67,14 +82,26 @@ fn text_value_postcondition_is_a_fixed_sandboxed_node_observation() -> Result<() .into_iter() .next() .ok_or_else(|| io::Error::other("locateNodes fixture did not bind its node"))?; - let remote = WebDriverBiDiRemoteNodeReference::new("node", Some("shared-input-42"))?; + let remote = WebDriverBiDiRemoteNodeReference::new("node", Some(shared_id))?; + Ok(AdmittedTextField { + registry, + browser_session, + browsing_context, + external_context: external_context.to_owned(), + handle, + remote, + }) +} +#[test] +fn text_value_postcondition_is_a_fixed_sandboxed_node_observation() -> Result<(), Box> { + let fixture = admitted_text_field("context-a", "shared-input-42")?; let command = WebDriverBiDiTextValueObservationCommand::new_for_current_node( 43, - "context-a", - &handle, - &remote, - ®istry, + &fixture.external_context, + &fixture.handle, + &fixture.remote, + &fixture.registry, )?; assert_eq!(command.command_id(), 43); @@ -91,3 +118,199 @@ fn text_value_postcondition_is_a_fixed_sandboxed_node_observation() -> Result<() ); Ok(()) } + +#[test] +fn text_value_postcondition_escapes_only_protocol_identifiers() -> Result<(), Box> { + let fixture = admitted_text_field(r#"context-"quoted"\path"#, r#"node-"quoted"\path"#)?; + let command = WebDriverBiDiTextValueObservationCommand::new_for_current_node( + 43, + &fixture.external_context, + &fixture.handle, + &fixture.remote, + &fixture.registry, + )?; + + assert!(command.as_json().contains(r#"context-\"quoted\"\\path"#)); + assert!(command.as_json().contains(r#"node-\"quoted\"\\path"#)); + assert_eq!( + command.function_declaration(), + "node => node.value", + "page/model text must never become executable source" + ); + Ok(()) +} + +#[test] +fn text_value_postcondition_rejects_invalid_command_id() -> Result<(), Box> { + let fixture = admitted_text_field("context-a", "shared-input-42")?; + let error = WebDriverBiDiTextValueObservationCommand::new_for_current_node( + MAX_WEBDRIVER_BIDI_COMMAND_ID + 1, + &fixture.external_context, + &fixture.handle, + &fixture.remote, + &fixture.registry, + ) + .err() + .ok_or("expected oversized command id rejection")?; + + assert_eq!( + error, + WebDriverBiDiTextValueObservationAuthorityError::Command( + WebDriverBiDiTextValueObservationCommandError::InvalidCommandId, + ) + ); + assert!(error.source().is_some()); + assert!(error.to_string().contains("rejected input")); + assert_eq!( + WebDriverBiDiTextValueObservationCommandError::InvalidCommandId.to_string(), + "WebDriver BiDi command id is outside the js-uint range" + ); + Ok(()) +} + +#[test] +fn text_value_postcondition_rejects_wrong_context_and_unadmitted_node() +-> Result<(), Box> { + let fixture = admitted_text_field("context-a", "shared-input-42")?; + let wrong_context = WebDriverBiDiTextValueObservationCommand::new_for_current_node( + 43, + "context-b", + &fixture.handle, + &fixture.remote, + &fixture.registry, + ) + .err() + .ok_or("expected wrong external context rejection")?; + assert_eq!( + wrong_context, + WebDriverBiDiTextValueObservationAuthorityError::BrowserAuthority( + BrowserRegistryError::ContextExternalIdentifierMismatch, + ) + ); + assert!(wrong_context.source().is_some()); + assert!(wrong_context.to_string().contains("browser authority")); + + let forged = WebDriverBiDiRemoteNodeReference::new("node", Some("unadmitted-node"))?; + let wrong_node = WebDriverBiDiTextValueObservationCommand::new_for_current_node( + 43, + &fixture.external_context, + &fixture.handle, + &forged, + &fixture.registry, + ) + .err() + .ok_or("expected unadmitted sharedId rejection")?; + assert_eq!( + wrong_node, + WebDriverBiDiTextValueObservationAuthorityError::NodeExternalIdentifierMismatch + ); + assert!(wrong_node.source().is_none()); + assert!(wrong_node.to_string().contains("wire node identifier")); + Ok(()) +} + +#[test] +fn text_value_postcondition_rejects_cross_registry_handle() -> Result<(), Box> { + let local = admitted_text_field("context-a", "shared-input-42")?; + let foreign = admitted_text_field("context-a", "shared-input-42")?; + + let error = WebDriverBiDiTextValueObservationCommand::new_for_current_node( + 43, + &local.external_context, + &foreign.handle, + &local.remote, + &local.registry, + ) + .err() + .ok_or("expected cross-registry node rejection")?; + assert_eq!( + error, + WebDriverBiDiTextValueObservationAuthorityError::NodeExternalIdentifierMismatch + ); + Ok(()) +} + +#[test] +fn text_value_postcondition_rejects_changed_origin_authority() -> Result<(), Box> { + let mut fixture = admitted_text_field("context-a", "shared-input-42")?; + fixture.registry.advance_document(fixture.browsing_context)?; + let changed_origin = Origin::parse("https://changed.example").map_err(|error| { + io::Error::other(format!( + "changed fixture origin rejected unexpectedly: {error:?}" + )) + })?; + fixture.registry.bind_context_origin( + fixture.browser_session, + fixture.browsing_context, + &changed_origin, + )?; + + let error = WebDriverBiDiTextValueObservationCommand::new_for_current_node( + 43, + &fixture.external_context, + &fixture.handle, + &fixture.remote, + &fixture.registry, + ) + .err() + .ok_or("expected changed-origin rejection")?; + assert!(matches!( + error, + WebDriverBiDiTextValueObservationAuthorityError::BrowserAuthority(_) + )); + assert!(error.source().is_some()); + assert!(error.to_string().contains("browser authority")); + Ok(()) +} + +#[test] +fn text_value_postcondition_rejects_stale_document_authority() -> Result<(), Box> { + let mut fixture = admitted_text_field("context-a", "shared-input-42")?; + let observed = fixture.handle.document_epoch(); + let current = fixture.registry.advance_document(fixture.browsing_context)?; + let origin = fixture.handle.origin().clone(); + fixture.registry.bind_context_origin( + fixture.browser_session, + fixture.browsing_context, + &origin, + )?; + + let error = WebDriverBiDiTextValueObservationCommand::new_for_current_node( + 43, + &fixture.external_context, + &fixture.handle, + &fixture.remote, + &fixture.registry, + ) + .err() + .ok_or("expected stale document rejection")?; + assert_eq!( + error, + WebDriverBiDiTextValueObservationAuthorityError::NodeHandle( + NodeHandleError::StaleDocumentEpoch { observed, current }, + ) + ); + assert!(error.source().is_some()); + assert!(error.to_string().contains("node authority")); + Ok(()) +} + +#[test] +fn text_value_postcondition_debug_omits_wire_identifiers() -> Result<(), Box> { + let fixture = admitted_text_field("buyer-private-context", "buyer-private-node")?; + let command = WebDriverBiDiTextValueObservationCommand::new_for_current_node( + 43, + &fixture.external_context, + &fixture.handle, + &fixture.remote, + &fixture.registry, + )?; + + let debug = format!("{command:?}"); + assert!(!debug.contains("buyer-private-context")); + assert!(!debug.contains("buyer-private-node")); + assert!(debug.contains("command_id: 43")); + assert!(debug.contains(WEBDRIVER_BIDI_SCRIPT_CALL_FUNCTION_METHOD)); + assert!(debug.contains(WEBDRIVER_BIDI_TEXT_VALUE_SANDBOX)); + Ok(()) +} From 6a2c3b39912ae29fbb868243a8e494a3d3222f34 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 10:23:19 +0900 Subject: [PATCH 6/9] style(core): apply canonical rustfmt to postcondition authority tests --- ...webdriver_bidi_type_text_postcondition_command.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs b/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs index 8931513b6..589e6245e 100644 --- a/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs +++ b/crates/originweave-core/tests/webdriver_bidi_type_text_postcondition_command.rs @@ -169,8 +169,8 @@ fn text_value_postcondition_rejects_invalid_command_id() -> Result<(), Box Result<(), Box> { +fn text_value_postcondition_rejects_wrong_context_and_unadmitted_node() -> Result<(), Box> +{ let fixture = admitted_text_field("context-a", "shared-input-42")?; let wrong_context = WebDriverBiDiTextValueObservationCommand::new_for_current_node( 43, @@ -233,7 +233,9 @@ fn text_value_postcondition_rejects_cross_registry_handle() -> Result<(), Box Result<(), Box> { let mut fixture = admitted_text_field("context-a", "shared-input-42")?; - fixture.registry.advance_document(fixture.browsing_context)?; + fixture + .registry + .advance_document(fixture.browsing_context)?; let changed_origin = Origin::parse("https://changed.example").map_err(|error| { io::Error::other(format!( "changed fixture origin rejected unexpectedly: {error:?}" @@ -267,7 +269,9 @@ fn text_value_postcondition_rejects_changed_origin_authority() -> Result<(), Box fn text_value_postcondition_rejects_stale_document_authority() -> Result<(), Box> { let mut fixture = admitted_text_field("context-a", "shared-input-42")?; let observed = fixture.handle.document_epoch(); - let current = fixture.registry.advance_document(fixture.browsing_context)?; + let current = fixture + .registry + .advance_document(fixture.browsing_context)?; let origin = fixture.handle.origin().clone(); fixture.registry.bind_context_origin( fixture.browser_session, From 7854394266d3f292e779193c01413a34f6798d7c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 10:40:30 +0900 Subject: [PATCH 7/9] docs(core): scope typed-text observation evidence Record the active-stack authority boundary for the fixed text-value observation and keep its browser-I/O and outcome-verification limits executable in the documentation contract.\n\nCommit-Message-Assisted-by: Claude (via Claude Code) Signed-off-by: Seongho Bae --- CHANGELOG.md | 3 ++- .../action-postcondition-evidence.md | 13 +++++++++++-- tests/test_product_documentation_contract.py | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 642e59794..4e900e0f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Same-call QueryNodes admission that transfers a non-cloneable SemanticObservation protocol-use proof by ownership into `bind_current_nodes` before an untrusted `locateNodes` result can become current `ObservedNodeHandle` values, so Navigation-only or TypedInput-only proofs cannot mint observation handles. - Registry-issued admitted node handles and authority-bound WebDriver BiDi pointer-click construction that revalidate the exact session, context, canonical origin, document epoch, registry provenance, and retained `sharedId` before serializing `input.performActions`; caller-constructed node tuples or arbitrary wire identifiers cannot become typed-input authority, and the command itself grants no policy or Agent authority. - Node-bound WebDriver BiDi text input that revalidates the exact session, browsing context, canonical origin, current document epoch, registry-issued node provenance, and admitted `sharedId` before serializing bounded protocol-safe non-secret `input.performActions`; the command focuses the admitted element before keyboard input, and its diagnostic representation exposes only command metadata and text byte length rather than typed text or the serialized wire payload. +- Fixed sandboxed text-value observation for an admitted current node, with product-owned `script.callFunction` source and no generic script surface; transport correlation and observed post-condition comparison remain separate active-stack work. - Fail-closed rejection of reviewed Unicode format and bidirectional-override characters in accessibility roles, accessible names, BiDi `sharedId` values, and registry external identifiers, while ordinary spaces in accessible names remain valid. - Credential-safe browser-protocol validation evidence that copies only the already validated protocol family, OriginWeave generation, adapter version, pinned protocol/browser revisions, and exact capability into cloneable audit metadata without recreating the non-cloneable validation prerequisite or granting browser/Agent authority. - Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors. @@ -99,4 +100,4 @@ All notable changes to OriginWeave are documented in this file. The format follo - The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it. - The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels. -[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD \ No newline at end of file +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD diff --git a/docs/traceability/action-postcondition-evidence.md b/docs/traceability/action-postcondition-evidence.md index aab59102e..f0d4ff18d 100644 --- a/docs/traceability/action-postcondition-evidence.md +++ b/docs/traceability/action-postcondition-evidence.md @@ -3,7 +3,7 @@ - **Documentation status:** Active-stack evidence dossier; protected-main truth is called out separately - **Canonical owner:** issue #28 (`Complete the first real Chromium agent vertical slice`) - **Protected-main baseline:** `542ca1e9c0a863595b8b6697790005d2471f5413` -- **Active stack tip at this revision:** PR #266 (`feat/core: add node-bound WebDriver BiDi text input`) +- **Active stack tip at this revision:** PR #269 (`feat/core: observe typed-text postconditions without ambient script authority`) - **Capability maturity:** **PARTIAL** - **Governing decisions:** Accepted ADR 0003 plus Proposed ADR 0106 preserve provenance-native evidence and separation of action execution from verification. @@ -65,6 +65,14 @@ A current-source privacy defect was found and repaired test-first on this same c The pre-documentation exact repaired head `1958720a2f0f7e33e40bcea0073c486f37ad278d` passed CI run `33451284736` and Manifest V3 Compatibility run `33451284820`; Rust contracts included formatting, workspace checks, full tests, strict Clippy, and public API documentation, while Production coverage passed exact owned-production function/line/region/branch enforcement. A later documentation-only head must obtain its own fresh exact-head evidence before these results can be treated as current for the PR. +### PR #269 — fixed text-value observation for an admitted current node + +**Capability maturity:** `IMPLEMENTED_ON_ACTIVE_STACK` + +PR #269 adds a fixed sandboxed text-value observation command on top of the node-bound text-input stack. Construction revalidates the exact registered session, external browsing context, canonical origin, current document epoch, registry-issued node provenance, and admitted `sharedId`. The caller cannot replace the `script.callFunction` method, `node => node.value` function declaration, isolated sandbox, argument shape, or result-ownership policy. + +The command only serializes an observation request. It performs no browser I/O, accepts no page/model-supplied script, grants no policy or action authority, and does not prove browser execution or post-condition success. Descendant transport and response slices must correlate the exact response and compare the returned non-secret text with the intended value before any action outcome can become verified. + ## 4. Non-transitive success semantics The intended first-slice chain remains: @@ -103,7 +111,8 @@ The first real Chromium vertical slice remains distributed rather than shipped a - PR #64 verified post-condition and interruption evidence; - PRs #261–#264 committed-navigation origin/subscription/admission lifecycle; - PR #265 send-time pointer input revalidation against admitted node authority; and -- PR #266 node-bound non-secret text input with privacy-safe diagnostics. +- PR #266 node-bound non-secret text input with privacy-safe diagnostics; and +- PR #269 fixed text-value observation construction, still without browser I/O or outcome verification. These pieces do not transfer evidence across heads. A descendant must be revalidated after any parent movement, and protected-main shipment requires fresh integrated acceptance after dependency-ordered merge by an authorized integrator. diff --git a/tests/test_product_documentation_contract.py b/tests/test_product_documentation_contract.py index 1313189ea..42263ded1 100644 --- a/tests/test_product_documentation_contract.py +++ b/tests/test_product_documentation_contract.py @@ -312,6 +312,23 @@ def test_release_contract_never_bypasses_evidence_or_reproducibility(self) -> No self.assertIn(phrase, release) self.assertNotIn("residual unrun evidence", release) + def test_typed_text_postcondition_observation_is_durably_scoped(self) -> None: + """The active observation primitive must not become a shipped-runtime claim.""" + + traceability = ( + ROOT / "docs/traceability/action-postcondition-evidence.md" + ).read_text(encoding="utf-8") + changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8") + for phrase in ( + "PR #269", + "script.callFunction", + "node => node.value", + "does not prove browser execution or post-condition success", + ): + with self.subTest(phrase=phrase): + self.assertIn(phrase, traceability) + self.assertIn("Fixed sandboxed text-value observation", changelog) + def test_traceability_labels_conversation_derived_future_work(self) -> None: """Conversation decisions must preserve canonical maturity instead of becoming shipped claims.""" traceability = (ROOT / "docs/traceability/README.md").read_text(encoding="utf-8") From 658fb676dbcf615e1c03e8a41f114005e6d1b770 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 10:27:35 +0900 Subject: [PATCH 8/9] test(network): require inherited pointer session safeguard --- ...nter_click_transport_session_provenance.rs | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 crates/originweave-network/tests/webdriver_bidi_pointer_click_transport_session_provenance.rs diff --git a/crates/originweave-network/tests/webdriver_bidi_pointer_click_transport_session_provenance.rs b/crates/originweave-network/tests/webdriver_bidi_pointer_click_transport_session_provenance.rs new file mode 100644 index 000000000..192c9a1d5 --- /dev/null +++ b/crates/originweave-network/tests/webdriver_bidi_pointer_click_transport_session_provenance.rs @@ -0,0 +1,191 @@ +use std::{ + error::Error, + io::{self, Read, Write}, + net::{TcpListener, TcpStream}, + thread, + time::Duration, +}; + +use originweave_core::{ + AdmittedNodeHandle, BoundedWebDriverBiDiResponseDocument, BrowserAuthorityRegistry, + BrowserContextDispatchTarget, BrowserContextOriginDispatchTarget, + BrowserContextOriginEpochDispatchTarget, BrowserProtocolAdapterDescriptor, + BrowserProtocolCapability, BrowserProtocolKind, Origin, OriginWeaveProtocolVersion, + ValidatedBrowserProtocolUse, WebDriverBiDiAccessibilityQuery, WebDriverBiDiLocateNodesCommand, + WebDriverBiDiPointerClickAuthorityError, WebDriverBiDiRemoteNodeReference, + WebDriverBiDiWebSocketEndpoint, +}; +use originweave_network::{ + WebDriverBiDiCommandCorrelation, WebDriverBiDiPointerClickSendError, + WebDriverBiDiTcpConnectionPlan, WebDriverBiDiWebSocketClientKey, + WebDriverBiDiWebSocketHandshakePlan, WebDriverBiDiWebSocketMaskKey, + send_webdriver_bidi_pointer_click, +}; + +const REGISTRY_SESSION_ID: &str = "01234567-89ab-cdef-0123-456789abcdef"; +const FOREIGN_TRANSPORT_SESSION_ID: &str = "fedcba98-7654-3210-fedc-ba9876543210"; +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"; +const ORIGINWEAVE_PROTOCOL_VERSION: OriginWeaveProtocolVersion = + OriginWeaveProtocolVersion::new(0, 1); +const ADAPTER_VERSION: &str = "originweave-bidi-v1"; +const PROTOCOL_REVISION: &str = "webdriver-bidi-wd-2026-06-01"; +const BROWSER_REVISION: &str = "chromium-r1639810"; + +fn protocol_proof( + capability: BrowserProtocolCapability, +) -> Result> { + let descriptor = BrowserProtocolAdapterDescriptor::new( + BrowserProtocolKind::WebDriverBiDi, + ORIGINWEAVE_PROTOCOL_VERSION, + ADAPTER_VERSION, + PROTOCOL_REVISION, + BROWSER_REVISION, + &[capability], + )?; + Ok(descriptor.validate_use( + ORIGINWEAVE_PROTOCOL_VERSION, + BrowserProtocolKind::WebDriverBiDi, + ADAPTER_VERSION, + PROTOCOL_REVISION, + BROWSER_REVISION, + capability, + )?) +} + +fn current_node_fixture() -> Result< + ( + BrowserAuthorityRegistry, + AdmittedNodeHandle, + WebDriverBiDiRemoteNodeReference, + ), + Box, +> { + let mut registry = BrowserAuthorityRegistry::new(); + let browser_session = registry.register_session(REGISTRY_SESSION_ID)?; + let browsing_context = registry.register_context(browser_session, "context-a")?; + let origin = Origin::parse("https://app.example") + .map_err(|error| io::Error::other(format!("fixture origin rejected: {error:?}")))?; + let epoch = registry.bind_context_origin(browser_session, browsing_context, &origin)?; + let target = BrowserContextOriginEpochDispatchTarget::new( + BrowserContextOriginDispatchTarget::new( + BrowserContextDispatchTarget::new(browser_session, browsing_context), + &origin, + ), + epoch, + ); + let query = WebDriverBiDiAccessibilityQuery::new(Some("button"), Some("Submit task"), 1)?; + let locate = WebDriverBiDiLocateNodesCommand::new(41, "context-a", &query)?; + let document = BoundedWebDriverBiDiResponseDocument::new( + r#"{"type":"success","id":41,"result":{"nodes":[{"type":"node","sharedId":"shared-node-42"}]}}"#, + )?; + let handle = locate + .bind_response_document_nodes( + document, + protocol_proof(BrowserProtocolCapability::SemanticObservation)?, + &mut registry, + target, + )? + .into_iter() + .next() + .ok_or_else(|| io::Error::other("locateNodes fixture did not bind its node"))?; + let remote = WebDriverBiDiRemoteNodeReference::new("node", Some("shared-node-42"))?; + Ok((registry, handle, remote)) +} + +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, + "opening request ended before the header terminator", + )); + } + request.extend_from_slice(&buffer[..count]); + } + Ok(()) +} + +#[test] +fn current_node_pointer_click_is_rejected_before_writing_to_a_foreign_session_transport() +-> Result<(), Box> { + let (registry, handle, remote) = current_node_fixture()?; + 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)?; + stream.set_read_timeout(Some(Duration::from_millis(500)))?; + let mut first_command_byte = [0_u8; 1]; + match stream.read(&mut first_command_byte) { + Ok(0) => Ok(false), + Ok(_) => Ok(true), + Err(source) + if matches!( + source.kind(), + io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut + ) => + { + Ok(false) + } + Err(source) => Err(source), + } + }); + + let endpoint = format!("ws://{local_addr}/session/{FOREIGN_TRANSPORT_SESSION_ID}"); + let target = WebDriverBiDiWebSocketEndpoint::new(&endpoint)? + .correlate_session_id(FOREIGN_TRANSPORT_SESSION_ID)? + .into_explicit_connect_target()?; + let connection = + WebDriverBiDiTcpConnectionPlan::new(target, Duration::from_secs(1), 1)?.connect()?; + let established = WebDriverBiDiWebSocketHandshakePlan::new( + connection, + WebDriverBiDiWebSocketClientKey::new(RFC6455_SAMPLE_KEY)?, + )? + .write_opening_request(Duration::from_millis(500))? + .read_opening_response(Duration::from_millis(500))?; + let mut correlation = WebDriverBiDiCommandCorrelation::new(); + + let send_result = send_webdriver_bidi_pointer_click( + protocol_proof(BrowserProtocolCapability::TypedInput)?, + 42, + "context-a", + &handle, + &remote, + ®istry, + established, + &mut correlation, + WebDriverBiDiWebSocketMaskKey::new([1, 2, 3, 4]), + Duration::from_millis(500), + ); + let command_byte_seen = server + .join() + .map_err(|_| io::Error::other("foreign-session pointer server panicked"))??; + + let error = send_result.err().ok_or_else(|| { + io::Error::other( + "registry session A unexpectedly dispatched pointer input on transport session B", + ) + })?; + assert!(matches!( + error, + WebDriverBiDiPointerClickSendError::Authority { + source: WebDriverBiDiPointerClickAuthorityError::BrowserAuthority(_) + } + )); + assert_eq!( + correlation.outstanding_count(), + 0, + "foreign-session rejection must happen before correlation registration" + ); + assert!( + !command_byte_seen, + "foreign-session rejection must happen before any pointer command-frame byte" + ); + Ok(()) +} From 3df2a631bacd7109b3982fdd7ac599d0bd92a589 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 10:29:32 +0900 Subject: [PATCH 9/9] docs: record fixed observation parent adoption evidence --- CHANGELOG.md | 1 + docs/doctoring.md | 9 +++++++++ .../action-postcondition-evidence.md | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f92d2ee7..c6ae34f31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ## [Unreleased] +- Preserve fixed field-observation checks while adopting current input safeguards; constructing a request still does not verify that the field changed. - Label earlier text-reply limitations as historical so they do not contradict the later click safeguards. - Preserve text-reply checks while adopting click-session and reply safeguards. A matched response still does not prove the requested field changed. - Reject text-entry replies received on a replacement connection without losing the original pending request. The original connection can still complete it; a reply alone does not prove the field changed. diff --git a/docs/doctoring.md b/docs/doctoring.md index 77b61acac..c9fa86bee 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -4,6 +4,15 @@ This document records external evidence that changes OriginWeave architecture, t ## Decision trace +### Fixed field observation retains current input safeguards + +The canonical foreign-session socket regression fails at `658fb676` on #269's +old parent. Ordinary merge `f883fd6f` adopts #268 `ff27220c`, preserving the fixed +observation implementation and all eight child regressions from `78543942`. +This reuses existing session and receipt policy; it introduces no new standards +claim or general-purpose scripting surface. Request construction, transport, +response admission and observed action success remain separate evidence stages. + ### Text-response stack retains pointer session and receipt safeguards Real-socket replay `716fd842` reproduced all three inherited pointer failures on diff --git a/docs/traceability/action-postcondition-evidence.md b/docs/traceability/action-postcondition-evidence.md index 56be2322f..151ae31f2 100644 --- a/docs/traceability/action-postcondition-evidence.md +++ b/docs/traceability/action-postcondition-evidence.md @@ -1,5 +1,24 @@ # Action Post-Condition Evidence Traceability +## Fixed field observation adopts current input safeguards — 2026-09-07 + +Ordinary merge `f883fd6f` adopts #268 `ff27220c` while retaining #269 +`78543942` and its fixed observation command and complete eight-test regression +file byte-for-byte. Actual RED `658fb676` first demonstrated that the old child +could dispatch a click to a foreign browser session. The inherited sender now +rejects it before writing or reserving a request. Parent connection-bound text +and pointer replies, command-family isolation and subscription safeguards are +retained together rather than copied into another implementation. + +The observation command still only constructs a request for the admitted field. +Its function, sandbox and argument shape remain fixed; callers cannot supply +script source. Current document, origin, session and registry provenance checks +and private diagnostics are preserved. Construction does not perform I/O, accept +a result, compare the field value or prove an authorized action succeeded. +The old child's hosted CI and MV3 success apply only to `78543942`; this combined +head requires fresh complete verification and actual visual inspection. The +checkpoints below retain historical evidence, not current-head acceptance. + ## Text responses retain pointer safeguards — 2026-09-07 Ordinary merge `34e537b1` adopts published #267 `ebd507ae` while preserving