diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index a977e722d6..e866ec6927 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -195,6 +195,17 @@ every member one at a time. The PID must survive each case, while an open replacement confirmed within the recovery timeout, so the runtime terminated the workload. +For RFC 0017 app-listener validation, enable the listener and supply the app TLS +Secret on the shared Deployment, plus a separate NetworkPolicy allowing the test +client/proxy to reach its app port. Runtime-port access does not grant app access. +Discovery returns the acknowledged owner's Pod IP and app port; no headless app +Service is needed. Exercise HTTP/WebSocket reconnection through graceful downscale +and abrupt loss, preserving workload generation/PID/in-memory state and the public +hostname. Verify stale-owner rejection independently of the replacement's success. +Shared serving leases expire within fifteen seconds of issuance and do not renew +from configuration alone. Do not claim seamless socket migration or infer external +ingress readiness from an advertisement. + ### 2. Deploy OpenShell **Iterative dev** (rebuilds on file changes, recommended during active development): diff --git a/crates/openshell-core/src/app_listener.rs b/crates/openshell-core/src/app_listener.rs index 6fe2d1e2b4..bece387213 100644 --- a/crates/openshell-core/src/app_listener.rs +++ b/crates/openshell-core/src/app_listener.rs @@ -50,6 +50,12 @@ impl AppListenerConfig { } } +/// A synchronous view of installed ingress, evaluated on the sandbox instance +/// runtime. The requested lease must fit entirely within serving freshness. +pub type InstallationSource = std::sync::Arc< + dyn Fn(std::time::Duration) -> crate::proto::SupervisorServiceAdvertisement + Send + Sync, +>; + #[cfg(test)] mod tests { use super::*; diff --git a/crates/openshell-core/tests/compute_driver_compat.rs b/crates/openshell-core/tests/compute_driver_compat.rs index 01baa27a45..22e5089fbf 100644 --- a/crates/openshell-core/tests/compute_driver_compat.rs +++ b/crates/openshell-core/tests/compute_driver_compat.rs @@ -186,6 +186,16 @@ async fn roundtrip(serve_image_updates: bool) { .connect() .await .unwrap(); + let mut apps = openshell_core::proto::compute::v1::compute_driver_app_endpoints_client::ComputeDriverAppEndpointsClient::new(channel.clone()); + assert_eq!( + apps.resolve_supervisor_app_endpoint( + openshell_core::proto::compute::v1::ResolveSupervisorAppEndpointRequest::default() + ) + .await + .unwrap_err() + .code(), + tonic::Code::Unimplemented, + ); let mut baseline = ComputeDriverClient::new(channel.clone()); assert_eq!( baseline diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index 5ae29112c8..9c0c519a3f 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -2241,6 +2241,201 @@ impl KubernetesComputeDriver { Ok(()) } + /// Resolve only a unique, live driver-owned supervisor. Multi-member pool + /// resolution must use the HA assignment/member API rather than guess. + pub async fn supervisor_app_endpoint( + &self, + sandbox_id: &str, + ) -> Result< + Option, + KubernetesDriverError, + > { + if !self.config.sandbox_runtime.app_listener.common.enabled { + return Ok(None); + } + let api = self + .supported_sandbox_api_for_lookup(self.client.clone()) + .await + .map_err(KubernetesDriverError::Message)?; + let objects = api + .api + .list(&ListParams::default().labels(&self.sandbox_lookup_selector(sandbox_id))) + .await + .map_err(KubernetesDriverError::from_kube)? + .items; + let [object]: [DynamicObject; 1] = match objects.try_into() { + Ok(objects) => objects, + Err(_) => return Ok(None), + }; + let namespace = object.metadata.namespace.as_deref().ok_or_else(|| { + KubernetesDriverError::Precondition("sandbox namespace missing".into()) + })?; + let uid = object + .metadata + .uid + .as_deref() + .ok_or_else(|| KubernetesDriverError::Precondition("sandbox UID missing".into()))?; + let placement = supervisor_placement(&object)?; + let mut pool_route = String::new(); + let mut ownership_epoch = 0; + let mut member_address = None; + let pods = if let SupervisorPlacement::Shared { route } = &placement { + let Some(shared) = &self.shared_supervisor else { + return Ok(None); + }; + let controller = shared.supervision().controller(); + let Some(view) = controller.pool_view() else { + return Ok(None); + }; + let Some(owner) = view + .owners + .get(route) + .filter(|_| view.desired.contains(route)) + else { + return Ok(None); + }; + let Some(member) = view.members.iter().find(|member| { + member.member_id == owner.member_id + && member.process_id == owner.process_id + && !member.leaving + }) else { + return Ok(None); + }; + if !matches!(controller.observed(route), + openshell_supervisor_control::ObservedInstance::Reported(status) + if status.sandbox_id == sandbox_id && status.ready + && status.phase == openshell_supervisor_control::proto::SandboxInstancePhase::Supervising as i32) + { + return Ok(None); + } + let address = member + .runtime_address + .parse::() + .map_err(|_| { + KubernetesDriverError::Precondition( + "pool member runtime address is not an IP endpoint".into(), + ) + })?; + let pod = + Api::::namespaced(self.client.clone(), &shared.config().service_namespace) + .get_opt(&owner.member_id) + .await + .map_err(KubernetesDriverError::from_kube)?; + let Some(pod) = pod else { + return Ok(None); + }; + let service = + Api::::namespaced(self.client.clone(), &shared.config().service_namespace) + .get(&shared.config().service_name) + .await + .map_err(KubernetesDriverError::from_kube)?; + let Some(selector) = service + .spec + .as_ref() + .and_then(|spec| spec.selector.as_ref()) + .filter(|selector| !selector.is_empty()) + else { + return Ok(None); + }; + if !selector.iter().all(|(key, value)| { + pod.metadata + .labels + .as_ref() + .and_then(|labels| labels.get(key)) + == Some(value) + }) { + return Ok(None); + } + pool_route = route.clone(); + ownership_epoch = owner.auth_epoch; + member_address = Some(address.ip()); + vec![pod] + } else { + let selector = + format!("openshell.ai/sandbox-id={sandbox_id},openshell.ai/component=supervisor"); + Api::::namespaced(self.client.clone(), namespace) + .list(&ListParams::default().labels(&selector)) + .await + .map_err(KubernetesDriverError::from_kube)? + .items + }; + // Includes terminating members in the ambiguity check; never guess a + // replacement's ownership during a rollout. + let [pod]: [Pod; 1] = match pods.try_into() { + Ok(pods) => pods, + Err(_) => return Ok(None), + }; + if pod.metadata.deletion_timestamp.is_some() { + return Ok(None); + } + let app_port = i32::from(self.config.sandbox_runtime.app_listener.common.port); + if !pod + .spec + .as_ref() + .and_then(|spec| { + spec.containers + .iter() + .find(|container| container.name == "supervisor") + }) + .is_some_and(|container| { + container.ports.as_ref().is_some_and(|ports| { + ports.iter().any(|port| { + port.name.as_deref() == Some("app") && port.container_port == app_port + }) + }) && container.env.as_ref().is_some_and(|env| { + env.iter().any(|entry| { + entry.name == "OPENSHELL_APP_LISTENER_ENABLED" + && entry.value.as_deref() == Some("true") + }) + }) + }) + { + return Ok(None); + } + let Some(pod_uid) = pod.metadata.uid.as_deref() else { + return Ok(None); + }; + if matches!(placement, SupervisorPlacement::Dedicated) + && !pod + .metadata + .owner_references + .as_ref() + .is_some_and(|owners| owners.iter().any(|o| o.uid == uid && o.kind == "Sandbox")) + { + return Ok(None); + } + let Some(status) = pod.status else { + return Ok(None); + }; + if status.phase.as_deref() != Some("Running") { + return Ok(None); + } + let Some(host) = status + .pod_ip + .filter(|ip| ip.parse::().is_ok()) + else { + return Ok(None); + }; + if member_address + .is_some_and(|address| host.parse::().ok() != Some(address)) + { + return Ok(None); + } + let component = match placement { + SupervisorPlacement::Dedicated => pod_uid.to_string(), + SupervisorPlacement::Shared { route } => route, + }; + Ok(Some( + openshell_core::proto::compute::v1::SupervisorAppEndpoint { + host, + port: u32::from(self.config.sandbox_runtime.app_listener.common.port), + runtime_identity: kubernetes_runtime_identity(namespace, uid, &component), + pool_route, + ownership_epoch, + }, + )) + } + pub async fn get_sandbox(&self, sandbox_id: &str) -> Result, String> { info!( sandbox_id = %sandbox_id, diff --git a/crates/openshell-driver-kubernetes/src/grpc.rs b/crates/openshell-driver-kubernetes/src/grpc.rs index c9adffcbf5..6ce79654ef 100644 --- a/crates/openshell-driver-kubernetes/src/grpc.rs +++ b/crates/openshell-driver-kubernetes/src/grpc.rs @@ -337,6 +337,40 @@ impl ComputeDriver for ComputeDriverService { } } +#[tonic::async_trait] +impl openshell_core::proto::compute::v1::compute_driver_app_endpoints_server::ComputeDriverAppEndpoints for ComputeDriverService { + async fn resolve_supervisor_app_endpoint( + &self, + request: Request, + ) -> Result< + Response, + Status, + > { + self.rpc_tracer + .trace( + openshell_otel::rpc::RESOLVE_SUPERVISOR_APP_ENDPOINT, + async { + let request = request.into_inner(); + if request.sandbox_id.is_empty() { + return Err(Status::invalid_argument("sandbox_id is required")); + } + let endpoint = self + .driver + .supervisor_app_endpoint(&request.sandbox_id) + .await + .map_err(|e| Status::unavailable(e.to_string()))?; + Ok(Response::new( + openshell_core::proto::compute::v1::ResolveSupervisorAppEndpointResponse { + sandbox_id: request.sandbox_id, + supervisor_app_endpoint: endpoint, + }, + )) + }, + ) + .await + } +} + #[tonic::async_trait] impl openshell_core::compute::ComputeDriver for ComputeDriverService { async fn update_sandbox_configuration( diff --git a/crates/openshell-driver-kubernetes/src/main.rs b/crates/openshell-driver-kubernetes/src/main.rs index 6fac8c30af..2a2610ca3a 100644 --- a/crates/openshell-driver-kubernetes/src/main.rs +++ b/crates/openshell-driver-kubernetes/src/main.rs @@ -343,6 +343,7 @@ async fn main() -> Result<()> { let driver_service = ComputeDriverService::new(driver); let configuration_updates_service = openshell_core::proto::compute::v1::compute_driver_configuration_updates_server::ComputeDriverConfigurationUpdatesServer::new(driver_service.clone()); + let app_endpoints_service = openshell_core::proto::compute::v1::compute_driver_app_endpoints_server::ComputeDriverAppEndpointsServer::new(driver_service.clone()); let service = ComputeDriverServer::new(driver_service); let shutdown = async move { shutdown_signal().await; @@ -358,6 +359,7 @@ async fn main() -> Result<()> { .layer(openshell_otel::compute_driver_rpc_layer()) .add_service(service) .add_service(configuration_updates_service) + .add_service(app_endpoints_service) .serve_with_incoming_shutdown( openshell_core::external_driver_socket::SameUidUnixIncoming::new(listener), shutdown, @@ -370,6 +372,7 @@ async fn main() -> Result<()> { .layer(openshell_otel::compute_driver_rpc_layer()) .add_service(service) .add_service(configuration_updates_service) + .add_service(app_endpoints_service) .serve_with_shutdown(args.bind_address, shutdown) .await .into_diagnostic() diff --git a/crates/openshell-gateway/src/lib.rs b/crates/openshell-gateway/src/lib.rs index f5f2b62ab7..08b0bf8472 100644 --- a/crates/openshell-gateway/src/lib.rs +++ b/crates/openshell-gateway/src/lib.rs @@ -342,6 +342,7 @@ impl openshell_server::ComputeDriverFactory for KubernetesFactory { let driver = std::sync::Arc::new( openshell_driver_kubernetes::ComputeDriverService::new_in_process(driver), ); + context.register_app_endpoints(driver.clone())?; Ok(( openshell_server::ComputeDriverInstance::InProcess(driver.clone()), Some(driver), diff --git a/crates/openshell-otel/src/grpc.rs b/crates/openshell-otel/src/grpc.rs index 9edb630318..85ac3ffe72 100644 --- a/crates/openshell-otel/src/grpc.rs +++ b/crates/openshell-otel/src/grpc.rs @@ -111,6 +111,10 @@ pub mod rpc { "GetSandbox", "openshell.compute.v1.ComputeDriver/GetSandbox", ); + pub const RESOLVE_SUPERVISOR_APP_ENDPOINT: ComputeDriverRpc = ComputeDriverRpc::new( + "ResolveSupervisorAppEndpoint", + "openshell.compute.v1.ComputeDriverAppEndpoints/ResolveSupervisorAppEndpoint", + ); pub const LIST_SANDBOXES: ComputeDriverRpc = ComputeDriverRpc::new( "ListSandboxes", "openshell.compute.v1.ComputeDriver/ListSandboxes", @@ -201,6 +205,7 @@ pub fn compute_driver_rpc_operation(path: &str) -> Option { Some("CreateSandbox") => Some(rpc::CREATE_SANDBOX), Some("UpdateSandboxConfiguration") => Some(rpc::UPDATE_SANDBOX_CONFIGURATION), Some("GetSandbox") => Some(rpc::GET_SANDBOX), + Some("ResolveSupervisorAppEndpoint") => Some(rpc::RESOLVE_SUPERVISOR_APP_ENDPOINT), Some("ListSandboxes") => Some(rpc::LIST_SANDBOXES), Some("StopSandbox") => Some(rpc::STOP_SANDBOX), Some("StartSandbox") => Some(rpc::START_SANDBOX), @@ -325,6 +330,7 @@ mod tests { rpc::CREATE_SANDBOX, rpc::UPDATE_SANDBOX_CONFIGURATION, rpc::GET_SANDBOX, + rpc::RESOLVE_SUPERVISOR_APP_ENDPOINT, rpc::LIST_SANDBOXES, rpc::STOP_SANDBOX, rpc::START_SANDBOX, diff --git a/crates/openshell-server/README.md b/crates/openshell-server/README.md index ef7b0546b5..05bff10e79 100644 --- a/crates/openshell-server/README.md +++ b/crates/openshell-server/README.md @@ -233,3 +233,23 @@ runs under `mise run test:rust:postgres` (see `TESTING.md`) and still accepts `OPENSHELL_REFRESH_TEST_DATABASE_URL` pointing to a disposable PostgreSQL database. It exercises independent sessions without the local mutex. Do not point this fixture at an existing deployment database. + +## Application route installation records + +Supervisor installation reports arrive only through the authenticated sandbox +session. The gateway issues bounded, single-use lease challenges and validates +reports under the sandbox mutation guard. Accepted records contain current +service declarations, configuration/runtime identity, session fencing, a +trusted driver-resolved app endpoint, and gateway lease expiry. The private +`ComputeDriverAppEndpoints.ResolveSupervisorAppEndpoint` extension RPC resolves this endpoint separately +from `GetSandbox`: endpoint lookup failures do not affect ordinary sandbox +observation. Unsupported drivers return `UNIMPLEMENTED`; an absent endpoint +withholds application publication. In-process factories opt in with +`ComputeDriverBuildContext::register_app_endpoints`; baseline factories need no +changes. Standalone drivers register the extension on their existing endpoint. +The generic +store key is prefixed `service-installation:`; records are ephemeral and do not +change durable sandbox protobufs. Reads revalidate owner, configuration, +generation, and expiry. Cleanup scans bounded pages and uses conditional deletes +so concurrent renewals are preserved. Discovery consumers must not treat these +records as application health or external ingress readiness. diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 2039ef4f7f..ce4ee5e1f8 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -78,12 +78,17 @@ pub const COMPUTE_RUNTIME_IDENTITY_ANNOTATION: &str = /// can never turn a later start into a resume. const RESUME_INTENT_ANNOTATION: &str = "internal.openshell.ai/resume-intent"; +#[cfg(test)] +use openshell_core::proto::compute::v1::compute_driver_app_endpoints_server::ComputeDriverAppEndpoints as _; #[cfg(unix)] use tower::service_fn; use tracing::{Instrument as _, debug, info, warn}; pub type DriverWatchStream = Pin> + Send>>; +/// Optional application endpoint resolver, independent of the baseline driver. +pub type SharedComputeDriverAppEndpoints = Arc; + pub type SharedComputeDriver = Arc + Send + Sync>; @@ -684,6 +689,7 @@ impl AcquiredRemoteDriverEndpoint { #[derive(Debug, Clone)] struct RemoteComputeDriver { client: RemoteComputeDriverClient, + app_endpoints_client: openshell_core::proto::compute::v1::compute_driver_app_endpoints_client::ComputeDriverAppEndpointsClient>, configuration_updates_client: openshell_core::proto::compute::v1::compute_driver_configuration_updates_client::ComputeDriverConfigurationUpdatesClient>, admission_acknowledgement: Arc>>, } @@ -696,6 +702,7 @@ impl RemoteComputeDriver { fn new(channel: Channel) -> Self { Self { client: ComputeDriverClient::with_interceptor(channel.clone(), TraceContextInterceptor), + app_endpoints_client: openshell_core::proto::compute::v1::compute_driver_app_endpoints_client::ComputeDriverAppEndpointsClient::with_interceptor(channel.clone(), TraceContextInterceptor), configuration_updates_client: openshell_core::proto::compute::v1::compute_driver_configuration_updates_client::ComputeDriverConfigurationUpdatesClient::with_interceptor(channel, TraceContextInterceptor), admission_acknowledgement: Arc::new(StdMutex::new(None)), } @@ -904,6 +911,19 @@ impl ComputeDriver for RemoteComputeDriver { } } +#[tonic::async_trait] +impl openshell_core::proto::compute::v1::compute_driver_app_endpoints_server::ComputeDriverAppEndpoints for RemoteComputeDriver { + async fn resolve_supervisor_app_endpoint( + &self, + request: Request, + ) -> Result< + tonic::Response, + Status, + > { + self.app_endpoints_client.clone().resolve_supervisor_app_endpoint(request).await + } +} + #[derive(Clone)] pub struct ComputeRuntime { isolation_backend: Option, @@ -911,6 +931,7 @@ pub struct ComputeRuntime { admission_acknowledgement: String, driver: TracedDriver, image_update_driver: Option, + app_endpoint_resolver: Option, driver_info: ComputeDriverInfoSnapshot, telemetry_compute_driver: TelemetryComputeDriver, driver_process: Option>, @@ -1029,6 +1050,7 @@ impl ComputeRuntime { isolation_backend: None, driver: TracedDriver::new(driver, driver_name), image_update_driver: None, + app_endpoint_resolver: None, admission: openshell_core::resource_admission::DriverAdmissionConfig::default(), admission_acknowledgement: capabilities.resource_admission_policy, driver_info, @@ -1182,7 +1204,19 @@ impl ComputeRuntime { supervisor_sessions, ) .await - .map(|runtime| runtime.with_image_update_driver(Some(driver))) + .map(|runtime| { + runtime + .with_image_update_driver(Some(driver.clone())) + .with_app_endpoint_resolver(Some(driver)) + }) + } + + pub(crate) fn with_app_endpoint_resolver( + mut self, + resolver: Option, + ) -> Self { + self.app_endpoint_resolver = resolver; + self } pub(crate) fn with_image_update_driver( @@ -6740,6 +6774,44 @@ impl ComputeRuntime { .await } + /// Resolve app endpoints independently of ordinary sandbox observation. + pub(crate) async fn supervisor_app_endpoint( + &self, + sandbox: &Sandbox, + ) -> Result, Status> { + let Some(resolver) = self.app_endpoint_resolver.clone() else { + return Ok(None); + }; + let id = sandbox.object_id().to_string(); + let request = openshell_core::proto::compute::v1::ResolveSupervisorAppEndpointRequest { + sandbox_id: id.clone(), + name: sandbox.object_name().to_string(), + }; + let response = match self + .driver + .call( + openshell_otel::rpc::RESOLVE_SUPERVISOR_APP_ENDPOINT, + Some(&id), + |_driver| async move { + resolver + .resolve_supervisor_app_endpoint(Request::new(request)) + .await + }, + ) + .await + { + Ok(response) => response.into_inner(), + Err(status) if status.code() == Code::Unimplemented => return Ok(None), + Err(status) => return Err(status), + }; + if response.sandbox_id != id { + return Err(Status::failed_precondition( + "driver returned a different sandbox", + )); + } + Ok(response.supervisor_app_endpoint) + } + async fn get_driver_sandbox( &self, sandbox_id: &str, @@ -8309,15 +8381,27 @@ fn is_terminal_failure_reason(reason: &str) -> bool { #[cfg(any(test, feature = "test-support"))] #[derive(Debug)] pub struct NoopTestDriver { + app_endpoint: Option, workspace_delete_failures: std::sync::atomic::AtomicUsize, sandbox_authentication: Option>, } #[cfg(any(test, feature = "test-support"))] impl NoopTestDriver { + #[cfg(test)] + pub fn with_app_endpoint( + endpoint: openshell_core::proto::compute::v1::SupervisorAppEndpoint, + ) -> Self { + Self { + app_endpoint: Some(endpoint), + ..Self::default() + } + } + #[cfg(test)] pub fn failing_workspace_deletes(count: usize) -> Self { Self { + app_endpoint: None, workspace_delete_failures: std::sync::atomic::AtomicUsize::new(count), sandbox_authentication: None, } @@ -8326,6 +8410,7 @@ impl NoopTestDriver { #[cfg(test)] pub fn authenticating_sandbox(sandbox_id: impl Into) -> Self { Self { + app_endpoint: None, workspace_delete_failures: std::sync::atomic::AtomicUsize::new(0), sandbox_authentication: Some(Ok((sandbox_id.into(), "test-runtime".to_string()))), } @@ -8337,6 +8422,7 @@ impl NoopTestDriver { runtime_identity: impl Into, ) -> Self { Self { + app_endpoint: None, workspace_delete_failures: std::sync::atomic::AtomicUsize::new(0), sandbox_authentication: Some(Ok((sandbox_id.into(), runtime_identity.into()))), } @@ -8345,6 +8431,7 @@ impl NoopTestDriver { #[cfg(test)] pub fn failing_sandbox_authentication(code: Code, message: impl Into) -> Self { Self { + app_endpoint: None, workspace_delete_failures: std::sync::atomic::AtomicUsize::new(0), sandbox_authentication: Some(Err((code, message.into()))), } @@ -8355,6 +8442,7 @@ impl NoopTestDriver { impl Default for NoopTestDriver { fn default() -> Self { Self { + app_endpoint: None, workspace_delete_failures: std::sync::atomic::AtomicUsize::new(0), sandbox_authentication: None, } @@ -8547,6 +8635,25 @@ impl ComputeDriver for NoopTestDriver { } } +#[cfg(any(test, feature = "test-support"))] +#[tonic::async_trait] +impl openshell_core::proto::compute::v1::compute_driver_app_endpoints_server::ComputeDriverAppEndpoints for NoopTestDriver { + async fn resolve_supervisor_app_endpoint( + &self, + request: Request, + ) -> Result< + tonic::Response, + Status, + > { + Ok(tonic::Response::new( + openshell_core::proto::compute::v1::ResolveSupervisorAppEndpointResponse { + sandbox_id: request.into_inner().sandbox_id, + supervisor_app_endpoint: self.app_endpoint.clone(), + }, + )) + } +} + #[cfg(any(test, feature = "test-support"))] pub async fn new_test_runtime(store: Arc) -> ComputeRuntime { new_test_runtime_for_driver(store, "test").await @@ -8585,6 +8692,7 @@ pub fn new_test_runtime_with_driver( isolation_backend: None, driver: TracedDriver::new(driver, "test".to_string()), image_update_driver: None, + app_endpoint_resolver: None, admission: openshell_core::resource_admission::DriverAdmissionConfig { allow_driver_config: true, ..Default::default() @@ -8994,6 +9102,9 @@ mod tests { #[derive(Debug, Default)] struct TestDriver { + endpoint_resolution: Option< + Result, + >, listed_sandboxes: Vec, current_sandboxes: Vec, workspace_rpcs_unimplemented: bool, @@ -9184,6 +9295,27 @@ mod tests { } } + #[tonic::async_trait] + impl openshell_core::proto::compute::v1::compute_driver_app_endpoints_server::ComputeDriverAppEndpoints for TestDriver { + async fn resolve_supervisor_app_endpoint( + &self, + _request: Request< + openshell_core::proto::compute::v1::ResolveSupervisorAppEndpointRequest, + >, + ) -> Result< + tonic::Response< + openshell_core::proto::compute::v1::ResolveSupervisorAppEndpointResponse, + >, + Status, + > { + match self.endpoint_resolution.clone() { + Some(Ok(response)) => Ok(tonic::Response::new(response)), + Some(Err(code)) => Err(Status::new(code, "endpoint lookup failed")), + None => Err(Status::unimplemented("endpoint resolution unsupported")), + } + } + } + #[tokio::test] async fn workspace_lifecycle_allows_legacy_driver_without_workspace_rpcs() { let runtime = test_runtime(Arc::new(TestDriver { @@ -9196,6 +9328,75 @@ mod tests { runtime.delete_workspace("legacy").await.unwrap(); } + #[tokio::test] + async fn app_endpoint_resolution_is_independent_of_sandbox_observation() { + use openshell_core::proto::compute::v1::{ + ResolveSupervisorAppEndpointResponse, SupervisorAppEndpoint, + }; + let endpoint = SupervisorAppEndpoint { + host: "10.0.0.7".into(), + port: 9443, + runtime_identity: "runtime-a".into(), + ..Default::default() + }; + let sandbox = sandbox_record("sandbox", "demo", SandboxPhase::Ready); + let cases = [ + (None, Ok(None)), + (Some(Err(Code::Unimplemented)), Ok(None)), + (Some(Err(Code::Unavailable)), Err(Code::Unavailable)), + ( + Some(Ok(ResolveSupervisorAppEndpointResponse { + sandbox_id: "sandbox".into(), + supervisor_app_endpoint: None, + })), + Ok(None), + ), + ( + Some(Ok(ResolveSupervisorAppEndpointResponse { + sandbox_id: "other-sandbox".into(), + supervisor_app_endpoint: Some(endpoint.clone()), + })), + Err(Code::FailedPrecondition), + ), + ( + Some(Ok(ResolveSupervisorAppEndpointResponse { + sandbox_id: "sandbox".into(), + supervisor_app_endpoint: Some(endpoint.clone()), + })), + Ok(Some(endpoint)), + ), + ]; + for (resolution, expected) in cases { + let driver = Arc::new(TestDriver { + endpoint_resolution: resolution, + current_sandboxes: vec![DriverSandbox { + id: "sandbox".into(), + name: "demo".into(), + ..Default::default() + }], + ..Default::default() + }); + let runtime = test_runtime(driver.clone()) + .await + .with_app_endpoint_resolver(Some(driver)); + assert_eq!( + runtime + .supervisor_app_endpoint(&sandbox) + .await + .map_err(|s| s.code()), + expected + ); + // An unsupported, failed, or invalid endpoint lookup must never fail + // ordinary sandbox observation. + let observed = runtime + .get_driver_sandbox("sandbox", "demo") + .await + .unwrap() + .unwrap(); + assert_eq!(observed.id, "sandbox"); + } + } + #[derive(Clone)] enum ControlledDeleteOutcome { Ok(bool), @@ -9788,6 +9989,7 @@ mod tests { isolation_backend: None, driver: TracedDriver::new(driver, "test-driver".to_string()), image_update_driver: None, + app_endpoint_resolver: None, admission: openshell_core::resource_admission::DriverAdmissionConfig { allow_driver_config: true, ..Default::default() @@ -17797,6 +17999,16 @@ mod tests { })) .await .unwrap(); + let error = remote + .resolve_supervisor_app_endpoint(Request::new( + openshell_core::proto::compute::v1::ResolveSupervisorAppEndpointRequest { + sandbox_id: sandbox.id.clone(), + name: sandbox.name.clone(), + }, + )) + .await + .unwrap_err(); + assert_eq!(error.code(), Code::Unimplemented); remote .list_sandboxes(Request::new(ListSandboxesRequest {})) .await @@ -17844,7 +18056,7 @@ mod tests { let traceparents = driver.traceparents(); assert_eq!( traceparents.len(), - 12, + 13, "the client interceptor should cover every RPC" ); assert!( diff --git a/crates/openshell-server/src/config_delivery/session.rs b/crates/openshell-server/src/config_delivery/session.rs index 2942d932b0..f70a5d1a75 100644 --- a/crates/openshell-server/src/config_delivery/session.rs +++ b/crates/openshell-server/src/config_delivery/session.rs @@ -1042,6 +1042,7 @@ mod tests { tx, supports_session_redirect: false, pushes_config: true, + advertises_services: false, } } diff --git a/crates/openshell-server/src/grpc/mod.rs b/crates/openshell-server/src/grpc/mod.rs index d066845fa4..2efcb9873e 100644 --- a/crates/openshell-server/src/grpc/mod.rs +++ b/crates/openshell-server/src/grpc/mod.rs @@ -13,6 +13,7 @@ mod sandbox; pub mod supervisor_host; pub use sandbox::mint_persisted_authentication; mod service; +pub mod service_advertisement; mod validation; pub mod workspace; @@ -1107,6 +1108,7 @@ pub mod test_support { outbound .send(SupervisorMessage { payload: Some(supervisor_message::Payload::Hello(SupervisorHello { + supports_service_advertisements: false, sandbox_id: sandbox_id.into(), instance_id: "instance".into(), supports_config_push: features == StreamFeatures::Apply, diff --git a/crates/openshell-server/src/grpc/policy/endpoint_status_tests.rs b/crates/openshell-server/src/grpc/policy/endpoint_status_tests.rs index eb36132fa2..883a69c498 100644 --- a/crates/openshell-server/src/grpc/policy/endpoint_status_tests.rs +++ b/crates/openshell-server/src/grpc/policy/endpoint_status_tests.rs @@ -1940,6 +1940,7 @@ async fn replacement_reset_timeout_invalidates_superseded_evidence() { let hello = SupervisorMessage { payload: Some(supervisor_message::Payload::Hello(SupervisorHello { + supports_service_advertisements: false, sandbox_id: sandbox_id.to_string(), instance_id: "instance-b".to_string(), connection_epoch: 1, @@ -2014,6 +2015,7 @@ async fn replacement_reset_retry_does_not_hold_gateway_shutdown() { let hello = SupervisorMessage { payload: Some(supervisor_message::Payload::Hello(SupervisorHello { + supports_service_advertisements: false, sandbox_id: sandbox_id.to_string(), instance_id: "instance-b".to_string(), connection_epoch: 1, diff --git a/crates/openshell-server/src/grpc/provider_readiness_tests.rs b/crates/openshell-server/src/grpc/provider_readiness_tests.rs index 7a306d905d..bfe729c044 100644 --- a/crates/openshell-server/src/grpc/provider_readiness_tests.rs +++ b/crates/openshell-server/src/grpc/provider_readiness_tests.rs @@ -68,6 +68,7 @@ impl TestRegistry { fn hello() -> SupervisorHello { SupervisorHello { + supports_service_advertisements: false, sandbox_id: Uuid::new_v4().to_string(), instance_id: Uuid::new_v4().to_string(), connection_epoch: 0, diff --git a/crates/openshell-server/src/grpc/service.rs b/crates/openshell-server/src/grpc/service.rs index bea999e5af..d063c315ad 100644 --- a/crates/openshell-server/src/grpc/service.rs +++ b/crates/openshell-server/src/grpc/service.rs @@ -93,22 +93,29 @@ pub(super) async fn expose_service_endpoint( target_port: u32, authorization_mode: ServiceAuthorizationMode, ) -> Result, Status> { + // Hold the hardening guard across sandbox validation, route counting and + // persistence. Acquisition is unconditional because advertisements also + // serialize service edits when the application listener is disabled. + let _mutation_guard = state + .compute + .mutation_guard(MutationScope::sandbox(workspace, sandbox.object_id())) + .await + .map_err(|error| { + super::persistence_error_to_status(error, "acquire sandbox mutation lock") + })?; + let current = state + .store + .get_message::(sandbox.object_id()) + .await + .map_err(|_| Status::unavailable("sandbox storage unavailable"))? + .filter(|s| { + s.metadata + .as_ref() + .is_none_or(|m| m.deletion_time.is_none()) + }) + .ok_or_else(|| Status::not_found("sandbox removed"))?; + let sandbox = ¤t; let sandbox_name = sandbox.object_name(); - // Count and persist under the same fleet-wide sandbox guard, including - // create-time exposure, so concurrent gateways cannot overfill the allowlist. - let _mutation_guard = if state.config.supervisor_app_listener.enabled { - Some( - state - .compute - .mutation_guard(MutationScope::sandbox(workspace, sandbox.object_id())) - .await - .map_err(|error| { - super::persistence_error_to_status(error, "acquire sandbox mutation lock") - })?, - ) - } else { - None - }; let now = crate::persistence::current_time_ms(); let key = service_routing::endpoint_key(sandbox_name, service); @@ -373,6 +380,11 @@ pub(super) async fn handle_delete_service( probe.resume.notified().await; } + let _guard = state + .compute + .mutation_guard(MutationScope::sandbox(workspace, sandbox.object_id())) + .await + .map_err(|_| Status::unavailable("service mutation lock unavailable"))?; let endpoint_id = endpoint.object_id().to_string(); let endpoint_version = endpoint.get_resource_version(); let deleted = state diff --git a/crates/openshell-server/src/grpc/service_advertisement.rs b/crates/openshell-server/src/grpc/service_advertisement.rs new file mode 100644 index 0000000000..8bc0d4ccf1 --- /dev/null +++ b/crates/openshell-server/src/grpc/service_advertisement.rs @@ -0,0 +1,783 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Expiring installation evidence. Addresses always cross the trusted driver +//! boundary; the authenticated supervisor supplies only installed declarations. + +use std::sync::Arc; +use std::time::Duration; + +use openshell_core::proto::{Sandbox, SandboxServiceRoute, SupervisorServiceAdvertisement}; +use openshell_core::{ObjectId, ObjectWorkspace}; +use prost::Message as _; +use serde::{Deserialize, Serialize}; +use tonic::Status; + +use crate::ServerState; +use crate::compute::{COMPUTE_RUNTIME_IDENTITY_ANNOTATION, MutationScope}; +use crate::persistence::{ObjectCursor, WriteCondition, current_time_ms}; +use crate::supervisor_owner::{OWNER_TTL, OwnerGuard, SupervisorOwnerIndex}; + +pub const OBJECT_TYPE: &str = "supervisor_service_installation"; +pub const LEASE: Duration = Duration::from_secs(15); +pub const REPORT_INTERVAL: Duration = Duration::from_secs(5); +const MAX_ROUTES: usize = 1024; + +/// A challenge is session-local and single-use. Its monotonic deadline starts +/// before transmission, so delayed answers never receive a fresh receipt lease. +pub struct LeaseWindow { + sequence: u64, + deadline: tokio::time::Instant, + expires_at_ms: i64, + answered: bool, +} + +impl Default for LeaseWindow { + fn default() -> Self { + Self { + sequence: 0, + deadline: tokio::time::Instant::now(), + expires_at_ms: 0, + answered: true, + } + } +} + +impl LeaseWindow { + pub fn issue(&mut self) -> Option { + self.sequence = self.sequence.checked_add(1)?; + self.deadline = tokio::time::Instant::now() + LEASE; + self.expires_at_ms = + current_time_ms() + i64::try_from(LEASE.as_millis()).expect("bounded lease"); + self.answered = false; + Some(openshell_core::proto::ServiceRouteLease { + sequence: self.sequence, + duration: openshell_core::time::duration_from_std(LEASE).ok(), + expires_at: openshell_core::time::timestamp_from_millis(self.expires_at_ms).ok(), + }) + } + + pub fn claim(&mut self, sequence: u64) -> Option { + if self.answered + || sequence != self.sequence + || tokio::time::Instant::now() >= self.deadline + { + return None; + } + self.answered = true; + Some(self.expires_at_ms) + } +} + +/// One record per immutable sandbox identity. These records are ephemeral: +/// reads revalidate owner, configuration, generation, and expiry. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Installation { + pub sandbox_id: String, + pub session_id: String, + pub supervisor_instance_id: String, + pub connection_epoch: u64, + pub runtime_identity: String, + #[serde(default)] + pub ownership_epoch: u64, + #[serde(default)] + pub pool_route: String, + pub service_domain: String, + pub configuration_instance_id: String, + pub config_revision: u64, + pub sequence: u64, + pub host: String, + pub port: u32, + pub expires_at_ms: i64, + pub routes: Vec>, +} + +fn runtime_identity(sandbox: &Sandbox) -> Result<&str, Status> { + sandbox + .metadata + .as_ref() + .and_then(|meta| meta.annotations.get(COMPUTE_RUNTIME_IDENTITY_ANNOTATION)) + .filter(|identity| !identity.is_empty()) + .map(String::as_str) + .ok_or_else(|| Status::failed_precondition("sandbox runtime identity unavailable")) +} + +fn validate_routes( + report: &SupervisorServiceAdvertisement, + declared: &[SandboxServiceRoute], +) -> Result<(), Status> { + if report.routes.len() > MAX_ROUTES { + return Err(Status::resource_exhausted( + "too many installed service routes", + )); + } + let mut ids = std::collections::BTreeSet::new(); + for route in &report.routes { + if !ids.insert(&route.endpoint_id) + || route.endpoint_id.is_empty() + || !declared.contains(route) + { + return Err(Status::failed_precondition( + "installed service route is not a current declaration", + )); + } + } + // Nonempty reports are complete replacements, never partial declarations. + if !report.routes.is_empty() && report.routes.len() != declared.len() { + return Err(Status::failed_precondition( + "incomplete installed service report", + )); + } + Ok(()) +} + +fn sandbox_term_matches( + sandbox: &Sandbox, + epoch: Option, +) -> bool { + let stored = sandbox.metadata.as_ref().and_then(|m| { + m.annotations + .get(crate::auth::sandbox_session::AUTH_EPOCH_ANNOTATION) + }); + match (stored, epoch) { + (None, None) => true, + (Some(value), Some(epoch)) => value.parse::().ok() == Some(epoch.get()), + _ => false, + } +} + +async fn current_endpoint( + state: &ServerState, + sandbox: &Sandbox, + owner: &OwnerGuard, +) -> Result, Status> { + if !sandbox_term_matches(sandbox, owner.auth_epoch) { + return Ok(None); + } + let Some(endpoint) = state.compute.supervisor_app_endpoint(sandbox).await? else { + return Ok(None); + }; + if !endpoint.pool_route.is_empty() + && (owner + .auth_epoch + .map_or(0, openshell_core::jwt::CredentialEpoch::get) + != endpoint.ownership_epoch + || !crate::supervisor_pool::app_endpoint_current(&state.store, sandbox, &endpoint) + .await + .map_err(|_| Status::unavailable("pool application authority unavailable"))?) + { + return Ok(None); + } + Ok(Some(endpoint)) +} + +/// A serving lease is issued only while the session still has durable runtime +/// authority. This check is repeated for every lease, independently of config. +pub async fn may_lease(state: &ServerState, owner: &OwnerGuard) -> Result { + let Some(sandbox) = state + .store + .get_message::(&owner.sandbox_id) + .await + .map_err(|_| Status::unavailable("sandbox storage unavailable"))? + else { + return Ok(false); + }; + if sandbox + .metadata + .as_ref() + .is_some_and(|m| m.deletion_time.is_some()) + { + return Ok(false); + } + let current = SupervisorOwnerIndex::new(state.store.clone(), OWNER_TTL) + .read(&owner.sandbox_id) + .await + .map_err(|_| Status::unavailable("supervisor ownership unavailable"))?; + if current.is_none_or(|current| { + !current.is_fresh(OWNER_TTL) + || current.session_id != owner.session_id + || current.auth_epoch != owner.auth_epoch + || current.supervisor_instance_id != owner.supervisor_instance_id + || current.connection_epoch != owner.connection_epoch + }) { + return Ok(false); + } + Ok(current_endpoint(state, &sandbox, owner).await?.is_some()) +} + +pub async fn accept( + state: &Arc, + owner: &OwnerGuard, + report: &SupervisorServiceAdvertisement, + expires_at_ms: i64, +) -> Result<(), Status> { + if !state.config.supervisor_app_listener.enabled { + return Ok(()); + } + if report.routes.len() > MAX_ROUTES { + return Err(Status::resource_exhausted( + "too many installed service routes", + )); + } + let initial = state + .store + .get_message::(&owner.sandbox_id) + .await + .map_err(|_| Status::unavailable("sandbox storage unavailable"))? + .ok_or_else(|| Status::not_found("sandbox removed"))?; + let _guard = state + .compute + .mutation_guard(MutationScope::sandbox( + initial.object_workspace(), + &owner.sandbox_id, + )) + .await + .map_err(|_| Status::unavailable("service installation lock unavailable"))?; + let sandbox = state + .store + .get_message::(&owner.sandbox_id) + .await + .map_err(|_| Status::unavailable("sandbox storage unavailable"))? + .ok_or_else(|| Status::not_found("sandbox removed"))?; + if sandbox + .metadata + .as_ref() + .is_some_and(|m| m.deletion_time.is_some()) + { + return Err(Status::failed_precondition("sandbox deleting")); + } + let index = SupervisorOwnerIndex::new(state.store.clone(), OWNER_TTL); + let current = index + .read(&owner.sandbox_id) + .await + .map_err(|_| Status::unavailable("supervisor ownership unavailable"))? + .ok_or_else(|| Status::failed_precondition("supervisor ownership missing"))?; + if !current.is_fresh(OWNER_TTL) + || current.session_id != owner.session_id + || current.supervisor_instance_id != owner.supervisor_instance_id + || current.connection_epoch != owner.connection_epoch + || current.auth_epoch != owner.auth_epoch + { + return Err(Status::failed_precondition("supervisor ownership replaced")); + } + let mut record = Installation { + sandbox_id: owner.sandbox_id.clone(), + session_id: owner.session_id.clone(), + supervisor_instance_id: owner.supervisor_instance_id.clone(), + connection_epoch: owner.connection_epoch, + runtime_identity: runtime_identity(&sandbox)?.to_string(), + ownership_epoch: owner + .auth_epoch + .map_or(0, openshell_core::jwt::CredentialEpoch::get), + pool_route: String::new(), + service_domain: report.service_domain.clone(), + configuration_instance_id: report.configuration_instance_id.clone(), + config_revision: report.config_revision, + sequence: report.lease_sequence, + host: String::new(), + port: 0, + expires_at_ms, + routes: report + .routes + .iter() + .map(prost::Message::encode_to_vec) + .collect(), + }; + if !report.routes.is_empty() { + if report.service_domain != state.config.supervisor_app_listener.domain { + return Err(Status::failed_precondition( + "installed listener domain is not current", + )); + } + let config = super::policy::build_sandbox_config_snapshot(state, &sandbox).await?; + if !config.configuration_admitted + || config.configuration_instance_id != report.configuration_instance_id + || config.config_revision != report.config_revision + { + return Err(Status::failed_precondition( + "installed configuration is not current", + )); + } + validate_routes(report, &config.service_routes)?; + let endpoint = current_endpoint(state, &sandbox, owner) + .await? + .ok_or_else(|| { + Status::failed_precondition("trusted supervisor app endpoint unavailable") + })?; + if endpoint.runtime_identity != record.runtime_identity + || endpoint.port != u32::from(state.config.supervisor_app_listener.port) + || endpoint.host.parse::().is_err() + { + return Err(Status::failed_precondition( + "driver endpoint does not match current runtime", + )); + } + record.pool_route = endpoint.pool_route; + record.host = endpoint.host; + record.port = endpoint.port; + } + // Recheck after external calls: a session replacement need not take the + // sandbox mutation lock. Readers make the same ownership check. + let current = index + .read(&owner.sandbox_id) + .await + .map_err(|_| Status::unavailable("supervisor ownership unavailable"))? + .ok_or_else(|| Status::failed_precondition("supervisor ownership missing"))?; + if !current.is_fresh(OWNER_TTL) + || current.session_id != record.session_id + || current.supervisor_instance_id != record.supervisor_instance_id + || current.connection_epoch != record.connection_epoch + || current.auth_epoch != owner.auth_epoch + { + return Err(Status::failed_precondition("supervisor ownership replaced")); + } + if expires_at_ms <= current_time_ms() { + return Err(Status::failed_precondition( + "service installation lease expired", + )); + } + let installation_id = format!("service-installation:{}", sandbox.object_id()); + let previous = state + .store + .get(OBJECT_TYPE, &installation_id) + .await + .map_err(|_| Status::unavailable("service installation storage unavailable"))?; + if let Some(previous) = &previous { + let old: Installation = serde_json::from_slice(&previous.payload) + .map_err(|_| Status::internal("invalid stored installation"))?; + if old.session_id == record.session_id && old.sequence >= record.sequence { + return Err(Status::failed_precondition( + "service installation report replayed", + )); + } + } + let payload = + serde_json::to_vec(&record).map_err(|_| Status::internal("encode service installation"))?; + state + .store + .put_if( + OBJECT_TYPE, + &installation_id, + sandbox.object_id(), + sandbox.object_workspace(), + &payload, + None, + previous.map_or(WriteCondition::MustCreate, |p| { + WriteCondition::MatchResourceVersion(p.resource_version) + }), + ) + .await + .map_err(|_| Status::unavailable("service installation write failed"))?; + Ok(()) +} + +/// Discovery must call this before publishing each persisted installation. +/// Freshness failures are absence; storage/decoding failures are errors. +pub async fn is_current(state: &ServerState, record: &Installation) -> Result { + if !state.config.supervisor_app_listener.enabled + || record.expires_at_ms <= current_time_ms() + || record.service_domain != state.config.supervisor_app_listener.domain + || record.routes.is_empty() + { + return Ok(false); + } + let Some(sandbox) = state + .store + .get_message::(&record.sandbox_id) + .await + .map_err(|_| Status::unavailable("sandbox storage unavailable"))? + else { + return Ok(false); + }; + if sandbox + .metadata + .as_ref() + .is_some_and(|m| m.deletion_time.is_some()) + || runtime_identity(&sandbox)? != record.runtime_identity + { + return Ok(false); + } + let Some(owner) = SupervisorOwnerIndex::new(state.store.clone(), OWNER_TTL) + .read(&record.sandbox_id) + .await + .map_err(|_| Status::unavailable("supervisor ownership unavailable"))? + else { + return Ok(false); + }; + if !owner.is_fresh(OWNER_TTL) + || owner.session_id != record.session_id + || owner.supervisor_instance_id != record.supervisor_instance_id + || owner.connection_epoch != record.connection_epoch + || owner + .auth_epoch + .map_or(0, openshell_core::jwt::CredentialEpoch::get) + != record.ownership_epoch + || !sandbox_term_matches(&sandbox, owner.auth_epoch) + { + return Ok(false); + } + if !record.pool_route.is_empty() { + let endpoint = openshell_core::proto::compute::v1::SupervisorAppEndpoint { + host: record.host.clone(), + port: record.port, + runtime_identity: record.runtime_identity.clone(), + pool_route: record.pool_route.clone(), + ownership_epoch: record.ownership_epoch, + }; + if !crate::supervisor_pool::app_endpoint_current(&state.store, &sandbox, &endpoint) + .await + .map_err(|_| Status::unavailable("pool application authority unavailable"))? + { + return Ok(false); + } + } + let config = super::policy::build_sandbox_config_snapshot(state, &sandbox).await?; + if !config.configuration_admitted + || config.configuration_instance_id != record.configuration_instance_id + || config.config_revision != record.config_revision + { + return Ok(false); + } + let routes = record + .routes + .iter() + .map(|bytes| SandboxServiceRoute::decode(bytes.as_slice())) + .collect::, _>>() + .map_err(|_| Status::internal("invalid stored service route"))?; + let report = SupervisorServiceAdvertisement { + routes, + ..Default::default() + }; + Ok(validate_routes(&report, &config.service_routes).is_ok()) +} + +pub fn spawn_cleanup(state: Arc, mut shutdown: tokio::sync::watch::Receiver) { + tokio::spawn(async move { + let mut cursor: Option = None; + let mut interval = tokio::time::interval(Duration::from_secs(30)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + loop { + tokio::select! { + _ = shutdown.changed() => return, + _ = interval.tick() => {} + } + let sweep = async { + let page = state + .store + .list_by_type_after(OBJECT_TYPE, cursor.as_ref(), 128) + .await + .map_err(|_| ())?; + let next = (page.len() == 128) + .then(|| ObjectCursor::from(page.last().expect("full page"))); + for record in page { + let installation: Installation = + serde_json::from_slice(&record.payload).map_err(|_| ())?; + if installation.expires_at_ms <= current_time_ms() + || matches!(is_current(&state, &installation).await, Ok(false)) + { + // Conditional deletion cannot erase a concurrent renewal. + let _ = state + .store + .delete_if(OBJECT_TYPE, &record.id, record.resource_version) + .await; + } + } + Ok::<_, ()>(next) + }; + tokio::select! { + _ = shutdown.changed() => return, + result = tokio::time::timeout(Duration::from_secs(5), sweep) => { + if let Ok(Ok(next)) = result { cursor = next; } + } + } + } + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + fn route() -> SandboxServiceRoute { + SandboxServiceRoute { + endpoint_id: "endpoint".into(), + name: "web".into(), + target_port: 8080, + authorization_mode: 1, + resource_version: 2, + } + } + + #[tokio::test(start_paused = true)] + async fn lease_is_single_use_and_delayed_or_superseded_reports_do_not_extend_it() { + let mut window = LeaseWindow::default(); + assert!(window.claim(0).is_none()); + let first = window.issue().unwrap(); + let first_expiry = window.expires_at_ms; + tokio::time::advance(Duration::from_secs(8)).await; + assert_eq!(window.claim(first.sequence), Some(first_expiry)); + assert!(window.claim(first.sequence).is_none()); + let second = window.issue().unwrap(); + assert!(window.claim(first.sequence).is_none()); + tokio::time::advance(LEASE).await; + assert!(window.claim(second.sequence).is_none()); + } + + #[tokio::test] + async fn withdrawals_persist_but_old_sessions_and_replays_are_rejected() { + use openshell_core::proto::datamodel::v1::ObjectMeta; + let mut state = crate::grpc::test_support::test_server_state().await; + Arc::get_mut(&mut state) + .unwrap() + .config + .supervisor_app_listener + .enabled = true; + let sandbox = Sandbox { + metadata: Some(ObjectMeta { + id: "sandbox".into(), + name: "demo".into(), + workspace: "default".into(), + annotations: [( + COMPUTE_RUNTIME_IDENTITY_ANNOTATION.into(), + "runtime-a".into(), + )] + .into(), + ..Default::default() + }), + ..Default::default() + }; + state.store.put_message(&sandbox).await.unwrap(); + let owners = SupervisorOwnerIndex::new(state.store.clone(), OWNER_TTL); + let first = owners + .publish( + "sandbox", + "session-a", + "instance", + 1, + "replica-a", + "local://replica-a", + ) + .await + .unwrap(); + let report = SupervisorServiceAdvertisement { + lease_sequence: 1, + ..Default::default() + }; + let expires = current_time_ms() + 10_000; + accept(&state, &first, &report, expires).await.unwrap(); + let stored = state + .store + .get(OBJECT_TYPE, "service-installation:sandbox") + .await + .unwrap() + .unwrap(); + let installation: Installation = serde_json::from_slice(&stored.payload).unwrap(); + assert_eq!(installation.session_id, "session-a"); + assert!(installation.routes.is_empty()); + assert!(!is_current(&state, &installation).await.unwrap()); + assert_eq!( + accept(&state, &first, &report, expires) + .await + .unwrap_err() + .code(), + tonic::Code::FailedPrecondition + ); + let second = owners + .publish( + "sandbox", + "session-b", + "instance", + 2, + "replica-b", + "local://replica-b", + ) + .await + .unwrap(); + let later = SupervisorServiceAdvertisement { + lease_sequence: 2, + ..Default::default() + }; + assert!(accept(&state, &first, &later, expires).await.is_err()); + accept(&state, &second, &report, expires).await.unwrap(); + assert!( + accept(&state, &second, &later, current_time_ms() - 1) + .await + .is_err() + ); + state.store.close().await; + assert_eq!( + accept(&state, &second, &later, expires) + .await + .unwrap_err() + .code(), + tonic::Code::Unavailable + ); + } + + pub async fn installed_fixture() + -> (Arc, OwnerGuard, SupervisorServiceAdvertisement) { + use openshell_core::proto::datamodel::v1::ObjectMeta; + use openshell_core::proto::{SandboxPolicy, SandboxSpec, ServiceEndpoint}; + let endpoint = openshell_core::proto::compute::v1::SupervisorAppEndpoint { + host: "10.0.0.7".into(), + port: 9443, + runtime_identity: "runtime-a".into(), + ..Default::default() + }; + let driver = Arc::new(crate::compute::NoopTestDriver::with_app_endpoint(endpoint)); + let mut state = crate::grpc::test_support::test_server_state_with_compute_driver( + "test", + driver.clone(), + ) + .await; + { + let inner = Arc::get_mut(&mut state).unwrap(); + inner.compute = inner + .compute + .clone() + .with_app_endpoint_resolver(Some(driver)); + } + Arc::get_mut(&mut state) + .unwrap() + .config + .supervisor_app_listener = openshell_core::app_listener::AppListenerConfig { + enabled: true, + port: 9443, + domain: "services.example.com".into(), + }; + let sandbox = Sandbox { + metadata: Some(ObjectMeta { + id: "sandbox".into(), + name: "demo".into(), + workspace: "default".into(), + annotations: [( + COMPUTE_RUNTIME_IDENTITY_ANNOTATION.into(), + "runtime-a".into(), + )] + .into(), + ..Default::default() + }), + spec: Some(SandboxSpec { + policy: Some(SandboxPolicy::default()), + ..Default::default() + }), + ..Default::default() + }; + state.store.put_message(&sandbox).await.unwrap(); + let endpoint = ServiceEndpoint { + metadata: Some(ObjectMeta { + id: "endpoint".into(), + name: "demo--web".into(), + workspace: "default".into(), + labels: [("sandbox".into(), "demo".into())].into(), + ..Default::default() + }), + sandbox_id: "sandbox".into(), + sandbox: "demo".into(), + name: "web".into(), + target_port: 8080, + domain: true, + authorization_mode: 1, + }; + state.store.put_message(&endpoint).await.unwrap(); + let owner = SupervisorOwnerIndex::new(state.store.clone(), OWNER_TTL) + .publish( + "sandbox", + "session", + "instance", + 1, + "replica", + "local://replica", + ) + .await + .unwrap(); + let config = super::super::policy::build_sandbox_config_snapshot(&state, &sandbox) + .await + .unwrap(); + assert!( + config.configuration_admitted, + "{}", + config.configuration_error + ); + let report = SupervisorServiceAdvertisement { + service_domain: "services.example.com".into(), + lease_sequence: 1, + configuration_instance_id: config.configuration_instance_id, + config_revision: config.config_revision, + routes: config.service_routes, + }; + (state, owner, report) + } + + #[tokio::test] + async fn accepted_installs_use_trusted_endpoints_and_withdraw_on_declaration_or_owner_change() { + let (state, owner, mut report) = installed_fixture().await; + accept(&state, &owner, &report, current_time_ms() + 10_000) + .await + .unwrap(); + let stored = state + .store + .get(OBJECT_TYPE, "service-installation:sandbox") + .await + .unwrap() + .unwrap(); + let installation: Installation = serde_json::from_slice(&stored.payload).unwrap(); + assert_eq!(installation.host, "10.0.0.7"); + assert_eq!(installation.port, 9443); + assert!(is_current(&state, &installation).await.unwrap()); + report.lease_sequence += 1; + report.routes[0].resource_version += 1; + assert!( + accept(&state, &owner, &report, current_time_ms() + 10_000) + .await + .is_err() + ); + let mut endpoint = state + .store + .get_message::("endpoint") + .await + .unwrap() + .unwrap(); + endpoint.target_port = 9090; + state.store.put_message(&endpoint).await.unwrap(); + assert!(!is_current(&state, &installation).await.unwrap()); + let index = SupervisorOwnerIndex::new(state.store.clone(), OWNER_TTL); + index + .publish( + "sandbox", + "replacement", + "instance", + 2, + "other-replica", + "local://other", + ) + .await + .unwrap(); + assert!( + accept(&state, &owner, &report, current_time_ms() + 10_000) + .await + .is_err() + ); + } + + #[test] + fn reports_cannot_broaden_or_partially_install_declarations() { + let declared = route(); + let mut report = SupervisorServiceAdvertisement { + routes: vec![declared.clone()], + ..Default::default() + }; + validate_routes(&report, std::slice::from_ref(&declared)).unwrap(); + report.routes[0].target_port = 9090; + assert!(validate_routes(&report, std::slice::from_ref(&declared)).is_err()); + report.routes = vec![declared.clone(), declared.clone()]; + assert!(validate_routes(&report, std::slice::from_ref(&declared)).is_err()); + report.routes = vec![declared.clone()]; + let mut second = declared.clone(); + second.endpoint_id = "second".into(); + second.name = "other".into(); + assert!(validate_routes(&report, &[declared.clone(), second]).is_err()); + report.routes.clear(); + validate_routes(&report, &[declared]).unwrap(); + } +} diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index 655ed9bd12..ab42a981d4 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -1124,6 +1124,7 @@ pub(crate) async fn run_server( ); ssh_sessions::spawn_session_reaper(store.clone(), Duration::from_hours(1)); supervisor_session::spawn_relay_reaper(state.clone(), Duration::from_secs(30)); + grpc::service_advertisement::spawn_cleanup(state.clone(), shutdown_rx.clone()); provider_profile_sources::spawn_refresh(state.clone()); provider_refresh::spawn_refresh_worker(state.clone(), Duration::from_mins(1)); @@ -1389,7 +1390,7 @@ async fn terminate_signal() { pub use compute::{ AcquiredRemoteDriverEndpoint, DriverWatchStream, ManagedDriverProcess, SharedComputeDriver, - SharedComputeDriverConfigurationUpdates, + SharedComputeDriverAppEndpoints, SharedComputeDriverConfigurationUpdates, }; /// Driver instance returned by a compiled compute-driver factory. @@ -1767,12 +1768,21 @@ pub struct ComputeDriverBuildContext<'a> { config: ComputeDriverConfigContext<'a>, shutdown_rx: watch::Receiver, shared_supervision: &'a OnceLock, + app_endpoint_resolver: &'a OnceLock, /// Recovery deadline from `[openshell.gateway.supervisor_pool]`, when /// configured. supervisor_recovery_timeout_secs: Option, } impl ComputeDriverBuildContext<'_> { + /// Register an optional in-process application endpoint resolver during build. + /// Existing factories need not provide this extension. + pub fn register_app_endpoints(&self, resolver: SharedComputeDriverAppEndpoints) -> Result<()> { + self.app_endpoint_resolver + .set(resolver) + .map_err(|_| Error::config("application endpoint resolver already registered")) + } + /// The gateway's coordination of the shared supervisor pool, for an /// in-process driver whose configuration places new generations on it. /// The gateway owns the desired set and ownership durably, serves each @@ -1927,6 +1937,7 @@ async fn build_compute_runtime( let runtime = match driver { ConfiguredComputeDriver::Registered(registration) => { let shared_supervision = OnceLock::new(); + let app_endpoint_resolver = OnceLock::new(); let build_context = ComputeDriverBuildContext { config: ComputeDriverConfigContext { driver_name: ®istration.name, @@ -1937,6 +1948,7 @@ async fn build_compute_runtime( }, shutdown_rx, shared_supervision: &shared_supervision, + app_endpoint_resolver: &app_endpoint_resolver, supervisor_recovery_timeout_secs: supervisor_pool .and_then(|pool| pool.recovery_timeout_secs), }; @@ -1966,7 +1978,8 @@ async fn build_compute_runtime( Error::execution(format!("failed to create compute runtime: {error}")) })? .with_supervisor_host(supervisor_host) - .with_image_update_driver(image_updates), + .with_image_update_driver(image_updates) + .with_app_endpoint_resolver(app_endpoint_resolver.into_inner()), ComputeDriverInstance::ManagedRemote(_) if supervisor_host.is_some() => { return Err(Error::config( "only an in-process compute driver can use the gateway's shared supervision", @@ -2526,6 +2539,7 @@ mod tests { }, shutdown_rx: shutdown_rx.clone(), shared_supervision: &shared, + app_endpoint_resolver: &std::sync::OnceLock::new(), supervisor_recovery_timeout_secs: configured, }; // The driver asks for shared supervision as before; it is the diff --git a/crates/openshell-server/src/storage_proto.rs b/crates/openshell-server/src/storage_proto.rs index 5f7d87555b..d8da420871 100644 --- a/crates/openshell-server/src/storage_proto.rs +++ b/crates/openshell-server/src/storage_proto.rs @@ -178,7 +178,7 @@ mod tests { // image wire identity, now with explicit presence. Compute configuration // updates use a separate capability/service and add one classified RPC. const PUBLIC_RPC_SCHEMA_SHA256: &str = - "33e32bb6cb2251b3e75c6425555502e6c2fd675b640094437ef06856a46866e3"; + "3b4c2af6cf22e7ed1d84279ff865acb0db7199717bee9bc041caef791334104d"; const DURABLE_SCHEMA_SHA256: &str = "cb15f8f79a855064a43fe8c6cd908bf07eca6604e2bf8ab7d2de0ef33e97da4e"; const PUBLIC_DURABLE_OVERLAP_SHA256: &str = @@ -652,7 +652,7 @@ mod tests { } assert_eq!( compiled_method_count, - 108 + PROVIDER_READINESS_RPC_SIGNATURES.len() + 109 + PROVIDER_READINESS_RPC_SIGNATURES.len() + PEER_OWNER_RPC_SIGNATURES.len() + PEER_CONFIG_RPC_SIGNATURES.len() + isolation_methods.len() @@ -723,7 +723,7 @@ mod tests { overlap_hash.as_str(), ), ( - (335, 31), + (337, 31), (100, 22), (86, 22), PUBLIC_RPC_SCHEMA_SHA256, diff --git a/crates/openshell-server/src/supervisor_pool.rs b/crates/openshell-server/src/supervisor_pool.rs index cd2dc478bc..b220b6398a 100644 --- a/crates/openshell-server/src/supervisor_pool.rs +++ b/crates/openshell-server/src/supervisor_pool.rs @@ -48,14 +48,14 @@ use std::future::Future; use std::sync::{Arc, Mutex}; use std::time::Duration; -use openshell_core::ObjectWorkspace as _; use openshell_core::jwt::{SandboxId, SupervisorTakeover}; use openshell_core::proto::datamodel::v1::{CredentialHandle, ObjectMeta, Provider}; use openshell_core::proto::{Sandbox, SandboxPhase}; use openshell_core::time::now_ms; +use openshell_core::{ObjectId as _, ObjectWorkspace as _}; use openshell_supervisor_control::pool_timing::RECORD_WRITE_BOUND; use openshell_supervisor_control::proto::{ - SandboxInstanceAssignment, SandboxInstanceStatus, SandboxInstanceTakeover, + SandboxInstanceAssignment, SandboxInstancePhase, SandboxInstanceStatus, SandboxInstanceTakeover, }; use openshell_supervisor_control::{ ConnectedMember, InstanceOwner, InstancePlacement, MAX_ASSIGNED_INSTANCES, MAX_POOL_INSTANCES, @@ -487,6 +487,96 @@ fn lock(mutex: &Mutex) -> std::sync::MutexGuard<'_, T> { .unwrap_or_else(std::sync::PoisonError::into_inner) } +/// Revalidate an application endpoint against durable pool authority, rather +/// than a replica's eventually refreshed placement cache. Never treats the +/// member's runtime port as an application port. +pub async fn app_endpoint_current( + store: &Store, + sandbox: &Sandbox, + endpoint: &openshell_core::proto::compute::v1::SupervisorAppEndpoint, +) -> Result { + let Some(metadata) = &sandbox.metadata else { + return Ok(false); + }; + let identity = PersistedSandboxIdentity::read(&metadata.annotations) + .map_err(|error| PersistenceError::Decode(error.to_string()))?; + let Some(assignment_row) = store + .get_by_name(ASSIGNMENT_OBJECT_TYPE, "", &endpoint.pool_route) + .await? + else { + return Ok(false); + }; + let assignment: AssignmentPayload = serde_json::from_slice(&assignment_row.payload) + .map_err(|error| PersistenceError::Decode(error.to_string()))?; + if assignment.route != endpoint.pool_route + || assignment.sandbox_id != sandbox.object_id() + || assignment.generation != identity.runtime_generation.as_str() + || assignment.phase != RecordPhase::Published + { + return Ok(false); + } + let Some(row) = store + .get(OWNER_OBJECT_TYPE, &owner_object_id(&endpoint.pool_route)) + .await? + else { + return Ok(false); + }; + let owner: OwnerPayload = serde_json::from_slice(&row.payload) + .map_err(|error| PersistenceError::Decode(error.to_string()))?; + if owner.route != endpoint.pool_route + || owner.sandbox_id != sandbox.object_id() + || owner.generation != identity.runtime_generation.as_str() + || owner.phase != OwnerPhase::Granted + || owner.auth_epoch != endpoint.ownership_epoch + || owner.auth_epoch != identity.auth_epoch.get() + { + return Ok(false); + } + let Some(member_row) = store + .get(MEMBER_OBJECT_TYPE, &member_object_id(&owner.member_id)) + .await? + else { + return Ok(false); + }; + if now_ms().saturating_sub(member_row.updated_at_ms) + >= i64::try_from(MEMBER_TTL.as_millis()).expect("bounded member TTL") + { + return Ok(false); + } + let member: MemberPayload = serde_json::from_slice(&member_row.payload) + .map_err(|error| PersistenceError::Decode(error.to_string()))?; + if member.member_id != owner.member_id + || member.process_id != owner.process_id + || member.leaving + || member + .runtime_address + .parse::() + .ok() + .map(|a| a.ip()) + != endpoint.host.parse::().ok() + { + return Ok(false); + } + let Some(report_row) = store + .get( + REPORT_OBJECT_TYPE, + &report_object_id(&owner.route, owner.auth_epoch), + ) + .await? + else { + return Ok(false); + }; + let report: ReportPayload = serde_json::from_slice(&report_row.payload) + .map_err(|error| PersistenceError::Decode(error.to_string()))?; + Ok(report.route == owner.route + && report.sandbox_id == owner.sandbox_id + && report.member_id == owner.member_id + && report.process_id == owner.process_id + && report.auth_epoch == owner.auth_epoch + && report.ready + && report.phase == SandboxInstancePhase::Supervising as i32) +} + /// The ring over live members that are not leaving. fn ring_of(members: &[LiveMember]) -> GatewayRing { GatewayRing::new( @@ -5133,6 +5223,104 @@ mod tests { assert_eq!((row.ready, row.connected_at_ms), (true, 2_000)); } + #[tokio::test] + async fn application_endpoints_require_current_acknowledged_owner_and_live_member() { + let fixture = Fixture::new().await; + fixture.member("m1", "p1", false).await; + let route = fixture.fill("m1", "p1", 1).await.remove(0); + let sandbox_id = fixture.owner(&route).await.unwrap().sandbox_id; + let mut sandbox = fixture + .store + .get_message::(&sandbox_id) + .await + .unwrap() + .unwrap(); + let mut endpoint = openshell_core::proto::compute::v1::SupervisorAppEndpoint { + host: "10.0.0.1".into(), + port: 9443, + pool_route: route.clone(), + ownership_epoch: 1, + ..Default::default() + }; + assert!( + !app_endpoint_current(&fixture.store, &sandbox, &endpoint) + .await + .unwrap() + ); + let report = ReportPayload { + route: route.clone(), + member_id: "m1".into(), + process_id: "p1".into(), + auth_epoch: 1, + sandbox_id: sandbox_id.clone(), + phase: SandboxInstancePhase::Supervising as i32, + ready: true, + exit_code: 0, + connected_at_ms: now_ms(), + }; + fixture.write_report(report.clone()).await; + assert!( + app_endpoint_current(&fixture.store, &sandbox, &endpoint) + .await + .unwrap() + ); + endpoint.host = "10.0.0.2".into(); + assert!( + !app_endpoint_current(&fixture.store, &sandbox, &endpoint) + .await + .unwrap() + ); + endpoint.host = "10.0.0.1".into(); + fixture.member("m1", "p1", true).await; + assert!( + !app_endpoint_current(&fixture.store, &sandbox, &endpoint) + .await + .unwrap() + ); + fixture.member("m1", "p1", false).await; + // Returning to the same host process is still a new ownership term. + grant_next_term(&fixture, &route, "m1", "p1").await; + sandbox.metadata.as_mut().unwrap().annotations.insert( + crate::auth::sandbox_session::AUTH_EPOCH_ANNOTATION.into(), + "2".into(), + ); + assert!( + !app_endpoint_current(&fixture.store, &sandbox, &endpoint) + .await + .unwrap() + ); + endpoint.ownership_epoch = 2; + assert!( + !app_endpoint_current(&fixture.store, &sandbox, &endpoint) + .await + .unwrap(), + "old acknowledged report cannot authorize the new term" + ); + fixture + .write_report(ReportPayload { + auth_epoch: 2, + ..report + }) + .await; + assert!( + app_endpoint_current(&fixture.store, &sandbox, &endpoint) + .await + .unwrap() + ); + fixture.member("m1", "p2", false).await; + assert!( + !app_endpoint_current(&fixture.store, &sandbox, &endpoint) + .await + .unwrap() + ); + fixture.remove_owner_record(&route).await; + assert!( + !app_endpoint_current(&fixture.store, &sandbox, &endpoint) + .await + .unwrap() + ); + } + #[tokio::test] async fn reports_count_only_for_their_owners_term() { let fixture = Fixture::new().await; diff --git a/crates/openshell-server/src/supervisor_session.rs b/crates/openshell-server/src/supervisor_session.rs index 248f5fab7b..ccc17ac8de 100644 --- a/crates/openshell-server/src/supervisor_session.rs +++ b/crates/openshell-server/src/supervisor_session.rs @@ -2205,6 +2205,7 @@ struct ConfigPushSetup { /// publication cannot strand the record; the lifetime guard moves into the /// session task or drops with early cleanup. struct SessionSetup { + advertises_services: bool, state: Arc, sandbox_id: String, instance_id: String, @@ -2223,6 +2224,7 @@ struct SessionSetup { async fn accept_supervisor_session(setup: SessionSetup) -> Result<(), Status> { let SessionSetup { + advertises_services, state, sandbox_id, instance_id, @@ -2488,6 +2490,7 @@ async fn accept_supervisor_session(setup: SessionSetup) -> Result<(), Status> { tx: session_tx, supports_session_redirect, pushes_config, + advertises_services, }; tokio::spawn(async move { let _session_lifetime = session_lifetime; @@ -2623,6 +2626,8 @@ pub async fn handle_connect_supervisor( > = Box::pin(tokio_stream::StreamExt::map(ReceiverStream::new(rx), Ok)); let setup = SessionSetup { + advertises_services: state.config.supervisor_app_listener.enabled + && hello.supports_service_advertisements, state: Arc::clone(state), sandbox_id, instance_id: hello.instance_id, @@ -2897,6 +2902,7 @@ pub(crate) struct AcceptedSession { /// bootstrap it was accepted with. False for a supervisor that keeps /// polling. pub(crate) pushes_config: bool, + pub(crate) advertises_services: bool, } async fn run_session_loop( @@ -2921,6 +2927,9 @@ async fn run_session_loop( let bootstrap_timeout = tokio::time::sleep(Duration::from_mins(2)); tokio::pin!(bootstrap_timeout); let mut bootstrap_complete = !session.pushes_config; + let mut app_timer = tokio::time::interval(crate::grpc::service_advertisement::REPORT_INTERVAL); + app_timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + let mut app_lease = crate::grpc::service_advertisement::LeaseWindow::default(); loop { tokio::select! { @@ -2955,6 +2964,13 @@ async fn run_session_loop( if matches!(msg.payload.as_ref(), Some(supervisor_message::Payload::ConfigUpdateResult(_))) { bootstrap_complete = true; } + if let Some(supervisor_message::Payload::ServiceAdvertisement(report)) = &msg.payload { + if let Some(app_expiry) = app_lease.claim(report.lease_sequence) + && !matches!(tokio::time::timeout(Duration::from_secs(2), crate::grpc::service_advertisement::accept(state, owner_guard, report, app_expiry)).await, Ok(Ok(()))) { + debug!(sandbox_id, "service installation report rejected or unavailable"); + } + continue; + } if !handle_supervisor_message(session, msg.payload).await { break; } @@ -2985,6 +3001,14 @@ async fn run_session_loop( } } } + _ = app_timer.tick(), if session.advertises_services => { + if !matches!(tokio::time::timeout(Duration::from_secs(2), + crate::grpc::service_advertisement::may_lease(state, owner_guard)).await, + Ok(Ok(true))) { continue; } + let Some(lease) = app_lease.issue() else { break; }; + let message = GatewayMessage { payload: Some(gateway_message::Payload::ServiceRouteLease(lease)) }; + if tx.try_send(message).is_err() { break; } + } _ = heartbeat_timer.tick() => { let hb = GatewayMessage { payload: Some(gateway_message::Payload::Heartbeat( @@ -3235,6 +3259,7 @@ mod tests { let (tx, mut rx) = mpsc::channel(4); let mut inbound = futures::stream::pending::>(); let session = AcceptedSession { + advertises_services: false, state: Arc::clone(&state), sandbox_id: "sb-no-peer".into(), session_id: "session-no-peer".into(), @@ -5747,6 +5772,7 @@ mod tests { }; let mut inbound = futures::stream::iter([Ok(failure)]).chain(futures::stream::pending()); let session = AcceptedSession { + advertises_services: false, state: Arc::clone(&state), sandbox_id: "sb-replaced".into(), session_id: "session-a".into(), @@ -5837,6 +5863,7 @@ mod tests { }; let mut inbound = futures::stream::iter([Ok(heartbeat)]).chain(futures::stream::pending()); let session = AcceptedSession { + advertises_services: false, state: Arc::clone(&state), sandbox_id: "sb-moved".into(), session_id: "session-replaced".into(), diff --git a/crates/openshell-server/src/test_support.rs b/crates/openshell-server/src/test_support.rs index e12bd617a9..b1d5736a93 100644 --- a/crates/openshell-server/src/test_support.rs +++ b/crates/openshell-server/src/test_support.rs @@ -282,6 +282,7 @@ impl FakeComputeDriver { let task = tokio::spawn(async move { tonic::transport::Server::builder() .add_service(ComputeDriverServer::new(driver.clone())) + .add_service(openshell_core::proto::compute::v1::compute_driver_app_endpoints_server::ComputeDriverAppEndpointsServer::new(driver.clone())) .add_service(openshell_core::proto::compute::v1::compute_driver_configuration_updates_server::ComputeDriverConfigurationUpdatesServer::new(driver.clone())) .serve_with_incoming(UnixIncoming { listener }) .await @@ -592,3 +593,19 @@ impl ComputeDriver for FakeComputeDriver { Ok(Response::new(DeleteWorkspaceResponse {})) } } + +#[tonic::async_trait] +impl openshell_core::proto::compute::v1::compute_driver_app_endpoints_server::ComputeDriverAppEndpoints for FakeComputeDriver { + async fn resolve_supervisor_app_endpoint( + &self, + request: Request, + ) -> Result< + Response, + Status, + > { + self.record_traceparent(request.metadata()); + Err(Status::unimplemented( + "supervisor application endpoint resolution is not supported", + )) + } +} diff --git a/crates/openshell-supervisor-process/src/delegated.rs b/crates/openshell-supervisor-process/src/delegated.rs index 32e3fd4433..eecbb9edbe 100644 --- a/crates/openshell-supervisor-process/src/delegated.rs +++ b/crates/openshell-supervisor-process/src/delegated.rs @@ -93,6 +93,7 @@ pub async fn start_boundary_access( config_apply_tx: tokio::sync::mpsc::Sender, config_push_updates: tokio::sync::watch::Sender, host_key: Option, + app_installations: Option, ) -> Result { let instance_id = uuid::Uuid::new_v4().to_string(); let terminating = Arc::new(AtomicBool::new(false)); @@ -127,6 +128,7 @@ pub async fn start_boundary_access( terminating.clone(), crate::supervisor_session::SessionRuntimeContext { instance_id: instance_id.clone(), + app_installations, session_id_updates: supervisor_session_updates, config_apply_tx, config_push_updates, @@ -374,6 +376,7 @@ mod tests { tokio::sync::mpsc::channel(1).0, tokio::sync::watch::channel(false).0, None, + None, ) .await .expect("access without SSH is portable"); @@ -415,6 +418,7 @@ mod tests { tokio::sync::mpsc::channel(1).0, tokio::sync::watch::channel(false).0, None, + None, ) .await; assert!( diff --git a/crates/openshell-supervisor-process/src/supervisor_session.rs b/crates/openshell-supervisor-process/src/supervisor_session.rs index 71313e19c0..733631ebaa 100644 --- a/crates/openshell-supervisor-process/src/supervisor_session.rs +++ b/crates/openshell-supervisor-process/src/supervisor_session.rs @@ -120,6 +120,7 @@ fn unapplied_update(update_id: String, code: &str, message: &str) -> ConfigUpdat /// Runtime identity and status channel shared with a supervisor session task. pub struct SessionRuntimeContext { + pub app_installations: Option, /// Identifies the local supervisor process across gateway reconnects. pub instance_id: String, /// Publishes the currently accepted gateway session to sibling reporters. @@ -413,6 +414,7 @@ pub fn spawn_with_readiness( ) -> (tokio::task::JoinHandle<()>, watch::Receiver) { let (ready_tx, ready_rx) = watch::channel(false); let config = SessionConfig { + app_installations: runtime.app_installations, gateway, sandbox_id, ssh_socket_path, @@ -429,6 +431,7 @@ pub fn spawn_with_readiness( } struct SessionConfig { + app_installations: Option, gateway: grpc_client::GatewayEndpoint, sandbox_id: String, ssh_socket_path: std::path::PathBuf, @@ -445,6 +448,26 @@ struct SessionConfig { ready_tx: watch::Sender, } +fn service_route_lease_remaining( + lease: &openshell_core::proto::ServiceRouteLease, +) -> Option { + let duration = lease + .duration + .as_ref() + .and_then(|d| openshell_core::time::duration_to_std(d).ok())?; + if duration.is_zero() || duration > Duration::from_secs(15) { + return None; + } + let expiry = lease + .expires_at + .as_ref() + .and_then(|t| openshell_core::time::timestamp_to_millis(t).ok())?; + let remaining = expiry.checked_sub(openshell_core::time::now_ms())?; + let remaining = Duration::from_millis(u64::try_from(remaining).ok()?); + let duration = duration.min(remaining); + (!duration.is_zero() && duration <= Duration::from_secs(15)).then_some(duration) +} + async fn run_session_loop(config: SessionConfig) { let mut backoff = ReconnectBackoff::new(); let mut attempt: u64 = 0; @@ -611,6 +634,7 @@ async fn run_single_session( redirected, supports_session_redirect: true, supports_config_push: true, + supports_service_advertisements: config.app_installations.is_some(), })), }) .await @@ -716,6 +740,18 @@ async fn run_single_session( owner_replica_id: r.owner_replica_id.clone(), }); } + if let Some(gateway_message::Payload::ServiceRouteLease(lease)) = &msg.payload { + if let Some(source) = &config.app_installations { + let duration = service_route_lease_remaining(lease); + if let Some(duration) = duration.filter(|d| !d.is_zero() && *d <= Duration::from_secs(15)) { + let mut report = source(duration); + report.lease_sequence = lease.sequence; + tx.send(SupervisorMessage { payload: Some(supervisor_message::Payload::ServiceAdvertisement(report)) }) + .await.map_err(|_| "outbound channel closed")?; + } + } + continue; + } let context = GatewayMessageContext { sandbox_id: &config.sandbox_id, ssh_socket_path: &config.ssh_socket_path, @@ -1850,3 +1886,27 @@ mod ocsf_event_tests { .expect("bridge should finish cleanly on an ordinary round trip"); } } + +#[cfg(test)] +mod app_lease_tests { + use super::*; + + #[test] + fn queued_or_expired_challenges_never_get_a_new_full_lease() { + let mut lease = openshell_core::proto::ServiceRouteLease { + sequence: 1, + duration: openshell_core::time::duration_from_std(Duration::from_secs(15)).ok(), + expires_at: openshell_core::time::timestamp_from_millis( + openshell_core::time::now_ms() + 1000, + ) + .ok(), + }; + let remaining = service_route_lease_remaining(&lease).unwrap(); + assert!(remaining <= Duration::from_secs(1)); + lease.expires_at = + openshell_core::time::timestamp_from_millis(openshell_core::time::now_ms() - 1).ok(); + assert!(service_route_lease_remaining(&lease).is_none()); + lease.expires_at = None; + assert!(service_route_lease_remaining(&lease).is_none()); + } +} diff --git a/crates/openshell-supervisor/README.md b/crates/openshell-supervisor/README.md index bbd59fbb56..1acb665472 100644 --- a/crates/openshell-supervisor/README.md +++ b/crates/openshell-supervisor/README.md @@ -290,3 +290,26 @@ URL selection. Dedicated and shared supervisors expose the app listener on their Pod IPs without app Services. Clients must select the member owning their sandbox. HA takeover may preserve the workload generation; existing ingress streams are not migrated. Runtime ownership fencing must reject stale connectors. + +### Installed-route evidence + +An enabled listener offers an installation view to the existing authenticated +sandbox supervisor session. The gateway issues a single-use lease challenge +every five seconds. The instance reports the complete installed allowlist only +when its remaining serving freshness covers the challenge's fifteen-second +lease. Expiry starts at gateway challenge issuance, so transport delay cannot +extend publication past confirmed listener freshness. Empty reports withdraw +installation evidence; suspended, expired, stopped, or not-yet-serving instances +cannot report live routes. + +Reports contain declaration identities/revisions and configuration identity, +not backend addresses. The gateway obtains endpoints from the compute driver +and validates runtime identity, declarations, and session ownership before +persisting ephemeral records. Shared Kubernetes pools resolve the acknowledged +owner's Pod IP and app port. Durable pool ownership, credential epoch, membership, +and acknowledged reports fence installation acceptance and discovery. Shared +listeners additionally require a gateway-issued serving lease, capped at fifteen +seconds from issuance, independently of configuration freshness. Expiry closes +streams; later renewal cannot revive them. Takeover preserves the workload and +public hostname while replacing the app endpoint. Clients reconnect after an +interruption; sockets are not migrated. diff --git a/crates/openshell-supervisor/src/app_listener.rs b/crates/openshell-supervisor/src/app_listener.rs index a2e95c38ba..c486f55b3b 100644 --- a/crates/openshell-supervisor/src/app_listener.rs +++ b/crates/openshell-supervisor/src/app_listener.rs @@ -122,7 +122,10 @@ struct Snapshot { generations: BTreeMap, next_generation: u64, expires: Option, + authority_expires: Option, workspace: String, + configuration_instance_id: String, + config_revision: u64, // Whole-sandbox suspension fence, independent of individual service edits. epoch: u64, } @@ -138,8 +141,10 @@ struct RouteToken { #[derive(Clone)] pub struct Routes { registration: Option>, + requires_authority: bool, sender: watch::Sender, stopped: Arc, + serving: Arc, #[cfg(test)] install_probe: Option>, } @@ -155,9 +160,11 @@ impl Default for Routes { Self { sender: watch::channel(Snapshot::default()).0, stopped: Arc::new(AtomicBool::new(false)), + serving: Arc::new(AtomicBool::new(false)), registration: None, #[cfg(test)] install_probe: None, + requires_authority: false, } } } @@ -170,14 +177,63 @@ impl Drop for ConfigurationGuard { } impl Routes { + pub(crate) fn installation_source(&self) -> openshell_core::app_listener::InstallationSource { + let routes = self.clone(); + Arc::new(move |duration| { + let snapshot = routes.sender.borrow(); + if routes.stopped.load(Ordering::Acquire) + || !routes.serving.load(Ordering::Acquire) + || snapshot.expires.is_none_or(|expiry| { + expiry.saturating_duration_since(Instant::now()) < duration + }) + { + return openshell_core::proto::SupervisorServiceAdvertisement::default(); + } + drop(snapshot); + if routes.requires_authority { + routes.sender.send_modify(|snapshot| { + if snapshot + .authority_expires + .is_none_or(|expiry| expiry <= Instant::now()) + { + // A later lease cannot revive a stream that already lost authority. + snapshot.epoch = snapshot.epoch.wrapping_add(1); + } + snapshot.authority_expires = Some(Instant::now() + duration); + }); + } + let snapshot = routes.sender.borrow(); + openshell_core::proto::SupervisorServiceAdvertisement { + service_domain: routes + .registration + .as_ref() + .map_or_else(String::new, |registration| { + registration.router.domain.clone() + }), + configuration_instance_id: snapshot.configuration_instance_id.clone(), + config_revision: snapshot.config_revision, + routes: snapshot.routes.values().cloned().collect(), + ..Default::default() + } + }) + } + pub(crate) fn configuration_guard(self) -> ConfigurationGuard { ConfigurationGuard(self) } + fn serving_expiry(&self, snapshot: &Snapshot) -> Option { + let config = snapshot.expires?; + if self.requires_authority { + Some(config.min(snapshot.authority_expires?)) + } else { + Some(config) + } + } + fn valid(&self, route: &SandboxServiceRoute, token: RouteToken) -> bool { let snapshot = self.sender.borrow(); - snapshot - .expires + self.serving_expiry(&snapshot) .is_some_and(|expiry| expiry > Instant::now()) && snapshot.epoch == token.epoch && snapshot.generations.get(&route.name) == Some(&token.generation) @@ -258,6 +314,10 @@ impl Routes { snapshot.epoch = snapshot.epoch.wrapping_add(1); } snapshot.workspace.clone_from(&result.workspace); + snapshot + .configuration_instance_id + .clone_from(&result.configuration_instance_id); + snapshot.config_revision = result.config_revision; snapshot.routes = routes; snapshot.generations = generations; snapshot.expires = Some(Instant::now() + FRESHNESS); @@ -294,6 +354,7 @@ impl Routes { snapshot.routes.clear(); snapshot.generations.clear(); snapshot.expires = None; + snapshot.authority_expires = None; snapshot.epoch = snapshot.epoch.wrapping_add(1); }); } @@ -311,7 +372,7 @@ impl Routes { encoded.strip_prefix(&format!("{prefix}--"))? }; let snapshot = self.sender.borrow(); - if snapshot.expires? <= Instant::now() { + if self.serving_expiry(&snapshot)? <= Instant::now() { return None; } Some(( @@ -334,7 +395,7 @@ impl Routes { { return; } - let Some(expiry) = snapshot.expires else { + let Some(expiry) = self.serving_expiry(&snapshot) else { return; }; expiry @@ -358,6 +419,7 @@ pub struct BoundListener { #[derive(Clone, Default)] pub struct Router { domain: String, + requires_authority: bool, registrations: Arc>>>, } @@ -441,6 +503,14 @@ impl HostHandle { pub fn router(&self) -> Router { self.router.clone() } + + /// Shared members require short gateway-issued ownership leases as well as + /// accepted configuration; configuration renewal alone cannot extend service. + pub(crate) fn leased_router(&self) -> Router { + let mut router = self.router.clone(); + router.requires_authority = true; + router + } } impl Drop for HostHandle { @@ -488,6 +558,7 @@ impl Router { Ok(InstanceListener { routes: Routes { registration: Some(registration), + requires_authority: self.requires_authority, ..Default::default() }, receiver, @@ -544,6 +615,7 @@ impl InstanceListener { /// Called on the sandbox instance runtime, preserving its OCSF/logging /// identity even when multiple sandboxes share one listener process. pub(crate) fn start(mut self, connector: Arc) -> Handle { + self.routes.serving.store(true, Ordering::Release); let retained_routes = self.routes.clone(); let task = tokio::spawn(async move { let routes = self.routes.clone(); @@ -863,6 +935,93 @@ mod tests { use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; use tokio_tungstenite::tungstenite::client::IntoClientRequest as _; + #[test] + fn installation_evidence_requires_a_serving_instance_and_sufficient_freshness() { + let routes = Routes::default(); + let source = routes.installation_source(); + let route = SandboxServiceRoute { + endpoint_id: "endpoint".into(), + name: "web".into(), + target_port: 8080, + authorization_mode: 1, + resource_version: 1, + }; + let mut config = installed(vec![route.clone()]); + config.config_revision = 42; + config.configuration_instance_id = "generation-a".into(); + routes.install(&config); + assert!(source(Duration::from_secs(15)).routes.is_empty()); + routes.serving.store(true, Ordering::Release); + let report = source(Duration::from_secs(15)); + assert_eq!(report.routes, vec![route]); + assert_eq!(report.config_revision, 42); + assert_eq!(report.configuration_instance_id, "generation-a"); + routes.sender.send_modify(|snapshot| { + snapshot.expires = Some(Instant::now() + Duration::from_secs(5)); + }); + assert!(source(Duration::from_secs(15)).routes.is_empty()); + routes.renew(); + assert!(!source(Duration::from_secs(15)).routes.is_empty()); + routes.suspend(); + routes.renew(); + assert!(source(Duration::from_secs(15)).routes.is_empty()); + routes.install(&config); + routes.stopped.store(true, Ordering::Release); + assert!(source(Duration::from_secs(15)).routes.is_empty()); + } + + #[tokio::test(start_paused = true)] + async fn shared_authority_expires_despite_config_renewal_and_fences_old_streams() { + let routes = Routes { + requires_authority: true, + ..Default::default() + }; + routes.serving.store(true, Ordering::Release); + let route = SandboxServiceRoute { + endpoint_id: "endpoint".into(), + name: "web".into(), + target_port: 8080, + authorization_mode: 1, + resource_version: 1, + }; + routes.install(&installed(vec![route.clone()])); + assert!( + routes + .resolve(HOST, "default--sandbox-a", "svc.openshell.internal") + .is_none() + ); + let source = routes.installation_source(); + assert_eq!(source(Duration::from_secs(15)).routes, vec![route.clone()]); + let (_, token) = routes + .resolve(HOST, "default--sandbox-a", "svc.openshell.internal") + .unwrap(); + tokio::time::advance(Duration::from_secs(10)).await; + routes.renew(); + assert!(routes.valid(&route, token)); + tokio::time::advance(Duration::from_secs(6)).await; + assert!(!routes.valid(&route, token)); + assert!( + routes + .resolve(HOST, "default--sandbox-a", "svc.openshell.internal") + .is_none() + ); + tokio::time::timeout(Duration::from_secs(1), routes.revoked(&route, token)) + .await + .unwrap(); + source(Duration::from_secs(15)); + assert!( + !routes.valid(&route, token), + "renewal cannot revive a prior stream" + ); + assert!( + routes + .resolve(HOST, "default--sandbox-a", "svc.openshell.internal") + .is_some() + ); + routes.suspend(); + assert!(source(Duration::from_secs(15)).routes.is_empty()); + } + #[test] fn listener_requires_explicit_enablement_and_complete_configuration() { let complete = BTreeMap::from([ diff --git a/crates/openshell-supervisor/src/lib.rs b/crates/openshell-supervisor/src/lib.rs index 5f38715cb6..611737ae6d 100644 --- a/crates/openshell-supervisor/src/lib.rs +++ b/crates/openshell-supervisor/src/lib.rs @@ -1730,6 +1730,9 @@ pub async fn run_sandbox_instance( config_apply_tx, config_push_updates, ssh_host_key, + app_routes + .as_ref() + .map(app_listener::Routes::installation_source), ) .instrument(tracing::info_span!(parent: &startup, "supervisor.access.start")) .await?; diff --git a/crates/openshell-supervisor/src/shared_host.rs b/crates/openshell-supervisor/src/shared_host.rs index 305ebb3ca2..5c7a425051 100644 --- a/crates/openshell-supervisor/src/shared_host.rs +++ b/crates/openshell-supervisor/src/shared_host.rs @@ -785,7 +785,7 @@ impl SharedHost { app_router: self .app_listener .as_ref() - .map(crate::app_listener::HostHandle::router), + .map(crate::app_listener::HostHandle::leased_router), command: main_process.command, workdir: registration.workdir, timeout_secs: 0, diff --git a/docs/how-it-works/gateways/configuration.mdx b/docs/how-it-works/gateways/configuration.mdx index 420bb6b848..3014b74bcb 100644 --- a/docs/how-it-works/gateways/configuration.mdx +++ b/docs/how-it-works/gateways/configuration.mdx @@ -1795,3 +1795,23 @@ unnamed service on sandbox `demo` in workspace `default` uses SNI and Host the owning supervisor while preserving that hostname. Automatic discovery and application URL publication are separate work; current CLI URLs still identify the existing gateway path. + +Enabled supervisors report installed service routes through their authenticated +gateway sessions. The gateway accepts only current configuration and ownership, +resolves the supervisor endpoint through the compute driver, and expires +installation evidence unless renewed. Reports do not establish application +health or confirm that external ingress is ready. No additional reporting port +or application credentials are required. Shared Kubernetes discovery selects the +acknowledged pool owner's Pod IP and app port. The gateway revalidates the +ownership term and live membership against shared storage. During takeover, +old installations are withdrawn and the new endpoint appears only after the +replacement installs the configuration. Application connections may need to +reconnect; the sandbox workload and public URL remain unchanged. + +Shared listeners require both current configuration and a gateway-issued serving +lease of at most fifteen seconds. Lease deadlines include queueing time; keep +supervisor and gateway clocks synchronized. Loss of serving authority closes +application streams even if configuration continues renewing. Under a partition, +allow for pool membership failure detection before the last serving lease expires. +Storage errors prevent lease renewal. Discovery expiry and serving expiry are +separate from external ingress readiness. diff --git a/e2e/kubernetes/app-listener/README.md b/e2e/kubernetes/app-listener/README.md index 24b64e3236..93dc17c0b6 100644 --- a/e2e/kubernetes/app-listener/README.md +++ b/e2e/kubernetes/app-listener/README.md @@ -174,3 +174,34 @@ subsequent discovery work. The local Rust listener tests use a real TLS client and backend RPC connector. They supplement this live cluster check; they do not establish HA pool recovery or real Kubernetes reachability. + +## Shared-supervisor HA and discovery + +Use the shared-supervisor HA overlay with the app-listener values and two gateway +replicas sharing PostgreSQL. Use a dedicated test release for destructive pool +checks. Give each proxy a discovery-only certificate with an explicit workspace +or all-workspaces grant; mount no application private keys in proxies. + +Create HTTP/WebSocket fixtures on distinct supervisor owners. Record the workload +Pod UID, runtime generation, PID, and an in-memory marker, along with each exact +hostname, discovered owner endpoint, and installation version. Query both gateway +replicas and verify both proxy replicas plus direct in-cluster TLS access. + +Exercise graceful 1 → 2 → 1 scaling and abrupt loss with the shared-supervisor +HA harness's node-runtime SIGKILL procedure. A force-delete alone may still allow +an orderly Leave and does not establish expiry-based recovery. Require unchanged +workload identity/memory, stable public URLs, replacement endpoint/version, and +successful HTTP/WebSocket reconnection. Keep another owner's WebSocket open to +check sibling isolation. A stale installation must disappear even if a consumer +retains it; shared listeners require an independent serving lease capped at +fifteen seconds from issuance. Queued challenges cannot restart that deadline. +Configuration renewal alone cannot extend serving authority. + +Also exercise gateway reconnection, discovery-reader revocation/regrant, route +withdrawal, and discovery outage. Measure serving and discovery expiry separately; +include membership failure detection and grant-file projection in the applicable +bounds. These checks do not establish physical node or network-partition behavior +when run on a single local k3d node. Existing sockets may break during takeover. + +Hashed multi-gateway hostnames still require the common hostname integration. +Consumers use returned hostnames unchanged. diff --git a/examples/isolation-backend-v8/src/driver.rs b/examples/isolation-backend-v8/src/driver.rs index adce131a8f..5bbdc8bcef 100644 --- a/examples/isolation-backend-v8/src/driver.rs +++ b/examples/isolation-backend-v8/src/driver.rs @@ -412,6 +412,7 @@ impl compute_driver_server::ComputeDriver for DriverService { runtime_identity: identity, })) } + async fn get_sandbox( &self, q: Request, diff --git a/proto/README.md b/proto/README.md index f029280f8d..7dd67c6530 100644 --- a/proto/README.md +++ b/proto/README.md @@ -86,6 +86,18 @@ identifies the service being exposed. Run `mise run pre-commit`, the affected SDK checks, and relevant server tests before submitting the change. +## Compute-driver application endpoints + +`compute_driver.proto` is the internal gateway/driver contract. Ordinary +`GetSandbox` observation does not resolve application routing endpoints. +The optional `ComputeDriverAppEndpoints.ResolveSupervisorAppEndpoint` service +returns the current trusted supervisor listener +and its runtime/ownership binding for advertisement validation. Drivers without +support return `UNIMPLEMENTED`; a successful response with no endpoint means no +safe endpoint is currently available. Neither result authorizes publication. +The gateway owns installation validation and the public discovery API. +The baseline `ComputeDriver` service and generated server trait stay unchanged. + ## Supervisor host protocol [`supervisor_host.proto`](supervisor_host.proto) is the private stream between diff --git a/proto/compute_driver.proto b/proto/compute_driver.proto index e50f41d0ee..88d090fc18 100644 --- a/proto/compute_driver.proto +++ b/proto/compute_driver.proto @@ -74,6 +74,17 @@ service ComputeDriver { rpc DeleteWorkspace(DeleteWorkspaceRequest) returns (DeleteWorkspaceResponse); } +// Optional application-endpoint resolution on the same driver endpoint. +// Drivers without this service return UNIMPLEMENTED; the baseline trait is unchanged. +service ComputeDriverAppEndpoints { + // Resolve the current supervisor application listener independently of sandbox + // observation. Unsupported drivers return UNIMPLEMENTED. An absent endpoint + // means no safe endpoint is currently available, not a sandbox lookup failure. + rpc ResolveSupervisorAppEndpoint(ResolveSupervisorAppEndpointRequest) + returns (ResolveSupervisorAppEndpointResponse); + +} + // Optional stopped-sandbox configuration update extension on the same endpoint. // Baseline generated driver traits remain unchanged. Negotiate // openshell.compute.update-sandbox-configuration before calling this service. @@ -450,6 +461,32 @@ message GetSandboxRequest { message GetSandboxResponse { // Platform-observed sandbox snapshot returned by the driver. DriverSandbox sandbox = 1; + reserved 2; + reserved "supervisor_app_endpoint"; +} + +message ResolveSupervisorAppEndpointRequest { + string sandbox_id = 1; + string name = 2; +} + +message ResolveSupervisorAppEndpointResponse { + // Immutable identity echoed for gateway response-binding validation. + string sandbox_id = 1; + // Trusted, generation-bound endpoint; absent when not safely resolvable. + SupervisorAppEndpoint supervisor_app_endpoint = 2; +} + +// Driver-owned app endpoint; absent when unsupported or not safely resolvable. +message SupervisorAppEndpoint { + string host = 1; + uint32 port = 2; + string runtime_identity = 3; + // Shared supervisors only: the pool route and acknowledged ownership term + // used to resolve this member-specific endpoint. Empty/zero for dedicated + // supervisors. The gateway revalidates these against durable pool authority. + string pool_route = 4; + uint64 ownership_epoch = 5; } message ListSandboxesRequest {} diff --git a/proto/openshell.proto b/proto/openshell.proto index e498a84af9..856a86a1f8 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -3202,6 +3202,7 @@ message SupervisorMessage { RelayOpenResult relay_open_result = 3; RelayClose relay_close = 4; ConfigUpdateResult config_update_result = 5; + SupervisorServiceAdvertisement service_advertisement = 6; } } @@ -3215,6 +3216,7 @@ message GatewayMessage { RelayClose relay_close = 5; SessionRedirect session_redirect = 6; ConfigUpdate config_update = 7; + ServiceRouteLease service_route_lease = 8; } } @@ -3242,6 +3244,7 @@ message SupervisorHello { // ConfigUpdate payloads, and answers each with its result and admission. // Gateways push configuration only to supervisors that set this. bool supports_config_push = 7; + bool supports_service_advertisements = 8; } // Gateway accepts the supervisor session. @@ -4219,3 +4222,25 @@ message EndpointFailure { // same value in the x-openshell-request-id response header. string request_id = 8; } + +// Gateway lease challenge on an authenticated sandbox supervisor session. +// Expiry is measured from gateway issuance, never report receipt. +message ServiceRouteLease { + uint64 sequence = 1; + google.protobuf.Duration duration = 2; + // Absolute deadline prevents a queued challenge extending serving authority + // after takeover. Hosts cap this by duration and require synchronized clocks. + google.protobuf.Timestamp expires_at = 3; +} + +// Complete installed-route replacement for a live lease challenge. The +// supervisor reports only if its remaining serving freshness covers duration. +// No address is accepted from the supervisor; the compute driver supplies it. +message SupervisorServiceAdvertisement { + uint64 lease_sequence = 1; + string configuration_instance_id = 2; + uint64 config_revision = 3; + repeated openshell.sandbox.v1.SandboxServiceRoute routes = 4; + // Domain actually installed by the process listener, not the gateway URL. + string service_domain = 5; +} diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index 1ae1800784..d387b39b8d 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -14049,6 +14049,7 @@ type SupervisorMessage struct { // *SupervisorMessage_RelayOpenResult // *SupervisorMessage_RelayClose // *SupervisorMessage_ConfigUpdateResult + // *SupervisorMessage_ServiceAdvertisement Payload isSupervisorMessage_Payload `protobuf_oneof:"payload"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -14136,6 +14137,15 @@ func (x *SupervisorMessage) GetConfigUpdateResult() *ConfigUpdateResult { return nil } +func (x *SupervisorMessage) GetServiceAdvertisement() *SupervisorServiceAdvertisement { + if x != nil { + if x, ok := x.Payload.(*SupervisorMessage_ServiceAdvertisement); ok { + return x.ServiceAdvertisement + } + } + return nil +} + type isSupervisorMessage_Payload interface { isSupervisorMessage_Payload() } @@ -14160,6 +14170,10 @@ type SupervisorMessage_ConfigUpdateResult struct { ConfigUpdateResult *ConfigUpdateResult `protobuf:"bytes,5,opt,name=config_update_result,json=configUpdateResult,proto3,oneof"` } +type SupervisorMessage_ServiceAdvertisement struct { + ServiceAdvertisement *SupervisorServiceAdvertisement `protobuf:"bytes,6,opt,name=service_advertisement,json=serviceAdvertisement,proto3,oneof"` +} + func (*SupervisorMessage_Hello) isSupervisorMessage_Payload() {} func (*SupervisorMessage_Heartbeat) isSupervisorMessage_Payload() {} @@ -14170,6 +14184,8 @@ func (*SupervisorMessage_RelayClose) isSupervisorMessage_Payload() {} func (*SupervisorMessage_ConfigUpdateResult) isSupervisorMessage_Payload() {} +func (*SupervisorMessage_ServiceAdvertisement) isSupervisorMessage_Payload() {} + // Envelope for gateway-to-supervisor messages on the ConnectSupervisor stream. type GatewayMessage struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -14182,6 +14198,7 @@ type GatewayMessage struct { // *GatewayMessage_RelayClose // *GatewayMessage_SessionRedirect // *GatewayMessage_ConfigUpdate + // *GatewayMessage_ServiceRouteLease Payload isGatewayMessage_Payload `protobuf_oneof:"payload"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -14287,6 +14304,15 @@ func (x *GatewayMessage) GetConfigUpdate() *ConfigUpdate { return nil } +func (x *GatewayMessage) GetServiceRouteLease() *ServiceRouteLease { + if x != nil { + if x, ok := x.Payload.(*GatewayMessage_ServiceRouteLease); ok { + return x.ServiceRouteLease + } + } + return nil +} + type isGatewayMessage_Payload interface { isGatewayMessage_Payload() } @@ -14319,6 +14345,10 @@ type GatewayMessage_ConfigUpdate struct { ConfigUpdate *ConfigUpdate `protobuf:"bytes,7,opt,name=config_update,json=configUpdate,proto3,oneof"` } +type GatewayMessage_ServiceRouteLease struct { + ServiceRouteLease *ServiceRouteLease `protobuf:"bytes,8,opt,name=service_route_lease,json=serviceRouteLease,proto3,oneof"` +} + func (*GatewayMessage_SessionAccepted) isGatewayMessage_Payload() {} func (*GatewayMessage_SessionRejected) isGatewayMessage_Payload() {} @@ -14333,6 +14363,8 @@ func (*GatewayMessage_SessionRedirect) isGatewayMessage_Payload() {} func (*GatewayMessage_ConfigUpdate) isGatewayMessage_Payload() {} +func (*GatewayMessage_ServiceRouteLease) isGatewayMessage_Payload() {} + // Supervisor identifies itself and the sandbox it manages. type SupervisorHello struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -14357,9 +14389,10 @@ type SupervisorHello struct { // polling for it: it applies the bootstrap ConfigUpdate and later // ConfigUpdate payloads, and answers each with its result and admission. // Gateways push configuration only to supervisors that set this. - SupportsConfigPush bool `protobuf:"varint,7,opt,name=supports_config_push,json=supportsConfigPush,proto3" json:"supports_config_push,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SupportsConfigPush bool `protobuf:"varint,7,opt,name=supports_config_push,json=supportsConfigPush,proto3" json:"supports_config_push,omitempty"` + SupportsServiceAdvertisements bool `protobuf:"varint,8,opt,name=supports_service_advertisements,json=supportsServiceAdvertisements,proto3" json:"supports_service_advertisements,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SupervisorHello) Reset() { @@ -14441,6 +14474,13 @@ func (x *SupervisorHello) GetSupportsConfigPush() bool { return false } +func (x *SupervisorHello) GetSupportsServiceAdvertisements() bool { + if x != nil { + return x.SupportsServiceAdvertisements + } + return false +} + // Gateway accepts the supervisor session. type SessionAccepted struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -19740,6 +19780,150 @@ func (x *EndpointFailure) GetRequestId() string { return "" } +// Gateway lease challenge on an authenticated sandbox supervisor session. +// Expiry is measured from gateway issuance, never report receipt. +type ServiceRouteLease struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + Duration *durationpb.Duration `protobuf:"bytes,2,opt,name=duration,proto3" json:"duration,omitempty"` + // Absolute deadline prevents a queued challenge extending serving authority + // after takeover. Hosts cap this by duration and require synchronized clocks. + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ServiceRouteLease) Reset() { + *x = ServiceRouteLease{} + mi := &file_openshell_proto_msgTypes[254] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ServiceRouteLease) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServiceRouteLease) ProtoMessage() {} + +func (x *ServiceRouteLease) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[254] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServiceRouteLease.ProtoReflect.Descriptor instead. +func (*ServiceRouteLease) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{254} +} + +func (x *ServiceRouteLease) GetSequence() uint64 { + if x != nil { + return x.Sequence + } + return 0 +} + +func (x *ServiceRouteLease) GetDuration() *durationpb.Duration { + if x != nil { + return x.Duration + } + return nil +} + +func (x *ServiceRouteLease) GetExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiresAt + } + return nil +} + +// Complete installed-route replacement for a live lease challenge. The +// supervisor reports only if its remaining serving freshness covers duration. +// No address is accepted from the supervisor; the compute driver supplies it. +type SupervisorServiceAdvertisement struct { + state protoimpl.MessageState `protogen:"open.v1"` + LeaseSequence uint64 `protobuf:"varint,1,opt,name=lease_sequence,json=leaseSequence,proto3" json:"lease_sequence,omitempty"` + ConfigurationInstanceId string `protobuf:"bytes,2,opt,name=configuration_instance_id,json=configurationInstanceId,proto3" json:"configuration_instance_id,omitempty"` + ConfigRevision uint64 `protobuf:"varint,3,opt,name=config_revision,json=configRevision,proto3" json:"config_revision,omitempty"` + Routes []*sandboxv1.SandboxServiceRoute `protobuf:"bytes,4,rep,name=routes,proto3" json:"routes,omitempty"` + // Domain actually installed by the process listener, not the gateway URL. + ServiceDomain string `protobuf:"bytes,5,opt,name=service_domain,json=serviceDomain,proto3" json:"service_domain,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SupervisorServiceAdvertisement) Reset() { + *x = SupervisorServiceAdvertisement{} + mi := &file_openshell_proto_msgTypes[255] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SupervisorServiceAdvertisement) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SupervisorServiceAdvertisement) ProtoMessage() {} + +func (x *SupervisorServiceAdvertisement) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[255] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SupervisorServiceAdvertisement.ProtoReflect.Descriptor instead. +func (*SupervisorServiceAdvertisement) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{255} +} + +func (x *SupervisorServiceAdvertisement) GetLeaseSequence() uint64 { + if x != nil { + return x.LeaseSequence + } + return 0 +} + +func (x *SupervisorServiceAdvertisement) GetConfigurationInstanceId() string { + if x != nil { + return x.ConfigurationInstanceId + } + return "" +} + +func (x *SupervisorServiceAdvertisement) GetConfigRevision() uint64 { + if x != nil { + return x.ConfigRevision + } + return 0 +} + +func (x *SupervisorServiceAdvertisement) GetRoutes() []*sandboxv1.SandboxServiceRoute { + if x != nil { + return x.Routes + } + return nil +} + +func (x *SupervisorServiceAdvertisement) GetServiceDomain() string { + if x != nil { + return x.ServiceDomain + } + return "" +} + var File_openshell_proto protoreflect.FileDescriptor const file_openshell_proto_rawDesc = "" + @@ -20792,15 +20976,16 @@ const file_openshell_proto_rawDesc = "" + "\x17PushSandboxLogsResponse\"m\n" + "\x16GetSandboxLogsResponse\x120\n" + "\x04logs\x18\x01 \x03(\v2\x1c.openshell.v1.SandboxLogLineR\x04logs\x12!\n" + - "\fbuffer_total\x18\x02 \x01(\rR\vbufferTotal\"\xf8\x02\n" + + "\fbuffer_total\x18\x02 \x01(\rR\vbufferTotal\"\xdd\x03\n" + "\x11SupervisorMessage\x125\n" + "\x05hello\x18\x01 \x01(\v2\x1d.openshell.v1.SupervisorHelloH\x00R\x05hello\x12A\n" + "\theartbeat\x18\x02 \x01(\v2!.openshell.v1.SupervisorHeartbeatH\x00R\theartbeat\x12K\n" + "\x11relay_open_result\x18\x03 \x01(\v2\x1d.openshell.v1.RelayOpenResultH\x00R\x0frelayOpenResult\x12;\n" + "\vrelay_close\x18\x04 \x01(\v2\x18.openshell.v1.RelayCloseH\x00R\n" + "relayClose\x12T\n" + - "\x14config_update_result\x18\x05 \x01(\v2 .openshell.v1.ConfigUpdateResultH\x00R\x12configUpdateResultB\t\n" + - "\apayload\"\xf9\x03\n" + + "\x14config_update_result\x18\x05 \x01(\v2 .openshell.v1.ConfigUpdateResultH\x00R\x12configUpdateResult\x12c\n" + + "\x15service_advertisement\x18\x06 \x01(\v2,.openshell.v1.SupervisorServiceAdvertisementH\x00R\x14serviceAdvertisementB\t\n" + + "\apayload\"\xcc\x04\n" + "\x0eGatewayMessage\x12J\n" + "\x10session_accepted\x18\x01 \x01(\v2\x1d.openshell.v1.SessionAcceptedH\x00R\x0fsessionAccepted\x12J\n" + "\x10session_rejected\x18\x02 \x01(\v2\x1d.openshell.v1.SessionRejectedH\x00R\x0fsessionRejected\x12>\n" + @@ -20810,8 +20995,9 @@ const file_openshell_proto_rawDesc = "" + "\vrelay_close\x18\x05 \x01(\v2\x18.openshell.v1.RelayCloseH\x00R\n" + "relayClose\x12J\n" + "\x10session_redirect\x18\x06 \x01(\v2\x1d.openshell.v1.SessionRedirectH\x00R\x0fsessionRedirect\x12A\n" + - "\rconfig_update\x18\a \x01(\v2\x1a.openshell.v1.ConfigUpdateH\x00R\fconfigUpdateB\t\n" + - "\apayload\"\xca\x02\n" + + "\rconfig_update\x18\a \x01(\v2\x1a.openshell.v1.ConfigUpdateH\x00R\fconfigUpdate\x12Q\n" + + "\x13service_route_lease\x18\b \x01(\v2\x1f.openshell.v1.ServiceRouteLeaseH\x00R\x11serviceRouteLeaseB\t\n" + + "\apayload\"\x92\x03\n" + "\x0fSupervisorHello\x12\x1d\n" + "\n" + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x1f\n" + @@ -20823,7 +21009,8 @@ const file_openshell_proto_rawDesc = "" + "redirected\x18\x05 \x01(\bR\n" + "redirected\x12:\n" + "\x19supports_session_redirect\x18\x06 \x01(\bR\x17supportsSessionRedirect\x120\n" + - "\x14supports_config_push\x18\a \x01(\bR\x12supportsConfigPush\"\x83\x02\n" + + "\x14supports_config_push\x18\a \x01(\bR\x12supportsConfigPush\x12F\n" + + "\x1fsupports_service_advertisements\x18\b \x01(\bR\x1dsupportsServiceAdvertisements\"\x83\x02\n" + "\x0fSessionAccepted\x12\x1d\n" + "\n" + "session_id\x18\x01 \x01(\tR\tsessionId\x128\n" + @@ -21220,7 +21407,18 @@ const file_openshell_proto_rawDesc = "" + "\x14upstream_http_status\x18\a \x01(\rH\x00R\x12upstreamHttpStatus\x88\x01\x01\x12\x1d\n" + "\n" + "request_id\x18\b \x01(\tR\trequestIdB\x17\n" + - "\x15_upstream_http_status*\xca\x01\n" + + "\x15_upstream_http_status\"\xa1\x01\n" + + "\x11ServiceRouteLease\x12\x1a\n" + + "\bsequence\x18\x01 \x01(\x04R\bsequence\x125\n" + + "\bduration\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\bduration\x129\n" + + "\n" + + "expires_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\"\x96\x02\n" + + "\x1eSupervisorServiceAdvertisement\x12%\n" + + "\x0elease_sequence\x18\x01 \x01(\x04R\rleaseSequence\x12:\n" + + "\x19configuration_instance_id\x18\x02 \x01(\tR\x17configurationInstanceId\x12'\n" + + "\x0fconfig_revision\x18\x03 \x01(\x04R\x0econfigRevision\x12A\n" + + "\x06routes\x18\x04 \x03(\v2).openshell.sandbox.v1.SandboxServiceRouteR\x06routes\x12%\n" + + "\x0eservice_domain\x18\x05 \x01(\tR\rserviceDomain*\xca\x01\n" + "\rExtensionKind\x12\x1e\n" + "\x1aEXTENSION_KIND_UNSPECIFIED\x10\x00\x12!\n" + "\x1dEXTENSION_KIND_COMPUTE_DRIVER\x10\x01\x12$\n" + @@ -21573,7 +21771,7 @@ func file_openshell_proto_rawDescGZIP() []byte { } var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 22) -var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 281) +var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 283) var file_openshell_proto_goTypes = []any{ (ExtensionKind)(0), // 0: openshell.v1.ExtensionKind (SandboxPhase)(0), // 1: openshell.v1.SandboxPhase @@ -21851,59 +22049,62 @@ var file_openshell_proto_goTypes = []any{ (*SandboxProvisioning)(nil), // 273: openshell.v1.SandboxProvisioning (*SandboxServiceExposure)(nil), // 274: openshell.v1.SandboxServiceExposure (*EndpointFailure)(nil), // 275: openshell.v1.EndpointFailure - nil, // 276: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 277: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 278: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 279: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 280: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - nil, // 281: openshell.v1.PlatformEvent.MetadataEntry - nil, // 282: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 283: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 284: openshell.v1.SandboxMapUpdate.ValuesEntry - nil, // 285: openshell.v1.SandboxResponse.ServiceUrlsEntry - nil, // 286: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 287: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 288: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry - nil, // 289: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - nil, // 290: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 291: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 292: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 293: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - nil, // 294: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 295: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 296: openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry - nil, // 297: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry - nil, // 298: openshell.v1.ProviderEnvironmentSnapshot.FilesEntry - nil, // 299: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 300: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 301: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 302: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*timestamppb.Timestamp)(nil), // 303: google.protobuf.Timestamp - (*datamodelv1.ObjectMeta)(nil), // 304: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 305: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 306: google.protobuf.Struct - (*durationpb.Duration)(nil), // 307: google.protobuf.Duration - (*datamodelv1.WorkspaceSelector)(nil), // 308: openshell.datamodel.v1.WorkspaceSelector - (*datamodelv1.Provider)(nil), // 309: openshell.datamodel.v1.Provider - (sandboxv1.PolicySource)(0), // 310: openshell.sandbox.v1.PolicySource - (*sandboxv1.NetworkEndpoint)(nil), // 311: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 312: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 313: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 314: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 315: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 316: openshell.sandbox.v1.L7Rule - (*sandboxv1.SandboxConfigSnapshot)(nil), // 317: openshell.sandbox.v1.SandboxConfigSnapshot - (*datamodelv1.Workspace)(nil), // 318: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 319: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 320: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 321: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 322: openshell.sandbox.v1.GetGatewayConfigResponse + (*ServiceRouteLease)(nil), // 276: openshell.v1.ServiceRouteLease + (*SupervisorServiceAdvertisement)(nil), // 277: openshell.v1.SupervisorServiceAdvertisement + nil, // 278: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 279: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 280: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 281: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 282: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + nil, // 283: openshell.v1.PlatformEvent.MetadataEntry + nil, // 284: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 285: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 286: openshell.v1.SandboxMapUpdate.ValuesEntry + nil, // 287: openshell.v1.SandboxResponse.ServiceUrlsEntry + nil, // 288: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 289: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 290: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry + nil, // 291: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + nil, // 292: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 293: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 294: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 295: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + nil, // 296: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 297: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + nil, // 298: openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry + nil, // 299: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry + nil, // 300: openshell.v1.ProviderEnvironmentSnapshot.FilesEntry + nil, // 301: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 302: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 303: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 304: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*timestamppb.Timestamp)(nil), // 305: google.protobuf.Timestamp + (*datamodelv1.ObjectMeta)(nil), // 306: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 307: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 308: google.protobuf.Struct + (*durationpb.Duration)(nil), // 309: google.protobuf.Duration + (*datamodelv1.WorkspaceSelector)(nil), // 310: openshell.datamodel.v1.WorkspaceSelector + (*datamodelv1.Provider)(nil), // 311: openshell.datamodel.v1.Provider + (sandboxv1.PolicySource)(0), // 312: openshell.sandbox.v1.PolicySource + (*sandboxv1.NetworkEndpoint)(nil), // 313: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 314: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 315: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 316: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 317: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 318: openshell.sandbox.v1.L7Rule + (*sandboxv1.SandboxConfigSnapshot)(nil), // 319: openshell.sandbox.v1.SandboxConfigSnapshot + (*datamodelv1.Workspace)(nil), // 320: openshell.datamodel.v1.Workspace + (*sandboxv1.SandboxServiceRoute)(nil), // 321: openshell.sandbox.v1.SandboxServiceRoute + (*sandboxv1.GetSandboxConfigRequest)(nil), // 322: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 323: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 324: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 325: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ - 303, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 303, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 305, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 305, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp 266, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential - 303, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp + 305, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp 14, // 4: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus 14, // 5: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus 33, // 6: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo @@ -21914,124 +22115,124 @@ var file_openshell_proto_depIdxs = []int32{ 36, // 11: openshell.v1.ResourceCapabilities.cpu:type_name -> openshell.v1.CpuResourceCapabilities 37, // 12: openshell.v1.ResourceCapabilities.memory:type_name -> openshell.v1.MemoryResourceCapabilities 38, // 13: openshell.v1.ResourceCapabilities.gpu:type_name -> openshell.v1.GpuResourceCapabilities - 304, // 14: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 306, // 14: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 40, // 15: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec 51, // 16: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus 50, // 17: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance - 276, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 278, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry 43, // 19: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 305, // 20: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 307, // 20: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy 41, // 21: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements 17, // 22: openshell.v1.SandboxSpec.restart_policy:type_name -> openshell.v1.SandboxRestartPolicy 42, // 23: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements - 277, // 24: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 278, // 25: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 279, // 26: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 306, // 27: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 306, // 28: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct - 304, // 29: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 279, // 24: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 280, // 25: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 281, // 26: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 308, // 27: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 308, // 28: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 306, // 29: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 45, // 30: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec 46, // 31: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig - 306, // 32: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct + 308, // 32: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct 48, // 33: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel - 280, // 34: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + 282, // 34: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry 47, // 35: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources 42, // 36: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements 49, // 37: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup - 307, // 38: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration + 309, // 38: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration 52, // 39: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition 1, // 40: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase 272, // 41: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus 188, // 42: openshell.v1.SandboxStatus.configuration_admission:type_name -> openshell.v1.SandboxConfigurationAdmission 273, // 43: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning - 303, // 44: openshell.v1.SandboxStatus.next_restart_time:type_name -> google.protobuf.Timestamp - 303, // 45: openshell.v1.SandboxStatus.main_process_started_time:type_name -> google.protobuf.Timestamp + 305, // 44: openshell.v1.SandboxStatus.next_restart_time:type_name -> google.protobuf.Timestamp + 305, // 45: openshell.v1.SandboxStatus.main_process_started_time:type_name -> google.protobuf.Timestamp 270, // 46: openshell.v1.SandboxStatus.last_token_grant_failure:type_name -> openshell.v1.TokenGrantFailureObservation - 303, // 47: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp - 303, // 48: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp - 281, // 49: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry - 308, // 50: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 305, // 47: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp + 305, // 48: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp + 283, // 49: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 310, // 50: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 40, // 51: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec - 282, // 52: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 283, // 53: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 284, // 52: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 285, // 53: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry 274, // 54: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure - 308, // 55: openshell.v1.UpdateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 55: openshell.v1.UpdateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 56, // 56: openshell.v1.UpdateSandboxRequest.command:type_name -> openshell.v1.SandboxCommandUpdate 57, // 57: openshell.v1.UpdateSandboxRequest.environment:type_name -> openshell.v1.SandboxMapUpdate 57, // 58: openshell.v1.UpdateSandboxRequest.labels:type_name -> openshell.v1.SandboxMapUpdate 57, // 59: openshell.v1.UpdateSandboxRequest.annotations:type_name -> openshell.v1.SandboxMapUpdate - 284, // 60: openshell.v1.SandboxMapUpdate.values:type_name -> openshell.v1.SandboxMapUpdate.ValuesEntry - 308, // 61: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 286, // 60: openshell.v1.SandboxMapUpdate.values:type_name -> openshell.v1.SandboxMapUpdate.ValuesEntry + 310, // 61: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 44, // 62: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 308, // 63: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 64: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 65: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 63: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 64: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 65: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 44, // 66: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate 44, // 67: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate 18, // 68: openshell.v1.DeleteSandboxTemplateResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 308, // 69: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 303, // 70: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp - 308, // 71: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 72: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 73: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 74: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 75: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 76: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 77: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 78: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 79: openshell.v1.SuspendSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 80: openshell.v1.ResumeSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 69: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 305, // 70: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp + 310, // 71: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 72: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 73: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 74: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 75: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 76: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 77: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 78: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 79: openshell.v1.SuspendSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 80: openshell.v1.ResumeSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 39, // 81: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox - 285, // 82: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry + 287, // 82: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry 39, // 83: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 309, // 84: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 311, // 84: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider 39, // 85: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox 86, // 86: openshell.v1.AttachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt 39, // 87: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox 86, // 88: openshell.v1.DetachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt 84, // 89: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision 82, // 90: openshell.v1.ConfigSnapshotRevision.provider_target:type_name -> openshell.v1.ProviderDesiredIdentity - 310, // 91: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource + 312, // 91: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource 5, // 92: openshell.v1.ConfigUpdateOperation.component:type_name -> openshell.v1.ConfigComponent 83, // 93: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision 7, // 94: openshell.v1.ConfigUpdateOperation.state:type_name -> openshell.v1.ConfigUpdateOperationState 6, // 95: openshell.v1.ConfigUpdateOperation.outcome:type_name -> openshell.v1.ConfigApplyOutcome - 303, // 96: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp - 303, // 97: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp - 303, // 98: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp + 305, // 96: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp + 305, // 97: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp + 305, // 98: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp 2, // 99: openshell.v1.ProviderMutationReceipt.kind:type_name -> openshell.v1.ProviderMutationKind 82, // 100: openshell.v1.ProviderMutationReceipt.desired:type_name -> openshell.v1.ProviderDesiredIdentity - 303, // 101: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp + 305, // 101: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp 4, // 102: openshell.v1.ProviderReadinessObservation.reason:type_name -> openshell.v1.ProviderReadinessReason 86, // 103: openshell.v1.ProviderReadinessStatus.receipt:type_name -> openshell.v1.ProviderMutationReceipt 3, // 104: openshell.v1.ProviderReadinessStatus.state:type_name -> openshell.v1.ProviderReadinessState 4, // 105: openshell.v1.ProviderReadinessStatus.reason:type_name -> openshell.v1.ProviderReadinessReason 87, // 106: openshell.v1.ProviderReadinessStatus.observed:type_name -> openshell.v1.ProviderReadinessObservation - 303, // 107: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp - 303, // 108: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp + 305, // 107: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp + 305, // 108: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp 85, // 109: openshell.v1.ProviderReadinessStatus.operation:type_name -> openshell.v1.ConfigUpdateOperation - 308, // 110: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 110: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 88, // 111: openshell.v1.GetSandboxProviderStatusResponse.status:type_name -> openshell.v1.ProviderReadinessStatus 87, // 112: openshell.v1.ReportProviderReadinessRequest.observation:type_name -> openshell.v1.ProviderReadinessObservation - 307, // 113: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration - 307, // 114: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration + 309, // 113: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration + 309, // 114: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration 18, // 115: openshell.v1.DeleteSandboxResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 308, // 116: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 303, // 117: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp - 308, // 118: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 116: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 305, // 117: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp + 310, // 118: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 20, // 119: openshell.v1.ExposeServiceRequest.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 308, // 120: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 121: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 120: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 121: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 103, // 122: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 308, // 123: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 123: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 18, // 124: openshell.v1.DeleteServiceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 304, // 125: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 306, // 125: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 20, // 126: openshell.v1.ServiceEndpoint.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode 102, // 127: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint 18, // 128: openshell.v1.RevokeSshSessionResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 308, // 129: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 286, // 130: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 307, // 131: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration + 310, // 129: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 288, // 130: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 309, // 131: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration 107, // 132: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout 108, // 133: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr 109, // 134: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit @@ -22040,133 +22241,133 @@ var file_openshell_proto_depIdxs = []int32{ 111, // 137: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit 106, // 138: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest 114, // 139: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 304, // 140: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 303, // 141: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp - 308, // 142: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 303, // 143: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp + 306, // 140: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 305, // 141: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp + 310, // 142: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 305, // 143: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp 39, // 144: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox 118, // 145: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine 53, // 146: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent 119, // 147: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning 230, // 148: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 303, // 149: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp - 287, // 150: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 308, // 151: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 309, // 152: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 308, // 153: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 154: openshell.v1.GetProviderCredentialsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 307, // 155: openshell.v1.GetProviderCredentialsRequest.minimum_remaining_lifetime:type_name -> google.protobuf.Duration - 303, // 156: openshell.v1.ProviderCredentialValue.expiration_time:type_name -> google.protobuf.Timestamp - 288, // 157: openshell.v1.GetProviderCredentialsResponse.credentials:type_name -> openshell.v1.GetProviderCredentialsResponse.CredentialsEntry - 308, // 158: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 159: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 309, // 160: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 289, // 161: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - 308, // 162: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 309, // 163: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 305, // 149: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp + 289, // 150: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 310, // 151: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 152: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 310, // 153: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 154: openshell.v1.GetProviderCredentialsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 309, // 155: openshell.v1.GetProviderCredentialsRequest.minimum_remaining_lifetime:type_name -> google.protobuf.Duration + 305, // 156: openshell.v1.ProviderCredentialValue.expiration_time:type_name -> google.protobuf.Timestamp + 290, // 157: openshell.v1.GetProviderCredentialsResponse.credentials:type_name -> openshell.v1.GetProviderCredentialsResponse.CredentialsEntry + 310, // 158: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 159: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 160: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 291, // 161: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + 310, // 162: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 163: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider 86, // 164: openshell.v1.ProviderResponse.target_receipts:type_name -> openshell.v1.ProviderMutationReceipt - 309, // 165: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 308, // 166: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 167: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 165: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 310, // 166: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 167: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 151, // 168: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 307, // 169: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration + 309, // 169: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration 134, // 170: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride 8, // 171: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType 135, // 172: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken 140, // 173: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh 136, // 174: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant 9, // 175: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 307, // 176: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration - 307, // 177: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration + 309, // 176: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration + 309, // 177: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration 138, // 178: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial 139, // 179: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput 9, // 180: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 303, // 181: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp - 303, // 182: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp - 303, // 183: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp + 305, // 181: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp + 305, // 182: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp + 305, // 183: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp 16, // 184: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 303, // 185: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp - 308, // 186: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 305, // 185: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp + 310, // 186: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 141, // 187: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 308, // 188: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 188: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 9, // 189: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 290, // 190: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 303, // 191: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp + 292, // 190: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 305, // 191: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp 141, // 192: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 308, // 193: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 193: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 141, // 194: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 308, // 195: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 195: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 18, // 196: openshell.v1.DeleteProviderRefreshResponse.outcome:type_name -> openshell.v1.DeletionOutcome 10, // 197: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory 137, // 198: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 311, // 199: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 312, // 200: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 313, // 199: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 314, // 200: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary 142, // 201: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 291, // 202: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 293, // 202: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry 152, // 203: openshell.v1.ProviderProfile.files:type_name -> openshell.v1.ProviderProfileFile 151, // 204: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile 151, // 205: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 308, // 206: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 206: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 132, // 207: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem 133, // 208: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic 151, // 209: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 308, // 210: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 210: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 132, // 211: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem 133, // 212: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic 151, // 213: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 308, // 214: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 214: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 132, // 215: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem 133, // 216: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic 18, // 217: openshell.v1.DeleteProviderResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 308, // 218: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 218: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 18, // 219: openshell.v1.DeleteProviderProfileResponse.outcome:type_name -> openshell.v1.DeletionOutcome 165, // 220: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 292, // 221: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 293, // 222: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - 294, // 223: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 295, // 224: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 294, // 221: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 295, // 222: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + 296, // 223: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 297, // 224: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry 4, // 225: openshell.v1.GetSandboxProviderEnvironmentResponse.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason - 296, // 226: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry + 298, // 226: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry 11, // 227: openshell.v1.ProviderEnvironmentValue.classification:type_name -> openshell.v1.ProviderEnvironmentValueClassification 166, // 228: openshell.v1.ProviderEnvironmentValue.static_credential_binding:type_name -> openshell.v1.StaticCredentialBinding - 303, // 229: openshell.v1.ProviderEnvironmentValue.expiration_time:type_name -> google.protobuf.Timestamp + 305, // 229: openshell.v1.ProviderEnvironmentValue.expiration_time:type_name -> google.protobuf.Timestamp 168, // 230: openshell.v1.ProviderEnvironmentSnapshot.values:type_name -> openshell.v1.ProviderEnvironmentValue - 297, // 231: openshell.v1.ProviderEnvironmentSnapshot.dynamic_credentials:type_name -> openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry + 299, // 231: openshell.v1.ProviderEnvironmentSnapshot.dynamic_credentials:type_name -> openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry 4, // 232: openshell.v1.ProviderEnvironmentSnapshot.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason - 298, // 233: openshell.v1.ProviderEnvironmentSnapshot.files:type_name -> openshell.v1.ProviderEnvironmentSnapshot.FilesEntry - 307, // 234: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration - 308, // 235: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 305, // 236: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 313, // 237: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 300, // 233: openshell.v1.ProviderEnvironmentSnapshot.files:type_name -> openshell.v1.ProviderEnvironmentSnapshot.FilesEntry + 309, // 234: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration + 310, // 235: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 307, // 236: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 315, // 237: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue 173, // 238: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 299, // 239: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 301, // 239: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry 174, // 240: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule 175, // 241: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint 176, // 242: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule 178, // 243: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules 179, // 244: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules 180, // 245: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 314, // 246: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 312, // 247: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 315, // 248: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 316, // 246: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 314, // 247: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 317, // 248: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule 177, // 249: openshell.v1.AddDenyRules.target:type_name -> openshell.v1.L7RuleTarget - 316, // 250: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 318, // 250: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule 177, // 251: openshell.v1.AddAllowRules.target:type_name -> openshell.v1.L7RuleTarget - 300, // 252: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 308, // 253: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 302, // 252: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 310, // 253: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 191, // 254: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 308, // 255: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 255: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 191, // 256: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision 13, // 257: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus 12, // 258: openshell.v1.SandboxConfigurationAdmission.state:type_name -> openshell.v1.ConfigurationAdmissionState 188, // 259: openshell.v1.ReportSandboxConfigurationRequest.admission:type_name -> openshell.v1.SandboxConfigurationAdmission 13, // 260: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 303, // 261: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp - 303, // 262: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp - 305, // 263: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 301, // 264: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 308, // 265: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 303, // 266: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp + 305, // 261: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp + 305, // 262: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp + 307, // 263: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 303, // 264: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 310, // 265: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 305, // 266: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp 118, // 267: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine 118, // 268: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine 198, // 269: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello @@ -22174,278 +22375,283 @@ var file_openshell_proto_depIdxs = []int32{ 223, // 271: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult 224, // 272: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose 202, // 273: openshell.v1.SupervisorMessage.config_update_result:type_name -> openshell.v1.ConfigUpdateResult - 199, // 274: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 204, // 275: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 207, // 276: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 212, // 277: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 224, // 278: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 205, // 279: openshell.v1.GatewayMessage.session_redirect:type_name -> openshell.v1.SessionRedirect - 200, // 280: openshell.v1.GatewayMessage.config_update:type_name -> openshell.v1.ConfigUpdate - 200, // 281: openshell.v1.SessionAccepted.bootstrap:type_name -> openshell.v1.ConfigUpdate - 307, // 282: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration - 317, // 283: openshell.v1.ConfigUpdate.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot - 169, // 284: openshell.v1.ConfigUpdate.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot - 6, // 285: openshell.v1.ConfigUpdateResult.outcome:type_name -> openshell.v1.ConfigApplyOutcome - 201, // 286: openshell.v1.ConfigUpdateResult.failure:type_name -> openshell.v1.ConfigApplyFailure - 188, // 287: openshell.v1.ConfigUpdateResult.admission:type_name -> openshell.v1.SandboxConfigurationAdmission - 203, // 288: openshell.v1.ConfigUpdateResult.policy_load:type_name -> openshell.v1.PolicyLoadResult - 213, // 289: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 214, // 290: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 215, // 291: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 212, // 292: openshell.v1.PeerRelayInit.relay_open:type_name -> openshell.v1.RelayOpen - 217, // 293: openshell.v1.PeerRelayFrame.init:type_name -> openshell.v1.PeerRelayInit - 219, // 294: openshell.v1.PeerNotifyConfigUpdateRequest.sandbox:type_name -> openshell.v1.PeerConfigSandboxTarget - 220, // 295: openshell.v1.PeerNotifyConfigUpdateRequest.provider:type_name -> openshell.v1.PeerConfigProviderTarget - 303, // 296: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp - 303, // 297: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp - 225, // 298: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 227, // 299: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 314, // 300: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 303, // 301: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp - 303, // 302: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp - 303, // 303: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp - 303, // 304: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp - 305, // 305: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 305, // 306: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 308, // 307: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 226, // 308: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 229, // 309: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 228, // 310: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 308, // 311: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 229, // 312: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 303, // 313: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp - 308, // 314: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 315: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 316: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 239, // 317: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval - 308, // 318: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 314, // 319: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 308, // 320: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 321: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 308, // 322: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 303, // 323: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp - 249, // 324: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 302, // 325: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 318, // 326: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 318, // 327: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 318, // 328: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 18, // 329: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 304, // 330: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 15, // 331: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 308, // 332: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 15, // 333: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 259, // 334: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 308, // 335: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 18, // 336: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 308, // 337: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 259, // 338: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 303, // 339: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp - 19, // 340: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult - 275, // 341: openshell.v1.EndpointObservation.failure:type_name -> openshell.v1.EndpointFailure - 267, // 342: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation - 269, // 343: openshell.v1.ReportEndpointStatusRequest.token_grant_status:type_name -> openshell.v1.TokenGrantStatus - 270, // 344: openshell.v1.TokenGrantStatus.last_failure:type_name -> openshell.v1.TokenGrantFailureObservation - 275, // 345: openshell.v1.TokenGrantFailureObservation.failure:type_name -> openshell.v1.EndpointFailure - 19, // 346: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult - 303, // 347: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp - 275, // 348: openshell.v1.EndpointStatus.last_failure:type_name -> openshell.v1.EndpointFailure - 303, // 349: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp - 303, // 350: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp - 303, // 351: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp - 303, // 352: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp - 303, // 353: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp - 303, // 354: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp - 303, // 355: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp - 303, // 356: openshell.v1.SandboxProvisioning.preparation_deadline:type_name -> google.protobuf.Timestamp - 303, // 357: openshell.v1.SandboxProvisioning.admission_start_time:type_name -> google.protobuf.Timestamp - 20, // 358: openshell.v1.SandboxServiceExposure.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 21, // 359: openshell.v1.EndpointFailure.domain:type_name -> openshell.v1.EndpointFailureDomain - 16, // 360: openshell.v1.EndpointFailure.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 123, // 361: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry.value:type_name -> openshell.v1.ProviderCredentialValue - 303, // 362: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 303, // 363: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 137, // 364: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 166, // 365: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 137, // 366: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 26, // 367: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 28, // 368: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 30, // 369: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 54, // 370: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 65, // 371: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest - 67, // 372: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 68, // 373: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 58, // 374: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest - 59, // 375: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest - 60, // 376: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest - 61, // 377: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest - 69, // 378: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 70, // 379: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 71, // 380: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 89, // 381: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 72, // 382: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 73, // 383: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 74, // 384: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 75, // 385: openshell.v1.OpenShell.SuspendSandbox:input_type -> openshell.v1.SuspendSandboxRequest - 76, // 386: openshell.v1.OpenShell.ResumeSandbox:input_type -> openshell.v1.ResumeSandboxRequest - 94, // 387: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 96, // 388: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 97, // 389: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 98, // 390: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 100, // 391: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 104, // 392: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 106, // 393: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 112, // 394: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 113, // 395: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 120, // 396: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 121, // 397: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 122, // 398: openshell.v1.OpenShell.GetProviderCredentials:input_type -> openshell.v1.GetProviderCredentialsRequest - 125, // 399: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 130, // 400: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 131, // 401: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 155, // 402: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 157, // 403: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 159, // 404: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 126, // 405: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 143, // 406: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 145, // 407: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 147, // 408: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 149, // 409: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 127, // 410: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 162, // 411: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 319, // 412: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 320, // 413: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 172, // 414: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 182, // 415: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 184, // 416: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 186, // 417: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 268, // 418: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 91, // 419: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 189, // 420: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest - 164, // 421: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 170, // 422: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest - 192, // 423: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 193, // 424: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 196, // 425: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 208, // 426: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest - 210, // 427: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest - 216, // 428: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 218, // 429: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame - 91, // 430: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 268, // 431: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 89, // 432: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 221, // 433: openshell.v1.OpenShell.PeerNotifyConfigUpdate:input_type -> openshell.v1.PeerNotifyConfigUpdateRequest - 116, // 434: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 231, // 435: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 233, // 436: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 235, // 437: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 237, // 438: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 240, // 439: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 242, // 440: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 244, // 441: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 246, // 442: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 248, // 443: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 22, // 444: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 24, // 445: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 251, // 446: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 253, // 447: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 255, // 448: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 257, // 449: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 260, // 450: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 262, // 451: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 264, // 452: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 55, // 453: openshell.v1.SandboxUpdates.UpdateSandbox:input_type -> openshell.v1.UpdateSandboxRequest - 27, // 454: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 29, // 455: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 31, // 456: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 77, // 457: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 66, // 458: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse - 77, // 459: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 78, // 460: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 62, // 461: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 62, // 462: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 63, // 463: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse - 64, // 464: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse - 79, // 465: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 80, // 466: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 81, // 467: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 90, // 468: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 93, // 469: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 77, // 470: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 77, // 471: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 77, // 472: openshell.v1.OpenShell.SuspendSandbox:output_type -> openshell.v1.SandboxResponse - 77, // 473: openshell.v1.OpenShell.ResumeSandbox:output_type -> openshell.v1.SandboxResponse - 95, // 474: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 103, // 475: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 103, // 476: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 99, // 477: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 101, // 478: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 105, // 479: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 110, // 480: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 112, // 481: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 110, // 482: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 128, // 483: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 128, // 484: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 124, // 485: openshell.v1.OpenShell.GetProviderCredentials:output_type -> openshell.v1.GetProviderCredentialsResponse - 129, // 486: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 154, // 487: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 153, // 488: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 156, // 489: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 158, // 490: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 160, // 491: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 128, // 492: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 144, // 493: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 146, // 494: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 148, // 495: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 150, // 496: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 161, // 497: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 163, // 498: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 321, // 499: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 322, // 500: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 181, // 501: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 183, // 502: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 185, // 503: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 187, // 504: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 271, // 505: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 92, // 506: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 190, // 507: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse - 167, // 508: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 171, // 509: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse - 195, // 510: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 194, // 511: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 197, // 512: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 209, // 513: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse - 211, // 514: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse - 216, // 515: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 218, // 516: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame - 92, // 517: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 271, // 518: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 90, // 519: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 222, // 520: openshell.v1.OpenShell.PeerNotifyConfigUpdate:output_type -> openshell.v1.PeerNotifyConfigUpdateResponse - 117, // 521: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 232, // 522: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 234, // 523: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 236, // 524: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 238, // 525: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 241, // 526: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 243, // 527: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 245, // 528: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 247, // 529: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 250, // 530: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 23, // 531: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 25, // 532: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 252, // 533: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 254, // 534: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 256, // 535: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 258, // 536: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 261, // 537: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 263, // 538: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 265, // 539: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 77, // 540: openshell.v1.SandboxUpdates.UpdateSandbox:output_type -> openshell.v1.SandboxResponse - 454, // [454:541] is the sub-list for method output_type - 367, // [367:454] is the sub-list for method input_type - 367, // [367:367] is the sub-list for extension type_name - 367, // [367:367] is the sub-list for extension extendee - 0, // [0:367] is the sub-list for field type_name + 277, // 274: openshell.v1.SupervisorMessage.service_advertisement:type_name -> openshell.v1.SupervisorServiceAdvertisement + 199, // 275: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 204, // 276: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 207, // 277: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 212, // 278: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 224, // 279: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 205, // 280: openshell.v1.GatewayMessage.session_redirect:type_name -> openshell.v1.SessionRedirect + 200, // 281: openshell.v1.GatewayMessage.config_update:type_name -> openshell.v1.ConfigUpdate + 276, // 282: openshell.v1.GatewayMessage.service_route_lease:type_name -> openshell.v1.ServiceRouteLease + 200, // 283: openshell.v1.SessionAccepted.bootstrap:type_name -> openshell.v1.ConfigUpdate + 309, // 284: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration + 319, // 285: openshell.v1.ConfigUpdate.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot + 169, // 286: openshell.v1.ConfigUpdate.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot + 6, // 287: openshell.v1.ConfigUpdateResult.outcome:type_name -> openshell.v1.ConfigApplyOutcome + 201, // 288: openshell.v1.ConfigUpdateResult.failure:type_name -> openshell.v1.ConfigApplyFailure + 188, // 289: openshell.v1.ConfigUpdateResult.admission:type_name -> openshell.v1.SandboxConfigurationAdmission + 203, // 290: openshell.v1.ConfigUpdateResult.policy_load:type_name -> openshell.v1.PolicyLoadResult + 213, // 291: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 214, // 292: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 215, // 293: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 212, // 294: openshell.v1.PeerRelayInit.relay_open:type_name -> openshell.v1.RelayOpen + 217, // 295: openshell.v1.PeerRelayFrame.init:type_name -> openshell.v1.PeerRelayInit + 219, // 296: openshell.v1.PeerNotifyConfigUpdateRequest.sandbox:type_name -> openshell.v1.PeerConfigSandboxTarget + 220, // 297: openshell.v1.PeerNotifyConfigUpdateRequest.provider:type_name -> openshell.v1.PeerConfigProviderTarget + 305, // 298: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp + 305, // 299: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp + 225, // 300: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 227, // 301: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 316, // 302: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 305, // 303: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp + 305, // 304: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp + 305, // 305: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp + 305, // 306: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp + 307, // 307: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 307, // 308: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 310, // 309: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 226, // 310: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 229, // 311: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 228, // 312: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 310, // 313: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 229, // 314: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 305, // 315: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp + 310, // 316: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 317: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 318: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 239, // 319: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval + 310, // 320: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 316, // 321: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 310, // 322: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 323: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 324: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 305, // 325: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp + 249, // 326: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 304, // 327: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 320, // 328: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 320, // 329: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 320, // 330: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 18, // 331: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 306, // 332: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 15, // 333: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 310, // 334: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 15, // 335: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 259, // 336: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 310, // 337: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 18, // 338: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 310, // 339: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 259, // 340: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 305, // 341: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp + 19, // 342: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult + 275, // 343: openshell.v1.EndpointObservation.failure:type_name -> openshell.v1.EndpointFailure + 267, // 344: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation + 269, // 345: openshell.v1.ReportEndpointStatusRequest.token_grant_status:type_name -> openshell.v1.TokenGrantStatus + 270, // 346: openshell.v1.TokenGrantStatus.last_failure:type_name -> openshell.v1.TokenGrantFailureObservation + 275, // 347: openshell.v1.TokenGrantFailureObservation.failure:type_name -> openshell.v1.EndpointFailure + 19, // 348: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult + 305, // 349: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp + 275, // 350: openshell.v1.EndpointStatus.last_failure:type_name -> openshell.v1.EndpointFailure + 305, // 351: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp + 305, // 352: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp + 305, // 353: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp + 305, // 354: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp + 305, // 355: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp + 305, // 356: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp + 305, // 357: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp + 305, // 358: openshell.v1.SandboxProvisioning.preparation_deadline:type_name -> google.protobuf.Timestamp + 305, // 359: openshell.v1.SandboxProvisioning.admission_start_time:type_name -> google.protobuf.Timestamp + 20, // 360: openshell.v1.SandboxServiceExposure.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 21, // 361: openshell.v1.EndpointFailure.domain:type_name -> openshell.v1.EndpointFailureDomain + 16, // 362: openshell.v1.EndpointFailure.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction + 309, // 363: openshell.v1.ServiceRouteLease.duration:type_name -> google.protobuf.Duration + 305, // 364: openshell.v1.ServiceRouteLease.expires_at:type_name -> google.protobuf.Timestamp + 321, // 365: openshell.v1.SupervisorServiceAdvertisement.routes:type_name -> openshell.sandbox.v1.SandboxServiceRoute + 123, // 366: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry.value:type_name -> openshell.v1.ProviderCredentialValue + 305, // 367: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 305, // 368: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 137, // 369: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 166, // 370: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 137, // 371: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 26, // 372: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 28, // 373: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 30, // 374: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 54, // 375: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 65, // 376: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest + 67, // 377: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 68, // 378: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 58, // 379: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest + 59, // 380: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest + 60, // 381: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest + 61, // 382: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest + 69, // 383: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 70, // 384: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 71, // 385: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 89, // 386: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 72, // 387: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 73, // 388: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 74, // 389: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 75, // 390: openshell.v1.OpenShell.SuspendSandbox:input_type -> openshell.v1.SuspendSandboxRequest + 76, // 391: openshell.v1.OpenShell.ResumeSandbox:input_type -> openshell.v1.ResumeSandboxRequest + 94, // 392: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 96, // 393: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 97, // 394: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 98, // 395: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 100, // 396: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 104, // 397: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 106, // 398: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 112, // 399: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 113, // 400: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 120, // 401: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 121, // 402: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 122, // 403: openshell.v1.OpenShell.GetProviderCredentials:input_type -> openshell.v1.GetProviderCredentialsRequest + 125, // 404: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 130, // 405: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 131, // 406: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 155, // 407: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 157, // 408: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 159, // 409: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 126, // 410: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 143, // 411: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 145, // 412: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 147, // 413: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 149, // 414: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 127, // 415: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 162, // 416: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 322, // 417: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 323, // 418: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 172, // 419: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 182, // 420: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 184, // 421: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 186, // 422: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 268, // 423: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 91, // 424: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 189, // 425: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest + 164, // 426: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 170, // 427: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 192, // 428: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 193, // 429: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 196, // 430: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 208, // 431: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest + 210, // 432: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest + 216, // 433: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 218, // 434: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame + 91, // 435: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 268, // 436: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 89, // 437: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 221, // 438: openshell.v1.OpenShell.PeerNotifyConfigUpdate:input_type -> openshell.v1.PeerNotifyConfigUpdateRequest + 116, // 439: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 231, // 440: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 233, // 441: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 235, // 442: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 237, // 443: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 240, // 444: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 242, // 445: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 244, // 446: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 246, // 447: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 248, // 448: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 22, // 449: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 24, // 450: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 251, // 451: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 253, // 452: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 255, // 453: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 257, // 454: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 260, // 455: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 262, // 456: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 264, // 457: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 55, // 458: openshell.v1.SandboxUpdates.UpdateSandbox:input_type -> openshell.v1.UpdateSandboxRequest + 27, // 459: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 29, // 460: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 31, // 461: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 77, // 462: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 66, // 463: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse + 77, // 464: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 78, // 465: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 62, // 466: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 62, // 467: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 63, // 468: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse + 64, // 469: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse + 79, // 470: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 80, // 471: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 81, // 472: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 90, // 473: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 93, // 474: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 77, // 475: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 77, // 476: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 77, // 477: openshell.v1.OpenShell.SuspendSandbox:output_type -> openshell.v1.SandboxResponse + 77, // 478: openshell.v1.OpenShell.ResumeSandbox:output_type -> openshell.v1.SandboxResponse + 95, // 479: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 103, // 480: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 103, // 481: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 99, // 482: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 101, // 483: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 105, // 484: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 110, // 485: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 112, // 486: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 110, // 487: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 128, // 488: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 128, // 489: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 124, // 490: openshell.v1.OpenShell.GetProviderCredentials:output_type -> openshell.v1.GetProviderCredentialsResponse + 129, // 491: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 154, // 492: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 153, // 493: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 156, // 494: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 158, // 495: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 160, // 496: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 128, // 497: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 144, // 498: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 146, // 499: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 148, // 500: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 150, // 501: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 161, // 502: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 163, // 503: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 324, // 504: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 325, // 505: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 181, // 506: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 183, // 507: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 185, // 508: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 187, // 509: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 271, // 510: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 92, // 511: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 190, // 512: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse + 167, // 513: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 171, // 514: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 195, // 515: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 194, // 516: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 197, // 517: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 209, // 518: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse + 211, // 519: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse + 216, // 520: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 218, // 521: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame + 92, // 522: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 271, // 523: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 90, // 524: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 222, // 525: openshell.v1.OpenShell.PeerNotifyConfigUpdate:output_type -> openshell.v1.PeerNotifyConfigUpdateResponse + 117, // 526: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 232, // 527: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 234, // 528: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 236, // 529: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 238, // 530: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 241, // 531: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 243, // 532: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 245, // 533: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 247, // 534: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 250, // 535: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 23, // 536: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 25, // 537: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 252, // 538: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 254, // 539: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 256, // 540: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 258, // 541: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 261, // 542: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 263, // 543: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 265, // 544: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 77, // 545: openshell.v1.SandboxUpdates.UpdateSandbox:output_type -> openshell.v1.SandboxResponse + 459, // [459:546] is the sub-list for method output_type + 372, // [372:459] is the sub-list for method input_type + 372, // [372:372] is the sub-list for extension type_name + 372, // [372:372] is the sub-list for extension extendee + 0, // [0:372] is the sub-list for field type_name } func init() { file_openshell_proto_init() } @@ -22502,6 +22708,7 @@ func file_openshell_proto_init() { (*SupervisorMessage_RelayOpenResult)(nil), (*SupervisorMessage_RelayClose)(nil), (*SupervisorMessage_ConfigUpdateResult)(nil), + (*SupervisorMessage_ServiceAdvertisement)(nil), } file_openshell_proto_msgTypes[175].OneofWrappers = []any{ (*GatewayMessage_SessionAccepted)(nil), @@ -22511,6 +22718,7 @@ func file_openshell_proto_init() { (*GatewayMessage_RelayClose)(nil), (*GatewayMessage_SessionRedirect)(nil), (*GatewayMessage_ConfigUpdate)(nil), + (*GatewayMessage_ServiceRouteLease)(nil), } file_openshell_proto_msgTypes[190].OneofWrappers = []any{ (*RelayOpen_Ssh)(nil), @@ -22537,7 +22745,7 @@ func file_openshell_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_openshell_proto_rawDesc), len(file_openshell_proto_rawDesc)), NumEnums: 22, - NumMessages: 281, + NumMessages: 283, NumExtensions: 0, NumServices: 2, }, diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index 879ab84d91..b2ec219393 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -1646,3 +1646,20 @@ renew a 30-second lease; rejected configuration or expired freshness closes routes. App traffic uses application/edge authentication independently of gateway user authentication. This listener stage does not add service discovery or change the public URLs printed by the CLI. + +When enabled application routes are not published, check the authenticated +supervisor session and installed configuration before checking ingress. The +gateway requests installation evidence every five seconds with a fifteen-second +lease measured from challenge issuance. Instances report only when their serving +freshness covers that lease. Replayed, late, stale-configuration, and replaced +session reports cannot renew evidence. Endpoint resolution comes from the +compute driver; ambiguous shared member ownership must not publish an address. + +For shared pools, compare the discovered endpoint to the +acknowledged owner's Pod IP and configured app port, not its runtime port or a +load-balanced supervisor Service. Publication requires a current ownership term, +live member, and acknowledged ready report. During takeover old streams may close; +verify workload identity/memory survives and new connections reach the replacement. +Shared serving leases last at most fifteen seconds from issuance, independently +of configuration renewal; include membership failure detection when measuring a +partition's stale-owner interval. Storage failures prevent renewal.