From 308fa77a5ea84ad428101792da9d8a7a0b9fcdac Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 05:46:41 +0900 Subject: [PATCH 1/8] test(core): require authority-bound semantic action target --- .../tests/semantic_node_action_target.rs | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 crates/originweave-core/tests/semantic_node_action_target.rs diff --git a/crates/originweave-core/tests/semantic_node_action_target.rs b/crates/originweave-core/tests/semantic_node_action_target.rs new file mode 100644 index 000000000..f3d094600 --- /dev/null +++ b/crates/originweave-core/tests/semantic_node_action_target.rs @@ -0,0 +1,106 @@ +use std::collections::BTreeSet; + +use originweave_core::{ + BrowserSessionId, BrowsingContextId, DocumentEpoch, NodeActionKind, NodeHandleError, + ObservationChannel, ObservedNodeHandle, Origin, SemanticNodeActionTarget, + SemanticNodeActionTargetError, SemanticNodeObservation, SemanticNodeObservationInput, +}; + +fn observation() -> Result { + let handle = ObservedNodeHandle::new( + BrowserSessionId::new(7).map_err(|error| error.to_string())?, + BrowsingContextId::new(11).map_err(|error| error.to_string())?, + Origin::parse("https://example.com").map_err(|error| format!("{error:?}"))?, + DocumentEpoch::new(3).map_err(|error| error.to_string())?, + 17, + ) + .map_err(|error| error.to_string())?; + + SemanticNodeObservation::new(SemanticNodeObservationInput { + handle, + parent: None, + children: Vec::new(), + role: "button".to_owned(), + accessible_name: "Save draft".to_owned(), + visible_text: Some("Save draft".to_owned()), + enabled: true, + visible: true, + selected: None, + supported_actions: BTreeSet::from([NodeActionKind::Click]), + evidence_channels: BTreeSet::from([ObservationChannel::Accessibility]), + }) + .map_err(|error| error.to_string()) +} + +#[test] +fn advertised_node_action_becomes_an_authority_bound_target() -> Result<(), String> { + let observed = observation()?; + let target = SemanticNodeActionTarget::from_observation(&observed, NodeActionKind::Click) + .map_err(|error| error.to_string())?; + + assert_eq!(target.handle(), observed.handle()); + assert_eq!(target.action(), NodeActionKind::Click); + Ok(()) +} + +#[test] +fn unsupported_node_action_fails_closed_without_minting_authority() -> Result<(), String> { + let observed = observation()?; + assert_eq!( + SemanticNodeActionTarget::from_observation(&observed, NodeActionKind::TypeText).err(), + Some(SemanticNodeActionTargetError::UnsupportedAction) + ); + Ok(()) +} + +#[test] +fn node_action_target_revalidates_exact_browser_authority() -> Result<(), String> { + let observed = observation()?; + let target = SemanticNodeActionTarget::from_observation(&observed, NodeActionKind::Click) + .map_err(|error| error.to_string())?; + let current_origin = Origin::parse("https://example.com").map_err(|error| format!("{error:?}"))?; + + target + .validate_current( + BrowserSessionId::new(7).map_err(|error| error.to_string())?, + BrowsingContextId::new(11).map_err(|error| error.to_string())?, + ¤t_origin, + DocumentEpoch::new(3).map_err(|error| error.to_string())?, + ) + .map_err(|error| error.to_string())?; + Ok(()) +} + +#[test] +fn node_action_target_rejects_stale_document_authority() -> Result<(), String> { + let observed = observation()?; + let target = SemanticNodeActionTarget::from_observation(&observed, NodeActionKind::Click) + .map_err(|error| error.to_string())?; + let current_origin = Origin::parse("https://example.com").map_err(|error| format!("{error:?}"))?; + let observed_epoch = DocumentEpoch::new(3).map_err(|error| error.to_string())?; + let current_epoch = DocumentEpoch::new(4).map_err(|error| error.to_string())?; + + assert_eq!( + target + .validate_current( + BrowserSessionId::new(7).map_err(|error| error.to_string())?, + BrowsingContextId::new(11).map_err(|error| error.to_string())?, + ¤t_origin, + current_epoch, + ) + .err(), + Some(NodeHandleError::StaleDocumentEpoch { + observed: observed_epoch, + current: current_epoch, + }) + ); + Ok(()) +} + +#[test] +fn node_action_target_error_is_stable_and_credential_free() { + assert_eq!( + SemanticNodeActionTargetError::UnsupportedAction.to_string(), + "semantic node action is not advertised by the observation" + ); +} From f2bb6db04488e4bfa873e290eb8b6283e1238fdf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 05:48:09 +0900 Subject: [PATCH 2/8] style(core): apply canonical action-target test formatting --- .../originweave-core/tests/semantic_node_action_target.rs | 6 ++++-- 1 file changed, 4 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 f3d094600..fbad934c4 100644 --- a/crates/originweave-core/tests/semantic_node_action_target.rs +++ b/crates/originweave-core/tests/semantic_node_action_target.rs @@ -58,7 +58,8 @@ fn node_action_target_revalidates_exact_browser_authority() -> Result<(), String let observed = observation()?; let target = SemanticNodeActionTarget::from_observation(&observed, NodeActionKind::Click) .map_err(|error| error.to_string())?; - let current_origin = Origin::parse("https://example.com").map_err(|error| format!("{error:?}"))?; + let current_origin = + Origin::parse("https://example.com").map_err(|error| format!("{error:?}"))?; target .validate_current( @@ -76,7 +77,8 @@ fn node_action_target_rejects_stale_document_authority() -> Result<(), String> { let observed = observation()?; let target = SemanticNodeActionTarget::from_observation(&observed, NodeActionKind::Click) .map_err(|error| error.to_string())?; - let current_origin = Origin::parse("https://example.com").map_err(|error| format!("{error:?}"))?; + let current_origin = + Origin::parse("https://example.com").map_err(|error| format!("{error:?}"))?; let observed_epoch = DocumentEpoch::new(3).map_err(|error| error.to_string())?; let current_epoch = DocumentEpoch::new(4).map_err(|error| error.to_string())?; From ef5fd33c918f5ffb53bce183bf7218f0ea52a0de Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 05:51:00 +0900 Subject: [PATCH 3/8] feat(core): add authority-bound semantic action target --- .../src/semantic_action_target.rs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 crates/originweave-core/src/semantic_action_target.rs diff --git a/crates/originweave-core/src/semantic_action_target.rs b/crates/originweave-core/src/semantic_action_target.rs new file mode 100644 index 000000000..85fe59d29 --- /dev/null +++ b/crates/originweave-core/src/semantic_action_target.rs @@ -0,0 +1,79 @@ +use std::fmt; + +use crate::{ + BrowserSessionId, BrowsingContextId, DocumentEpoch, NodeActionKind, NodeHandleError, + ObservedNodeHandle, Origin, SemanticNodeObservation, +}; + +/// One node-local action bound to the exact browser authority that produced its observation. +/// +/// This value narrows descriptive observation evidence into a stale-checkable action target. It +/// does not grant policy authority, classify business risk, or execute browser input. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SemanticNodeActionTarget { + handle: ObservedNodeHandle, + action: NodeActionKind, +} + +impl SemanticNodeActionTarget { + /// Construct a target only when the observation advertised the requested node-local action. + pub fn from_observation( + observation: &SemanticNodeObservation, + action: NodeActionKind, + ) -> Result { + if !observation.supported_actions().contains(&action) { + return Err(SemanticNodeActionTargetError::UnsupportedAction); + } + Ok(Self { + handle: observation.handle().clone(), + action, + }) + } + + /// Return the exact OriginWeave-owned node handle retained by this target. + #[must_use] + pub const fn handle(&self) -> &ObservedNodeHandle { + &self.handle + } + + /// Return the descriptive node-local action selected from the observation. + #[must_use] + pub const fn action(&self) -> NodeActionKind { + self.action + } + + /// Revalidate session, context, origin, and document authority immediately before later use. + pub fn validate_current( + &self, + current_session: BrowserSessionId, + current_context: BrowsingContextId, + current_origin: &Origin, + current_epoch: DocumentEpoch, + ) -> Result<(), NodeHandleError> { + self.handle.validate_current( + current_session, + current_context, + current_origin, + current_epoch, + ) + } +} + +/// A bounded validation failure when deriving one semantic node action target. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SemanticNodeActionTargetError { + /// The requested action was not advertised by the semantic observation. + UnsupportedAction, +} + +impl fmt::Display for SemanticNodeActionTargetError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnsupportedAction => { + formatter.write_str("semantic node action is not advertised by the observation") + } + } + } +} + +impl std::error::Error for SemanticNodeActionTargetError {} From 01b1500137bdaa8d6d011f9e167ec90eb0f30191 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 05:51:12 +0900 Subject: [PATCH 4/8] feat(core): export semantic action target contract --- crates/originweave-core/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index 61803eb6b..e604c8997 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -2,7 +2,7 @@ //! //! This crate keeps the long-lived value contracts in `contracts`, the //! protocol-identifier registry in a focused module, and bounded semantic -//! observations in a separate authority-preserving module. +//! observations in separate authority-preserving modules. #![forbid(unsafe_code)] #![deny(missing_docs)] @@ -11,12 +11,14 @@ mod browser_registry; #[cfg(test)] mod browser_registry_coverage; mod contracts; +mod semantic_action_target; mod semantic_observation; pub use browser_registry::{ BrowserAuthorityRegistry, BrowserRegistryError, MAX_EXTERNAL_BROWSER_IDENTIFIER_BYTES, }; pub use contracts::*; +pub use semantic_action_target::{SemanticNodeActionTarget, SemanticNodeActionTargetError}; pub use semantic_observation::{ MAX_ACCESSIBLE_NAME_BYTES, MAX_SEMANTIC_CHILDREN, MAX_SEMANTIC_ROLE_BYTES, MAX_VISIBLE_TEXT_BYTES, NodeActionKind, ObservationChannel, SemanticNodeObservation, From c81c273e302a47ca9c333e82cc1503d4a43b4e6e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 05:52:23 +0900 Subject: [PATCH 5/8] test(core): prove semantic action target authority invalidation --- .../tests/semantic_node_action_target.rs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/crates/originweave-core/tests/semantic_node_action_target.rs b/crates/originweave-core/tests/semantic_node_action_target.rs index fbad934c4..33d001551 100644 --- a/crates/originweave-core/tests/semantic_node_action_target.rs +++ b/crates/originweave-core/tests/semantic_node_action_target.rs @@ -72,6 +72,82 @@ fn node_action_target_revalidates_exact_browser_authority() -> Result<(), String Ok(()) } +#[test] +fn node_action_target_rejects_cross_session_authority() -> Result<(), String> { + let observed = observation()?; + let target = SemanticNodeActionTarget::from_observation(&observed, NodeActionKind::Click) + .map_err(|error| error.to_string())?; + let current_origin = + Origin::parse("https://example.com").map_err(|error| format!("{error:?}"))?; + let observed_session = BrowserSessionId::new(7).map_err(|error| error.to_string())?; + let current_session = BrowserSessionId::new(8).map_err(|error| error.to_string())?; + + assert_eq!( + target + .validate_current( + current_session, + BrowsingContextId::new(11).map_err(|error| error.to_string())?, + ¤t_origin, + DocumentEpoch::new(3).map_err(|error| error.to_string())?, + ) + .err(), + Some(NodeHandleError::BrowserSessionMismatch { + observed: observed_session, + current: current_session, + }) + ); + Ok(()) +} + +#[test] +fn node_action_target_rejects_cross_context_authority() -> Result<(), String> { + let observed = observation()?; + let target = SemanticNodeActionTarget::from_observation(&observed, NodeActionKind::Click) + .map_err(|error| error.to_string())?; + let current_origin = + Origin::parse("https://example.com").map_err(|error| format!("{error:?}"))?; + let observed_context = BrowsingContextId::new(11).map_err(|error| error.to_string())?; + let current_context = BrowsingContextId::new(12).map_err(|error| error.to_string())?; + + assert_eq!( + target + .validate_current( + BrowserSessionId::new(7).map_err(|error| error.to_string())?, + current_context, + ¤t_origin, + DocumentEpoch::new(3).map_err(|error| error.to_string())?, + ) + .err(), + Some(NodeHandleError::BrowsingContextMismatch { + observed: observed_context, + current: current_context, + }) + ); + Ok(()) +} + +#[test] +fn node_action_target_rejects_cross_origin_authority() -> Result<(), String> { + let observed = observation()?; + let target = SemanticNodeActionTarget::from_observation(&observed, NodeActionKind::Click) + .map_err(|error| error.to_string())?; + let current_origin = + Origin::parse("https://other.example").map_err(|error| format!("{error:?}"))?; + + assert_eq!( + target + .validate_current( + BrowserSessionId::new(7).map_err(|error| error.to_string())?, + BrowsingContextId::new(11).map_err(|error| error.to_string())?, + ¤t_origin, + DocumentEpoch::new(3).map_err(|error| error.to_string())?, + ) + .err(), + Some(NodeHandleError::OriginMismatch) + ); + Ok(()) +} + #[test] fn node_action_target_rejects_stale_document_authority() -> Result<(), String> { let observed = observation()?; From efe440c7a609cac187faacfa03a4df904a99386f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 05:54:01 +0900 Subject: [PATCH 6/8] docs(changelog): record semantic action target boundary --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 315343a22..8f1d9106e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Universally value-redacted network evidence with explicit path, metadata, and provenance bounds; ambiguous path rejection; validated source URLs; lowercase SHA-256 identifiers; and verification state. - Authority-bound, bounded semantic node observations with typed node-local action evidence and explicit observation-channel provenance for the first Chromium vertical slice; observation metadata grants no execution authority. - Bounded typed semantic node queries over reviewed role, accessible-name, and node-action evidence, without exposing raw DOM/protocol selector languages or granting execution authority. +- Authority-bound semantic node action targets that accept only observation-advertised node-local actions and revalidate exact session, context, origin, and document authority before later use without granting policy or browser-execution authority. - Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement. - Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge. - Architecture, agent, security, contribution, research, database naming, roadmap, quality-gate, and TLS service-identity ADR documentation. From 7656d9823b4f6e09d2a12ef4a205af792ecd31cc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 04:46:26 -0700 Subject: [PATCH 7/8] fix(core): revalidate action targets against live registry --- .../src/semantic_action_target.rs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/crates/originweave-core/src/semantic_action_target.rs b/crates/originweave-core/src/semantic_action_target.rs index 85fe59d29..6d4a1a418 100644 --- a/crates/originweave-core/src/semantic_action_target.rs +++ b/crates/originweave-core/src/semantic_action_target.rs @@ -1,8 +1,8 @@ use std::fmt; use crate::{ - BrowserSessionId, BrowsingContextId, DocumentEpoch, NodeActionKind, NodeHandleError, - ObservedNodeHandle, Origin, SemanticNodeObservation, + BrowserAuthorityRegistry, BrowserRegistryError, NodeActionKind, ObservedNodeHandle, + SemanticNodeObservation, }; /// One node-local action bound to the exact browser authority that produced its observation. @@ -42,20 +42,15 @@ impl SemanticNodeActionTarget { self.action } - /// Revalidate session, context, origin, and document authority immediately before later use. + /// Revalidate this target against current registry-owned browser authority before later use. + /// + /// The exact node binding must still be live in `registry`; a caller cannot revive a retired + /// or stale target merely by presenting a self-consistent session/context/origin/epoch tuple. pub fn validate_current( &self, - current_session: BrowserSessionId, - current_context: BrowsingContextId, - current_origin: &Origin, - current_epoch: DocumentEpoch, - ) -> Result<(), NodeHandleError> { - self.handle.validate_current( - current_session, - current_context, - current_origin, - current_epoch, - ) + registry: &BrowserAuthorityRegistry, + ) -> Result<(), BrowserRegistryError> { + registry.validate_node_handle(&self.handle) } } From 4daeb267234922d5ce1dcee6a3bb2f45128f6461 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 04:49:06 -0700 Subject: [PATCH 8/8] docs(changelog): record registry-live action targets --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ad884ac3..866a81a08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Universally value-redacted network evidence with explicit path, metadata, and provenance bounds; ambiguous path rejection; validated source URLs; lowercase SHA-256 identifiers; and verification state. - Authority-bound, bounded semantic node observations with typed node-local action evidence and explicit observation-channel provenance for the first Chromium vertical slice; observation metadata grants no execution authority. - Bounded typed semantic node queries over reviewed role, accessible-name, and node-action evidence, without exposing raw DOM/protocol selector languages or granting execution authority. +- Authority-bound semantic node action targets accept only observation-advertised node-local actions and revalidate the exact node binding against the live browser authority registry before later use, so retired or stale handles cannot be revived by caller-supplied authority tuples. - Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement. - Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge. - Architecture, agent, security, contribution, research, database naming, roadmap, quality-gate, and TLS service-identity ADR documentation.