use std::collections::BTreeSet;
use super::{AmbientScoping, AMBIENT_THREAD_LOCAL_CATALOG, AUDITED_LATENT_CAPABILITIES};
#[test]
fn every_ambient_shape_thread_local_is_cataloged() {
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
.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| {
name == "tests" || name == "test_util" || name.ends_with("_tests")
})
{
continue;
}
if path.is_dir() {
collect(&path, out);
} else if path.extension().and_then(|extension| extension.to_str()) == Some("rs") {
let content = std::fs::read_to_string(&path).expect("read src file");
let mut pending_static: Option<String> = None;
for line in content.lines() {
let Some(index) = line.find("static ") else {
if line.contains("RefCell") {
if let Some(name) = pending_static.take() {
if is_ambient_shape(&name) {
out.insert(name);
}
}
}
continue;
};
let after = &line[index + "static ".len()..];
let name: String = after
.chars()
.take_while(|character| {
character.is_ascii_alphanumeric() || *character == '_'
})
.collect();
if name.is_empty() {
continue;
}
if line.contains("RefCell") {
if is_ambient_shape(&name) {
out.insert(name);
}
} else {
pending_static = Some(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 or is safely Uncaptured."
);
let stale: Vec<_> = cataloged.difference(&discovered).cloned().collect();
assert!(
stale.is_empty(),
"AMBIENT_THREAD_LOCAL_CATALOG names thread-local(s) no longer in src: {stale:?}"
);
}
#[test]
fn captured_catalog_matches_scope_fields() {
let captured: BTreeSet<&str> = AMBIENT_THREAD_LOCAL_CATALOG
.iter()
.filter(|(_, scoping)| matches!(scoping, AmbientScoping::Captured))
.map(|(name, _)| *name)
.collect();
let expected = BTreeSet::from([
"EXECUTION_POLICY_STACK",
"EXECUTION_APPROVAL_POLICY_STACK",
"OPERATOR_APPROVAL_GRANT_STACK",
"COMMAND_POLICY_STACK",
"DYNAMIC_PERMISSION_STACK",
"RUNTIME_CONTEXT_OVERLAY_STACK",
"AUTONOMY_POLICY_STACK",
"PERSONA_STACK",
"STEP_STACK",
"ACTIVE_CONTEXT_SUSPENSION_STACK",
"LLM_RENDER_STACK",
"ACTIVE_HARN_CONNECTOR_CTX",
"TRUSTED_BRIDGE_CALL_DEPTH",
"COMMAND_POLICY_HOOK_DEPTH",
"TOOL_PRECHECK_STACK",
"TOOL_PRECHECK_DEPTH",
"VM_EXECUTION_CONTEXT",
"VM_SOURCE_DIR",
"CURRENT_MUTATION_SESSION",
"SESSION_ENVIRONMENT_CONTEXT",
"CURRENT_HOST_BRIDGE",
"CURRENT_SESSION_STACK",
"LLM_CONFIG_OVERRIDES_CONTEXT",
"LLM_RUNTIME_PROVIDER_ENDPOINTS_CONTEXT",
"LLM_CAPABILITY_OVERRIDES_CONTEXT",
"LLM_MOCK_CONTEXT",
"EGRESS_POLICY_CONTEXT",
"ACTIVE_EXECUTION_SCOPE_STACK",
"RUN_EVENT_SINK_CONTEXT",
"TRANSCRIPT_DIR_STACK",
]);
assert_eq!(
captured, expected,
"the catalog's Captured set diverged from AmbientExecutionScope's swapped fields"
);
}
#[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"
),
AmbientScoping::Captured => panic!(
"{latent} is now Captured; wire it fully and drop it from \
AUDITED_LATENT_CAPABILITIES"
),
}
}
}