Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
308fa77
test(core): require authority-bound semantic action target
seonghobae Aug 10, 2026
f2bb6db
style(core): apply canonical action-target test formatting
seonghobae Aug 10, 2026
ef5fd33
feat(core): add authority-bound semantic action target
seonghobae Aug 10, 2026
01b1500
feat(core): export semantic action target contract
seonghobae Aug 10, 2026
c81c273
test(core): prove semantic action target authority invalidation
seonghobae Aug 10, 2026
efe440c
docs(changelog): record semantic action target boundary
seonghobae Aug 10, 2026
325070e
merge: align semantic action target with current query authority
seonghobae Aug 15, 2026
c33cbd2
merge: align semantic action target with current query authority
seonghobae Aug 15, 2026
55ad82d
chore(core): align semantic action-target stack with current query au…
seonghobae Aug 17, 2026
184fa70
chore(core): align semantic action-target stack with current query au…
seonghobae Aug 17, 2026
093873e
chore(core): align semantic action target with latest query authority
seonghobae Aug 20, 2026
867db7d
test(core): require registry-live semantic action targets
seonghobae Aug 21, 2026
7656d98
fix(core): revalidate action targets against live registry
seonghobae Aug 21, 2026
4daeb26
docs(changelog): record registry-live action targets
seonghobae Aug 21, 2026
034cc7f
chore(core): align semantic action target with query root
seonghobae Aug 22, 2026
d6fb55c
chore(browser): realign semantic action stack
seonghobae Aug 23, 2026
657f18b
merge: refresh semantic action target onto live query prerequisite
seonghobae Aug 23, 2026
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -81,4 +82,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
[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD
4 changes: 3 additions & 1 deletion crates/originweave-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//!
//! This crate keeps the long-lived value contracts in `contracts`, the
//! protocol-identifier registry and extension authority in focused modules,
//! and bounded semantic observations in a separate authority-preserving module.
//! and bounded semantic observations in separate authority-preserving modules.

#![forbid(unsafe_code)]
#![deny(missing_docs)]
Expand All @@ -13,6 +13,7 @@ mod browser_registry_coverage;
mod contract_errors;
mod contracts;
mod extension_authority;
mod semantic_action_target;
mod semantic_observation;

pub use browser_registry::{
Expand All @@ -30,6 +31,7 @@ pub use extension_authority::{
AgentTaskId, AgentTaskIdError, ExtensionAccessDecision, ExtensionAccessRequest,
ExtensionAgentGrant, evaluate_extension_access,
};
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,
Expand Down
74 changes: 74 additions & 0 deletions crates/originweave-core/src/semantic_action_target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use std::fmt;

use crate::{
BrowserAuthorityRegistry, BrowserRegistryError, NodeActionKind, ObservedNodeHandle,
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<Self, SemanticNodeActionTargetError> {
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 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,
registry: &BrowserAuthorityRegistry,
) -> Result<(), BrowserRegistryError> {
registry.validate_node_handle(&self.handle)
}
}

/// 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 {}
150 changes: 150 additions & 0 deletions crates/originweave-core/tests/semantic_node_action_target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use std::collections::BTreeSet;

use originweave_core::{
BrowserAuthorityRegistry, BrowserRegistryError, BrowserSessionId, BrowsingContextId,
NodeActionKind, ObservationChannel, Origin, SemanticNodeActionTarget,
SemanticNodeActionTargetError, SemanticNodeObservation, SemanticNodeObservationInput,
};

struct ObservationFixture {
registry: BrowserAuthorityRegistry,
session: BrowserSessionId,
context: BrowsingContextId,
observation: SemanticNodeObservation,
}

fn observation_fixture() -> Result<ObservationFixture, String> {
let mut registry = BrowserAuthorityRegistry::new();
let session = registry
.register_session("semantic-action-session")
.map_err(|error| error.to_string())?;
let context = registry
.register_context(session, "semantic-action-context")
.map_err(|error| error.to_string())?;
let origin = Origin::parse("https://example.com").map_err(|error| format!("{error:?}"))?;
let handle = registry
.bind_node(session, context, &origin, "semantic-action-node")
.map_err(|error| error.to_string())?;
let observation = 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]),
},
&registry,
)
.map_err(|error| error.to_string())?;

Ok(ObservationFixture {
registry,
session,
context,
observation,
})
}

#[test]
fn advertised_node_action_becomes_an_authority_bound_target() -> Result<(), String> {
let fixture = observation_fixture()?;
let target =
SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click)
.map_err(|error| error.to_string())?;

assert_eq!(target.handle(), fixture.observation.handle());
assert_eq!(target.action(), NodeActionKind::Click);
Ok(())
}

#[test]
fn unsupported_node_action_fails_closed_without_minting_authority() -> Result<(), String> {
let fixture = observation_fixture()?;
assert_eq!(
SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::TypeText)
.err(),
Some(SemanticNodeActionTargetError::UnsupportedAction)
);
Ok(())
}

#[test]
fn node_action_target_revalidates_live_registry_authority() -> Result<(), String> {
let fixture = observation_fixture()?;
let target =
SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click)
.map_err(|error| error.to_string())?;

target
.validate_current(&fixture.registry)
.map_err(|error| error.to_string())?;
Ok(())
}

#[test]
fn node_action_target_rejects_retired_context_authority() -> Result<(), String> {
let mut fixture = observation_fixture()?;
let target =
SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click)
.map_err(|error| error.to_string())?;
fixture
.registry
.remove_context(fixture.context)
.map_err(|error| error.to_string())?;

assert_eq!(
target.validate_current(&fixture.registry).err(),
Some(BrowserRegistryError::UnknownBrowsingContext)
);
Ok(())
}

#[test]
fn node_action_target_rejects_retired_session_authority() -> Result<(), String> {
let mut fixture = observation_fixture()?;
let target =
SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click)
.map_err(|error| error.to_string())?;
fixture
.registry
.remove_session(fixture.session)
.map_err(|error| error.to_string())?;

assert_eq!(
target.validate_current(&fixture.registry).err(),
Some(BrowserRegistryError::UnknownBrowserSession)
);
Ok(())
}

#[test]
fn node_action_target_rejects_stale_document_authority() -> Result<(), String> {
let mut fixture = observation_fixture()?;
let target =
SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click)
.map_err(|error| error.to_string())?;
fixture
.registry
.advance_document(fixture.context)
.map_err(|error| error.to_string())?;

assert_eq!(
target.validate_current(&fixture.registry).err(),
Some(BrowserRegistryError::UnknownNodeAuthority)
);
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"
);
}
Loading