Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -11,6 +11,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
- Canonical OriginWeave protocol-version parsing for exact `originweave/<major>.<minor>` syntax, with typed fail-closed rejection of malformed, ambiguous, overflowed, or noncanonical serialized generations; parsing does not negotiate compatibility or grant adapter authority.
- Atomic browser-protocol use validation that requires the exact OriginWeave protocol generation, caller-supplied runtime protocol family, exact pinned runtime protocol/browser revisions, and an explicitly declared capability in deterministic fail-closed order before producing non-cloneable validation evidence; this metadata proof does not authenticate the adapter or grant browser/Agent authority.
- Runtime browser-adapter version binding at the atomic protocol-use boundary: the caller-supplied bounded adapter-version token must exactly match the reviewed descriptor version before runtime revision or capability checks can succeed, preventing adapter-build drift from silently reusing otherwise matching protocol/browser metadata without authenticating or attesting the adapter process.
- Same-call browser-protocol dispatch gating that validates current protocol family, adapter version, pinned protocol/browser revisions, OriginWeave generation, and required capability before invoking one callback, transferring the non-cloneable validation proof by ownership without turning metadata validation into browser or Agent authority.
- 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.
- Typed browser actions, capabilities, risk classes, execution modes, robots decisions, secret-delivery contracts, immutable canonical action-intent digests, and intent-bound approval scopes.
Expand Down
71 changes: 71 additions & 0 deletions crates/originweave-core/src/browser_protocol_dispatch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use crate::{
BrowserProtocolAdapterDescriptor, BrowserProtocolCapability, BrowserProtocolKind,
BrowserProtocolUseValidationError, OriginWeaveProtocolVersion, ValidatedBrowserProtocolUse,
};

/// Current runtime metadata sampled from the browser-protocol adapter about to perform I/O.
///
/// This value is untrusted descriptive input. Constructing it does not validate or authenticate an
/// adapter, browser, or protocol revision and grants no browser or Agent authority. The descriptor
/// validates every field against its reviewed metadata before a dispatch callback can run.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BrowserProtocolRuntimeMetadata<'a> {
kind: BrowserProtocolKind,
adapter_version: &'a str,
protocol_revision: &'a str,
browser_revision: &'a str,
}

impl<'a> BrowserProtocolRuntimeMetadata<'a> {
/// Build one runtime metadata snapshot for immediate validation and dispatch.
///
/// String syntax and descriptor equality are intentionally checked later by
/// [`BrowserProtocolAdapterDescriptor::dispatch_if_runtime_matches`], so malformed caller data
/// remains representable as input that the fail-closed boundary can reject deterministically.
pub const fn new(
kind: BrowserProtocolKind,
adapter_version: &'a str,
protocol_revision: &'a str,
browser_revision: &'a str,
) -> Self {
Self {
kind,
adapter_version,
protocol_revision,
browser_revision,
}
}
}

impl BrowserProtocolAdapterDescriptor {
/// Validate current browser-protocol metadata and immediately invoke one dispatch callback.
///
/// `runtime_metadata` must be sampled from the trusted adapter that is about to perform the
/// operation. Validation occurs before `dispatch` is invoked, and the callback receives the
/// resulting non-cloneable [`ValidatedBrowserProtocolUse`] by ownership so this boundary does
/// not turn successful validation into reusable ambient authority.
///
/// A successful callback invocation does not authenticate the adapter process, authorize a
/// browser session, browsing context, origin, destination, secret, or approval, or prove a
/// browser post-condition. Those remain separate higher-level execution boundaries.
pub fn dispatch_if_runtime_matches<R, F>(
&self,
required_originweave_protocol_version: OriginWeaveProtocolVersion,
runtime_metadata: BrowserProtocolRuntimeMetadata<'_>,
required_capability: BrowserProtocolCapability,
dispatch: F,
) -> Result<R, BrowserProtocolUseValidationError>
where
F: FnOnce(ValidatedBrowserProtocolUse) -> R,
{
let validated = self.validate_use(
required_originweave_protocol_version,
runtime_metadata.kind,
runtime_metadata.adapter_version,
runtime_metadata.protocol_revision,
runtime_metadata.browser_revision,
required_capability,
)?;
Ok(dispatch(validated))
}
}
2 changes: 2 additions & 0 deletions crates/originweave-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#![deny(missing_docs)]

mod browser_protocol;
mod browser_protocol_dispatch;
mod browser_registry;
#[cfg(test)]
mod browser_registry_coverage;
Expand All @@ -22,6 +23,7 @@ pub use browser_protocol::{
BrowserProtocolVersionRequirementError, MAX_BROWSER_PROTOCOL_METADATA_BYTES,
OriginWeaveProtocolVersion, OriginWeaveProtocolVersionParseError, ValidatedBrowserProtocolUse,
};
pub use browser_protocol_dispatch::BrowserProtocolRuntimeMetadata;
pub use browser_registry::{
BrowserAuthorityRegistry, BrowserRegistryError, MAX_EXTERNAL_BROWSER_IDENTIFIER_BYTES,
};
Expand Down
121 changes: 121 additions & 0 deletions crates/originweave-core/tests/browser_protocol_runtime_dispatch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use std::{cell::Cell, error::Error};

use originweave_core::{
BrowserProtocolAdapterDescriptor, BrowserProtocolCapability, BrowserProtocolKind,
BrowserProtocolRuntimeMetadata, BrowserProtocolUseValidationError, OriginWeaveProtocolVersion,
ValidatedBrowserProtocolUse,
};

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";

type DispatchOutcome = Result<(String, BrowserProtocolCapability), &'static str>;
type DispatchFn = fn(ValidatedBrowserProtocolUse) -> DispatchOutcome;

thread_local! {
static DISPATCH_CALLED: Cell<bool> = const { Cell::new(false) };
}

fn descriptor() -> Result<BrowserProtocolAdapterDescriptor, Box<dyn Error>> {
Ok(BrowserProtocolAdapterDescriptor::new(
BrowserProtocolKind::WebDriverBiDi,
ORIGINWEAVE_PROTOCOL_VERSION,
ADAPTER_VERSION,
PROTOCOL_REVISION,
BROWSER_REVISION,
&[BrowserProtocolCapability::Navigation],
)?)
}

fn runtime_metadata(adapter_version: &str) -> BrowserProtocolRuntimeMetadata<'_> {
BrowserProtocolRuntimeMetadata::new(
BrowserProtocolKind::WebDriverBiDi,
adapter_version,
PROTOCOL_REVISION,
BROWSER_REVISION,
)
}

fn reset_dispatch_marker() {
DISPATCH_CALLED.with(|called| called.set(false));
}

fn dispatch_was_called() -> bool {
DISPATCH_CALLED.with(Cell::get)
}

fn successful_dispatch(validated: ValidatedBrowserProtocolUse) -> DispatchOutcome {
DISPATCH_CALLED.with(|called| called.set(true));
Ok((
validated.adapter_version().to_owned(),
validated.capability(),
))
}

fn failing_dispatch(_: ValidatedBrowserProtocolUse) -> DispatchOutcome {
DISPATCH_CALLED.with(|called| called.set(true));
Err("adapter-failure")
}

#[test]
fn exact_runtime_validation_hands_single_use_proof_to_dispatch() -> Result<(), Box<dyn Error>> {
let descriptor = descriptor()?;
reset_dispatch_marker();

let dispatch_result = descriptor.dispatch_if_runtime_matches(
ORIGINWEAVE_PROTOCOL_VERSION,
runtime_metadata(ADAPTER_VERSION),
BrowserProtocolCapability::Navigation,
successful_dispatch as DispatchFn,
)?;

assert!(dispatch_was_called());
assert_eq!(
dispatch_result,
Ok((
ADAPTER_VERSION.to_owned(),
BrowserProtocolCapability::Navigation
))
);
Ok(())
}

#[test]
fn runtime_mismatch_prevents_dispatch_callback() -> Result<(), Box<dyn Error>> {
let descriptor = descriptor()?;
reset_dispatch_marker();

let result = descriptor.dispatch_if_runtime_matches(
ORIGINWEAVE_PROTOCOL_VERSION,
runtime_metadata("originweave-bidi-v2"),
BrowserProtocolCapability::Navigation,
successful_dispatch as DispatchFn,
);

assert_eq!(
result,
Err(BrowserProtocolUseValidationError::AdapterVersionMismatch)
);
assert!(!dispatch_was_called());
Ok(())
}

#[test]
fn adapter_callback_failure_remains_separate_after_validation() -> Result<(), Box<dyn Error>> {
let descriptor = descriptor()?;
reset_dispatch_marker();

let dispatch_result = descriptor.dispatch_if_runtime_matches(
ORIGINWEAVE_PROTOCOL_VERSION,
runtime_metadata(ADAPTER_VERSION),
BrowserProtocolCapability::Navigation,
failing_dispatch as DispatchFn,
)?;

assert!(dispatch_was_called());
assert_eq!(dispatch_result, Err("adapter-failure"));
Ok(())
}
Loading