@@ -2293,6 +2293,21 @@ impl ComputeRuntime {
22932293 /// Should be called once at gateway startup, before watchers spawn,
22942294 /// so the watch loop sees the post-start state on its first poll.
22952295 pub async fn start_persisted_sandboxes ( & self ) -> Result < ( ) , String > {
2296+ self . start_persisted_sandboxes_with_authentication ( |_| Ok ( Vec :: new ( ) ) , |_| { } )
2297+ . await
2298+ }
2299+
2300+ /// Reconcile persisted running intent and provision fresh launch
2301+ /// authentication before a restored runtime reconnects.
2302+ pub async fn start_persisted_sandboxes_with_authentication < Authentication , Failed > (
2303+ & self ,
2304+ launch_authentication_for : Authentication ,
2305+ authentication_failed : Failed ,
2306+ ) -> Result < ( ) , String >
2307+ where
2308+ Authentication : Fn ( & Sandbox ) -> Result < Vec < u8 > , String > ,
2309+ Failed : Fn ( & str ) ,
2310+ {
22962311 self . recover_persisted_lifecycle_transitions ( ) . await ?;
22972312 if !self . driver_info . gateway_manages_lifecycle {
22982313 return Ok ( ( ) ) ;
@@ -2333,6 +2348,29 @@ impl ComputeRuntime {
23332348 sandbox_resource_version ( & sandbox) ,
23342349 )
23352350 . into_string ( ) ;
2351+ let launch_authentication = match launch_authentication_for ( & sandbox) {
2352+ Ok ( authentication) => authentication,
2353+ Err ( err) => {
2354+ warn ! (
2355+ sandbox_id = %sandbox. object_id( ) ,
2356+ sandbox_name = %sandbox. object_name( ) ,
2357+ error = %err,
2358+ "Failed to prepare sandbox authentication during gateway startup"
2359+ ) ;
2360+ if !recoverable_error {
2361+ self . mark_sandbox_error (
2362+ & sandbox,
2363+ "AuthenticationFailed" ,
2364+ & format ! (
2365+ "Failed to prepare sandbox authentication during gateway startup: {err}"
2366+ ) ,
2367+ )
2368+ . await ;
2369+ }
2370+ failed += 1 ;
2371+ continue ;
2372+ }
2373+ } ;
23362374 match self
23372375 . driver
23382376 . call (
@@ -2341,12 +2379,13 @@ impl ComputeRuntime {
23412379 |driver| {
23422380 let sandbox_id = sandbox_id. clone ( ) ;
23432381 let sandbox_name = sandbox_name. clone ( ) ;
2382+ let launch_authentication = launch_authentication. clone ( ) ;
23442383 async move {
23452384 driver
23462385 . start_sandbox ( Request :: new ( StartSandboxRequest {
23472386 sandbox_id,
23482387 sandbox_name,
2349- launch_authentication : Vec :: new ( ) ,
2388+ launch_authentication,
23502389 generation_id,
23512390 } ) )
23522391 . await
@@ -2374,6 +2413,7 @@ impl ComputeRuntime {
23742413 }
23752414 }
23762415 Err ( err) if err. code ( ) == Code :: NotFound => {
2416+ authentication_failed ( sandbox. object_id ( ) ) ;
23772417 // Backend resource is gone but the store still
23782418 // remembers the sandbox. Mark Error so the UI
23792419 // surfaces the inconsistency; the reconcile loop
@@ -2395,6 +2435,7 @@ impl ComputeRuntime {
23952435 missing += 1 ;
23962436 }
23972437 Err ( err) => {
2438+ authentication_failed ( sandbox. object_id ( ) ) ;
23982439 warn ! (
23992440 sandbox_id = %sandbox. object_id( ) ,
24002441 sandbox_name = %sandbox. object_name( ) ,
@@ -5552,6 +5593,7 @@ mod tests {
55525593 start_blocked : AtomicBool ,
55535594 start_calls : AtomicUsize ,
55545595 start_requests : TestMutex < Vec < ( String , String ) > > ,
5596+ start_authentications : TestMutex < Vec < Vec < u8 > > > ,
55555597 start_outcome : TestMutex < ControlledLifecycleOutcome > ,
55565598 get_started : Notify ,
55575599 get_release : Semaphore ,
@@ -5585,6 +5627,7 @@ mod tests {
55855627 start_blocked : AtomicBool :: new ( false ) ,
55865628 start_calls : AtomicUsize :: new ( 0 ) ,
55875629 start_requests : TestMutex :: new ( Vec :: new ( ) ) ,
5630+ start_authentications : TestMutex :: new ( Vec :: new ( ) ) ,
55885631 start_outcome : TestMutex :: new ( ControlledLifecycleOutcome :: Ok ) ,
55895632 get_started : Notify :: new ( ) ,
55905633 get_release : Semaphore :: new ( 0 ) ,
@@ -5683,6 +5726,13 @@ mod tests {
56835726 . clone ( )
56845727 }
56855728
5729+ fn start_authentications ( & self ) -> Vec < Vec < u8 > > {
5730+ self . start_authentications
5731+ . lock ( )
5732+ . expect ( "start authentications lock poisoned" )
5733+ . clone ( )
5734+ }
5735+
56865736 fn send_event ( & self , event : WatchSandboxesEvent ) {
56875737 self . watch_tx
56885738 . send ( Ok ( event) )
@@ -5828,6 +5878,10 @@ mod tests {
58285878 . lock ( )
58295879 . expect ( "start requests lock poisoned" )
58305880 . push ( ( request. sandbox_id , request. sandbox_name ) ) ;
5881+ self . start_authentications
5882+ . lock ( )
5883+ . expect ( "start authentications lock poisoned" )
5884+ . push ( request. launch_authentication ) ;
58315885 self . start_calls . fetch_add ( 1 , Ordering :: SeqCst ) ;
58325886 self . start_started . notify_one ( ) ;
58335887 if self . start_blocked . load ( Ordering :: SeqCst ) {
@@ -10872,6 +10926,31 @@ mod tests {
1087210926 ) ;
1087310927 }
1087410928
10929+ #[ tokio:: test]
10930+ async fn start_persisted_sandboxes_supplies_fresh_authentication ( ) {
10931+ let driver = ControlledDriver :: new ( ) ;
10932+ let runtime =
10933+ test_runtime_with_gateway_managed_lifecycle ( driver. clone ( ) , "arbitrary" ) . await ;
10934+ runtime
10935+ . store
10936+ . put_message ( & sandbox_record ( "sb-1" , "sandbox" , SandboxPhase :: Ready ) )
10937+ . await
10938+ . unwrap ( ) ;
10939+
10940+ runtime
10941+ . start_persisted_sandboxes_with_authentication (
10942+ |sandbox| Ok ( format ! ( "authentication:{}" , sandbox. object_id( ) ) . into_bytes ( ) ) ,
10943+ |_| { } ,
10944+ )
10945+ . await
10946+ . unwrap ( ) ;
10947+
10948+ assert_eq ! (
10949+ driver. start_authentications( ) ,
10950+ vec![ b"authentication:sb-1" . to_vec( ) ]
10951+ ) ;
10952+ }
10953+
1087510954 #[ tokio:: test]
1087610955 async fn startup_sweep_rechecks_intent_after_acquiring_gate ( ) {
1087710956 let driver = ControlledDriver :: new ( ) ;
0 commit comments