Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
424d048
test(network): require active subscription for navigation admission
seonghobae Aug 31, 2026
1dfdf3d
feat(network): bind navigation event admission to subscription
seonghobae Aug 31, 2026
0612b56
feat(network): retain subscription command admission binding
seonghobae Aug 31, 2026
8419b08
feat(network): export subscription-bound navigation admission
seonghobae Aug 31, 2026
3083843
fix(network): require subscribed observation for document advance
seonghobae Aug 31, 2026
cf1cd71
fix(network): require subscribed observation for origin binding
seonghobae Aug 31, 2026
4dbbb96
test(network): share subscribed navigation loopback fixture
seonghobae Aug 31, 2026
b9c01dd
test(network): exercise document advance through active subscription
seonghobae Aug 31, 2026
20fc200
test(network): bind navigation origin through active subscription
seonghobae Aug 31, 2026
31b5c2a
test(network): cover subscription admission failure contracts
seonghobae Aug 31, 2026
9e151d6
style(network): apply canonical subscription admission format
seonghobae Aug 31, 2026
df9d643
test(network): reject replayed subscribed navigation event
seonghobae Aug 31, 2026
3461bdc
style(network): apply canonical replay regression format
seonghobae Aug 31, 2026
f88521a
fix(network): prevent subscribed navigation replay
seonghobae Aug 31, 2026
1434d24
docs(network): expose replay-bounded admission contract
seonghobae Aug 31, 2026
4dc8c5c
style(network): apply canonical replay guard formatting
seonghobae Aug 31, 2026
24281e2
test(network): update navigation fixture for mutable admission
seonghobae Aug 31, 2026
f6f6dcc
test(network): cover navigation admission failure bounds
seonghobae Aug 31, 2026
45b73a6
test(network): exercise subscription admission diagnostics
seonghobae Aug 31, 2026
86dce3e
style(network): apply canonical rustfmt
seonghobae Aug 31, 2026
9c4116b
merge: converge subscription admission on teardown authority repair
seonghobae Aug 31, 2026
44c44b4
test: replay received-response provenance before parent adoption
seonghobae Sep 6, 2026
cf0f245
fix: preserve admission contracts on current unsubscribe parent
seonghobae Sep 6, 2026
73f11de
test(network): reject fabricated subscription admission provenance
seonghobae Sep 6, 2026
43d3b5a
fix(network): bind subscription receipts to the original sent command
seonghobae Sep 6, 2026
918c4eb
test(network): reject crossed subscription transport evidence
seonghobae Sep 6, 2026
bc2e69d
test(network): reproduce crossed-connection subscription provenance
seonghobae Sep 6, 2026
f80a17f
fix(network): bind subscription correlation to connection
seonghobae Sep 6, 2026
a3dfd19
fix(network): retain subscription send connection provenance
seonghobae Sep 6, 2026
3e66c44
fix(network): require connection-bound subscription receipts
seonghobae Sep 6, 2026
d9b9013
fix(network): bind navigation event admission to receipt connection
seonghobae Sep 6, 2026
c46642e
test(network): consume subscription messages through connection reader
seonghobae Sep 6, 2026
9805cc1
test(network): read subscription receipt with connection provenance
seonghobae Sep 6, 2026
b72fc01
test(network): preserve admission diagnostics with connection provenance
seonghobae Sep 6, 2026
bbb7c87
test(network): preserve admission contracts on connection-bound messages
seonghobae Sep 6, 2026
ed434fc
test(network): prove crossed-connection receipt and event rejection
seonghobae Sep 6, 2026
bd29f40
fix(network): require original subscription receive connection
seonghobae Sep 6, 2026
d8983d5
test(network): keep same-connection admission failure contracts
seonghobae Sep 6, 2026
ced211a
test(network): preserve subscription response failures with receive p…
seonghobae Sep 6, 2026
7ae9657
test(network): bind subscription fixture while retaining unsubscribe …
seonghobae Sep 6, 2026
7c20907
test(network): migrate subscription receipt fixture without widening …
seonghobae Sep 6, 2026
8ebcc61
test(docs): track subscription receive provenance contract
seonghobae Sep 6, 2026
1d6bd00
fix(network): combine subscription receive provenance repairs
seonghobae Sep 6, 2026
da4a11f
fix(network): share connection provenance validation
seonghobae Sep 6, 2026
701fcaa
test(network): cover subscription connection mismatch diagnostic
seonghobae Sep 6, 2026
6fca12b
style(network): format subscription document tests
seonghobae Sep 6, 2026
d2769a9
style(network): format navigation origin tests
seonghobae Sep 6, 2026
fa015c8
style(network): format transport provenance tests
seonghobae Sep 6, 2026
f87f095
style(network): format subscription response failure tests
seonghobae Sep 6, 2026
ccef158
fix(network): preserve intervening subscription quality repairs
seonghobae Sep 6, 2026
bc7a516
style(network): reconcile intervening formatting with pinned rustfmt
seonghobae Sep 6, 2026
52cff96
style(network): format subscription admission tests
seonghobae Sep 6, 2026
3c8da79
docs: bind combined provenance proof to its exact local revision
seonghobae Sep 6, 2026
ad14ce1
fix(network): adopt final released subscription repair lineage
seonghobae Sep 6, 2026
2a9fdc5
docs: record final shared lineage adoption boundary
seonghobae Sep 6, 2026
c41e737
test(network): reject substituted navigation registries
seonghobae Sep 6, 2026
b3ffeac
test(network): adapt fixture origin error to test result
seonghobae Sep 6, 2026
d5600aa
fix(network): preserve original registry through navigation mutation
seonghobae Sep 6, 2026
e686b3a
docs: specify original registry ownership and recovery evidence
seonghobae Sep 6, 2026
3378761
test(core): cover retained registry identity after owner retirement
seonghobae Sep 6, 2026
10f138f
test(core): preserve panic-free registry diagnostic assertions
seonghobae Sep 6, 2026
92fd0b0
test: reject stale responses after actual subscription resend
seonghobae Sep 6, 2026
15aea15
test: expose stale error retirement and pre-upgrade socket alias
seonghobae Sep 6, 2026
d75f780
fix: seal typed command dispatch history on each connection
seonghobae Sep 6, 2026
02793e0
test: verify exclusive dispatch lanes and preserve socket failure probes
seonghobae Sep 6, 2026
7a944b2
docs: record sealed dispatch policy and realistic stale-response evid…
seonghobae Sep 6, 2026
6009682
test: synchronize buffered response before rejected resend
seonghobae Sep 6, 2026
e6c02cf
test: observe zero-byte closure of unread buffered responses
seonghobae Sep 6, 2026
8050515
test(network): exercise the public opening deadline boundary
seonghobae Sep 6, 2026
2c45cea
test(network): expose retained admission after unsubscribe construction
seonghobae Sep 6, 2026
ce6f6fd
test(network): expose unsubscribe transport provenance gaps
seonghobae Sep 6, 2026
267b8c3
fix(network): consume and bind subscription teardown authority
seonghobae Sep 6, 2026
69866d8
test(network): preserve teardown failures on their original connection
seonghobae Sep 6, 2026
7fb93e7
test(docs): track consuming subscription teardown handoff
seonghobae Sep 6, 2026
b4702cd
test: reject registry session on foreign BiDi transport
seonghobae Sep 6, 2026
53df652
fix(network): reject foreign registry session before subscription dis…
seonghobae Sep 6, 2026
657d0c9
test(core): cover exact and retired session provenance
seonghobae Sep 6, 2026
2fc4fe6
docs: record subscription session provenance boundary
seonghobae Sep 6, 2026
11861c1
test(network): synchronize seed Pong before simulated disconnect
seonghobae Sep 6, 2026
6f331a5
test(core): cover session mismatch in unit error contract
seonghobae Sep 6, 2026
637fd97
test(network): replay replacement pointer replies before parent adoption
seonghobae Sep 6, 2026
92e576c
merge: preserve subscription authority while adopting sealed pointer …
seonghobae Sep 6, 2026
4339571
docs(network): distinguish parent evidence from stronger child safegu…
seonghobae Sep 6, 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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,22 @@ All notable changes to OriginWeave are documented in this file. The format follo

## [Unreleased]

- Integrated the current navigation-subscription safeguards while preserving active-subscription admission, replay rejection and stale-document checks. A response from a replacement connection still cannot complete an earlier session shutdown; this source integration is not real-browser or release acceptance.

### Changed

- Reject replacement-connection click replies while retaining increasing request numbers, original subscription ownership and same-connection shutdown checks.

- Reject a navigation subscription aimed at a different browser session before sending it, without creating or replacing browser state.
- Keep connection-failure checks focused on the requested action by completing fixture setup before the simulated peer disconnects.

- Keep subscription shutdown on its original connection and reject replies from another connection without losing the pending request. Beginning shutdown ends local event admission; a failed shutdown requires a new subscription before admission resumes.

- Prevent an earlier browser reply from completing a later request that reuses its number. Typed browser requests now use increasing numbers on each connection, and low-level protocol traffic uses a separate connection.

- Keep navigation subscriptions and accepted navigation events attached to their original browser state, so a replacement state with matching local identifiers cannot send a request or change another document.
- Reject navigation-subscription replies and events received on a different connection, even when their session and request details match. Rejected messages leave the original request and document unchanged, so the original connection can still complete its work.
- Prevent an unsent navigation subscription from borrowing another request's successful response, including when separate sessions reuse the same local numbers. Re-registering a completed request number without sending a new request cannot recreate its consumed subscription.
- Carried replacement-connection subscription-reply rejection into unsubscribe preparation, preserving opaque identifiers and existing teardown checks without claiming that pending events have drained.
- Reject navigation-subscription replies received on replacement connections while keeping the original request available for its own reply; a successful subscription still does not prove that a navigation occurred.
- Carried replacement-connection click-reply rejection into the navigation-subscription stack while preserving deadline rejection, unrelated pending requests and conservative handling of uncertain writes.
Expand Down
50 changes: 50 additions & 0 deletions crates/originweave-core/src/browser_authority_registry.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::sync::Arc;

use crate::browser_registry::BrowserAuthorityRegistry as RawBrowserAuthorityRegistry;
use crate::{
BrowserRegistryError, BrowserSessionId, BrowsingContextId, DocumentEpoch, ObservedNodeHandle,
Expand All @@ -15,14 +17,24 @@ use crate::{
/// before atomically minting handles.
pub struct BrowserAuthorityRegistry {
inner: RawBrowserAuthorityRegistry,
registry_identity: Arc<()>,
}

/// Opaque process-local identity of one browser-authority registry allocation.
///
/// Cloning preserves the same registry identity without keeping its mutable state alive. This
/// witness contains no wire identifier, address, durable identity, or grant of browser authority.
/// It must be captured by a trusted admission boundary and revalidated with current context state.
#[derive(Clone)]
pub struct BrowserRegistryIdentity(Arc<()>);

impl BrowserAuthorityRegistry {
/// Create an empty registry with the reviewed default per-namespace identifier capacity.
#[must_use]
pub fn new() -> Self {
Self {
inner: RawBrowserAuthorityRegistry::new(),
registry_identity: Arc::new(()),
}
}

Expand All @@ -34,9 +46,33 @@ impl BrowserAuthorityRegistry {
pub fn with_identifier_limit(maximum_identifier: u64) -> Self {
Self {
inner: RawBrowserAuthorityRegistry::with_identifier_limit(maximum_identifier),
registry_identity: Arc::new(()),
}
}

/// Capture this registry's opaque identity for later exact-owner revalidation.
///
/// The identity survives moves of this registry but cannot match a replacement registry,
/// including one whose local session and context identifiers have identical numeric values.
#[must_use]
pub fn registry_identity(&self) -> BrowserRegistryIdentity {
BrowserRegistryIdentity(Arc::clone(&self.registry_identity))
}

/// Reject a witness issued by any other registry before consulting registry-local identifiers.
///
/// Success proves only registry ownership; callers must still validate live session, context,
/// document epoch, origin and operation-specific authority at their actual use boundary.
pub fn require_identity(
&self,
expected_identity: &BrowserRegistryIdentity,
) -> Result<(), BrowserRegistryError> {
if !Arc::ptr_eq(&self.registry_identity, &expected_identity.0) {
return Err(BrowserRegistryError::RegistryInstanceMismatch);
}
Ok(())
}

/// Register one opaque external browser-session identifier.
pub fn register_session(
&mut self,
Expand All @@ -45,6 +81,20 @@ impl BrowserAuthorityRegistry {
self.inner.register_session(external_identifier)
}

/// Require external protocol session text to name this exact currently registered session.
///
/// This read-only check reuses the canonical mapping and never creates authority from transport
/// text. Unknown, retired and mismatched sessions fail closed. Success does not authenticate a
/// browser process or authorize an action; callers must also revalidate registry ownership.
pub fn require_registered_session_external_identifier(
&self,
browser_session: BrowserSessionId,
external_identifier: &str,
) -> Result<(), BrowserRegistryError> {
self.inner
.require_session_external_identifier(browser_session, external_identifier)
}

/// Register one opaque external browsing-context identifier inside a known browser session.
pub fn register_context(
&mut self,
Expand Down
40 changes: 40 additions & 0 deletions crates/originweave-core/src/browser_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,19 @@ impl BrowserAuthorityRegistry {
})
}

/// Check the existing canonical external-session mapping without creating authority.
pub(crate) fn require_session_external_identifier(
&self,
browser_session: BrowserSessionId,
external_identifier: &str,
) -> Result<(), BrowserRegistryError> {
validate_external_identifier(external_identifier)?;
if self.session_by_external.get(external_identifier).copied() != Some(browser_session) {
return Err(BrowserRegistryError::SessionExternalIdentifierMismatch);
}
Ok(())
}

/// Register one opaque external browsing-context identifier inside a known browser session.
///
/// A newly registered context starts at document epoch one. The same external context text in
Expand Down Expand Up @@ -427,10 +440,14 @@ impl Default for BrowserAuthorityRegistry {
/// A fail-closed error produced while translating external browser identifiers into local authority.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrowserRegistryError {
/// A retained authority witness belongs to a different registry instance.
RegistryInstanceMismatch,
/// An external identifier was empty, contained control, whitespace, or Unicode format text, or exceeded the reviewed byte bound.
InvalidExternalIdentifier,
/// The supplied OriginWeave browser session is not registered in this registry.
UnknownBrowserSession,
/// The transport-level session identifier does not name the supplied registered session.
SessionExternalIdentifierMismatch,
/// The supplied OriginWeave browsing context is not registered in this registry.
UnknownBrowsingContext,
/// The browsing context belongs to another browser session.
Expand All @@ -457,12 +474,18 @@ pub enum BrowserRegistryError {
impl fmt::Display for BrowserRegistryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RegistryInstanceMismatch => {
formatter.write_str("browser authority belongs to another registry instance")
}
Self::InvalidExternalIdentifier => formatter.write_str(
"external browser identifier must contain 1 to 512 UTF-8 bytes without control, whitespace, or Unicode format characters",
),
Self::UnknownBrowserSession => {
formatter.write_str("browser session is not registered in this authority registry")
}
Self::SessionExternalIdentifierMismatch => formatter.write_str(
"browser session external identifier does not match the registered session",
),
Self::UnknownBrowsingContext => {
formatter.write_str("browsing context is not registered in this authority registry")
}
Expand Down Expand Up @@ -836,13 +859,30 @@ mod tests {

#[test]
fn browser_registry_errors_have_non_sensitive_deterministic_text() {
let original_registry = crate::BrowserAuthorityRegistry::new();
let retained_identity = original_registry.registry_identity().clone();
assert!(
original_registry
.require_identity(&retained_identity)
.is_ok()
);
drop(original_registry);
let replacement_registry = crate::BrowserAuthorityRegistry::with_identifier_limit(8);
let replacement_errors: Vec<_> = replacement_registry
.require_identity(&retained_identity)
.err()
.into_iter()
.collect();
assert_eq!(replacement_errors.len(), 1);
let expected_values = values(BrowserSessionId::new(1));
let actual_values = values(BrowserSessionId::new(2));
assert_eq!(expected_values.len(), 1);
assert_eq!(actual_values.len(), 1);
let errors = [
replacement_errors[0],
BrowserRegistryError::InvalidExternalIdentifier,
BrowserRegistryError::UnknownBrowserSession,
BrowserRegistryError::SessionExternalIdentifierMismatch,
BrowserRegistryError::UnknownBrowsingContext,
BrowserRegistryError::ContextSessionMismatch {
expected: expected_values[0],
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 @@ -41,7 +41,7 @@ mod webdriver_bidi_result;
mod webdriver_bidi_websocket_connect_target;
mod webdriver_bidi_websocket_endpoint;

pub use browser_authority_registry::BrowserAuthorityRegistry;
pub use browser_authority_registry::{BrowserAuthorityRegistry, BrowserRegistryIdentity};
pub use browser_protocol::{
BrowserProtocolAdapterDescriptor, BrowserProtocolCapability,
BrowserProtocolCapabilityRequirementError, BrowserProtocolDescriptorError, BrowserProtocolKind,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use originweave_core::{BrowserAuthorityRegistry, BrowserRegistryError, BrowserSessionId};

#[test]
fn session_mapping_is_read_only_exact_and_revoked_on_retirement() -> Result<(), BrowserRegistryError>
{
let mut registry = BrowserAuthorityRegistry::new();
let first = registry.register_session("session-a")?;
let second = registry.register_session("session-b")?;
registry.require_registered_session_external_identifier(first, "session-a")?;
for (session, external) in [
(first, "session-b"),
(second, "session-a"),
(first, "unknown"),
(
BrowserSessionId::new(99)
.map_err(|_| BrowserRegistryError::InternalAuthorityInvariant)?,
"session-a",
),
] {
assert_eq!(
registry.require_registered_session_external_identifier(session, external),
Err(BrowserRegistryError::SessionExternalIdentifierMismatch)
);
}
assert_eq!(
registry.require_registered_session_external_identifier(first, "\n"),
Err(BrowserRegistryError::InvalidExternalIdentifier)
);
registry.remove_session(first)?;
assert_eq!(
registry.require_registered_session_external_identifier(first, "session-a"),
Err(BrowserRegistryError::SessionExternalIdentifierMismatch)
);
let replacement = registry.register_session("session-a")?;
assert_ne!(first, replacement);
registry.require_registered_session_external_identifier(replacement, "session-a")?;
assert_eq!(
registry.require_registered_session_external_identifier(first, "session-a"),
Err(BrowserRegistryError::SessionExternalIdentifierMismatch)
);
assert_eq!(
BrowserRegistryError::SessionExternalIdentifierMismatch.to_string(),
"browser session external identifier does not match the registered session"
);
Ok(())
}
13 changes: 12 additions & 1 deletion crates/originweave-network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
//! complete local-end JSON envelopes, tracks bounded command-response correlation,
//! transports a narrowly typed pointer click, admits its typed correlated protocol
//! response, sends a context-bound committed-navigation subscription and retains
//! its typed bounded correlated identifier, explicitly unsubscribes that exact
//! its typed bounded correlated identifier, binds navigation-event admission to
//! that active command/receipt lifecycle with bounded fail-closed navigation replay
//! prevention, explicitly unsubscribes that exact
//! retained identifier, admits its typed correlated unsubscribe response, admits a
//! bounded navigation observation for one exact registered context and URL, rotates
//! that context's document epoch only from an exact caller-captured pre-action
Expand All @@ -33,6 +35,7 @@ mod webdriver_bidi_connection;
mod webdriver_bidi_json_envelope;
mod webdriver_bidi_navigation_committed_postcondition;
mod webdriver_bidi_navigation_committed_subscription;
mod webdriver_bidi_navigation_committed_subscription_admission;
mod webdriver_bidi_navigation_committed_subscription_response;
mod webdriver_bidi_navigation_committed_unsubscribe;
mod webdriver_bidi_navigation_committed_unsubscribe_response;
Expand Down Expand Up @@ -83,6 +86,14 @@ pub use webdriver_bidi_navigation_committed_subscription::{
WebDriverBiDiNavigationCommittedSubscriptionCommand,
WebDriverBiDiNavigationCommittedSubscriptionCommandError,
};
pub use webdriver_bidi_navigation_committed_subscription_admission::{
MAX_WEBDRIVER_BIDI_NAVIGATION_COMMITTED_ADMISSIONS,
WebDriverBiDiNavigationCommittedSubscribedObservation,
WebDriverBiDiNavigationCommittedSubscriptionAdmission,
WebDriverBiDiNavigationCommittedSubscriptionAdmissionError,
WebDriverBiDiNavigationCommittedSubscriptionBinding,
WebDriverBiDiNavigationCommittedSubscriptionEventError,
};
pub use webdriver_bidi_navigation_committed_subscription_response::{
MAX_WEBDRIVER_BIDI_SUBSCRIPTION_IDENTIFIER_BYTES,
WebDriverBiDiNavigationCommittedSubscriptionResponseError,
Expand Down
Loading
Loading