use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use std::task::{Context, Poll};
use pin_project_lite::pin_project;
use super::command_policy::{
swap_command_policy_hook_depth, swap_command_policy_stack, CommandPolicy,
};
use super::policy::{
swap_approval_policy_stack, swap_execution_policy_stack, swap_trusted_bridge_depth,
CapabilityPolicy, ToolApprovalPolicy,
};
use super::{swap_mutation_session, MutationSessionRecord, RunExecutionRecord};
use crate::agent_sessions::swap_current_session_stack;
use crate::autonomy::{swap_autonomy_policy_stack, AutonomyPolicy};
use crate::connectors::harn_module::swap_active_harn_connector_ctx;
use crate::connectors::ConnectorCtx;
use crate::llm::permissions::{swap_dynamic_permission_stack, DynamicPermissionPolicy};
use crate::llm::swap_current_host_bridge;
use crate::runtime_context::{swap_runtime_context_overlay_stack, RuntimeContextOverlay};
use crate::stdlib::process::{swap_source_dir, swap_thread_execution_context};
use crate::stdlib::template::llm_context::{swap_llm_render_stack, LlmRenderContext};
#[derive(Default, Clone)]
pub(crate) struct AmbientExecutionScope {
execution: Vec<CapabilityPolicy>,
approval: Vec<ToolApprovalPolicy>,
command: Vec<CommandPolicy>,
permissions: Vec<DynamicPermissionPolicy>,
runtime_context: Vec<RuntimeContextOverlay>,
autonomy: Vec<AutonomyPolicy>,
llm_render: Vec<LlmRenderContext>,
connector_ctx: Vec<ConnectorCtx>,
session_stack: Vec<String>,
execution_context: Option<RunExecutionRecord>,
source_dir: Option<PathBuf>,
mutation_session: Option<MutationSessionRecord>,
host_bridge: Option<std::sync::Arc<crate::bridge::HostBridge>>,
trusted_depth: usize,
command_hook_depth: usize,
}
fn clone_via_swap<T: Clone + Default>(swap: impl Fn(T) -> T) -> T {
let owned = swap(T::default());
let cloned = owned.clone();
let _ = swap(owned);
cloned
}
impl AmbientExecutionScope {
pub(crate) fn capture_inherited() -> Self {
Self {
command: clone_via_swap(swap_command_policy_stack),
permissions: clone_via_swap(swap_dynamic_permission_stack),
runtime_context: clone_via_swap(swap_runtime_context_overlay_stack),
autonomy: clone_via_swap(swap_autonomy_policy_stack),
execution_context: clone_via_swap(swap_thread_execution_context),
source_dir: clone_via_swap(swap_source_dir),
mutation_session: clone_via_swap(swap_mutation_session),
host_bridge: clone_via_swap(swap_current_host_bridge),
..Self::default()
}
}
pub(crate) fn capture_for_inline_subtask() -> Self {
Self {
execution: clone_via_swap(swap_execution_policy_stack),
approval: clone_via_swap(swap_approval_policy_stack),
command: clone_via_swap(swap_command_policy_stack),
permissions: clone_via_swap(swap_dynamic_permission_stack),
runtime_context: clone_via_swap(swap_runtime_context_overlay_stack),
autonomy: clone_via_swap(swap_autonomy_policy_stack),
llm_render: clone_via_swap(swap_llm_render_stack),
connector_ctx: clone_via_swap(swap_active_harn_connector_ctx),
session_stack: clone_via_swap(swap_current_session_stack),
execution_context: clone_via_swap(swap_thread_execution_context),
source_dir: clone_via_swap(swap_source_dir),
mutation_session: clone_via_swap(swap_mutation_session),
host_bridge: clone_via_swap(swap_current_host_bridge),
trusted_depth: clone_via_swap(swap_trusted_bridge_depth),
command_hook_depth: clone_via_swap(swap_command_policy_hook_depth),
}
}
fn swap_in(self) -> Self {
Self {
execution: swap_execution_policy_stack(self.execution),
approval: swap_approval_policy_stack(self.approval),
command: swap_command_policy_stack(self.command),
permissions: swap_dynamic_permission_stack(self.permissions),
runtime_context: swap_runtime_context_overlay_stack(self.runtime_context),
autonomy: swap_autonomy_policy_stack(self.autonomy),
llm_render: swap_llm_render_stack(self.llm_render),
connector_ctx: swap_active_harn_connector_ctx(self.connector_ctx),
session_stack: swap_current_session_stack(self.session_stack),
execution_context: swap_thread_execution_context(self.execution_context),
source_dir: swap_source_dir(self.source_dir),
mutation_session: swap_mutation_session(self.mutation_session),
host_bridge: swap_current_host_bridge(self.host_bridge),
trusted_depth: swap_trusted_bridge_depth(self.trusted_depth),
command_hook_depth: swap_command_policy_hook_depth(self.command_hook_depth),
}
}
}
#[cfg(test)]
#[derive(Clone, Copy)]
enum AmbientScoping {
Captured,
Uncaptured(&'static str),
}
#[cfg(test)]
const AMBIENT_THREAD_LOCAL_CATALOG: &[(&str, AmbientScoping)] = &[
("EXECUTION_POLICY_STACK", AmbientScoping::Captured),
("EXECUTION_APPROVAL_POLICY_STACK", AmbientScoping::Captured),
("COMMAND_POLICY_STACK", AmbientScoping::Captured),
("DYNAMIC_PERMISSION_STACK", AmbientScoping::Captured),
("RUNTIME_CONTEXT_OVERLAY_STACK", AmbientScoping::Captured),
("AUTONOMY_POLICY_STACK", AmbientScoping::Captured),
("LLM_RENDER_STACK", AmbientScoping::Captured),
("ACTIVE_HARN_CONNECTOR_CTX", AmbientScoping::Captured),
("TRUSTED_BRIDGE_CALL_DEPTH", AmbientScoping::Captured),
("COMMAND_POLICY_HOOK_DEPTH", AmbientScoping::Captured),
("VM_EXECUTION_CONTEXT", AmbientScoping::Captured),
("VM_SOURCE_DIR", AmbientScoping::Captured),
("CURRENT_MUTATION_SESSION", AmbientScoping::Captured),
("CURRENT_HOST_BRIDGE", AmbientScoping::Captured),
("CURRENT_SESSION_STACK", AmbientScoping::Captured),
(
"SECURITY_POLICY_STACK",
AmbientScoping::Uncaptured(
"[latent-capability] security/mod.rs MCP-schema/security policy; not \
set per-worker today. Capture when a fan-out child reads it across an await.",
),
),
(
"ACTIVE_TENANT_STACK",
AmbientScoping::Uncaptured(
"[latent-capability] harness_tenant.rs tenant identity; not set per-worker today.",
),
),
(
"ACTIVE_PRINCIPAL_STACK",
AmbientScoping::Uncaptured(
"[latent-capability] harness_auth.rs principal identity; not set per-worker today.",
),
),
(
"REQUIRE_EXPLICIT_EGRESS_POLICY_DEPTH",
AmbientScoping::Uncaptured(
"[latent-capability] egress/mod.rs egress-policy enforcement depth; not entered \
per-worker today.",
),
),
(
"REQUIRE_SSRF_GUARD_DEPTH",
AmbientScoping::Uncaptured(
"[latent-capability] egress/mod.rs SSRF-guard depth; not entered per-worker today.",
),
),
(
"REDACTION_POLICY_STACK",
AmbientScoping::Uncaptured(
"[latent-capability] redact/mod.rs redaction policy; pushed around synchronous \
redaction, not held across a child await today.",
),
),
(
"ACTIVE_REQUEST_ID_STACK",
AmbientScoping::Uncaptured(
"[latent-capability] observability/request_id.rs request-id breadcrumb; attribution \
only, no capability decision rides on it.",
),
),
(
"PERSONA_STACK",
AmbientScoping::Uncaptured(
"step_runtime.rs snapshots+restores this at the worker boundary (own isolation \
path); not read raw across a fan-out child await.",
),
),
(
"STEP_STACK",
AmbientScoping::Uncaptured(
"step_runtime.rs snapshots+restores this at the worker boundary (own isolation \
path); not read raw across a fan-out child await.",
),
),
(
"CURRENT_TOOL_CALL_STACK",
AmbientScoping::Uncaptured(
"agent_sessions.rs tool-call breadcrumb; pushed+popped within a single synchronous \
dispatch frame.",
),
),
(
"TRANSCRIPT_DIR_STACK",
AmbientScoping::Uncaptured(
"llm/agent_observe.rs transcript output dir; push/pop balanced within an observe \
scope.",
),
),
(
"VM_TRACE_STACK",
AmbientScoping::Uncaptured(
"stdlib/logging.rs log-trace breadcrumb (trace ids for log lines); attribution \
only, no capability decision rides on it.",
),
),
(
"ACTIVE_DISPATCH_CONTEXT",
AmbientScoping::Uncaptured(
"triggers/dispatcher trigger-dispatch context for the dispatcher runner, not the \
fan-out worker path.",
),
),
(
"CURRENT_WORKFLOW_SKILL_CONTEXT",
AmbientScoping::Uncaptured(
"orchestration/mod.rs workflow skill context; the workflow runner pins itself to \
one LocalSet task, so every stage observes the same context (see its doc-comment).",
),
),
];
#[cfg(test)]
const AUDITED_LATENT_CAPABILITIES: &[&str] = &[
"SECURITY_POLICY_STACK",
"ACTIVE_TENANT_STACK",
"ACTIVE_PRINCIPAL_STACK",
"REQUIRE_EXPLICIT_EGRESS_POLICY_DEPTH",
"REQUIRE_SSRF_GUARD_DEPTH",
];
pin_project! {
pub(crate) struct Scoped<F> {
#[pin]
inner: F,
scope: Option<AmbientExecutionScope>,
}
}
pub(crate) fn scope_ambient<F: Future>(scope: AmbientExecutionScope, inner: F) -> Scoped<F> {
Scoped {
inner,
scope: Some(scope),
}
}
struct RestoreGuard<'a> {
outer: Option<AmbientExecutionScope>,
slot: &'a mut Option<AmbientExecutionScope>,
}
impl Drop for RestoreGuard<'_> {
fn drop(&mut self) {
if let Some(outer) = self.outer.take() {
*self.slot = Some(outer.swap_in());
}
}
}
impl<F: Future> Future for Scoped<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
let this = self.project();
let task_scope = this.scope.take().unwrap_or_default();
let outer = task_scope.swap_in();
let _restore = RestoreGuard {
outer: Some(outer),
slot: this.scope,
};
this.inner.poll(cx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::orchestration::{current_execution_policy, push_execution_policy};
fn policy_named(tool: &str) -> CapabilityPolicy {
CapabilityPolicy {
tools: vec![tool.to_string()],
..Default::default()
}
}
#[tokio::test]
async fn scoped_tasks_do_not_cross_wire_execution_policy() {
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let alpha = tokio::task::spawn_local(scope_ambient(
AmbientExecutionScope::default(),
async {
push_execution_policy(policy_named("alpha"));
tokio::task::yield_now().await;
tokio::task::yield_now().await;
current_execution_policy().map(|p| p.tools)
},
));
let beta = tokio::task::spawn_local(scope_ambient(
AmbientExecutionScope::default(),
async {
push_execution_policy(policy_named("beta"));
tokio::task::yield_now().await;
tokio::task::yield_now().await;
current_execution_policy().map(|p| p.tools)
},
));
assert_eq!(alpha.await.unwrap(), Some(vec!["alpha".to_string()]));
assert_eq!(beta.await.unwrap(), Some(vec!["beta".to_string()]));
})
.await;
assert!(current_execution_policy().is_none());
}
#[tokio::test]
async fn scoped_tasks_preserve_child_current_session_for_write_attribution() {
let parent_session = format!("parent-{}", uuid::Uuid::now_v7());
let alpha_session = format!("alpha-{}", uuid::Uuid::now_v7());
let beta_session = format!("beta-{}", uuid::Uuid::now_v7());
for session in [&parent_session, &alpha_session, &beta_session] {
crate::agent_sessions::clear_session_changed_paths(session);
}
let _parent_guard = crate::agent_sessions::enter_current_session(parent_session.clone());
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let alpha_scope = AmbientExecutionScope::capture_inherited();
let beta_scope = AmbientExecutionScope::capture_inherited();
let alpha_id = alpha_session.clone();
let beta_id = beta_session.clone();
let alpha = tokio::task::spawn_local(scope_ambient(alpha_scope, async move {
assert!(
crate::agent_sessions::current_session_id().is_none(),
"child scope must not inherit the parent session"
);
let _guard = crate::agent_sessions::enter_current_session(alpha_id.clone());
tokio::task::yield_now().await;
tokio::task::yield_now().await;
let current = crate::agent_sessions::current_session_id()
.expect("child session survives await");
crate::agent_sessions::record_session_changed_path(¤t, "src/alpha.rs");
current
}));
let beta = tokio::task::spawn_local(scope_ambient(beta_scope, async move {
assert!(
crate::agent_sessions::current_session_id().is_none(),
"child scope must not inherit the parent session"
);
let _guard = crate::agent_sessions::enter_current_session(beta_id.clone());
tokio::task::yield_now().await;
tokio::task::yield_now().await;
let current = crate::agent_sessions::current_session_id()
.expect("child session survives await");
crate::agent_sessions::record_session_changed_path(¤t, "src/beta.rs");
current
}));
assert_eq!(alpha.await.unwrap(), alpha_session);
assert_eq!(beta.await.unwrap(), beta_session);
})
.await;
assert_eq!(
crate::agent_sessions::current_session_id().as_deref(),
Some(parent_session.as_str()),
"parent session restored after child polls"
);
assert_eq!(
crate::agent_sessions::take_session_changed_paths(&alpha_session),
vec!["src/alpha.rs".to_string()]
);
assert_eq!(
crate::agent_sessions::take_session_changed_paths(&beta_session),
vec!["src/beta.rs".to_string()]
);
assert!(
crate::agent_sessions::take_session_changed_paths(&parent_session).is_empty(),
"child writes must not attribute to parent"
);
}
#[tokio::test]
async fn inline_subtask_scope_carries_worker_session_under_contention() {
let parent_session = format!("parent-{}", uuid::Uuid::now_v7());
let alpha_session = format!("alpha-{}", uuid::Uuid::now_v7());
let beta_session = format!("beta-{}", uuid::Uuid::now_v7());
for session in [&parent_session, &alpha_session, &beta_session] {
crate::agent_sessions::clear_session_changed_paths(session);
}
let _parent_guard = crate::agent_sessions::enter_current_session(parent_session.clone());
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let run_worker = |worker_session: String, path: &'static str| {
let worker_scope = AmbientExecutionScope::capture_inherited();
tokio::task::spawn_local(scope_ambient(worker_scope, async move {
assert!(
crate::agent_sessions::current_session_id().is_none(),
"fan-out worker must not inherit the parent session"
);
let _guard =
crate::agent_sessions::enter_current_session(worker_session.clone());
tokio::task::yield_now().await;
let control = tokio::task::spawn_local(scope_ambient(
AmbientExecutionScope::capture_inherited(),
async move {
tokio::task::yield_now().await;
tokio::task::yield_now().await;
crate::agent_sessions::current_session_id()
},
))
.await
.unwrap();
assert!(
control.is_none(),
"control subtask must NOT inherit the worker session"
);
let observed = tokio::task::spawn_local(scope_ambient(
AmbientExecutionScope::capture_for_inline_subtask(),
async move {
tokio::task::yield_now().await;
tokio::task::yield_now().await;
let session = crate::agent_sessions::current_session_id();
if let Some(ref session) = session {
crate::agent_sessions::record_session_changed_path(
session, path,
);
}
session
},
))
.await
.unwrap();
observed
}))
};
let alpha = run_worker(alpha_session.clone(), "src/alpha.rs");
let beta = run_worker(beta_session.clone(), "src/beta.rs");
assert_eq!(
alpha.await.unwrap().as_deref(),
Some(alpha_session.as_str()),
"alpha's inline subtask must observe alpha's worker session"
);
assert_eq!(
beta.await.unwrap().as_deref(),
Some(beta_session.as_str()),
"beta's inline subtask must observe beta's worker session"
);
})
.await;
assert_eq!(
crate::agent_sessions::take_session_changed_paths(&alpha_session),
vec!["src/alpha.rs".to_string()],
"alpha's dispatched write attributes to alpha's session"
);
assert_eq!(
crate::agent_sessions::take_session_changed_paths(&beta_session),
vec!["src/beta.rs".to_string()],
"beta's dispatched write attributes to beta's session"
);
assert!(
crate::agent_sessions::take_session_changed_paths(&parent_session).is_empty(),
"inline-subtask writes must not attribute to the parent"
);
}
#[tokio::test]
async fn scope_is_restored_after_completion() {
let local = tokio::task::LocalSet::new();
local
.run_until(async {
tokio::task::spawn_local(scope_ambient(AmbientExecutionScope::default(), async {
push_execution_policy(policy_named("gamma"));
tokio::task::yield_now().await;
}))
.await
.unwrap();
})
.await;
assert!(current_execution_policy().is_none());
}
fn execution_context_named(name: &str) -> RunExecutionRecord {
let mut env = std::collections::BTreeMap::new();
env.insert("WORKER".to_string(), name.to_string());
RunExecutionRecord {
cwd: Some(format!("/worktrees/{name}")),
env,
..Default::default()
}
}
fn mutation_session_named(name: &str) -> MutationSessionRecord {
MutationSessionRecord {
session_id: format!("session-{name}"),
run_id: Some(format!("run-{name}")),
..Default::default()
}
}
fn test_host_bridge(start_id: u64) -> std::sync::Arc<crate::bridge::HostBridge> {
let pending =
std::sync::Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new()));
let cancelled = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let writer: crate::bridge::HostBridgeWriter = std::sync::Arc::new(|_| Ok(()));
std::sync::Arc::new(crate::bridge::HostBridge::from_parts_with_writer(
pending, cancelled, writer, start_id,
))
}
#[tokio::test]
async fn scoped_tasks_inherit_and_isolate_current_host_bridge() {
crate::llm::clear_current_host_bridge();
let parent_bridge = test_host_bridge(100);
crate::llm::install_current_host_bridge(parent_bridge.clone());
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let run_worker = |name: &'static str, start_id: u64| {
let scope = AmbientExecutionScope::capture_inherited();
let expected_parent = parent_bridge.clone();
let worker_bridge = test_host_bridge(start_id);
let expected_worker = worker_bridge.clone();
tokio::task::spawn_local(scope_ambient(scope, async move {
let inherited = crate::llm::current_host_bridge()
.expect("worker inherits parent host bridge");
assert!(
std::sync::Arc::ptr_eq(&inherited, &expected_parent),
"{name} must inherit the parent host bridge before installing its own"
);
crate::llm::install_current_host_bridge(worker_bridge);
tokio::task::yield_now().await;
tokio::task::yield_now().await;
let observed = crate::llm::current_host_bridge()
.expect("worker host bridge survives awaits");
assert!(
std::sync::Arc::ptr_eq(&observed, &expected_worker),
"{name} must keep its own host bridge after sibling interleaving"
);
}))
};
let alpha = run_worker("alpha", 200);
let beta = run_worker("beta", 300);
alpha.await.unwrap();
beta.await.unwrap();
})
.await;
let restored =
crate::llm::current_host_bridge().expect("parent host bridge restored after workers");
assert!(std::sync::Arc::ptr_eq(&restored, &parent_bridge));
crate::llm::clear_current_host_bridge();
}
#[tokio::test]
async fn scoped_tasks_do_not_cross_wire_execution_context() {
use crate::stdlib::process::{current_execution_context, set_thread_execution_context};
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let alpha = tokio::task::spawn_local(scope_ambient(
AmbientExecutionScope::default(),
async {
set_thread_execution_context(Some(execution_context_named("alpha")));
tokio::task::yield_now().await;
tokio::task::yield_now().await;
current_execution_context()
.map(|ctx| (ctx.cwd, ctx.env.get("WORKER").cloned()))
},
));
let beta = tokio::task::spawn_local(scope_ambient(
AmbientExecutionScope::default(),
async {
set_thread_execution_context(Some(execution_context_named("beta")));
tokio::task::yield_now().await;
tokio::task::yield_now().await;
current_execution_context()
.map(|ctx| (ctx.cwd, ctx.env.get("WORKER").cloned()))
},
));
assert_eq!(
alpha.await.unwrap(),
Some((
Some("/worktrees/alpha".to_string()),
Some("alpha".to_string())
))
);
assert_eq!(
beta.await.unwrap(),
Some((
Some("/worktrees/beta".to_string()),
Some("beta".to_string())
))
);
})
.await;
assert!(crate::stdlib::process::current_execution_context().is_none());
}
#[tokio::test]
async fn scoped_tasks_do_not_cross_wire_mutation_session() {
use crate::orchestration::{current_mutation_session, install_current_mutation_session};
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let alpha = tokio::task::spawn_local(scope_ambient(
AmbientExecutionScope::default(),
async {
install_current_mutation_session(Some(mutation_session_named("alpha")));
tokio::task::yield_now().await;
tokio::task::yield_now().await;
current_mutation_session().map(|s| (s.session_id, s.run_id))
},
));
let beta = tokio::task::spawn_local(scope_ambient(
AmbientExecutionScope::default(),
async {
install_current_mutation_session(Some(mutation_session_named("beta")));
tokio::task::yield_now().await;
tokio::task::yield_now().await;
current_mutation_session().map(|s| (s.session_id, s.run_id))
},
));
assert_eq!(
alpha.await.unwrap(),
Some(("session-alpha".to_string(), Some("run-alpha".to_string())))
);
assert_eq!(
beta.await.unwrap(),
Some(("session-beta".to_string(), Some("run-beta".to_string())))
);
})
.await;
assert!(crate::orchestration::current_mutation_session().is_none());
}
#[test]
fn drift_every_ambient_shape_thread_local_is_cataloged() {
use std::collections::BTreeSet;
fn is_ambient_shape(name: &str) -> bool {
name == "VM_SOURCE_DIR"
|| name == "CURRENT_HOST_BRIDGE"
|| name.ends_with("_STACK")
|| name.ends_with("_DEPTH")
|| name.ends_with("_CONTEXT")
|| name.ends_with("_SESSION")
|| name.ends_with("_CTX")
}
fn collect(dir: &std::path::Path, out: &mut BTreeSet<String>) {
for entry in std::fs::read_dir(dir).expect("read_dir src") {
let path = entry.expect("dir entry").path();
if path.to_string_lossy().contains("test") {
continue;
}
if path.is_dir() {
collect(&path, out);
} else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
let content = std::fs::read_to_string(&path).expect("read src file");
for line in content.lines() {
if !line.contains("RefCell") {
continue;
}
let Some(idx) = line.find("static ") else {
continue;
};
let after = &line[idx + "static ".len()..];
let name: String = after
.chars()
.take_while(|c| c.is_ascii_alphanumeric() || *c == '_')
.collect();
if !name.is_empty() && is_ambient_shape(&name) {
out.insert(name);
}
}
}
}
}
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut discovered = BTreeSet::new();
collect(&src, &mut discovered);
let cataloged: BTreeSet<String> = AMBIENT_THREAD_LOCAL_CATALOG
.iter()
.map(|(name, _)| (*name).to_string())
.collect();
let missing: Vec<_> = discovered.difference(&cataloged).cloned().collect();
assert!(
missing.is_empty(),
"new ambient-shape thread-local(s) not classified in \
AMBIENT_THREAD_LOCAL_CATALOG (orchestration/ambient_scope.rs): {missing:?}. Decide \
whether each must be Captured into AmbientExecutionScope (it is held across a \
fan-out worker's awaits and would otherwise cross-wire siblings) or is safely \
Uncaptured, then add it to the catalog. This is the F1/F2 drift guard."
);
let stale: Vec<_> = cataloged.difference(&discovered).cloned().collect();
assert!(
stale.is_empty(),
"AMBIENT_THREAD_LOCAL_CATALOG names thread-local(s) no longer in src \
(renamed/removed?): {stale:?}. Update the catalog."
);
}
#[test]
fn captured_catalog_matches_scope_fields() {
use std::collections::BTreeSet;
let captured: BTreeSet<&str> = AMBIENT_THREAD_LOCAL_CATALOG
.iter()
.filter(|(_, scoping)| matches!(scoping, AmbientScoping::Captured))
.map(|(name, _)| *name)
.collect();
let expected: BTreeSet<&str> = [
"EXECUTION_POLICY_STACK",
"EXECUTION_APPROVAL_POLICY_STACK",
"COMMAND_POLICY_STACK",
"DYNAMIC_PERMISSION_STACK",
"RUNTIME_CONTEXT_OVERLAY_STACK",
"AUTONOMY_POLICY_STACK",
"LLM_RENDER_STACK",
"ACTIVE_HARN_CONNECTOR_CTX",
"TRUSTED_BRIDGE_CALL_DEPTH",
"COMMAND_POLICY_HOOK_DEPTH",
"VM_EXECUTION_CONTEXT",
"VM_SOURCE_DIR",
"CURRENT_MUTATION_SESSION",
"CURRENT_HOST_BRIDGE",
"CURRENT_SESSION_STACK",
]
.into_iter()
.collect();
assert_eq!(
captured, expected,
"the catalog's Captured set diverged from AmbientExecutionScope's swapped fields; \
keep the struct fields, swap_in, and the catalog in lockstep."
);
}
#[test]
fn audited_latent_capabilities_are_cataloged() {
for latent in AUDITED_LATENT_CAPABILITIES {
let found = AMBIENT_THREAD_LOCAL_CATALOG
.iter()
.find(|(name, _)| name == latent);
let Some((_, scoping)) = found else {
panic!("{latent} missing from AMBIENT_THREAD_LOCAL_CATALOG");
};
match scoping {
AmbientScoping::Uncaptured(reason) => assert!(
reason.contains("[latent-capability]"),
"{latent} must keep its [latent-capability] reason tag so the call-out stays visible"
),
AmbientScoping::Captured => panic!(
"{latent} is now Captured — wire it fully and drop it from \
AUDITED_LATENT_CAPABILITIES"
),
}
}
}
}