From bad2a3d83a85cb95e7d364221a5bc9bc90489093 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 08:10:57 +0900 Subject: [PATCH 01/10] test(policy): require dispatch-time node revalidation --- .../semantic_node_dispatch_revalidation.rs | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs diff --git a/crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs b/crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs new file mode 100644 index 000000000..0b8ff26ff --- /dev/null +++ b/crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs @@ -0,0 +1,132 @@ +use std::cell::Cell; +use std::collections::BTreeSet; + +use originweave_core::{ + ActionIntentDigest, ActionKind, ActionRequest, ApprovalEvidence, BrowserSessionId, + BrowsingContextId, DocumentEpoch, ExecutionPurpose, InstructionSource, NodeActionKind, + ObservationChannel, ObservedNodeHandle, Origin, PolicyContext, RobotsDecision, SecretDelivery, + SemanticNodeActionBinding, SemanticNodeActionTarget, SemanticNodeObservation, + SemanticNodeObservationInput, SessionMode, +}; +use originweave_policy::PolicyAuthorizedSemanticNodeAction; + +const VALID_INTENT: &str = + "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + +fn origin(value: &str) -> Result { + Origin::parse(value).map_err(|error| format!("{error:?}")) +} + +fn authorized_action() -> Result { + let site = origin("https://app.example")?; + let handle = ObservedNodeHandle::new( + BrowserSessionId::new(7).map_err(|error| error.to_string())?, + BrowsingContextId::new(11).map_err(|error| error.to_string())?, + site.clone(), + DocumentEpoch::new(3).map_err(|error| error.to_string())?, + 17, + ) + .map_err(|error| error.to_string())?; + let observation = SemanticNodeObservation::new(SemanticNodeObservationInput { + handle, + parent: None, + children: Vec::new(), + role: "button".to_owned(), + accessible_name: "Continue".to_owned(), + visible_text: Some("Continue".to_owned()), + enabled: true, + visible: true, + selected: None, + supported_actions: BTreeSet::from([NodeActionKind::Click]), + evidence_channels: BTreeSet::from([ObservationChannel::Accessibility]), + }) + .map_err(|error| error.to_string())?; + let target = SemanticNodeActionTarget::from_observation(&observation, NodeActionKind::Click) + .map_err(|error| error.to_string())?; + let request = ActionRequest::new( + ActionKind::Navigate, + site.clone(), + site.clone(), + InstructionSource::User, + SecretDelivery::None, + ActionIntentDigest::parse(VALID_INTENT).map_err(|error| format!("{error:?}"))?, + ); + let binding = SemanticNodeActionBinding::new(target, request).map_err(|error| error.to_string())?; + let context = PolicyContext::new( + SessionMode::AgentTask, + ExecutionPurpose::UserDelegatedTask, + BTreeSet::from([ActionKind::Navigate.required_capability()]), + BTreeSet::from([site.clone()]), + BTreeSet::from([site]), + RobotsDecision::Allowed, + ApprovalEvidence::None, + ); + + PolicyAuthorizedSemanticNodeAction::authorize(binding, &context) + .map_err(|error| error.to_string()) +} + +#[test] +fn dispatch_callback_runs_only_after_exact_browser_revalidation() -> Result<(), String> { + let authorized = authorized_action()?; + let called = Cell::new(false); + + let result = authorized + .dispatch_if_current( + BrowserSessionId::new(7).map_err(|error| error.to_string())?, + BrowsingContextId::new(11).map_err(|error| error.to_string())?, + &origin("https://app.example")?, + DocumentEpoch::new(3).map_err(|error| error.to_string())?, + |binding| { + called.set(true); + ( + binding.target().action(), + binding.request().action(), + ) + }, + ) + .map_err(|error| error.to_string())?; + + assert!(called.get()); + assert_eq!(result, (NodeActionKind::Click, ActionKind::Navigate)); + Ok(()) +} + +#[test] +fn stale_browser_authority_never_reaches_dispatch_callback() -> Result<(), String> { + let authorized = authorized_action()?; + let called = Cell::new(false); + + let error = authorized + .dispatch_if_current( + BrowserSessionId::new(7).map_err(|error| error.to_string())?, + BrowsingContextId::new(11).map_err(|error| error.to_string())?, + &origin("https://app.example")?, + DocumentEpoch::new(4).map_err(|error| error.to_string())?, + |_binding| called.set(true), + ) + .err() + .ok_or_else(|| "stale browser authority unexpectedly reached dispatch".to_owned())?; + + assert!(!called.get()); + assert!(error.to_string().contains("stale")); + Ok(()) +} + +#[test] +fn adapter_failure_remains_separate_after_successful_revalidation() -> Result<(), String> { + let authorized = authorized_action()?; + + let adapter_result = authorized + .dispatch_if_current( + BrowserSessionId::new(7).map_err(|error| error.to_string())?, + BrowsingContextId::new(11).map_err(|error| error.to_string())?, + &origin("https://app.example")?, + DocumentEpoch::new(3).map_err(|error| error.to_string())?, + |_binding| -> Result<(), &'static str> { Err("adapter failed") }, + ) + .map_err(|error| error.to_string())?; + + assert_eq!(adapter_result, Err("adapter failed")); + Ok(()) +} From fb9aa5833d649148e056652157db890814013705 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 08:14:47 +0900 Subject: [PATCH 02/10] style(policy): apply canonical dispatch revalidation rustfmt --- .../tests/semantic_node_dispatch_revalidation.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs b/crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs index 0b8ff26ff..323ddbcd0 100644 --- a/crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs +++ b/crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs @@ -51,7 +51,8 @@ fn authorized_action() -> Result { SecretDelivery::None, ActionIntentDigest::parse(VALID_INTENT).map_err(|error| format!("{error:?}"))?, ); - let binding = SemanticNodeActionBinding::new(target, request).map_err(|error| error.to_string())?; + let binding = + SemanticNodeActionBinding::new(target, request).map_err(|error| error.to_string())?; let context = PolicyContext::new( SessionMode::AgentTask, ExecutionPurpose::UserDelegatedTask, @@ -79,10 +80,7 @@ fn dispatch_callback_runs_only_after_exact_browser_revalidation() -> Result<(), DocumentEpoch::new(3).map_err(|error| error.to_string())?, |binding| { called.set(true); - ( - binding.target().action(), - binding.request().action(), - ) + (binding.target().action(), binding.request().action()) }, ) .map_err(|error| error.to_string())?; From 4c3bc7331ed4a8354afe12a6b6e0465937fb3b9a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 08:15:53 +0900 Subject: [PATCH 03/10] feat(policy): revalidate node authority at dispatch boundary --- .../src/semantic_node_action.rs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/originweave-policy/src/semantic_node_action.rs b/crates/originweave-policy/src/semantic_node_action.rs index 5789587b6..880411466 100644 --- a/crates/originweave-policy/src/semantic_node_action.rs +++ b/crates/originweave-policy/src/semantic_node_action.rs @@ -54,6 +54,33 @@ impl PolicyAuthorizedSemanticNodeAction { current_epoch, ) } + + /// Revalidate exact browser authority and immediately invoke one adapter dispatch callback. + /// + /// The supplied session, context, origin, and document epoch must be trusted adapter state + /// sampled for the action that is about to be dispatched. The callback is never invoked when + /// that state no longer matches the semantic-node binding. A successful callback invocation + /// does not authenticate the adapter, grant destination, secret, or approval authority, or + /// prove the action's post-condition; those remain separate execution boundaries. + pub fn dispatch_if_current( + &self, + current_session: BrowserSessionId, + current_context: BrowsingContextId, + current_origin: &Origin, + current_epoch: DocumentEpoch, + dispatch: F, + ) -> Result + where + F: FnOnce(&SemanticNodeActionBinding) -> R, + { + self.validate_current( + current_session, + current_context, + current_origin, + current_epoch, + )?; + Ok(dispatch(&self.binding)) + } } /// A fail-closed outcome that did not produce a policy-authorized semantic-node action. From a897cd19f3f5e2d7c8669af39d8c4c958c4c6d94 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 08:17:34 +0900 Subject: [PATCH 04/10] docs(changelog): record dispatch-time node revalidation --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1d9106e..266fa260b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Authority-bound, bounded semantic node observations with typed node-local action evidence and explicit observation-channel provenance for the first Chromium vertical slice; observation metadata grants no execution authority. - Bounded typed semantic node queries over reviewed role, accessible-name, and node-action evidence, without exposing raw DOM/protocol selector languages or granting execution authority. - Authority-bound semantic node action targets that accept only observation-advertised node-local actions and revalidate exact session, context, origin, and document authority before later use without granting policy or browser-execution authority. +- Same-call semantic-node dispatch boundary that revalidates exact browser session, browsing context, canonical origin, and document epoch before invoking an already policy-authorized adapter callback, while keeping adapter execution outcome and post-condition proof separate. - Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement. - Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge. - Architecture, agent, security, contribution, research, database naming, roadmap, quality-gate, and TLS service-identity ADR documentation. @@ -76,4 +77,4 @@ All notable changes to OriginWeave are documented in this file. The format follo - The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it. - The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels. -[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD \ No newline at end of file From c93b90a316b83a160cf80008cc25c78aa32302f9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:06:24 +0900 Subject: [PATCH 05/10] test(policy): cover dispatch revalidation generic paths --- .../semantic_node_dispatch_revalidation.rs | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs b/crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs index 323ddbcd0..c9c4e438a 100644 --- a/crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs +++ b/crates/originweave-policy/tests/semantic_node_dispatch_revalidation.rs @@ -67,6 +67,22 @@ fn authorized_action() -> Result { .map_err(|error| error.to_string()) } +fn dispatch_unit_callback( + authorized: &PolicyAuthorizedSemanticNodeAction, + document_epoch: u64, + called: &Cell, +) -> Result<(), String> { + authorized + .dispatch_if_current( + BrowserSessionId::new(7).map_err(|error| error.to_string())?, + BrowsingContextId::new(11).map_err(|error| error.to_string())?, + &origin("https://app.example")?, + DocumentEpoch::new(document_epoch).map_err(|error| error.to_string())?, + |_binding| called.set(true), + ) + .map_err(|error| error.to_string()) +} + #[test] fn dispatch_callback_runs_only_after_exact_browser_revalidation() -> Result<(), String> { let authorized = authorized_action()?; @@ -95,19 +111,15 @@ fn stale_browser_authority_never_reaches_dispatch_callback() -> Result<(), Strin let authorized = authorized_action()?; let called = Cell::new(false); - let error = authorized - .dispatch_if_current( - BrowserSessionId::new(7).map_err(|error| error.to_string())?, - BrowsingContextId::new(11).map_err(|error| error.to_string())?, - &origin("https://app.example")?, - DocumentEpoch::new(4).map_err(|error| error.to_string())?, - |_binding| called.set(true), - ) + dispatch_unit_callback(&authorized, 3, &called)?; + assert!(called.replace(false)); + + let error = dispatch_unit_callback(&authorized, 4, &called) .err() .ok_or_else(|| "stale browser authority unexpectedly reached dispatch".to_owned())?; assert!(!called.get()); - assert!(error.to_string().contains("stale")); + assert!(error.contains("stale")); Ok(()) } From 45071df24c0e12762d4c78dcd1153eb80489c470 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 10:45:24 -0700 Subject: [PATCH 06/10] test(policy): align inherited action-binding formatting --- .../tests/semantic_node_action_binding.rs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/crates/originweave-core/tests/semantic_node_action_binding.rs b/crates/originweave-core/tests/semantic_node_action_binding.rs index 71a34b661..4122a40bf 100644 --- a/crates/originweave-core/tests/semantic_node_action_binding.rs +++ b/crates/originweave-core/tests/semantic_node_action_binding.rs @@ -74,8 +74,9 @@ fn action_request(source: Origin, target: Origin) -> Result Result<(), String> { let fixture = observation_fixture()?; - let target = SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) - .map_err(|error| error.to_string())?; + let target = + SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) + .map_err(|error| error.to_string())?; let request = action_request( origin("https://app.example")?, origin("https://next.example")?, @@ -92,8 +93,9 @@ fn node_action_binding_preserves_node_target_and_business_request() -> Result<() #[test] fn node_action_binding_rejects_request_from_another_document_origin() -> Result<(), String> { let fixture = observation_fixture()?; - let target = SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) - .map_err(|error| error.to_string())?; + let target = + SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) + .map_err(|error| error.to_string())?; let request = action_request( origin("https://other.example")?, origin("https://next.example")?, @@ -110,8 +112,9 @@ fn node_action_binding_rejects_request_from_another_document_origin() -> Result< fn node_action_binding_does_not_conflate_source_node_with_navigation_target() -> Result<(), String> { let fixture = observation_fixture()?; - let target = SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) - .map_err(|error| error.to_string())?; + let target = + SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) + .map_err(|error| error.to_string())?; let destination = origin("https://destination.example")?; let request = action_request(origin("https://app.example")?, destination.clone())?; @@ -123,10 +126,12 @@ fn node_action_binding_does_not_conflate_source_node_with_navigation_target() -> } #[test] -fn node_action_binding_revalidates_registry_owned_authority_before_dispatch() -> Result<(), String> { +fn node_action_binding_revalidates_registry_owned_authority_before_dispatch() -> Result<(), String> +{ let fixture = observation_fixture()?; - let target = SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) - .map_err(|error| error.to_string())?; + let target = + SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) + .map_err(|error| error.to_string())?; let request = action_request( origin("https://app.example")?, origin("https://next.example")?, @@ -143,8 +148,9 @@ fn node_action_binding_revalidates_registry_owned_authority_before_dispatch() -> #[test] fn node_action_binding_rejects_stale_document_before_dispatch() -> Result<(), String> { let mut fixture = observation_fixture()?; - let target = SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) - .map_err(|error| error.to_string())?; + let target = + SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) + .map_err(|error| error.to_string())?; let request = action_request( origin("https://app.example")?, origin("https://next.example")?, @@ -166,8 +172,9 @@ fn node_action_binding_rejects_stale_document_before_dispatch() -> Result<(), St #[test] fn node_action_binding_rejects_retired_session_before_dispatch() -> Result<(), String> { let mut fixture = observation_fixture()?; - let target = SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) - .map_err(|error| error.to_string())?; + let target = + SemanticNodeActionTarget::from_observation(&fixture.observation, NodeActionKind::Click) + .map_err(|error| error.to_string())?; let request = action_request( origin("https://app.example")?, origin("https://next.example")?, From a5cf2a1e886b305bc60409b03437d4ddb2167d6c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 13:53:37 -0700 Subject: [PATCH 07/10] fix(policy): keep dispatch revalidation registry-owned --- CHANGELOG.md | 3 ++- .../src/semantic_node_action.rs | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ca81cfbe..044dbd056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Fail-closed resolved-destination policy with IPv4/IPv6 special-purpose and reviewed cloud-platform endpoint classification, IPv4-mapped canonicalization, explicit class grants, non-empty origin-bound DNS snapshots capped at 256 resolver addresses, concrete connection pinning, DNS-set expansion detection, and per-hop redirect reauthorization. - Direct-only `originweave-network` TCP boundary with explicit canonical `SocketAddr` authority, zero IPv6 flow and scope metadata unless separately modeled, a non-cloneable single-use plan, a 30-second per-attempt timeout ceiling, at most four attempts, exact `peer_addr` verification before stream exposure, and no hostname re-resolution or ambient proxy inheritance. - Authenticated `originweave-tls` service-identity boundary that consumes an existing verified TCP stream, requires exact TLS-origin and transport-origin equality, derives RFC 9525 DNS or literal-IP reference identity only from the canonical HTTPS origin, validates WebPKI with explicit roots and fixed time, permits only TLS 1.2 and TLS 1.3, and never reconnects or resolves. -- Bounded TLS policy for total handshake time, ALPN identifiers, trust-root count and bytes, and server-presented certificate count and bytes, with explicit optional-versus-required ALPN behavior and `NotConfigured` revocation evidence. +- Bounded TLS policy for total handshake time, ALPN identifiers, trust-root count and bytes, and server-presented certificate count and bytes, with explicit optional-versus-required ALPN behavior. - Credential-free TLS evidence containing canonical origin, requested and observed peer, DNS/IP reference identity, TLS version, cipher-suite identifier, selected ALPN or explicit absence, leaf certificate and SPKI hashes, server-presented certificate hashes and bounds, trust-bundle identity and hash, validity interval, fixed verification time, revocation configuration, and measured handshake duration. - Credential-free connection and redirect evidence containing canonical addresses, destination classes, target digests, hop numbers, and approved-address counts. - Credential-free verified TCP evidence containing the logical origin, requested socket, observed peer, destination class, successful attempt number, and per-attempt timeout. @@ -29,6 +29,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Authority-bound, bounded semantic node observations with typed node-local action evidence and explicit observation-channel provenance for the first Chromium vertical slice; observation metadata grants no execution authority. - Bounded typed semantic node queries over reviewed role, accessible-name, and node-action evidence, without exposing raw DOM/protocol selector languages or granting execution authority. - Authority-bound semantic node action targets accept only observation-advertised node-local actions and revalidate the exact node binding against the live browser authority registry before later use, so retired or stale handles cannot be revived by caller-supplied authority tuples. +- Policy-authorized semantic node actions revalidate the exact registry-owned node binding in the same call that hands the binding to one adapter dispatch callback, preventing a validate-then-dispatch race from reviving retired or stale authority. - Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement. - Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge. - Architecture, agent, security, contribution, research, database naming, roadmap, quality-gate, and TLS service-identity ADR documentation. diff --git a/crates/originweave-policy/src/semantic_node_action.rs b/crates/originweave-policy/src/semantic_node_action.rs index c7797d60b..e1081f09b 100644 --- a/crates/originweave-policy/src/semantic_node_action.rs +++ b/crates/originweave-policy/src/semantic_node_action.rs @@ -50,6 +50,25 @@ impl PolicyAuthorizedSemanticNodeAction { ) -> Result<(), BrowserRegistryError> { self.binding.validate_current(registry) } + + /// Revalidate registry-owned browser authority and invoke one dispatch callback in the same call. + /// + /// The registry must be the trusted adapter's current authority registry for the action that is + /// about to be dispatched. The callback is never invoked if the retained node binding is stale, + /// retired, forged, or belongs to another registry. A successful callback invocation does not + /// authenticate the adapter, grant destination, secret, or approval authority, or prove the + /// action's post-condition; those remain separate execution boundaries. + pub fn dispatch_if_current( + &self, + registry: &BrowserAuthorityRegistry, + dispatch: F, + ) -> Result + where + F: FnOnce(&SemanticNodeActionBinding) -> R, + { + self.validate_current(registry)?; + Ok(dispatch(&self.binding)) + } } /// A fail-closed outcome that did not produce a policy-authorized semantic-node action. From 67944f7310be3e7497bcdab37816921b41fa78f0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 13:54:39 -0700 Subject: [PATCH 08/10] docs: preserve TLS revocation truth in dispatch stack --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 044dbd056..a2a04314b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Fail-closed resolved-destination policy with IPv4/IPv6 special-purpose and reviewed cloud-platform endpoint classification, IPv4-mapped canonicalization, explicit class grants, non-empty origin-bound DNS snapshots capped at 256 resolver addresses, concrete connection pinning, DNS-set expansion detection, and per-hop redirect reauthorization. - Direct-only `originweave-network` TCP boundary with explicit canonical `SocketAddr` authority, zero IPv6 flow and scope metadata unless separately modeled, a non-cloneable single-use plan, a 30-second per-attempt timeout ceiling, at most four attempts, exact `peer_addr` verification before stream exposure, and no hostname re-resolution or ambient proxy inheritance. - Authenticated `originweave-tls` service-identity boundary that consumes an existing verified TCP stream, requires exact TLS-origin and transport-origin equality, derives RFC 9525 DNS or literal-IP reference identity only from the canonical HTTPS origin, validates WebPKI with explicit roots and fixed time, permits only TLS 1.2 and TLS 1.3, and never reconnects or resolves. -- Bounded TLS policy for total handshake time, ALPN identifiers, trust-root count and bytes, and server-presented certificate count and bytes, with explicit optional-versus-required ALPN behavior. +- Bounded TLS policy for total handshake time, ALPN identifiers, trust-root count and bytes, and server-presented certificate count and bytes, with explicit optional-versus-required ALPN behavior and `NotConfigured` revocation evidence. - Credential-free TLS evidence containing canonical origin, requested and observed peer, DNS/IP reference identity, TLS version, cipher-suite identifier, selected ALPN or explicit absence, leaf certificate and SPKI hashes, server-presented certificate hashes and bounds, trust-bundle identity and hash, validity interval, fixed verification time, revocation configuration, and measured handshake duration. - Credential-free connection and redirect evidence containing canonical addresses, destination classes, target digests, hop numbers, and approved-address counts. - Credential-free verified TCP evidence containing the logical origin, requested socket, observed peer, destination class, successful attempt number, and per-attempt timeout. From f1b81e564ee0d1db8de08445ada942a9fe0dd008 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 18:54:09 +0900 Subject: [PATCH 09/10] fix(policy): preserve dispatch coverage observability --- crates/originweave-policy/src/semantic_node_action.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/originweave-policy/src/semantic_node_action.rs b/crates/originweave-policy/src/semantic_node_action.rs index e1081f09b..c46068947 100644 --- a/crates/originweave-policy/src/semantic_node_action.rs +++ b/crates/originweave-policy/src/semantic_node_action.rs @@ -66,8 +66,8 @@ impl PolicyAuthorizedSemanticNodeAction { where F: FnOnce(&SemanticNodeActionBinding) -> R, { - self.validate_current(registry)?; - Ok(dispatch(&self.binding)) + self.validate_current(registry) + .map(|()| dispatch(&self.binding)) } } From 8bf00d848432aa26118d69996e03abdb38fe10b4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 19:34:01 +0900 Subject: [PATCH 10/10] docs: document semantic node dispatch revalidation --- docs/doctoring.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/doctoring.md b/docs/doctoring.md index f0133bb5d..f84e6aa9a 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -89,6 +89,7 @@ NIST AI 600-1 provides generative-AI lifecycle risk guidance. WASP demonstrates ### Web-agent observation and evaluation Mind2Web reports that raw real-world HTML is often too large for direct LLM use and that filtering improves effectiveness and efficiency. OriginWeave prioritizes typed tools, structured data, redacted network responses, accessibility/DOM/layout semantics, and only then visual fallback. WebArena motivates repeatable task-success and failure-recovery benchmarks instead of anecdotal demonstrations. +Semantic node actions revalidate the registry-owned node binding at the adapter handoff, so observation evidence cannot revive a retired or stale node handle. ### Learned test-time orchestration