From f4521bacf98f052f275fc87d0f75c108f6e096e9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 06:24:51 +0900 Subject: [PATCH 1/8] test(extension): require managed policy freshness --- .../tests/agent_task_extension_policy.rs | 64 +++++++++++++++++-- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/crates/originweave-policy/tests/agent_task_extension_policy.rs b/crates/originweave-policy/tests/agent_task_extension_policy.rs index 8ea2f0de5..f82a6249a 100644 --- a/crates/originweave-policy/tests/agent_task_extension_policy.rs +++ b/crates/originweave-policy/tests/agent_task_extension_policy.rs @@ -22,11 +22,11 @@ fn context(value: u64) -> BrowsingContextId { #[test] fn empty_agent_task_extension_policy_denies_every_extension() { - let policy = AgentTaskExtensionPolicy::new([]); + let policy = AgentTaskExtensionPolicy::new([], 10, 20); let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); assert_eq!( - evaluate_agent_task_extension(&extension, &policy), + evaluate_agent_task_extension(&extension, &policy, 15), AgentTaskExtensionDecision::DenyNotManaged ); } @@ -35,25 +35,75 @@ fn empty_agent_task_extension_policy_denies_every_extension() { fn managed_agent_task_extension_policy_allows_only_exact_identifiers() { let allowed = extension_id("abcdefghijklmnopabcdefghijklmnop"); let other = extension_id("bcdefghijklmnopabcdefghijklmnopa"); - let policy = AgentTaskExtensionPolicy::new([allowed.clone(), allowed.clone()]); + let policy = AgentTaskExtensionPolicy::new([allowed.clone(), allowed.clone()], 10, 20); assert_eq!( - evaluate_agent_task_extension(&allowed, &policy), + evaluate_agent_task_extension(&allowed, &policy, 10), AgentTaskExtensionDecision::AllowManagedExtension ); assert_eq!( - evaluate_agent_task_extension(&other, &policy), + evaluate_agent_task_extension(&other, &policy, 19), AgentTaskExtensionDecision::DenyNotManaged ); } +#[test] +fn managed_agent_task_extension_policy_fails_closed_outside_its_validity_window() { + let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); + let policy = AgentTaskExtensionPolicy::new([extension.clone()], 10, 20); + + assert_eq!( + evaluate_agent_task_extension(&extension, &policy, 9), + AgentTaskExtensionDecision::DenyPolicyNotYetValid + ); + assert_eq!( + evaluate_agent_task_extension(&extension, &policy, 20), + AgentTaskExtensionDecision::DenyPolicyExpired + ); + assert_eq!( + evaluate_agent_task_extension(&extension, &policy, u64::MAX), + AgentTaskExtensionDecision::DenyPolicyExpired + ); +} + +#[test] +fn invalid_managed_extension_policy_window_fails_closed_before_membership() { + let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); + let reversed = AgentTaskExtensionPolicy::new([extension.clone()], 20, 10); + let empty = AgentTaskExtensionPolicy::new([extension.clone()], 20, 20); + + assert_eq!( + evaluate_agent_task_extension(&extension, &reversed, 15), + AgentTaskExtensionDecision::DenyInvalidPolicyWindow + ); + assert_eq!( + evaluate_agent_task_extension(&extension, &empty, 20), + AgentTaskExtensionDecision::DenyInvalidPolicyWindow + ); +} + +#[test] +fn maximum_timestamp_window_remains_half_open_without_overflow() { + let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); + let policy = AgentTaskExtensionPolicy::new([extension.clone()], u64::MAX - 1, u64::MAX); + + assert_eq!( + evaluate_agent_task_extension(&extension, &policy, u64::MAX - 1), + AgentTaskExtensionDecision::AllowManagedExtension + ); + assert_eq!( + evaluate_agent_task_extension(&extension, &policy, u64::MAX), + AgentTaskExtensionDecision::DenyPolicyExpired + ); +} + #[test] fn managed_agent_task_extension_admission_does_not_mint_agent_capability() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new([extension.clone()]); + let policy = AgentTaskExtensionPolicy::new([extension.clone()], 10, 20); assert_eq!( - evaluate_agent_task_extension(&extension, &policy), + evaluate_agent_task_extension(&extension, &policy, 15), AgentTaskExtensionDecision::AllowManagedExtension ); From 66dbc574518740b8c09a558f7b54e7f5629256fe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 06:27:57 +0900 Subject: [PATCH 2/8] feat(extension): fail closed on stale managed policy --- crates/originweave-policy/src/lib.rs | 36 ++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/crates/originweave-policy/src/lib.rs b/crates/originweave-policy/src/lib.rs index 3dda94b02..53d89119a 100644 --- a/crates/originweave-policy/src/lib.rs +++ b/crates/originweave-policy/src/lib.rs @@ -26,6 +26,8 @@ use originweave_core::{ #[derive(Debug, Clone, PartialEq, Eq)] pub struct AgentTaskExtensionPolicy { managed_extensions: BTreeSet, + valid_from: u64, + valid_until: u64, } impl AgentTaskExtensionPolicy { @@ -33,13 +35,20 @@ impl AgentTaskExtensionPolicy { /// /// Duplicate identifiers collapse to one exact managed identity. An empty /// iterator therefore represents the default policy that admits no extension. + /// `valid_from` is inclusive and `valid_until` is exclusive. Both values are + /// opaque timestamps in the same caller-defined trusted time domain that will + /// be supplied to [`evaluate_agent_task_extension`]. This constructor does not + /// authenticate policy provenance or attest a clock; an invalid or empty + /// validity window is retained so evaluation can fail closed deterministically. #[must_use] - pub fn new(managed_extensions: I) -> Self + pub fn new(managed_extensions: I, valid_from: u64, valid_until: u64) -> Self where I: IntoIterator, { Self { managed_extensions: managed_extensions.into_iter().collect(), + valid_from, + valid_until, } } } @@ -51,19 +60,38 @@ pub enum AgentTaskExtensionDecision { AllowManagedExtension, /// The extension identity is absent from the managed allow-list. DenyNotManaged, + /// The configured policy validity window is empty or reversed. + DenyInvalidPolicyWindow, + /// The trusted evaluation time precedes the policy validity window. + DenyPolicyNotYetValid, + /// The trusted evaluation time is at or beyond the policy expiry boundary. + DenyPolicyExpired, } /// Evaluate extension admission without minting OriginWeave Agent capability. /// /// This pure boundary answers only whether the exact canonical extension may be -/// present in the caller's managed Agent Task profile. Chromium permissions, -/// installation state, native messaging, and [`originweave_core::ExtensionAgentGrant`] -/// remain separate authorities. +/// present in the caller's managed Agent Task profile at `trusted_time`. +/// `trusted_time`, [`AgentTaskExtensionPolicy::new`] `valid_from`, and `valid_until` +/// must use the same caller-attested time domain; this function does not read or +/// attest a clock. The validity window is half-open: `valid_from <= trusted_time < +/// valid_until`. Chromium permissions, installation state, native messaging, and +/// [`originweave_core::ExtensionAgentGrant`] remain separate authorities. #[must_use] pub fn evaluate_agent_task_extension( extension_id: &ExtensionId, policy: &AgentTaskExtensionPolicy, + trusted_time: u64, ) -> AgentTaskExtensionDecision { + if policy.valid_from >= policy.valid_until { + return AgentTaskExtensionDecision::DenyInvalidPolicyWindow; + } + if trusted_time < policy.valid_from { + return AgentTaskExtensionDecision::DenyPolicyNotYetValid; + } + if trusted_time >= policy.valid_until { + return AgentTaskExtensionDecision::DenyPolicyExpired; + } if policy.managed_extensions.contains(extension_id) { AgentTaskExtensionDecision::AllowManagedExtension } else { From 759d2f04d81dcf52bca29b88d860fb9aaeca56e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 07:16:58 +0900 Subject: [PATCH 3/8] docs(changelog): record extension policy freshness --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b934daab..d89bd3675 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Added +- Time-bounded managed extension admission for isolated Agent Task profiles with an explicit half-open validity window and caller-supplied trusted evaluation time; invalid, not-yet-valid, and expired policy states fail closed before exact extension allow-list membership is considered. - Fail-closed managed extension admission for isolated Agent Task profiles: an empty policy admits no extension, only exact canonical `ExtensionId` allow-list membership is accepted, duplicate entries cannot widen authority, and successful profile admission remains separate from `ExtensionAgentGrant` capability. - Explicit reduced-assurance classification for attached human tabs when trusted adapter evidence says an existing extension can influence page state; the narrow rule does not detect extensions, prove extension absence, grant Agent authority, or turn an unclassified context into high-assurance evidence. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. From b63fb4d36c7665275d48a34c9e182191d912190d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 02:09:25 +0900 Subject: [PATCH 4/8] test(extension): bound managed policy lifetime --- .../tests/agent_task_extension_policy.rs | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/crates/originweave-policy/tests/agent_task_extension_policy.rs b/crates/originweave-policy/tests/agent_task_extension_policy.rs index f82a6249a..f60ed0ccd 100644 --- a/crates/originweave-policy/tests/agent_task_extension_policy.rs +++ b/crates/originweave-policy/tests/agent_task_extension_policy.rs @@ -22,7 +22,7 @@ fn context(value: u64) -> BrowsingContextId { #[test] fn empty_agent_task_extension_policy_denies_every_extension() { - let policy = AgentTaskExtensionPolicy::new([], 10, 20); + let policy = AgentTaskExtensionPolicy::new([], 10, 20, 10); let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); assert_eq!( @@ -35,7 +35,7 @@ fn empty_agent_task_extension_policy_denies_every_extension() { fn managed_agent_task_extension_policy_allows_only_exact_identifiers() { let allowed = extension_id("abcdefghijklmnopabcdefghijklmnop"); let other = extension_id("bcdefghijklmnopabcdefghijklmnopa"); - let policy = AgentTaskExtensionPolicy::new([allowed.clone(), allowed.clone()], 10, 20); + let policy = AgentTaskExtensionPolicy::new([allowed.clone(), allowed.clone()], 10, 20, 10); assert_eq!( evaluate_agent_task_extension(&allowed, &policy, 10), @@ -50,7 +50,7 @@ fn managed_agent_task_extension_policy_allows_only_exact_identifiers() { #[test] fn managed_agent_task_extension_policy_fails_closed_outside_its_validity_window() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new([extension.clone()], 10, 20); + let policy = AgentTaskExtensionPolicy::new([extension.clone()], 10, 20, 10); assert_eq!( evaluate_agent_task_extension(&extension, &policy, 9), @@ -69,8 +69,9 @@ fn managed_agent_task_extension_policy_fails_closed_outside_its_validity_window( #[test] fn invalid_managed_extension_policy_window_fails_closed_before_membership() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let reversed = AgentTaskExtensionPolicy::new([extension.clone()], 20, 10); - let empty = AgentTaskExtensionPolicy::new([extension.clone()], 20, 20); + let reversed = AgentTaskExtensionPolicy::new([extension.clone()], 20, 10, 10); + let empty = AgentTaskExtensionPolicy::new([extension.clone()], 20, 20, 10); + let zero_maximum = AgentTaskExtensionPolicy::new([extension.clone()], 20, 21, 0); assert_eq!( evaluate_agent_task_extension(&extension, &reversed, 15), @@ -80,12 +81,37 @@ fn invalid_managed_extension_policy_window_fails_closed_before_membership() { evaluate_agent_task_extension(&extension, &empty, 20), AgentTaskExtensionDecision::DenyInvalidPolicyWindow ); + assert_eq!( + evaluate_agent_task_extension(&extension, &zero_maximum, 20), + AgentTaskExtensionDecision::DenyInvalidPolicyWindow + ); +} + +#[test] +fn managed_extension_policy_window_cannot_exceed_local_maximum() { + let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); + let exact = AgentTaskExtensionPolicy::new([extension.clone()], 10, 20, 10); + let overlong = AgentTaskExtensionPolicy::new([extension.clone()], 10, 21, 10); + + assert_eq!( + evaluate_agent_task_extension(&extension, &exact, 19), + AgentTaskExtensionDecision::AllowManagedExtension + ); + assert_eq!( + evaluate_agent_task_extension(&extension, &overlong, 19), + AgentTaskExtensionDecision::DenyPolicyWindowExceedsMaximum + ); } #[test] fn maximum_timestamp_window_remains_half_open_without_overflow() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new([extension.clone()], u64::MAX - 1, u64::MAX); + let policy = AgentTaskExtensionPolicy::new( + [extension.clone()], + u64::MAX - 1, + u64::MAX, + 1, + ); assert_eq!( evaluate_agent_task_extension(&extension, &policy, u64::MAX - 1), @@ -100,7 +126,7 @@ fn maximum_timestamp_window_remains_half_open_without_overflow() { #[test] fn managed_agent_task_extension_admission_does_not_mint_agent_capability() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new([extension.clone()], 10, 20); + let policy = AgentTaskExtensionPolicy::new([extension.clone()], 10, 20, 10); assert_eq!( evaluate_agent_task_extension(&extension, &policy, 15), From 0962a34bd344292d7fa52753b9fdfacab8620943 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 02:11:48 +0900 Subject: [PATCH 5/8] style(extension): format policy lifetime regression --- .../tests/agent_task_extension_policy.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/originweave-policy/tests/agent_task_extension_policy.rs b/crates/originweave-policy/tests/agent_task_extension_policy.rs index f60ed0ccd..984fd68b8 100644 --- a/crates/originweave-policy/tests/agent_task_extension_policy.rs +++ b/crates/originweave-policy/tests/agent_task_extension_policy.rs @@ -106,12 +106,7 @@ fn managed_extension_policy_window_cannot_exceed_local_maximum() { #[test] fn maximum_timestamp_window_remains_half_open_without_overflow() { let extension = extension_id("abcdefghijklmnopabcdefghijklmnop"); - let policy = AgentTaskExtensionPolicy::new( - [extension.clone()], - u64::MAX - 1, - u64::MAX, - 1, - ); + let policy = AgentTaskExtensionPolicy::new([extension.clone()], u64::MAX - 1, u64::MAX, 1); assert_eq!( evaluate_agent_task_extension(&extension, &policy, u64::MAX - 1), From 9ea75cb0aba2b5273a4b5f35adb31776437ecc5e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 02:16:05 +0900 Subject: [PATCH 6/8] feat(extension): bound managed policy lifetime --- crates/originweave-policy/src/lib.rs | 42 +++++++++++++++++++--------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/crates/originweave-policy/src/lib.rs b/crates/originweave-policy/src/lib.rs index 53d89119a..2d2d8f687 100644 --- a/crates/originweave-policy/src/lib.rs +++ b/crates/originweave-policy/src/lib.rs @@ -28,6 +28,7 @@ pub struct AgentTaskExtensionPolicy { managed_extensions: BTreeSet, valid_from: u64, valid_until: u64, + maximum_window: u64, } impl AgentTaskExtensionPolicy { @@ -35,13 +36,20 @@ impl AgentTaskExtensionPolicy { /// /// Duplicate identifiers collapse to one exact managed identity. An empty /// iterator therefore represents the default policy that admits no extension. - /// `valid_from` is inclusive and `valid_until` is exclusive. Both values are - /// opaque timestamps in the same caller-defined trusted time domain that will - /// be supplied to [`evaluate_agent_task_extension`]. This constructor does not - /// authenticate policy provenance or attest a clock; an invalid or empty - /// validity window is retained so evaluation can fail closed deterministically. + /// `valid_from` is inclusive and `valid_until` is exclusive. `maximum_window` + /// is the reviewed local ceiling for that interval. All three values are opaque + /// timestamps or durations in the same caller-defined trusted time domain and + /// units supplied to [`evaluate_agent_task_extension`]. This constructor does + /// not authenticate policy provenance or attest a clock; an invalid, empty, or + /// overlong validity window is retained so evaluation can fail closed + /// deterministically. #[must_use] - pub fn new(managed_extensions: I, valid_from: u64, valid_until: u64) -> Self + pub fn new( + managed_extensions: I, + valid_from: u64, + valid_until: u64, + maximum_window: u64, + ) -> Self where I: IntoIterator, { @@ -49,6 +57,7 @@ impl AgentTaskExtensionPolicy { managed_extensions: managed_extensions.into_iter().collect(), valid_from, valid_until, + maximum_window, } } } @@ -60,8 +69,10 @@ pub enum AgentTaskExtensionDecision { AllowManagedExtension, /// The extension identity is absent from the managed allow-list. DenyNotManaged, - /// The configured policy validity window is empty or reversed. + /// The configured validity window is empty/reversed or its local ceiling is zero. DenyInvalidPolicyWindow, + /// The configured policy validity interval exceeds the reviewed local maximum. + DenyPolicyWindowExceedsMaximum, /// The trusted evaluation time precedes the policy validity window. DenyPolicyNotYetValid, /// The trusted evaluation time is at or beyond the policy expiry boundary. @@ -72,20 +83,25 @@ pub enum AgentTaskExtensionDecision { /// /// This pure boundary answers only whether the exact canonical extension may be /// present in the caller's managed Agent Task profile at `trusted_time`. -/// `trusted_time`, [`AgentTaskExtensionPolicy::new`] `valid_from`, and `valid_until` -/// must use the same caller-attested time domain; this function does not read or -/// attest a clock. The validity window is half-open: `valid_from <= trusted_time < -/// valid_until`. Chromium permissions, installation state, native messaging, and -/// [`originweave_core::ExtensionAgentGrant`] remain separate authorities. +/// `trusted_time`, [`AgentTaskExtensionPolicy::new`] `valid_from`, `valid_until`, +/// and `maximum_window` must use one caller-attested time domain and compatible +/// units; this function does not read or attest a clock. The validity window is +/// half-open (`valid_from <= trusted_time < valid_until`) and must not exceed the +/// reviewed local maximum. Chromium permissions, installation state, native +/// messaging, and [`originweave_core::ExtensionAgentGrant`] remain separate +/// authorities. #[must_use] pub fn evaluate_agent_task_extension( extension_id: &ExtensionId, policy: &AgentTaskExtensionPolicy, trusted_time: u64, ) -> AgentTaskExtensionDecision { - if policy.valid_from >= policy.valid_until { + if policy.valid_from >= policy.valid_until || policy.maximum_window == 0 { return AgentTaskExtensionDecision::DenyInvalidPolicyWindow; } + if policy.valid_until - policy.valid_from > policy.maximum_window { + return AgentTaskExtensionDecision::DenyPolicyWindowExceedsMaximum; + } if trusted_time < policy.valid_from { return AgentTaskExtensionDecision::DenyPolicyNotYetValid; } From d7d7ea299af3c1d65a150a74a9fc4b3390344b70 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 07:17:04 +0900 Subject: [PATCH 7/8] fix(stack): preserve live managed-extension ancestry --- crates/originweave-core/src/lib.rs | 24 +++++++++++++++++++ .../tests/native_messaging_authority.rs | 5 ++++ 2 files changed, 29 insertions(+) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index de817f130..b8cce88af 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -1125,6 +1125,18 @@ impl NativeMessagingHostGrant { host_name, } } + + /// Return the extension identity granted native-messaging access. + #[must_use] + pub const fn extension_id(&self) -> &ExtensionId { + &self.extension_id + } + + /// Return the exact native-messaging host identity in this grant. + #[must_use] + pub const fn host_name(&self) -> &NativeMessagingHostName { + &self.host_name + } } /// One extension request to connect to an exact native-messaging host. @@ -1143,6 +1155,18 @@ impl NativeMessagingAccessRequest { host_name, } } + + /// Return the extension identity requesting native-messaging access. + #[must_use] + pub const fn extension_id(&self) -> &ExtensionId { + &self.extension_id + } + + /// Return the exact native-messaging host identity requested. + #[must_use] + pub const fn host_name(&self) -> &NativeMessagingHostName { + &self.host_name + } } /// Result of evaluating native-messaging access against one explicit host grant. diff --git a/crates/originweave-core/tests/native_messaging_authority.rs b/crates/originweave-core/tests/native_messaging_authority.rs index f0a830cda..5f4f611f1 100644 --- a/crates/originweave-core/tests/native_messaging_authority.rs +++ b/crates/originweave-core/tests/native_messaging_authority.rs @@ -54,7 +54,12 @@ fn native_messaging_requires_an_explicit_exact_extension_and_host_grant() { let other_host = host_name("com.contextualwisdom.other_host"); let grant = NativeMessagingHostGrant::new(allowed_extension.clone(), allowed_host.clone()); + assert_eq!(grant.extension_id(), &allowed_extension); + assert_eq!(grant.host_name(), &allowed_host); + let exact = NativeMessagingAccessRequest::new(allowed_extension.clone(), allowed_host.clone()); + assert_eq!(exact.extension_id(), &allowed_extension); + assert_eq!(exact.host_name(), &allowed_host); assert_eq!( evaluate_native_messaging_access(&exact, Some(&grant)), NativeMessagingAccessDecision::Allow From 891e8b7271d4e1cfcda615053efe54ddf4ce2627 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 20:21:39 +0900 Subject: [PATCH 8/8] docs: restore managed extension freshness changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1093841d4..8e84819b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Added +- Time-bounded managed extension admission for isolated Agent Task profiles with an explicit half-open validity window and caller-supplied trusted evaluation time; invalid, not-yet-valid, and expired policy states fail closed before exact extension allow-list membership is considered. - Fail-closed managed extension admission for isolated Agent Task profiles: an empty policy admits no extension, only exact canonical `ExtensionId` allow-list membership is accepted, duplicate entries cannot widen authority, and successful profile admission remains separate from `ExtensionAgentGrant` capability. - Explicit reduced-assurance classification for attached human tabs when trusted adapter evidence says an existing extension can influence page state; the narrow rule does not detect extensions, prove extension absence, grant Agent authority, or turn an unclassified context into high-assurance evidence. - Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline.