@@ -97,8 +97,8 @@ pub(crate) fn ocsf_ctx() -> &'static SandboxContext {
9797/// to gate the agent-controlled mutation surface. Exposed `pub(crate)` so
9898/// unit tests in sibling modules can flip the flag through a serialized
9999/// guard (see `policy_local::tests::ProposalsFlagGuard`).
100- pub ( crate ) static AGENT_PROPOSALS_ENABLED :
101- OnceLock < Arc < std :: sync :: atomic :: AtomicBool > > = OnceLock :: new ( ) ;
100+ pub ( crate ) static AGENT_PROPOSALS_ENABLED : OnceLock < Arc < std :: sync :: atomic :: AtomicBool > > =
101+ OnceLock :: new ( ) ;
102102
103103/// Read the current value of the agent proposals feature flag.
104104///
@@ -113,17 +113,22 @@ pub(crate) fn agent_proposals_enabled() -> bool {
113113/// Test-only helpers shared across sibling test modules.
114114#[ cfg( test) ]
115115pub ( crate ) mod test_helpers {
116- #![ allow( clippy:: redundant_pub_crate, reason = "intentional crate-private module" ) ]
116+ #![ allow(
117+ clippy:: redundant_pub_crate,
118+ reason = "intentional crate-private module"
119+ ) ]
117120 use std:: sync:: Arc ;
118- use std:: sync:: Mutex ;
119- use std:: sync:: MutexGuard ;
121+ use std:: sync:: LazyLock ;
120122 use std:: sync:: atomic:: { AtomicBool , Ordering } ;
123+ use tokio:: sync:: MutexGuard ;
124+
125+ static PROPOSALS_FLAG_LOCK : LazyLock < tokio:: sync:: Mutex < ( ) > > =
126+ LazyLock :: new ( || tokio:: sync:: Mutex :: new ( ( ) ) ) ;
121127
122128 /// Guard for tests that toggle the process-wide
123- /// `AGENT_PROPOSALS_ENABLED` flag. Acquires a process-wide mutex on
124- /// construction so concurrent tests don't race on the atomic, swaps in
125- /// the requested value, and restores the previous value on drop. Hold
126- /// the guard for the duration of any code that reads
129+ /// `AGENT_PROPOSALS_ENABLED` flag. Acquires a process-wide async mutex,
130+ /// swaps in the requested value, and restores the previous value on drop.
131+ /// Hold the guard for the duration of any code that reads
127132 /// `agent_proposals_enabled()`.
128133 pub ( crate ) struct ProposalsFlagGuard {
129134 prev : bool ,
@@ -132,9 +137,17 @@ pub(crate) mod test_helpers {
132137 }
133138
134139 impl ProposalsFlagGuard {
135- pub ( crate ) fn set ( enabled : bool ) -> Self {
136- static LOCK : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
137- let lock = LOCK . lock ( ) . unwrap_or_else ( std:: sync:: PoisonError :: into_inner) ;
140+ pub ( crate ) async fn set ( enabled : bool ) -> Self {
141+ let lock = PROPOSALS_FLAG_LOCK . lock ( ) . await ;
142+ Self :: with_lock ( enabled, lock)
143+ }
144+
145+ pub ( crate ) fn set_blocking ( enabled : bool ) -> Self {
146+ let lock = PROPOSALS_FLAG_LOCK . blocking_lock ( ) ;
147+ Self :: with_lock ( enabled, lock)
148+ }
149+
150+ fn with_lock ( enabled : bool , lock : MutexGuard < ' static , ( ) > ) -> Self {
138151 let flag = super :: AGENT_PROPOSALS_ENABLED
139152 . get_or_init ( || Arc :: new ( AtomicBool :: new ( false ) ) )
140153 . clone ( ) ;
@@ -394,7 +407,10 @@ pub async fn run_sandbox(
394407 // gates the skill install, the policy.local route handler, and the L7
395408 // deny body's `next_steps` field — see `agent_proposals_enabled()`.
396409 let proposals_enabled = Arc :: new ( std:: sync:: atomic:: AtomicBool :: new ( false ) ) ;
397- if AGENT_PROPOSALS_ENABLED . set ( proposals_enabled. clone ( ) ) . is_err ( ) {
410+ if AGENT_PROPOSALS_ENABLED
411+ . set ( proposals_enabled. clone ( ) )
412+ . is_err ( )
413+ {
398414 debug ! ( "agent proposals flag already initialized, keeping existing" ) ;
399415 }
400416
@@ -426,9 +442,7 @@ pub async fn run_sandbox(
426442 }
427443 }
428444 } else {
429- debug ! (
430- "agent_policy_proposals_enabled is false at startup; skipping skill install"
431- ) ;
445+ debug ! ( "agent_policy_proposals_enabled is false at startup; skipping skill install" ) ;
432446 }
433447
434448 // Generate ephemeral CA and TLS state for HTTPS L7 inspection.
0 commit comments