From 66f6ad78004471ffb4cc881a20f67ac512dc4fb2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 14:46:05 +0900 Subject: [PATCH 1/6] test(core): require exact runtime browser revisions --- .../browser_protocol_runtime_revision.rs | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 crates/originweave-core/tests/browser_protocol_runtime_revision.rs diff --git a/crates/originweave-core/tests/browser_protocol_runtime_revision.rs b/crates/originweave-core/tests/browser_protocol_runtime_revision.rs new file mode 100644 index 000000000..db5e13172 --- /dev/null +++ b/crates/originweave-core/tests/browser_protocol_runtime_revision.rs @@ -0,0 +1,99 @@ +use std::error::Error; + +use originweave_core::{ + BrowserProtocolAdapterDescriptor, BrowserProtocolCapability, BrowserProtocolKind, + BrowserProtocolRuntimeRequirementError, 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> { + Ok(BrowserProtocolAdapterDescriptor::new( + BrowserProtocolKind::WebDriverBiDi, + ORIGINWEAVE_PROTOCOL_VERSION, + ADAPTER_VERSION, + PROTOCOL_REVISION, + BROWSER_REVISION, + &[BrowserProtocolCapability::Navigation], + )?) +} + +#[test] +fn exact_runtime_revisions_are_required_before_adapter_use() -> Result<(), Box> { + let descriptor = descriptor()?; + assert_eq!( + descriptor.require_runtime_revisions(PROTOCOL_REVISION, BROWSER_REVISION), + Ok(()) + ); + Ok(()) +} + +#[test] +fn runtime_revision_drift_fails_closed() -> Result<(), Box> { + let descriptor = descriptor()?; + assert_eq!( + descriptor.require_runtime_revisions("webdriver-bidi-wd-2026-07-01", BROWSER_REVISION), + Err(BrowserProtocolRuntimeRequirementError::ProtocolRevisionMismatch) + ); + assert_eq!( + descriptor.require_runtime_revisions(PROTOCOL_REVISION, "chromium-r1639811"), + Err(BrowserProtocolRuntimeRequirementError::BrowserRevisionMismatch) + ); + assert_eq!( + descriptor.require_runtime_revisions( + "webdriver-bidi-wd-2026-07-01", + "chromium-r1639811" + ), + Err(BrowserProtocolRuntimeRequirementError::ProtocolRevisionMismatch) + ); + Ok(()) +} + +#[test] +fn malformed_runtime_revision_evidence_fails_before_comparison() -> Result<(), Box> { + let descriptor = descriptor()?; + assert_eq!( + descriptor.require_runtime_revisions("webdriver bidi current", BROWSER_REVISION), + Err(BrowserProtocolRuntimeRequirementError::InvalidProtocolRevision) + ); + assert_eq!( + descriptor.require_runtime_revisions(PROTOCOL_REVISION, "chromium/current"), + Err(BrowserProtocolRuntimeRequirementError::InvalidBrowserRevision) + ); + assert_eq!( + descriptor.require_runtime_revisions("", ""), + Err(BrowserProtocolRuntimeRequirementError::InvalidProtocolRevision) + ); + Ok(()) +} + +#[test] +fn runtime_requirement_errors_are_stable_and_source_free() { + let cases = [ + ( + BrowserProtocolRuntimeRequirementError::InvalidProtocolRevision, + "runtime browser protocol revision must be a bounded ASCII metadata token", + ), + ( + BrowserProtocolRuntimeRequirementError::InvalidBrowserRevision, + "runtime browser revision must be a bounded ASCII metadata token", + ), + ( + BrowserProtocolRuntimeRequirementError::ProtocolRevisionMismatch, + "runtime browser protocol revision does not match the pinned adapter revision", + ), + ( + BrowserProtocolRuntimeRequirementError::BrowserRevisionMismatch, + "runtime browser revision does not match the pinned adapter browser revision", + ), + ]; + + for (error, expected) in cases { + assert_eq!(error.to_string(), expected); + assert!(error.source().is_none()); + } +} From 03f7b188b4290ed6eb748df102bbe20119e8fa6b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 14:49:30 +0900 Subject: [PATCH 2/6] style(core): apply canonical runtime revision test formatting --- .../tests/browser_protocol_runtime_revision.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/originweave-core/tests/browser_protocol_runtime_revision.rs b/crates/originweave-core/tests/browser_protocol_runtime_revision.rs index db5e13172..60a04acdb 100644 --- a/crates/originweave-core/tests/browser_protocol_runtime_revision.rs +++ b/crates/originweave-core/tests/browser_protocol_runtime_revision.rs @@ -44,10 +44,7 @@ fn runtime_revision_drift_fails_closed() -> Result<(), Box> { Err(BrowserProtocolRuntimeRequirementError::BrowserRevisionMismatch) ); assert_eq!( - descriptor.require_runtime_revisions( - "webdriver-bidi-wd-2026-07-01", - "chromium-r1639811" - ), + descriptor.require_runtime_revisions("webdriver-bidi-wd-2026-07-01", "chromium-r1639811"), Err(BrowserProtocolRuntimeRequirementError::ProtocolRevisionMismatch) ); Ok(()) From e7c3e3e2dfa58b9a14f3a4027511cea50cd0324a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 14:52:11 +0900 Subject: [PATCH 3/6] feat(core): validate browser runtime revisions --- .../originweave-core/src/browser_protocol.rs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/crates/originweave-core/src/browser_protocol.rs b/crates/originweave-core/src/browser_protocol.rs index 981597e03..bbb6ca8ce 100644 --- a/crates/originweave-core/src/browser_protocol.rs +++ b/crates/originweave-core/src/browser_protocol.rs @@ -246,6 +246,33 @@ impl BrowserProtocolAdapterDescriptor { } } + /// Require exact runtime browser-protocol and browser revisions before use. + /// + /// The caller must derive both values from the trusted runtime adapter that + /// is about to perform browser work. This deterministic comparison does not + /// authenticate or attest that caller. It only prevents a descriptor pinned + /// to one validated upstream-protocol/browser pair from being silently used + /// when the supplied runtime evidence is malformed or has drifted. + pub fn require_runtime_revisions( + &self, + protocol_revision: &str, + browser_revision: &str, + ) -> Result<(), BrowserProtocolRuntimeRequirementError> { + if !metadata_token_is_valid(protocol_revision) { + return Err(BrowserProtocolRuntimeRequirementError::InvalidProtocolRevision); + } + if !metadata_token_is_valid(browser_revision) { + return Err(BrowserProtocolRuntimeRequirementError::InvalidBrowserRevision); + } + if self.protocol_revision != protocol_revision { + return Err(BrowserProtocolRuntimeRequirementError::ProtocolRevisionMismatch); + } + if self.browser_revision != browser_revision { + return Err(BrowserProtocolRuntimeRequirementError::BrowserRevisionMismatch); + } + Ok(()) + } + /// Require one explicitly declared adapter capability before later use. /// /// This method never infers support from the browser protocol family. An @@ -317,6 +344,40 @@ impl fmt::Display for BrowserProtocolVersionRequirementError { impl std::error::Error for BrowserProtocolVersionRequirementError {} +/// Failure to require exact pinned runtime revision evidence from an adapter. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BrowserProtocolRuntimeRequirementError { + /// The runtime upstream-protocol revision token was malformed. + InvalidProtocolRevision, + /// The runtime browser revision token was malformed. + InvalidBrowserRevision, + /// The runtime upstream-protocol revision differs from the pinned descriptor. + ProtocolRevisionMismatch, + /// The runtime browser revision differs from the pinned descriptor. + BrowserRevisionMismatch, +} + +impl fmt::Display for BrowserProtocolRuntimeRequirementError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidProtocolRevision => formatter.write_str( + "runtime browser protocol revision must be a bounded ASCII metadata token", + ), + Self::InvalidBrowserRevision => { + formatter.write_str("runtime browser revision must be a bounded ASCII metadata token") + } + Self::ProtocolRevisionMismatch => formatter.write_str( + "runtime browser protocol revision does not match the pinned adapter revision", + ), + Self::BrowserRevisionMismatch => formatter.write_str( + "runtime browser revision does not match the pinned adapter browser revision", + ), + } + } +} + +impl std::error::Error for BrowserProtocolRuntimeRequirementError {} + /// Failure to require one browser protocol capability from an adapter. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BrowserProtocolCapabilityRequirementError { From 4b1ce55bd946a3fb57c50c9ea4b2456590e3dccf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 14:52:42 +0900 Subject: [PATCH 4/6] feat(core): export browser runtime revision error --- crates/originweave-core/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index 5b6275eec..6a8c7b6bd 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -17,8 +17,9 @@ mod contracts; pub use browser_protocol::{ BrowserProtocolAdapterDescriptor, BrowserProtocolCapability, BrowserProtocolCapabilityRequirementError, BrowserProtocolDescriptorError, BrowserProtocolKind, - BrowserProtocolVersionRequirementError, MAX_BROWSER_PROTOCOL_METADATA_BYTES, - OriginWeaveProtocolVersion, OriginWeaveProtocolVersionParseError, + BrowserProtocolRuntimeRequirementError, BrowserProtocolVersionRequirementError, + MAX_BROWSER_PROTOCOL_METADATA_BYTES, OriginWeaveProtocolVersion, + OriginWeaveProtocolVersionParseError, }; pub use browser_registry::{ BrowserAuthorityRegistry, BrowserRegistryError, MAX_EXTERNAL_BROWSER_IDENTIFIER_BYTES, From f0fc8f9cfc66dd8b7664b058a8243cc2bf9d95e5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 15:10:20 +0900 Subject: [PATCH 5/6] style(core): apply canonical runtime revision formatting --- crates/originweave-core/src/browser_protocol.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/originweave-core/src/browser_protocol.rs b/crates/originweave-core/src/browser_protocol.rs index bbb6ca8ce..a23e6c3e1 100644 --- a/crates/originweave-core/src/browser_protocol.rs +++ b/crates/originweave-core/src/browser_protocol.rs @@ -363,9 +363,8 @@ impl fmt::Display for BrowserProtocolRuntimeRequirementError { Self::InvalidProtocolRevision => formatter.write_str( "runtime browser protocol revision must be a bounded ASCII metadata token", ), - Self::InvalidBrowserRevision => { - formatter.write_str("runtime browser revision must be a bounded ASCII metadata token") - } + Self::InvalidBrowserRevision => formatter + .write_str("runtime browser revision must be a bounded ASCII metadata token"), Self::ProtocolRevisionMismatch => formatter.write_str( "runtime browser protocol revision does not match the pinned adapter revision", ), From 415b8862ba5210a7bc55c38da00c87f512fbd932 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 06:42:19 +0900 Subject: [PATCH 6/6] docs: record runtime revision boundary --- CHANGELOG.md | 1 + tests/test_repository_contract.py | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bedf905a5..63d231262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - 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/.` 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. - 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. diff --git a/tests/test_repository_contract.py b/tests/test_repository_contract.py index 360e11143..4c529bd21 100644 --- a/tests/test_repository_contract.py +++ b/tests/test_repository_contract.py @@ -175,6 +175,12 @@ def test_product_name_is_consistent_in_binding_documents(self) -> None: self.assertNotIn("TraceWeave", text, relative) self.assertNotIn("ProofRail", text, relative) + def test_runtime_revision_boundary_is_recorded_in_the_changelog(self) -> None: + """The public runtime-revision boundary must remain visible in release history.""" + + changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8") + self.assertIn("require_runtime_revisions", changelog) + def test_database_contract_requires_two_word_snake_case(self) -> None: """Persistent naming policy must include the mandated canonical form.""" @@ -185,4 +191,4 @@ def test_database_contract_requires_two_word_snake_case(self) -> None: if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main()