use std::future::Future;
use std::sync::Arc;
use super::{scope_ambient, AmbientExecutionScope, Scoped};
use crate::autonomy::AutonomyPolicy;
use crate::llm::permissions::DynamicPermissionPolicy;
use crate::orchestration::{CapabilityPolicy, CommandPolicy, ToolApprovalPolicy};
#[derive(Clone, Debug)]
pub(crate) struct RegisteredExecutionPolicy(Option<Arc<CapabilityPolicy>>);
impl RegisteredExecutionPolicy {
pub(crate) fn capture() -> Self {
Self(crate::orchestration::current_execution_policy().map(Arc::new))
}
pub(crate) fn scope<F: Future>(&self, inner: F) -> Scoped<F> {
let policy = self.0.as_deref().cloned();
scope_modified(inner, |scope| {
scope.execution = policy.into_iter().collect();
scope.trusted_depth = 0;
})
}
}
fn scope_modified<F: Future>(
inner: F,
modify: impl FnOnce(&mut AmbientExecutionScope),
) -> Scoped<F> {
let mut scope = AmbientExecutionScope::capture_for_inline_subtask();
modify(&mut scope);
scope_ambient(scope, inner)
}
pub fn scope_execution_policy<F: Future>(
policy: CapabilityPolicy,
inner: F,
) -> impl Future<Output = F::Output> {
scope_modified(inner, |scope| scope.execution.push(policy))
}
pub fn scope_ambient_context<F: Future>(inner: F) -> impl Future<Output = F::Output> {
scope_modified(inner, |_| {})
}
pub(crate) fn scope_approval_policy<F: Future>(policy: ToolApprovalPolicy, inner: F) -> Scoped<F> {
scope_modified(inner, |scope| scope.approval.push(policy))
}
pub(crate) fn scope_command_policy<F: Future>(policy: CommandPolicy, inner: F) -> Scoped<F> {
scope_modified(inner, |scope| scope.command.push(policy))
}
pub(crate) fn scope_dynamic_permissions<F: Future>(
policy: DynamicPermissionPolicy,
inner: F,
) -> Scoped<F> {
scope_modified(inner, |scope| scope.permissions.push(policy))
}
pub(crate) fn scope_autonomy_policy<F: Future>(policy: AutonomyPolicy, inner: F) -> Scoped<F> {
scope_modified(inner, |scope| scope.autonomy.push(policy))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::orchestration::{
clear_execution_policy_stacks, current_execution_policy, push_execution_policy,
};
fn policy_named(tool: &str) -> CapabilityPolicy {
CapabilityPolicy {
tools: vec![tool.to_string()],
..CapabilityPolicy::default()
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn execution_policy_scope_survives_await_and_restores_the_caller() {
clear_execution_policy_stacks();
push_execution_policy(policy_named("outer"));
scope_execution_policy(policy_named("scoped"), async {
assert_eq!(
current_execution_policy().unwrap().tools,
vec!["scoped".to_string()]
);
tokio::task::yield_now().await;
assert_eq!(
current_execution_policy().unwrap().tools,
vec!["scoped".to_string()]
);
})
.await;
assert_eq!(
current_execution_policy().unwrap().tools,
vec!["outer".to_string()]
);
clear_execution_policy_stacks();
}
#[tokio::test]
async fn registered_policy_survives_yield_and_restores_emitter_trust_after_error() {
use crate::orchestration::{
allow_trusted_bridge_calls, enforce_current_policy_for_capability, pop_execution_policy,
};
clear_execution_policy_stacks();
let mut registered = policy_named("listener");
registered.side_effect_level = Some("read_only".into());
push_execution_policy(registered.clone());
let captured = RegisteredExecutionPolicy::capture();
pop_execution_policy();
let mut emitter = policy_named("emitter");
emitter.side_effect_level = Some("read_only".into());
push_execution_policy(emitter.clone());
let trusted = allow_trusted_bridge_calls();
let state_write = || {
enforce_current_policy_for_capability(
harn_builtin_meta::CapabilityId::Runtime,
"store_set",
&[],
)
};
let result = captured
.scope(async {
assert_eq!(current_execution_policy(), Some(registered.clone()));
assert!(
state_write().is_err(),
"emitter trust must not bypass the listener policy"
);
tokio::task::yield_now().await;
assert_eq!(current_execution_policy(), Some(registered));
assert!(state_write().is_err());
Err::<(), _>("listener failed")
})
.await;
assert_eq!(result, Err("listener failed"));
assert_eq!(current_execution_policy(), Some(emitter));
assert!(
state_write().is_ok(),
"the emitter's trusted scope must be restored"
);
drop(trusted);
assert!(
state_write().is_err(),
"the restored trust guard must still unwind"
);
pop_execution_policy();
assert!(current_execution_policy().is_none());
}
}