@@ -274,6 +274,8 @@ pub struct ComputeDriverInfoSnapshot {
274274 pub driver_name : String ,
275275 /// Driver-reported implementation version from the startup capability snapshot.
276276 pub driver_version : String ,
277+ /// Whether the driver asks the gateway to reconcile compute across restarts.
278+ pub gateway_managed_lifecycle : bool ,
277279}
278280
279281/// Interval between store-vs-backend reconciliation sweeps.
@@ -609,6 +611,7 @@ impl ComputeRuntime {
609611 name : driver_name. clone ( ) ,
610612 driver_name : capabilities. driver_name ,
611613 driver_version : capabilities. driver_version ,
614+ gateway_managed_lifecycle : capabilities. gateway_managed_lifecycle ,
612615 } ;
613616 let default_image = capabilities. default_image ;
614617 let gateway_listener_requirements = match driver
@@ -2039,14 +2042,9 @@ impl ComputeRuntime {
20392042 /// persisted lifecycle intent.
20402043 ///
20412044 /// An explicit sandbox stop persists `Stopped`; gateway shutdown does not.
2042- /// Docker, Podman, and VM compute is stopped through the same public driver
2043- /// RPC and restarted from retained running intent. Kubernetes remains
2044- /// cluster-owned.
2045+ /// Drivers request this sweep through their startup capability snapshot.
20452046 async fn stop_persisted_sandboxes_on_shutdown ( & self ) -> Result < ( ) , String > {
2046- if !matches ! (
2047- self . driver_kind( ) ,
2048- Some ( ComputeDriverKind :: Docker | ComputeDriverKind :: Podman | ComputeDriverKind :: Vm )
2049- ) {
2047+ if !self . driver_info . gateway_managed_lifecycle {
20502048 return Ok ( ( ) ) ;
20512049 }
20522050
@@ -2129,17 +2127,15 @@ impl ComputeRuntime {
21292127 /// Reconcile running intent for local compute after a gateway restart.
21302128 ///
21312129 /// `StartSandbox` is idempotent, so call it for every persisted phase that
2132- /// requires running compute on Docker, Podman, and VM. Stable stopped,
2133- /// deleting, and error states are deliberately left alone.
2130+ /// requires running compute for drivers that request gateway-managed
2131+ /// lifecycle. Stable stopped, deleting, and error states are deliberately
2132+ /// left alone.
21342133 ///
21352134 /// Should be called once at gateway startup, before watchers spawn,
21362135 /// so the watch loop sees the post-start state on its first poll.
21372136 pub async fn start_persisted_sandboxes ( & self ) -> Result < ( ) , String > {
21382137 self . recover_persisted_lifecycle_transitions ( ) . await ?;
2139- if !matches ! (
2140- self . driver_kind( ) ,
2141- Some ( ComputeDriverKind :: Docker | ComputeDriverKind :: Podman | ComputeDriverKind :: Vm )
2142- ) {
2138+ if !self . driver_info . gateway_managed_lifecycle {
21432139 return Ok ( ( ) ) ;
21442140 }
21452141
@@ -3910,6 +3906,7 @@ impl ComputeDriver for NoopTestDriver {
39103906 driver_name : "noop-test-driver" . to_string ( ) ,
39113907 driver_version : "test" . to_string ( ) ,
39123908 default_image : "openshell/sandbox:test" . to_string ( ) ,
3909+ gateway_managed_lifecycle : false ,
39133910 } ,
39143911 ) )
39153912 }
@@ -4050,6 +4047,7 @@ pub async fn new_test_runtime_with_driver(
40504047 name : driver_name. to_string ( ) ,
40514048 driver_name : driver_name. to_string ( ) ,
40524049 driver_version : "test" . to_string ( ) ,
4050+ gateway_managed_lifecycle : false ,
40534051 } ,
40544052 driver_process : None ,
40554053 default_image : "openshell/sandbox:test" . to_string ( ) ,
@@ -4212,6 +4210,7 @@ mod tests {
42124210 driver_name : "test-driver" . to_string ( ) ,
42134211 driver_version : "test" . to_string ( ) ,
42144212 default_image : "openshell/sandbox:test" . to_string ( ) ,
4213+ gateway_managed_lifecycle : false ,
42154214 } ) )
42164215 }
42174216
@@ -4529,6 +4528,7 @@ mod tests {
45294528 driver_name : "controlled-test-driver" . to_string ( ) ,
45304529 driver_version : "test" . to_string ( ) ,
45314530 default_image : "openshell/sandbox:test" . to_string ( ) ,
4531+ gateway_managed_lifecycle : false ,
45324532 } ) )
45334533 }
45344534
@@ -4731,6 +4731,7 @@ mod tests {
47314731 name : driver_name. to_string ( ) ,
47324732 driver_name : driver_name. to_string ( ) ,
47334733 driver_version : "test" . to_string ( ) ,
4734+ gateway_managed_lifecycle : false ,
47344735 } ,
47354736 driver_process : None ,
47364737 default_image : "openshell/sandbox:test" . to_string ( ) ,
@@ -4746,6 +4747,15 @@ mod tests {
47464747 }
47474748 }
47484749
4750+ async fn test_runtime_with_gateway_managed_lifecycle (
4751+ driver : SharedComputeDriver ,
4752+ driver_name : & str ,
4753+ ) -> ComputeRuntime {
4754+ let mut runtime = test_runtime_for_driver ( driver, driver_name) . await ;
4755+ runtime. driver_info . gateway_managed_lifecycle = true ;
4756+ runtime
4757+ }
4758+
47494759 fn register_test_supervisor_session ( runtime : & ComputeRuntime , sandbox_id : & str ) {
47504760 let ( tx, _rx) = mpsc:: channel ( 1 ) ;
47514761 let ( shutdown_tx, _shutdown_rx) = oneshot:: channel ( ) ;
@@ -8126,7 +8136,8 @@ mod tests {
81268136 #[ tokio:: test]
81278137 async fn shutdown_stops_running_intent_without_changing_persisted_phase ( ) {
81288138 let driver = ControlledDriver :: new ( ) ;
8129- let runtime = test_runtime_for_driver ( driver. clone ( ) , "docker" ) . await ;
8139+ let runtime =
8140+ test_runtime_with_gateway_managed_lifecycle ( driver. clone ( ) , "arbitrary" ) . await ;
81308141
81318142 for ( id, name, phase) in [
81328143 ( "sb-unspecified" , "unspecified" , SandboxPhase :: Unspecified ) ,
@@ -8185,7 +8196,8 @@ mod tests {
81858196 async fn shutdown_stop_sweep_continues_after_driver_errors ( ) {
81868197 let driver = ControlledDriver :: new ( ) ;
81878198 driver. set_stop_outcome ( ControlledLifecycleOutcome :: Error ( "runtime angry" ) ) ;
8188- let runtime = test_runtime_for_driver ( driver. clone ( ) , "podman" ) . await ;
8199+ let runtime =
8200+ test_runtime_with_gateway_managed_lifecycle ( driver. clone ( ) , "arbitrary" ) . await ;
81898201 for ( id, name) in [ ( "sb-1" , "one" ) , ( "sb-2" , "two" ) ] {
81908202 runtime
81918203 . store
@@ -8204,10 +8216,11 @@ mod tests {
82048216 }
82058217
82068218 #[ tokio:: test]
8207- async fn shutdown_stop_sweep_runs_for_each_local_driver ( ) {
8208- for driver_name in [ "docker " , "podman" , "vm "] {
8219+ async fn shutdown_stop_sweep_runs_for_any_capable_driver ( ) {
8220+ for driver_name in [ "arbitrary " , "docker " ] {
82098221 let driver = ControlledDriver :: new ( ) ;
8210- let runtime = test_runtime_for_driver ( driver. clone ( ) , driver_name) . await ;
8222+ let runtime =
8223+ test_runtime_with_gateway_managed_lifecycle ( driver. clone ( ) , driver_name) . await ;
82118224 runtime
82128225 . store
82138226 . put_message ( & sandbox_record ( "sb-1" , "sandbox" , SandboxPhase :: Ready ) )
@@ -8228,8 +8241,8 @@ mod tests {
82288241 }
82298242
82308243 #[ tokio:: test]
8231- async fn shutdown_stop_sweep_skips_kubernetes_and_extension_drivers ( ) {
8232- for driver_name in [ "kubernetes" , "extension" ] {
8244+ async fn shutdown_stop_sweep_skips_drivers_without_capability ( ) {
8245+ for driver_name in [ "docker" , " kubernetes", "extension" ] {
82338246 let driver = ControlledDriver :: new ( ) ;
82348247 let runtime = test_runtime_for_driver ( driver. clone ( ) , driver_name) . await ;
82358248 runtime
@@ -8254,7 +8267,8 @@ mod tests {
82548267 #[ tokio:: test]
82558268 async fn start_persisted_sandboxes_starts_running_phases ( ) {
82568269 let driver = ControlledDriver :: new ( ) ;
8257- let runtime = test_runtime_for_driver ( driver. clone ( ) , "docker" ) . await ;
8270+ let runtime =
8271+ test_runtime_with_gateway_managed_lifecycle ( driver. clone ( ) , "arbitrary" ) . await ;
82588272
82598273 for ( id, name, phase) in [
82608274 ( "sb-unspecified" , "unspecified" , SandboxPhase :: Unspecified ) ,
@@ -8293,7 +8307,7 @@ mod tests {
82938307 async fn start_persisted_sandboxes_marks_missing_backend_as_error ( ) {
82948308 let driver = ControlledDriver :: new ( ) ;
82958309 driver. set_start_outcome ( ControlledLifecycleOutcome :: NotFound ) ;
8296- let runtime = test_runtime_for_driver ( driver, "podman " ) . await ;
8310+ let runtime = test_runtime_with_gateway_managed_lifecycle ( driver, "arbitrary " ) . await ;
82978311
82988312 let sandbox = sandbox_record ( "sb-1" , "missing" , SandboxPhase :: Ready ) ;
82998313 runtime. store . put_message ( & sandbox) . await . unwrap ( ) ;
@@ -8323,7 +8337,7 @@ mod tests {
83238337 async fn start_persisted_sandboxes_marks_failed_start_as_error ( ) {
83248338 let driver = ControlledDriver :: new ( ) ;
83258339 driver. set_start_outcome ( ControlledLifecycleOutcome :: Error ( "runtime angry" ) ) ;
8326- let runtime = test_runtime_for_driver ( driver, "vm " ) . await ;
8340+ let runtime = test_runtime_with_gateway_managed_lifecycle ( driver, "arbitrary " ) . await ;
83278341
83288342 let sandbox = sandbox_record ( "sb-1" , "broken" , SandboxPhase :: Provisioning ) ;
83298343 runtime. store . put_message ( & sandbox) . await . unwrap ( ) ;
@@ -8350,10 +8364,11 @@ mod tests {
83508364 }
83518365
83528366 #[ tokio:: test]
8353- async fn start_persisted_sandboxes_runs_for_each_local_driver ( ) {
8354- for driver_name in [ "docker " , "podman" , "vm "] {
8367+ async fn start_persisted_sandboxes_runs_for_any_capable_driver ( ) {
8368+ for driver_name in [ "arbitrary " , "docker " ] {
83558369 let driver = ControlledDriver :: new ( ) ;
8356- let runtime = test_runtime_for_driver ( driver. clone ( ) , driver_name) . await ;
8370+ let runtime =
8371+ test_runtime_with_gateway_managed_lifecycle ( driver. clone ( ) , driver_name) . await ;
83578372 let sandbox = sandbox_record ( "sb-1" , "local" , SandboxPhase :: Ready ) ;
83588373 runtime. store . put_message ( & sandbox) . await . unwrap ( ) ;
83598374
@@ -8368,8 +8383,8 @@ mod tests {
83688383 }
83698384
83708385 #[ tokio:: test]
8371- async fn start_persisted_sandboxes_skips_kubernetes_and_extension_drivers ( ) {
8372- for driver_name in [ "kubernetes" , "extension" ] {
8386+ async fn start_persisted_sandboxes_skips_drivers_without_capability ( ) {
8387+ for driver_name in [ "docker" , " kubernetes", "extension" ] {
83738388 let driver = ControlledDriver :: new ( ) ;
83748389 let runtime = test_runtime_for_driver ( driver. clone ( ) , driver_name) . await ;
83758390 let sandbox = sandbox_record ( "sb-1" , "remote" , SandboxPhase :: Ready ) ;
@@ -8596,6 +8611,7 @@ mod tests {
85968611 let driver = FakeComputeDriver :: new ( )
85978612 . with_driver_name ( "fake-remote-driver" )
85988613 . with_default_image ( "openshell/sandbox:remote" )
8614+ . with_gateway_managed_lifecycle ( )
85998615 . with_gateway_listener_requirement (
86008616 "172.19.0.1:17670" ,
86018617 "external driver managed bridge" ,
0 commit comments