Skip to content
Merged
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
9f987ae
test(core): require atomic browser protocol use validation
seonghobae Aug 12, 2026
e5a5598
style(core): apply canonical browser protocol validation formatting
seonghobae Aug 12, 2026
8d8e9b9
feat(core): validate browser protocol use prerequisites atomically
seonghobae Aug 12, 2026
d39cbf5
feat(core): export browser protocol use validation types
seonghobae Aug 12, 2026
72c4c33
test(core): cover browser protocol validation error evidence
seonghobae Aug 12, 2026
6346164
test(core): require runtime browser protocol kind binding
seonghobae Aug 12, 2026
de107ca
test(core): bind all validated uses to runtime protocol kind
seonghobae Aug 12, 2026
3294f27
feat(core): bind validated use to runtime protocol kind
seonghobae Aug 12, 2026
9aed5ae
docs(changelog): record browser protocol use validation
seonghobae Aug 12, 2026
f42c3c2
merge: align atomic browser protocol validation with current runtime …
seonghobae Aug 15, 2026
d4e22cd
merge: align runtime protocol-kind binding with current atomic valida…
seonghobae Aug 15, 2026
2f5bb55
merge: align atomic protocol validation with current runtime prerequi…
seonghobae Aug 16, 2026
c2b572f
merge: align runtime-kind binding with current atomic validation
seonghobae Aug 16, 2026
2c9ab91
chore(core): align protocol use validation with current runtime checks
seonghobae Aug 18, 2026
d534f72
Merge pull request #112 from ContextualWisdomLab/feat/browser-protoco…
seonghobae Aug 26, 2026
4ca4c71
Merge remote-tracking branch 'origin/feat/originweave-protocol-versio…
seonghobae Aug 26, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
- 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.
- Public `require_runtime_revisions` validation that fails closed when caller-supplied runtime protocol or browser revision evidence is malformed or differs from the descriptor's pinned revisions, preserving typed malformed-versus-drift errors without authenticating or attesting the adapter process.
- 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.
- 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
130 changes: 130 additions & 0 deletions crates/originweave-core/src/browser_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,95 @@ impl BrowserProtocolAdapterDescriptor {
Err(BrowserProtocolCapabilityRequirementError::UnsupportedCapability(capability))
}
}

/// Validate all adapter metadata prerequisites for one immediate browser operation.
///
/// Validation is intentionally ordered and fail closed: the exact
/// OriginWeave Protocol generation is checked first, then the caller-supplied
/// runtime protocol family, then the supplied runtime protocol/browser
/// revisions, and finally the required adapter capability. Success returns
/// a non-cloneable value that a later trusted transport can consume as proof
/// that these metadata prerequisites were checked together. It is not
/// browser or Agent authority and does not authenticate or attest the caller
/// supplying runtime metadata.
pub fn validate_use(
&self,
required_originweave_protocol_version: OriginWeaveProtocolVersion,
runtime_kind: BrowserProtocolKind,
runtime_protocol_revision: &str,
runtime_browser_revision: &str,
required_capability: BrowserProtocolCapability,
) -> Result<ValidatedBrowserProtocolUse, BrowserProtocolUseValidationError> {
self.require_originweave_protocol_version(required_originweave_protocol_version)
.map_err(BrowserProtocolUseValidationError::ProtocolVersion)?;
if self.kind != runtime_kind {
return Err(BrowserProtocolUseValidationError::ProtocolKindMismatch {
descriptor_kind: self.kind,
runtime_kind,
});
}
self.require_runtime_revisions(runtime_protocol_revision, runtime_browser_revision)
.map_err(BrowserProtocolUseValidationError::RuntimeRevision)?;
self.require_capability(required_capability)
.map_err(BrowserProtocolUseValidationError::Capability)?;

Ok(ValidatedBrowserProtocolUse {
descriptor: self.clone(),
capability: required_capability,
})
}
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
}

/// Snapshot proving that one descriptor passed all browser-protocol metadata checks for one use.
///
/// Only [`BrowserProtocolAdapterDescriptor::validate_use`] can construct this
/// value. It intentionally does not implement `Clone`: a future trusted browser
/// transport can consume the value by ownership at the operation boundary
/// rather than treating it as reusable ambient authority. The value still does
/// not authenticate an adapter or attest that supplied runtime metadata came
/// from the running browser process.
#[derive(Debug, PartialEq, Eq)]
pub struct ValidatedBrowserProtocolUse {
descriptor: BrowserProtocolAdapterDescriptor,
capability: BrowserProtocolCapability,
}

impl ValidatedBrowserProtocolUse {
/// Return the validated browser protocol family.
#[must_use]
pub const fn kind(&self) -> BrowserProtocolKind {
self.descriptor.kind
}

/// Return the validated OriginWeave Protocol generation.
#[must_use]
pub const fn originweave_protocol_version(&self) -> OriginWeaveProtocolVersion {
self.descriptor.originweave_protocol_version
}

/// Return the validated bounded adapter-version metadata token.
#[must_use]
pub fn adapter_version(&self) -> &str {
&self.descriptor.adapter_version
}

/// Return the validated bounded upstream protocol-revision metadata token.
#[must_use]
pub fn protocol_revision(&self) -> &str {
&self.descriptor.protocol_revision
}

/// Return the validated bounded browser-revision metadata token.
#[must_use]
pub fn browser_revision(&self) -> &str {
&self.descriptor.browser_revision
}

/// Return the exact adapter capability validated for this use.
#[must_use]
pub const fn capability(&self) -> BrowserProtocolCapability {
self.capability
}
}

const fn capability_rank(capability: BrowserProtocolCapability) -> u8 {
Expand Down Expand Up @@ -398,6 +487,47 @@ impl fmt::Display for BrowserProtocolCapabilityRequirementError {

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

/// Failure to validate all browser-protocol metadata prerequisites for one use.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrowserProtocolUseValidationError {
/// The descriptor targets the wrong OriginWeave Protocol generation.
ProtocolVersion(BrowserProtocolVersionRequirementError),
/// The runtime transport reports a different protocol family than the descriptor.
ProtocolKindMismatch {
/// Browser protocol family pinned by the adapter descriptor.
descriptor_kind: BrowserProtocolKind,
/// Browser protocol family reported by the runtime transport.
runtime_kind: BrowserProtocolKind,
},
/// The supplied runtime protocol or browser revision is invalid or has drifted.
RuntimeRevision(BrowserProtocolRuntimeRequirementError),
/// The descriptor does not explicitly declare the required capability.
Capability(BrowserProtocolCapabilityRequirementError),
}

impl fmt::Display for BrowserProtocolUseValidationError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ProtocolVersion(error) => error.fmt(formatter),
Self::ProtocolKindMismatch { .. } => formatter
.write_str("runtime browser protocol kind does not match the pinned adapter kind"),
Self::RuntimeRevision(error) => error.fmt(formatter),
Self::Capability(error) => error.fmt(formatter),
}
}
}

impl std::error::Error for BrowserProtocolUseValidationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::ProtocolVersion(error) => Some(error),
Self::ProtocolKindMismatch { .. } => None,
Self::RuntimeRevision(error) => Some(error),
Self::Capability(error) => Some(error),
}
}
}

/// Failure to construct canonical browser protocol adapter metadata.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrowserProtocolDescriptorError {
Expand Down
6 changes: 3 additions & 3 deletions crates/originweave-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ mod extension_authority;
pub use browser_protocol::{
BrowserProtocolAdapterDescriptor, BrowserProtocolCapability,
BrowserProtocolCapabilityRequirementError, BrowserProtocolDescriptorError, BrowserProtocolKind,
BrowserProtocolRuntimeRequirementError, BrowserProtocolVersionRequirementError,
MAX_BROWSER_PROTOCOL_METADATA_BYTES, OriginWeaveProtocolVersion,
OriginWeaveProtocolVersionParseError,
BrowserProtocolRuntimeRequirementError, BrowserProtocolUseValidationError,
BrowserProtocolVersionRequirementError, MAX_BROWSER_PROTOCOL_METADATA_BYTES,
OriginWeaveProtocolVersion, OriginWeaveProtocolVersionParseError, ValidatedBrowserProtocolUse,
};
pub use browser_registry::{
BrowserAuthorityRegistry, BrowserRegistryError, MAX_EXTERNAL_BROWSER_IDENTIFIER_BYTES,
Expand Down
185 changes: 185 additions & 0 deletions crates/originweave-core/tests/browser_protocol_use_validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
use std::error::Error;

use originweave_core::{
BrowserProtocolAdapterDescriptor, BrowserProtocolCapability,
BrowserProtocolCapabilityRequirementError, BrowserProtocolKind,
BrowserProtocolRuntimeRequirementError, BrowserProtocolUseValidationError,
BrowserProtocolVersionRequirementError, OriginWeaveProtocolVersion,
};

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

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

#[test]
fn validated_use_binds_all_required_adapter_metadata() -> Result<(), Box<dyn Error>> {
let descriptor = descriptor()?;
let validated = descriptor.validate_use(
ORIGINWEAVE_PROTOCOL_VERSION,
BrowserProtocolKind::WebDriverBiDi,
PROTOCOL_REVISION,
BROWSER_REVISION,
BrowserProtocolCapability::Navigation,
)?;

assert_eq!(validated.kind(), BrowserProtocolKind::WebDriverBiDi);
assert_eq!(
validated.originweave_protocol_version(),
ORIGINWEAVE_PROTOCOL_VERSION
);
assert_eq!(validated.adapter_version(), ADAPTER_VERSION);
assert_eq!(validated.protocol_revision(), PROTOCOL_REVISION);
assert_eq!(validated.browser_revision(), BROWSER_REVISION);
assert_eq!(
validated.capability(),
BrowserProtocolCapability::Navigation
);
Ok(())
}

#[test]
fn protocol_generation_mismatch_precedes_runtime_and_capability_checks()
-> Result<(), Box<dyn Error>> {
let descriptor = descriptor()?;
let wrong_generation = OriginWeaveProtocolVersion::new(0, 2);

assert_eq!(
descriptor.validate_use(
wrong_generation,
BrowserProtocolKind::ChromeDevToolsProtocol,
"runtime revision with spaces",
"browser/revision",
BrowserProtocolCapability::NetworkObservation,
),
Err(BrowserProtocolUseValidationError::ProtocolVersion(
BrowserProtocolVersionRequirementError::ProtocolVersionMismatch {
required: wrong_generation,
actual: ORIGINWEAVE_PROTOCOL_VERSION,
}
))
);
Ok(())
}

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

let error = descriptor.validate_use(
ORIGINWEAVE_PROTOCOL_VERSION,
BrowserProtocolKind::ChromeDevToolsProtocol,
"runtime revision with spaces",
"browser/revision",
BrowserProtocolCapability::NetworkObservation,
);

assert_eq!(
error,
Err(BrowserProtocolUseValidationError::ProtocolKindMismatch {
descriptor_kind: BrowserProtocolKind::WebDriverBiDi,
runtime_kind: BrowserProtocolKind::ChromeDevToolsProtocol,
})
);
let error = error.err().ok_or("expected protocol kind mismatch")?;
assert_eq!(
error.to_string(),
"runtime browser protocol kind does not match the pinned adapter kind"
);
assert!(error.source().is_none());
Ok(())
}

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

assert_eq!(
descriptor.validate_use(
ORIGINWEAVE_PROTOCOL_VERSION,
BrowserProtocolKind::WebDriverBiDi,
"webdriver-bidi-wd-2026-07-01",
BROWSER_REVISION,
BrowserProtocolCapability::NetworkObservation,
),
Err(BrowserProtocolUseValidationError::RuntimeRevision(
BrowserProtocolRuntimeRequirementError::ProtocolRevisionMismatch,
))
);
Ok(())
}

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

assert_eq!(
descriptor.validate_use(
ORIGINWEAVE_PROTOCOL_VERSION,
BrowserProtocolKind::WebDriverBiDi,
PROTOCOL_REVISION,
BROWSER_REVISION,
BrowserProtocolCapability::NetworkObservation,
),
Err(BrowserProtocolUseValidationError::Capability(
BrowserProtocolCapabilityRequirementError::UnsupportedCapability(
BrowserProtocolCapability::NetworkObservation,
),
))
);
Ok(())
}

#[test]
fn validation_errors_preserve_stable_typed_sources() {
let wrong_generation = OriginWeaveProtocolVersion::new(0, 2);
let cases = [
(
BrowserProtocolUseValidationError::ProtocolVersion(
BrowserProtocolVersionRequirementError::ProtocolVersionMismatch {
required: wrong_generation,
actual: ORIGINWEAVE_PROTOCOL_VERSION,
},
),
"browser protocol adapter targets originweave/0.1 but originweave/0.2 is required",
),
(
BrowserProtocolUseValidationError::RuntimeRevision(
BrowserProtocolRuntimeRequirementError::ProtocolRevisionMismatch,
),
"runtime browser protocol revision does not match the pinned adapter revision",
),
(
BrowserProtocolUseValidationError::Capability(
BrowserProtocolCapabilityRequirementError::UnsupportedCapability(
BrowserProtocolCapability::NetworkObservation,
),
),
"browser protocol adapter does not declare required network-observation capability",
),
];

for (error, expected) in cases {
assert_eq!(error.to_string(), expected);
assert_eq!(
error.source().map(ToString::to_string).as_deref(),
Some(expected)
);
}
}
Loading