Skip to content
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
- Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant.
- Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules.
- Versioned browser-protocol adapter metadata that distinguishes WebDriver BiDi from pinned CDP, binds bounded adapter/browser revision tokens to an explicit duplicate-free capability set, normalizes capability-set identity independently of caller ordering, and exposes typed fail-closed capability requirements without granting browser, action, network, or secret authority by protocol kind alone.
- 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.
- 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.
- Deterministic fail-closed policy evaluation for untrusted instructions, origin grants, crawler restrictions, execution-mode and purpose consistency, approvals, and brokered secrets.
Expand Down
49 changes: 48 additions & 1 deletion crates/originweave-core/src/browser_protocol.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fmt;
use std::{fmt, str::FromStr};

/// Maximum UTF-8 byte length for browser protocol adapter metadata tokens.
pub const MAX_BROWSER_PROTOCOL_METADATA_BYTES: usize = 128;
Expand Down Expand Up @@ -42,6 +42,53 @@ impl fmt::Display for OriginWeaveProtocolVersion {
}
}

impl FromStr for OriginWeaveProtocolVersion {
type Err = OriginWeaveProtocolVersionParseError;

fn from_str(value: &str) -> Result<Self, Self::Err> {
let Some(remainder) = value.strip_prefix("originweave/") else {
return Err(OriginWeaveProtocolVersionParseError::InvalidFormat);
};
let Some((major_text, minor_text)) = remainder.split_once('.') else {
return Err(OriginWeaveProtocolVersionParseError::InvalidFormat);
};
if minor_text.contains('.') {
return Err(OriginWeaveProtocolVersionParseError::InvalidFormat);
}
let Ok(major) = major_text.parse::<u16>() else {
return Err(OriginWeaveProtocolVersionParseError::InvalidFormat);
};
let Ok(minor) = minor_text.parse::<u16>() else {
return Err(OriginWeaveProtocolVersionParseError::InvalidFormat);
};

let version = Self::new(major, minor);
if version.to_string() != value {
return Err(OriginWeaveProtocolVersionParseError::InvalidFormat);
}
Comment thread
seonghobae marked this conversation as resolved.
Ok(version)
}
}

/// Failure to parse a canonical serialized OriginWeave Protocol generation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OriginWeaveProtocolVersionParseError {
/// The value did not use the exact canonical `originweave/<major>.<minor>` syntax.
InvalidFormat,
}

impl fmt::Display for OriginWeaveProtocolVersionParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidFormat => formatter.write_str(
"OriginWeave protocol version must use canonical originweave/<major>.<minor> syntax",
),
}
}
}

impl std::error::Error for OriginWeaveProtocolVersionParseError {}

/// Browser automation protocol family used by one versioned adapter.
///
/// The protocol family is descriptive metadata only. Selecting a kind does not
Expand Down
2 changes: 1 addition & 1 deletion crates/originweave-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub use browser_protocol::{
BrowserProtocolAdapterDescriptor, BrowserProtocolCapability,
BrowserProtocolCapabilityRequirementError, BrowserProtocolDescriptorError, BrowserProtocolKind,
BrowserProtocolVersionRequirementError, MAX_BROWSER_PROTOCOL_METADATA_BYTES,
OriginWeaveProtocolVersion,
OriginWeaveProtocolVersion, OriginWeaveProtocolVersionParseError,
};
pub use browser_registry::{
BrowserAuthorityRegistry, BrowserRegistryError, MAX_EXTERNAL_BROWSER_IDENTIFIER_BYTES,
Expand Down
59 changes: 59 additions & 0 deletions crates/originweave-core/tests/protocol_version_parsing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#![allow(clippy::expect_used)]

use std::error::Error;
use std::str::FromStr;

use originweave_core::{OriginWeaveProtocolVersion, OriginWeaveProtocolVersionParseError};

#[test]
fn canonical_protocol_versions_parse_and_round_trip() -> Result<(), Box<dyn Error>> {
let current = OriginWeaveProtocolVersion::from_str("originweave/0.1")?;
assert_eq!(current, OriginWeaveProtocolVersion::new(0, 1));
assert_eq!(current.to_string(), "originweave/0.1");

let maximum = OriginWeaveProtocolVersion::from_str("originweave/65535.65535")?;
assert_eq!(maximum, OriginWeaveProtocolVersion::new(u16::MAX, u16::MAX));
assert_eq!(maximum.to_string(), "originweave/65535.65535");
Ok(())
}

#[test]
fn malformed_or_noncanonical_protocol_versions_fail_closed() {
let malformed = [
"",
"originweave/",
"originweave/0",
"originweave/0.",
"originweave/.1",
"originweave/0.1.0",
"OriginWeave/0.1",
"originweave/00.1",
"originweave/0.01",
"originweave/+0.1",
"originweave/0.+1",
"originweave/-0.1",
"originweave/0.-1",
"originweave/65536.1",
"originweave/0.65536",
" originweave/0.1",
"originweave/0.1 ",
"originweave/0.1",
];

for value in malformed {
assert_eq!(
OriginWeaveProtocolVersion::from_str(value),
Err(OriginWeaveProtocolVersionParseError::InvalidFormat)
);
}
}

#[test]
fn protocol_version_parse_error_is_stable_and_source_free() {
let error = OriginWeaveProtocolVersionParseError::InvalidFormat;
assert_eq!(
error.to_string(),
"OriginWeave protocol version must use canonical originweave/<major>.<minor> syntax"
);
assert!(error.source().is_none());
}
Loading