From f845e51c5f31d7b700aeaf65ce7818d9d5f940e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 10:04:58 +0900 Subject: [PATCH 1/2] test(core): reject disabled semantic actions --- .../tests/semantic_node_action_target.rs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/tests/semantic_node_action_target.rs b/crates/originweave-core/tests/semantic_node_action_target.rs index 33d00155..af095a0c 100644 --- a/crates/originweave-core/tests/semantic_node_action_target.rs +++ b/crates/originweave-core/tests/semantic_node_action_target.rs @@ -7,6 +7,10 @@ use originweave_core::{ }; fn observation() -> Result { + observation_with_enabled(true) +} + +fn observation_with_enabled(enabled: bool) -> Result { let handle = ObservedNodeHandle::new( BrowserSessionId::new(7).map_err(|error| error.to_string())?, BrowsingContextId::new(11).map_err(|error| error.to_string())?, @@ -23,10 +27,10 @@ fn observation() -> Result { role: "button".to_owned(), accessible_name: "Save draft".to_owned(), visible_text: Some("Save draft".to_owned()), - enabled: true, + enabled, visible: true, selected: None, - supported_actions: BTreeSet::from([NodeActionKind::Click]), + supported_actions: BTreeSet::from([NodeActionKind::Click, NodeActionKind::ScrollIntoView]), evidence_channels: BTreeSet::from([ObservationChannel::Accessibility]), }) .map_err(|error| error.to_string()) @@ -53,6 +57,26 @@ fn unsupported_node_action_fails_closed_without_minting_authority() -> Result<() Ok(()) } +#[test] +fn disabled_interactive_node_action_fails_closed() -> Result<(), String> { + let observed = observation_with_enabled(false)?; + assert_eq!( + SemanticNodeActionTarget::from_observation(&observed, NodeActionKind::Click).err(), + Some(SemanticNodeActionTargetError::NodeNotEnabled) + ); + Ok(()) +} + +#[test] +fn disabled_node_can_still_be_targeted_for_scroll_only() -> Result<(), String> { + let observed = observation_with_enabled(false)?; + let target = + SemanticNodeActionTarget::from_observation(&observed, NodeActionKind::ScrollIntoView) + .map_err(|error| error.to_string())?; + assert_eq!(target.action(), NodeActionKind::ScrollIntoView); + Ok(()) +} + #[test] fn node_action_target_revalidates_exact_browser_authority() -> Result<(), String> { let observed = observation()?; @@ -181,4 +205,8 @@ fn node_action_target_error_is_stable_and_credential_free() { SemanticNodeActionTargetError::UnsupportedAction.to_string(), "semantic node action is not advertised by the observation" ); + assert_eq!( + SemanticNodeActionTargetError::NodeNotEnabled.to_string(), + "semantic node is not enabled for the requested action" + ); } From bd75a43ddcd0a7afa4f032ecc2b930d742c3ece5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 10:06:27 +0900 Subject: [PATCH 2/2] fix(core): reject disabled semantic actions --- crates/originweave-core/src/semantic_action_target.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/originweave-core/src/semantic_action_target.rs b/crates/originweave-core/src/semantic_action_target.rs index 85fe59d2..9fbdd00f 100644 --- a/crates/originweave-core/src/semantic_action_target.rs +++ b/crates/originweave-core/src/semantic_action_target.rs @@ -16,7 +16,7 @@ pub struct SemanticNodeActionTarget { } impl SemanticNodeActionTarget { - /// Construct a target only when the observation advertised the requested node-local action. + /// Construct a target only when the observation advertises a currently coherent node action. pub fn from_observation( observation: &SemanticNodeObservation, action: NodeActionKind, @@ -24,6 +24,9 @@ impl SemanticNodeActionTarget { if !observation.supported_actions().contains(&action) { return Err(SemanticNodeActionTargetError::UnsupportedAction); } + if action != NodeActionKind::ScrollIntoView && !observation.is_enabled() { + return Err(SemanticNodeActionTargetError::NodeNotEnabled); + } Ok(Self { handle: observation.handle().clone(), action, @@ -64,6 +67,8 @@ impl SemanticNodeActionTarget { pub enum SemanticNodeActionTargetError { /// The requested action was not advertised by the semantic observation. UnsupportedAction, + /// The observation reported the target disabled for an interactive action. + NodeNotEnabled, } impl fmt::Display for SemanticNodeActionTargetError { @@ -72,6 +77,9 @@ impl fmt::Display for SemanticNodeActionTargetError { Self::UnsupportedAction => { formatter.write_str("semantic node action is not advertised by the observation") } + Self::NodeNotEnabled => { + formatter.write_str("semantic node is not enabled for the requested action") + } } } }