@@ -96,8 +96,8 @@ pub(crate) fn ocsf_ctx() -> &'static SandboxContext {
9696/// to gate the agent-controlled mutation surface. Exposed `pub(crate)` so
9797/// unit tests in sibling modules can flip the flag through a serialized
9898/// guard (see `policy_local::tests::ProposalsFlagGuard`).
99- pub ( crate ) static AGENT_PROPOSALS_ENABLED :
100- OnceLock < Arc < std :: sync :: atomic :: AtomicBool > > = OnceLock :: new ( ) ;
99+ pub ( crate ) static AGENT_PROPOSALS_ENABLED : OnceLock < Arc < std :: sync :: atomic :: AtomicBool > > =
100+ OnceLock :: new ( ) ;
101101
102102/// Read the current value of the agent proposals feature flag.
103103///
@@ -112,17 +112,22 @@ pub(crate) fn agent_proposals_enabled() -> bool {
112112/// Test-only helpers shared across sibling test modules.
113113#[ cfg( test) ]
114114pub ( crate ) mod test_helpers {
115- #![ allow( clippy:: redundant_pub_crate, reason = "intentional crate-private module" ) ]
115+ #![ allow(
116+ clippy:: redundant_pub_crate,
117+ reason = "intentional crate-private module"
118+ ) ]
116119 use std:: sync:: Arc ;
117- use std:: sync:: Mutex ;
118- use std:: sync:: MutexGuard ;
120+ use std:: sync:: LazyLock ;
119121 use std:: sync:: atomic:: { AtomicBool , Ordering } ;
122+ use tokio:: sync:: MutexGuard ;
123+
124+ static PROPOSALS_FLAG_LOCK : LazyLock < tokio:: sync:: Mutex < ( ) > > =
125+ LazyLock :: new ( || tokio:: sync:: Mutex :: new ( ( ) ) ) ;
120126
121127 /// Guard for tests that toggle the process-wide
122- /// `AGENT_PROPOSALS_ENABLED` flag. Acquires a process-wide mutex on
123- /// construction so concurrent tests don't race on the atomic, swaps in
124- /// the requested value, and restores the previous value on drop. Hold
125- /// the guard for the duration of any code that reads
128+ /// `AGENT_PROPOSALS_ENABLED` flag. Acquires a process-wide async mutex,
129+ /// swaps in the requested value, and restores the previous value on drop.
130+ /// Hold the guard for the duration of any code that reads
126131 /// `agent_proposals_enabled()`.
127132 pub ( crate ) struct ProposalsFlagGuard {
128133 prev : bool ,
@@ -131,9 +136,17 @@ pub(crate) mod test_helpers {
131136 }
132137
133138 impl ProposalsFlagGuard {
134- pub ( crate ) fn set ( enabled : bool ) -> Self {
135- static LOCK : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
136- let lock = LOCK . lock ( ) . unwrap_or_else ( std:: sync:: PoisonError :: into_inner) ;
139+ pub ( crate ) async fn set ( enabled : bool ) -> Self {
140+ let lock = PROPOSALS_FLAG_LOCK . lock ( ) . await ;
141+ Self :: with_lock ( enabled, lock)
142+ }
143+
144+ pub ( crate ) fn set_blocking ( enabled : bool ) -> Self {
145+ let lock = PROPOSALS_FLAG_LOCK . blocking_lock ( ) ;
146+ Self :: with_lock ( enabled, lock)
147+ }
148+
149+ fn with_lock ( enabled : bool , lock : MutexGuard < ' static , ( ) > ) -> Self {
137150 let flag = super :: AGENT_PROPOSALS_ENABLED
138151 . get_or_init ( || Arc :: new ( AtomicBool :: new ( false ) ) )
139152 . clone ( ) ;
@@ -390,7 +403,10 @@ pub async fn run_sandbox(
390403 // gates the skill install, the policy.local route handler, and the L7
391404 // deny body's `next_steps` field — see `agent_proposals_enabled()`.
392405 let proposals_enabled = Arc :: new ( std:: sync:: atomic:: AtomicBool :: new ( false ) ) ;
393- if AGENT_PROPOSALS_ENABLED . set ( proposals_enabled. clone ( ) ) . is_err ( ) {
406+ if AGENT_PROPOSALS_ENABLED
407+ . set ( proposals_enabled. clone ( ) )
408+ . is_err ( )
409+ {
394410 debug ! ( "agent proposals flag already initialized, keeping existing" ) ;
395411 }
396412
@@ -422,9 +438,7 @@ pub async fn run_sandbox(
422438 }
423439 }
424440 } else {
425- debug ! (
426- "agent_policy_proposals_enabled is false at startup; skipping skill install"
427- ) ;
441+ debug ! ( "agent_policy_proposals_enabled is false at startup; skipping skill install" ) ;
428442 }
429443
430444 // Generate ephemeral CA and TLS state for HTTPS L7 inspection.
0 commit comments