Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
- 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.
- Policy-authorized semantic node actions revalidate the exact registry-owned node binding in the same call that hands the binding to one adapter dispatch callback, preventing a validate-then-dispatch race from reviving retired or stale 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.
Expand Down
19 changes: 19 additions & 0 deletions crates/originweave-policy/src/semantic_node_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,25 @@ impl PolicyAuthorizedSemanticNodeAction {
) -> Result<(), BrowserRegistryError> {
self.binding.validate_current(registry)
}

/// Revalidate registry-owned browser authority and invoke one dispatch callback in the same call.
///
/// The registry must be the trusted adapter's current authority registry for the action that is
/// about to be dispatched. The callback is never invoked if the retained node binding is stale,
/// retired, forged, or belongs to another registry. A successful callback invocation does not
/// authenticate the adapter, grant destination, secret, or approval authority, or prove the
/// action's post-condition; those remain separate execution boundaries.
pub fn dispatch_if_current<R, F>(
&self,
registry: &BrowserAuthorityRegistry,
dispatch: F,
) -> Result<R, BrowserRegistryError>
where
F: FnOnce(&SemanticNodeActionBinding) -> R,
{
self.validate_current(registry)
.map(|()| dispatch(&self.binding))
}
}

/// A fail-closed outcome that did not produce a policy-authorized semantic-node action.
Expand Down
150 changes: 150 additions & 0 deletions crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use std::cell::Cell;
use std::collections::BTreeSet;

use originweave_core::{
ActionIntentDigest, ActionKind, ActionRequest, ApprovalEvidence, BrowserAuthorityRegistry,
BrowserRegistryError, BrowsingContextId, ExecutionPurpose, InstructionSource, NodeActionKind,
ObservationChannel, Origin, PolicyContext, RobotsDecision, SecretDelivery,
SemanticNodeActionBinding, SemanticNodeActionTarget, SemanticNodeObservation,
SemanticNodeObservationInput, SessionMode,
};
use originweave_policy::PolicyAuthorizedSemanticNodeAction;

const VALID_INTENT: &str =
"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";

struct AuthorizedFixture {
registry: BrowserAuthorityRegistry,
context: BrowsingContextId,
authorized: PolicyAuthorizedSemanticNodeAction,
}

fn origin(value: &str) -> Result<Origin, String> {
Origin::parse(value).map_err(|error| format!("{error:?}"))
}

fn authorized_action() -> Result<AuthorizedFixture, String> {
let mut registry = BrowserAuthorityRegistry::new();
let session = registry
.register_session("semantic-dispatch-session")
.map_err(|error| error.to_string())?;
let context = registry
.register_context(session, "semantic-dispatch-context")
.map_err(|error| error.to_string())?;
let site = origin("https://app.example")?;
let handle = registry
.bind_node(session, context, &site, "semantic-dispatch-node")
.map_err(|error| error.to_string())?;
let observation = SemanticNodeObservation::new(
SemanticNodeObservationInput {
handle,
parent: None,
children: Vec::new(),
role: "button".to_owned(),
accessible_name: "Continue".to_owned(),
visible_text: Some("Continue".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())?;
let target = SemanticNodeActionTarget::from_observation(&observation, NodeActionKind::Click)
.map_err(|error| error.to_string())?;
let request = ActionRequest::new(
ActionKind::Navigate,
site.clone(),
site.clone(),
InstructionSource::User,
SecretDelivery::None,
ActionIntentDigest::parse(VALID_INTENT).map_err(|error| format!("{error:?}"))?,
);
let binding =
SemanticNodeActionBinding::new(target, request).map_err(|error| error.to_string())?;
let policy_context = PolicyContext::new(
SessionMode::AgentTask,
ExecutionPurpose::UserDelegatedTask,
BTreeSet::from([ActionKind::Navigate.required_capability()]),
BTreeSet::from([site.clone()]),
BTreeSet::from([site]),
RobotsDecision::Allowed,
ApprovalEvidence::None,
);
let authorized = PolicyAuthorizedSemanticNodeAction::authorize(binding, &policy_context)
.map_err(|error| error.to_string())?;

Ok(AuthorizedFixture {
registry,
context,
authorized,
})
}

fn dispatch_unit_callback(
authorized: &PolicyAuthorizedSemanticNodeAction,
registry: &BrowserAuthorityRegistry,
called: &Cell<bool>,
) -> Result<(), BrowserRegistryError> {
authorized.dispatch_if_current(registry, |_binding| called.set(true))
}

#[test]
fn dispatch_callback_runs_only_after_registry_owned_browser_revalidation() -> Result<(), String> {
let fixture = authorized_action()?;
let called = Cell::new(false);

let result = fixture
.authorized
.dispatch_if_current(&fixture.registry, |binding| {
called.set(true);
(binding.target().action(), binding.request().action())
})
.map_err(|error| error.to_string())?;

assert!(called.get());
assert_eq!(result, (NodeActionKind::Click, ActionKind::Navigate));
Ok(())
}

#[test]
fn stale_registry_authority_never_reaches_dispatch_callback() -> Result<(), String> {
let mut fixture = authorized_action()?;
let called = Cell::new(false);

dispatch_unit_callback(&fixture.authorized, &fixture.registry, &called)
.map_err(|error| error.to_string())?;
assert!(called.replace(false));

fixture
.registry
.advance_document(fixture.context)
.map_err(|error| error.to_string())?;

assert_eq!(
fixture
.authorized
.dispatch_if_current(&fixture.registry, |_binding| called.set(true))
.err(),
Some(BrowserRegistryError::UnknownNodeAuthority)
);
assert!(!called.get());
Ok(())
}

#[test]
fn adapter_failure_remains_separate_after_successful_revalidation() -> Result<(), String> {
let fixture = authorized_action()?;

let adapter_result = fixture
.authorized
.dispatch_if_current(&fixture.registry, |_binding| -> Result<(), &'static str> {
Err("adapter failed")
})
.map_err(|error| error.to_string())?;

assert_eq!(adapter_result, Err("adapter failed"));
Ok(())
}
1 change: 1 addition & 0 deletions docs/doctoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ NIST AI 600-1 provides generative-AI lifecycle risk guidance. WASP demonstrates
### Web-agent observation and evaluation

Mind2Web reports that raw real-world HTML is often too large for direct LLM use and that filtering improves effectiveness and efficiency. OriginWeave prioritizes typed tools, structured data, redacted network responses, accessibility/DOM/layout semantics, and only then visual fallback. WebArena motivates repeatable task-success and failure-recovery benchmarks instead of anecdotal demonstrations.
Semantic node actions revalidate the registry-owned node binding at the adapter handoff, so observation evidence cannot revive a retired or stale node handle.

### Learned test-time orchestration

Expand Down
Loading