diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index b9498783ff..81a14579f3 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-driver-docker/src/lib.rs b/crates/openshell-driver-docker/src/lib.rs index 970c8d4aa9..95b53dba88 100644 --- a/crates/openshell-driver-docker/src/lib.rs +++ b/crates/openshell-driver-docker/src/lib.rs @@ -3187,6 +3187,18 @@ impl ComputeDriver for ComputeDriverService { .await } + async fn resolve_supervisor_app_endpoint( + &self, + _request: Request, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented( + "supervisor application endpoint resolution is not supported", + )) + } + async fn get_sandbox( &self, request: Request, @@ -3358,6 +3370,18 @@ impl ComputeDriver for DockerComputeDriver { Ok(Response::new(ValidateSandboxCreateResponse {})) } + async fn resolve_supervisor_app_endpoint( + &self, + _request: Request, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented( + "supervisor application endpoint resolution is not supported", + )) + } + async fn get_sandbox( &self, request: Request, diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index faafebfb3c..5619f1271f 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -2227,6 +2227,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 ddfaac8b10..7e38461d5e 100644 --- a/crates/openshell-driver-kubernetes/src/grpc.rs +++ b/crates/openshell-driver-kubernetes/src/grpc.rs @@ -107,6 +107,37 @@ impl ComputeDriver for ComputeDriverService { .await } + 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 + } + async fn get_sandbox( &self, request: Request, diff --git a/crates/openshell-driver-mxc/src/grpc.rs b/crates/openshell-driver-mxc/src/grpc.rs index 91c61d2088..9240700ed1 100644 --- a/crates/openshell-driver-mxc/src/grpc.rs +++ b/crates/openshell-driver-mxc/src/grpc.rs @@ -70,6 +70,18 @@ impl ComputeDriver for ComputeDriverService { Ok(Response::new(ValidateSandboxCreateResponse {})) } + async fn resolve_supervisor_app_endpoint( + &self, + _request: Request, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented( + "supervisor application endpoint resolution is not supported", + )) + } + async fn get_sandbox( &self, request: Request, diff --git a/crates/openshell-driver-podman/src/grpc.rs b/crates/openshell-driver-podman/src/grpc.rs index 59b2fe1f60..acfb463fb7 100644 --- a/crates/openshell-driver-podman/src/grpc.rs +++ b/crates/openshell-driver-podman/src/grpc.rs @@ -102,6 +102,18 @@ impl ComputeDriver for ComputeDriverService { .await } + async fn resolve_supervisor_app_endpoint( + &self, + _request: Request, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented( + "supervisor application endpoint resolution is not supported", + )) + } + async fn get_sandbox( &self, request: Request, diff --git a/crates/openshell-driver-vm/src/driver.rs b/crates/openshell-driver-vm/src/driver.rs index 29930d98bc..885559f858 100644 --- a/crates/openshell-driver-vm/src/driver.rs +++ b/crates/openshell-driver-vm/src/driver.rs @@ -5362,6 +5362,18 @@ impl ComputeDriver for VmDriver { Ok(Response::new(response)) } + async fn resolve_supervisor_app_endpoint( + &self, + _request: Request, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented( + "supervisor application endpoint resolution is not supported", + )) + } + async fn get_sandbox( &self, request: Request, diff --git a/crates/openshell-otel/src/grpc.rs b/crates/openshell-otel/src/grpc.rs index 8184caae35..decfbccc01 100644 --- a/crates/openshell-otel/src/grpc.rs +++ b/crates/openshell-otel/src/grpc.rs @@ -106,6 +106,10 @@ pub mod rpc { "GetSandbox", "openshell.compute.v1.ComputeDriver/GetSandbox", ); + pub const RESOLVE_SUPERVISOR_APP_ENDPOINT: ComputeDriverRpc = ComputeDriverRpc::new( + "ResolveSupervisorAppEndpoint", + "openshell.compute.v1.ComputeDriver/ResolveSupervisorAppEndpoint", + ); pub const LIST_SANDBOXES: ComputeDriverRpc = ComputeDriverRpc::new( "ListSandboxes", "openshell.compute.v1.ComputeDriver/ListSandboxes", @@ -195,6 +199,7 @@ pub fn compute_driver_rpc_operation(path: &str) -> Option { Some("ValidateSandboxCreate") => Some(rpc::VALIDATE_SANDBOX_CREATE), Some("CreateSandbox") => Some(rpc::CREATE_SANDBOX), 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), @@ -318,6 +323,7 @@ mod tests { rpc::VALIDATE_SANDBOX_CREATE, rpc::CREATE_SANDBOX, 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 bd0ab19332..00d30eaac0 100644 --- a/crates/openshell-server/README.md +++ b/crates/openshell-server/README.md @@ -189,3 +189,20 @@ 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 +compute-driver `ResolveSupervisorAppEndpoint` 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. 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 215b864e4b..bc239ee518 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -770,6 +770,16 @@ impl ComputeDriver for RemoteComputeDriver { client.validate_sandbox_create(request).await } + async fn resolve_supervisor_app_endpoint( + &self, + request: Request, + ) -> Result< + tonic::Response, + Status, + > { + self.client().resolve_supervisor_app_endpoint(request).await + } + async fn get_sandbox( &self, request: Request, @@ -6555,6 +6565,41 @@ impl ComputeRuntime { .await } + /// Resolve app endpoints independently of ordinary sandbox observation. + pub(crate) async fn supervisor_app_endpoint( + &self, + sandbox: &Sandbox, + ) -> Result, Status> { + 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 { + driver + .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, @@ -8107,15 +8152,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, } @@ -8124,6 +8181,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()))), } @@ -8135,6 +8193,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()))), } @@ -8143,6 +8202,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()))), } @@ -8153,6 +8213,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, } @@ -8226,6 +8287,21 @@ impl ComputeDriver 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(), + }, + )) + } + async fn get_sandbox( &self, _request: Request, @@ -8788,6 +8864,9 @@ mod tests { #[derive(Debug, Default)] struct TestDriver { + endpoint_resolution: Option< + Result, + >, listed_sandboxes: Vec, current_sandboxes: Vec, workspace_rpcs_unimplemented: bool, @@ -8862,6 +8941,24 @@ mod tests { Ok(tonic::Response::new(ValidateSandboxCreateResponse {})) } + 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")), + } + } + async fn get_sandbox( &self, request: Request, @@ -8990,6 +9087,73 @@ 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 runtime = test_runtime(Arc::new(TestDriver { + endpoint_resolution: resolution, + current_sandboxes: vec![DriverSandbox { + id: "sandbox".into(), + name: "demo".into(), + ..Default::default() + }], + ..Default::default() + })) + .await; + 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), @@ -9311,6 +9475,22 @@ mod tests { Ok(tonic::Response::new(ValidateSandboxCreateResponse {})) } + 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, + > { + Err(Status::unimplemented( + "supervisor application endpoint resolution is not supported", + )) + } + async fn get_sandbox( &self, _request: Request, @@ -12592,6 +12772,22 @@ mod tests { self.0.validate_sandbox_create(request).await } + 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, + > { + Err(Status::unimplemented( + "supervisor application endpoint resolution is not supported", + )) + } + async fn get_sandbox( &self, request: Request, @@ -17580,6 +17776,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 @@ -17612,7 +17818,7 @@ mod tests { let traceparents = driver.traceparents(); assert_eq!( traceparents.len(), - 10, + 11, "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 8af2709134..d5b658a12d 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; @@ -1097,6 +1098,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 a47791e8d8..e3a1728bf4 100644 --- a/crates/openshell-server/src/grpc/service.rs +++ b/crates/openshell-server/src/grpc/service.rs @@ -91,6 +91,26 @@ pub(super) async fn expose_service_endpoint( target_port: u32, authorization_mode: ServiceAuthorizationMode, ) -> Result, Status> { + let _guard = state + .compute + .mutation_guard(crate::compute::MutationScope::sandbox( + workspace, + sandbox.object_id(), + )) + .await + .map_err(|_| Status::unavailable("service mutation lock unavailable"))?; + 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(); let now = crate::persistence::current_time_ms(); @@ -347,6 +367,14 @@ pub(super) async fn handle_delete_service( probe.resume.notified().await; } + let _guard = state + .compute + .mutation_guard(crate::compute::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..38949d8d85 --- /dev/null +++ b/crates/openshell-server/src/grpc/service_advertisement.rs @@ -0,0 +1,773 @@ +// 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).await; + 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 9a90c19169..854e5b4ec9 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -1123,6 +1123,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)); diff --git a/crates/openshell-server/src/storage_proto.rs b/crates/openshell-server/src/storage_proto.rs index 76cb2fc701..b1dd6e9e5e 100644 --- a/crates/openshell-server/src/storage_proto.rs +++ b/crates/openshell-server/src/storage_proto.rs @@ -164,7 +164,7 @@ mod tests { // SandboxPhase enum. Stored rows never carried those values, so prior // payloads decode unchanged. const PUBLIC_RPC_SCHEMA_SHA256: &str = - "73e29104ad78d7682b859edfc15ebe2c7319f53d72948cab4dd2c99daed8d11c"; + "ef8a86dbefbde690c1159c4b2756b4b881513bc3f65e578ee5eb10d0a3f94640"; const DURABLE_SCHEMA_SHA256: &str = "ffb20595e36032a9476d1c6956e2dcdc814f85286fc4735751e5ec6f499ea9ed"; const PUBLIC_DURABLE_OVERLAP_SHA256: &str = @@ -631,7 +631,7 @@ mod tests { } assert_eq!( compiled_method_count, - 106 + PROVIDER_READINESS_RPC_SIGNATURES.len() + 107 + PROVIDER_READINESS_RPC_SIGNATURES.len() + PEER_OWNER_RPC_SIGNATURES.len() + PEER_CONFIG_RPC_SIGNATURES.len() + isolation_methods.len() @@ -702,7 +702,7 @@ mod tests { overlap_hash.as_str(), ), ( - (331, 31), + (333, 31), (95, 22), (83, 22), PUBLIC_RPC_SCHEMA_SHA256, diff --git a/crates/openshell-server/src/supervisor_pool.rs b/crates/openshell-server/src/supervisor_pool.rs index ff715ab63f..e06352f119 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( @@ -5124,6 +5214,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 454af49d8c..6198ce5469 100644 --- a/crates/openshell-server/src/test_support.rs +++ b/crates/openshell-server/src/test_support.rs @@ -352,6 +352,19 @@ impl ComputeDriver for FakeComputeDriver { Ok(Response::new(ValidateSandboxCreateResponse {})) } + 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", + )) + } + async fn get_sandbox( &self, request: Request, 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 f0e02f4c0c..0b1c30c5f1 100644 --- a/crates/openshell-supervisor/README.md +++ b/crates/openshell-supervisor/README.md @@ -284,3 +284,26 @@ URL selection. Shared supervisors expose the app listener on each member's Pod IP without a shared app Service. 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 ed8d8943cb..df2d3dd3b6 100644 --- a/crates/openshell-supervisor/src/app_listener.rs +++ b/crates/openshell-supervisor/src/app_listener.rs @@ -125,7 +125,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, } @@ -141,8 +144,10 @@ struct RouteToken { #[derive(Clone)] pub struct Routes { registration: Option>, + requires_authority: bool, sender: watch::Sender, stopped: Arc, + serving: Arc, } impl Default for Routes { @@ -150,7 +155,9 @@ 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, + requires_authority: false, } } } @@ -163,14 +170,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) @@ -246,6 +302,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); @@ -282,6 +342,7 @@ impl Routes { snapshot.routes.clear(); snapshot.generations.clear(); snapshot.expires = None; + snapshot.authority_expires = None; snapshot.epoch = snapshot.epoch.wrapping_add(1); }); } @@ -299,7 +360,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(( @@ -322,7 +383,7 @@ impl Routes { { return; } - let Some(expiry) = snapshot.expires else { + let Some(expiry) = self.serving_expiry(&snapshot) else { return; }; expiry @@ -346,6 +407,7 @@ pub struct BoundListener { #[derive(Clone, Default)] pub struct Router { domain: String, + requires_authority: bool, registrations: Arc>>>, } @@ -423,6 +485,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 { @@ -470,6 +540,7 @@ impl Router { Ok(InstanceListener { routes: Routes { registration: Some(registration), + requires_authority: self.requires_authority, ..Default::default() }, receiver, @@ -526,6 +597,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(); @@ -844,6 +916,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 b6de5f1893..55a4d601d7 100644 --- a/crates/openshell-supervisor/src/lib.rs +++ b/crates/openshell-supervisor/src/lib.rs @@ -1715,6 +1715,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 7920468494..b442768fc1 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 8cb93de45b..c3edf21808 100644 --- a/docs/how-it-works/gateways/configuration.mdx +++ b/docs/how-it-works/gateways/configuration.mdx @@ -1737,3 +1737,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 3e26d81db6..55909b336e 100644 --- a/e2e/kubernetes/app-listener/README.md +++ b/e2e/kubernetes/app-listener/README.md @@ -75,3 +75,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..73063cfa21 100644 --- a/examples/isolation-backend-v8/src/driver.rs +++ b/examples/isolation-backend-v8/src/driver.rs @@ -412,6 +412,18 @@ impl compute_driver_server::ComputeDriver for DriverService { runtime_identity: identity, })) } + async fn resolve_supervisor_app_endpoint( + &self, + _request: Request, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented( + "supervisor application endpoint resolution is not supported", + )) + } + async fn get_sandbox( &self, q: Request, diff --git a/proto/README.md b/proto/README.md index f029280f8d..32efbbbd20 100644 --- a/proto/README.md +++ b/proto/README.md @@ -86,6 +86,16 @@ 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. +`ResolveSupervisorAppEndpoint` 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. + ## 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 2eba7ef2af..f69d55ffdd 100644 --- a/proto/compute_driver.proto +++ b/proto/compute_driver.proto @@ -39,6 +39,12 @@ service ComputeDriver { // Fetch the platform-observed sandbox state for one sandbox. rpc GetSandbox(GetSandboxRequest) returns (GetSandboxResponse); + // 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); + // List platform-observed sandbox state for all sandboxes. rpc ListSandboxes(ListSandboxesRequest) returns (ListSandboxesResponse); @@ -421,6 +427,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 1be9dc7da8..40a55d6728 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -3152,6 +3152,7 @@ message SupervisorMessage { RelayOpenResult relay_open_result = 3; RelayClose relay_close = 4; ConfigUpdateResult config_update_result = 5; + SupervisorServiceAdvertisement service_advertisement = 6; } } @@ -3165,6 +3166,7 @@ message GatewayMessage { RelayClose relay_close = 5; SessionRedirect session_redirect = 6; ConfigUpdate config_update = 7; + ServiceRouteLease service_route_lease = 8; } } @@ -3192,6 +3194,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. @@ -4169,3 +4172,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 d60dd50a68..b30c227dde 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -13839,6 +13839,7 @@ type SupervisorMessage struct { // *SupervisorMessage_RelayOpenResult // *SupervisorMessage_RelayClose // *SupervisorMessage_ConfigUpdateResult + // *SupervisorMessage_ServiceAdvertisement Payload isSupervisorMessage_Payload `protobuf_oneof:"payload"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -13926,6 +13927,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() } @@ -13950,6 +13960,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() {} @@ -13960,6 +13974,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"` @@ -13972,6 +13988,7 @@ type GatewayMessage struct { // *GatewayMessage_RelayClose // *GatewayMessage_SessionRedirect // *GatewayMessage_ConfigUpdate + // *GatewayMessage_ServiceRouteLease Payload isGatewayMessage_Payload `protobuf_oneof:"payload"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -14077,6 +14094,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() } @@ -14109,6 +14135,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() {} @@ -14123,6 +14153,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"` @@ -14147,9 +14179,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() { @@ -14231,6 +14264,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"` @@ -19530,6 +19570,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[251] + 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[251] + 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{251} +} + +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[252] + 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[252] + 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{252} +} + +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 = "" + @@ -20563,15 +20747,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" + @@ -20581,8 +20766,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" + @@ -20594,7 +20780,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" + @@ -20991,7 +21178,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" + @@ -21341,7 +21539,7 @@ func file_openshell_proto_rawDescGZIP() []byte { } var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 22) -var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 277) +var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 279) var file_openshell_proto_goTypes = []any{ (ExtensionKind)(0), // 0: openshell.v1.ExtensionKind (SandboxPhase)(0), // 1: openshell.v1.SandboxPhase @@ -21616,58 +21814,61 @@ var file_openshell_proto_goTypes = []any{ (*SandboxProvisioning)(nil), // 270: openshell.v1.SandboxProvisioning (*SandboxServiceExposure)(nil), // 271: openshell.v1.SandboxServiceExposure (*EndpointFailure)(nil), // 272: openshell.v1.EndpointFailure - nil, // 273: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 274: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 275: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 276: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 277: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - nil, // 278: openshell.v1.PlatformEvent.MetadataEntry - nil, // 279: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 280: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 281: openshell.v1.SandboxResponse.ServiceUrlsEntry - nil, // 282: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 283: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 284: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry - nil, // 285: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - nil, // 286: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 287: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 288: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 289: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - nil, // 290: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 291: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 292: openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry - nil, // 293: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry - nil, // 294: openshell.v1.ProviderEnvironmentSnapshot.FilesEntry - nil, // 295: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 296: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 297: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 298: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*timestamppb.Timestamp)(nil), // 299: google.protobuf.Timestamp - (*datamodelv1.ObjectMeta)(nil), // 300: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 301: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 302: google.protobuf.Struct - (*durationpb.Duration)(nil), // 303: google.protobuf.Duration - (*datamodelv1.WorkspaceSelector)(nil), // 304: openshell.datamodel.v1.WorkspaceSelector - (*datamodelv1.Provider)(nil), // 305: openshell.datamodel.v1.Provider - (sandboxv1.PolicySource)(0), // 306: openshell.sandbox.v1.PolicySource - (*sandboxv1.NetworkEndpoint)(nil), // 307: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 308: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 309: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 310: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 311: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 312: openshell.sandbox.v1.L7Rule - (*sandboxv1.SandboxConfigSnapshot)(nil), // 313: openshell.sandbox.v1.SandboxConfigSnapshot - (*datamodelv1.Workspace)(nil), // 314: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 315: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 316: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 317: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 318: openshell.sandbox.v1.GetGatewayConfigResponse + (*ServiceRouteLease)(nil), // 273: openshell.v1.ServiceRouteLease + (*SupervisorServiceAdvertisement)(nil), // 274: openshell.v1.SupervisorServiceAdvertisement + nil, // 275: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 276: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 277: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 278: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 279: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + nil, // 280: openshell.v1.PlatformEvent.MetadataEntry + nil, // 281: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 282: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 283: openshell.v1.SandboxResponse.ServiceUrlsEntry + nil, // 284: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 285: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 286: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry + nil, // 287: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + nil, // 288: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 289: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 290: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 291: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + nil, // 292: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 293: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + nil, // 294: openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry + nil, // 295: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry + nil, // 296: openshell.v1.ProviderEnvironmentSnapshot.FilesEntry + nil, // 297: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 298: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 299: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 300: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*timestamppb.Timestamp)(nil), // 301: google.protobuf.Timestamp + (*datamodelv1.ObjectMeta)(nil), // 302: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 303: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 304: google.protobuf.Struct + (*durationpb.Duration)(nil), // 305: google.protobuf.Duration + (*datamodelv1.WorkspaceSelector)(nil), // 306: openshell.datamodel.v1.WorkspaceSelector + (*datamodelv1.Provider)(nil), // 307: openshell.datamodel.v1.Provider + (sandboxv1.PolicySource)(0), // 308: openshell.sandbox.v1.PolicySource + (*sandboxv1.NetworkEndpoint)(nil), // 309: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 310: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 311: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 312: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 313: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 314: openshell.sandbox.v1.L7Rule + (*sandboxv1.SandboxConfigSnapshot)(nil), // 315: openshell.sandbox.v1.SandboxConfigSnapshot + (*datamodelv1.Workspace)(nil), // 316: openshell.datamodel.v1.Workspace + (*sandboxv1.SandboxServiceRoute)(nil), // 317: openshell.sandbox.v1.SandboxServiceRoute + (*sandboxv1.GetSandboxConfigRequest)(nil), // 318: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 319: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 320: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 321: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ - 299, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 299, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 301, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 301, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp 263, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential - 299, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp + 301, // 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 @@ -21678,118 +21879,118 @@ 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 - 300, // 14: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 302, // 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 - 273, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 275, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry 43, // 19: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 301, // 20: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 303, // 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 - 274, // 24: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 275, // 25: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 276, // 26: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 302, // 27: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 302, // 28: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct - 300, // 29: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 276, // 24: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 277, // 25: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 278, // 26: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 304, // 27: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 304, // 28: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 302, // 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 - 302, // 32: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct + 304, // 32: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct 48, // 33: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel - 277, // 34: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + 279, // 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 - 303, // 38: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration + 305, // 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 269, // 41: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus 185, // 42: openshell.v1.SandboxStatus.configuration_admission:type_name -> openshell.v1.SandboxConfigurationAdmission 270, // 43: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning - 299, // 44: openshell.v1.SandboxStatus.next_restart_time:type_name -> google.protobuf.Timestamp - 299, // 45: openshell.v1.SandboxStatus.main_process_started_time:type_name -> google.protobuf.Timestamp + 301, // 44: openshell.v1.SandboxStatus.next_restart_time:type_name -> google.protobuf.Timestamp + 301, // 45: openshell.v1.SandboxStatus.main_process_started_time:type_name -> google.protobuf.Timestamp 267, // 46: openshell.v1.SandboxStatus.last_token_grant_failure:type_name -> openshell.v1.TokenGrantFailureObservation - 299, // 47: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp - 299, // 48: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp - 278, // 49: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry - 304, // 50: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 47: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp + 301, // 48: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp + 280, // 49: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 306, // 50: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 40, // 51: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec - 279, // 52: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 280, // 53: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 281, // 52: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 282, // 53: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry 271, // 54: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure - 304, // 55: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 55: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 44, // 56: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 304, // 57: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 58: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 59: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 57: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 58: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 59: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 44, // 60: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate 44, // 61: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate 18, // 62: openshell.v1.DeleteSandboxTemplateResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 304, // 63: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 299, // 64: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp - 304, // 65: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 66: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 67: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 68: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 69: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 70: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 71: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 72: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 73: openshell.v1.SuspendSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 74: openshell.v1.ResumeSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 63: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 64: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp + 306, // 65: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 66: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 67: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 68: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 69: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 70: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 71: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 72: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 73: openshell.v1.SuspendSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 74: openshell.v1.ResumeSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 39, // 75: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox - 281, // 76: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry + 283, // 76: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry 39, // 77: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 305, // 78: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 307, // 78: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider 39, // 79: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox 83, // 80: openshell.v1.AttachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt 39, // 81: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox 83, // 82: openshell.v1.DetachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt 81, // 83: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision 79, // 84: openshell.v1.ConfigSnapshotRevision.provider_target:type_name -> openshell.v1.ProviderDesiredIdentity - 306, // 85: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource + 308, // 85: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource 5, // 86: openshell.v1.ConfigUpdateOperation.component:type_name -> openshell.v1.ConfigComponent 80, // 87: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision 7, // 88: openshell.v1.ConfigUpdateOperation.state:type_name -> openshell.v1.ConfigUpdateOperationState 6, // 89: openshell.v1.ConfigUpdateOperation.outcome:type_name -> openshell.v1.ConfigApplyOutcome - 299, // 90: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp - 299, // 91: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp - 299, // 92: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp + 301, // 90: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp + 301, // 91: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp + 301, // 92: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp 2, // 93: openshell.v1.ProviderMutationReceipt.kind:type_name -> openshell.v1.ProviderMutationKind 79, // 94: openshell.v1.ProviderMutationReceipt.desired:type_name -> openshell.v1.ProviderDesiredIdentity - 299, // 95: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp + 301, // 95: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp 4, // 96: openshell.v1.ProviderReadinessObservation.reason:type_name -> openshell.v1.ProviderReadinessReason 83, // 97: openshell.v1.ProviderReadinessStatus.receipt:type_name -> openshell.v1.ProviderMutationReceipt 3, // 98: openshell.v1.ProviderReadinessStatus.state:type_name -> openshell.v1.ProviderReadinessState 4, // 99: openshell.v1.ProviderReadinessStatus.reason:type_name -> openshell.v1.ProviderReadinessReason 84, // 100: openshell.v1.ProviderReadinessStatus.observed:type_name -> openshell.v1.ProviderReadinessObservation - 299, // 101: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp - 299, // 102: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp + 301, // 101: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp + 301, // 102: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp 82, // 103: openshell.v1.ProviderReadinessStatus.operation:type_name -> openshell.v1.ConfigUpdateOperation - 304, // 104: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 104: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 85, // 105: openshell.v1.GetSandboxProviderStatusResponse.status:type_name -> openshell.v1.ProviderReadinessStatus 84, // 106: openshell.v1.ReportProviderReadinessRequest.observation:type_name -> openshell.v1.ProviderReadinessObservation - 303, // 107: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration - 303, // 108: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration + 305, // 107: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration + 305, // 108: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration 18, // 109: openshell.v1.DeleteSandboxResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 304, // 110: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 299, // 111: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp - 304, // 112: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 110: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 111: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp + 306, // 112: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 20, // 113: openshell.v1.ExposeServiceRequest.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 304, // 114: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 115: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 114: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 115: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 100, // 116: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 304, // 117: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 117: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 18, // 118: openshell.v1.DeleteServiceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 300, // 119: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 302, // 119: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta 20, // 120: openshell.v1.ServiceEndpoint.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode 99, // 121: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint 18, // 122: openshell.v1.RevokeSshSessionResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 304, // 123: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 282, // 124: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 303, // 125: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration + 306, // 123: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 284, // 124: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 305, // 125: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration 104, // 126: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout 105, // 127: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr 106, // 128: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit @@ -21798,133 +21999,133 @@ var file_openshell_proto_depIdxs = []int32{ 108, // 131: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit 103, // 132: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest 111, // 133: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 300, // 134: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 299, // 135: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp - 304, // 136: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 299, // 137: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp + 302, // 134: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 301, // 135: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp + 306, // 136: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 137: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp 39, // 138: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox 115, // 139: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine 53, // 140: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent 116, // 141: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning 227, // 142: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 299, // 143: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp - 283, // 144: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 304, // 145: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 305, // 146: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 304, // 147: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 148: openshell.v1.GetProviderCredentialsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 303, // 149: openshell.v1.GetProviderCredentialsRequest.minimum_remaining_lifetime:type_name -> google.protobuf.Duration - 299, // 150: openshell.v1.ProviderCredentialValue.expiration_time:type_name -> google.protobuf.Timestamp - 284, // 151: openshell.v1.GetProviderCredentialsResponse.credentials:type_name -> openshell.v1.GetProviderCredentialsResponse.CredentialsEntry - 304, // 152: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 153: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 305, // 154: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 285, // 155: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - 304, // 156: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 305, // 157: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 301, // 143: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp + 285, // 144: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 306, // 145: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 307, // 146: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 306, // 147: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 148: openshell.v1.GetProviderCredentialsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 305, // 149: openshell.v1.GetProviderCredentialsRequest.minimum_remaining_lifetime:type_name -> google.protobuf.Duration + 301, // 150: openshell.v1.ProviderCredentialValue.expiration_time:type_name -> google.protobuf.Timestamp + 286, // 151: openshell.v1.GetProviderCredentialsResponse.credentials:type_name -> openshell.v1.GetProviderCredentialsResponse.CredentialsEntry + 306, // 152: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 153: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 307, // 154: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 287, // 155: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + 306, // 156: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 307, // 157: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider 83, // 158: openshell.v1.ProviderResponse.target_receipts:type_name -> openshell.v1.ProviderMutationReceipt - 305, // 159: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 304, // 160: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 161: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 307, // 159: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 306, // 160: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 161: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 148, // 162: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 303, // 163: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration + 305, // 163: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration 131, // 164: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride 8, // 165: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType 132, // 166: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken 137, // 167: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh 133, // 168: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant 9, // 169: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 303, // 170: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration - 303, // 171: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration + 305, // 170: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration + 305, // 171: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration 135, // 172: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial 136, // 173: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput 9, // 174: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 299, // 175: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp - 299, // 176: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp - 299, // 177: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp + 301, // 175: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp + 301, // 176: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp + 301, // 177: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp 16, // 178: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 299, // 179: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp - 304, // 180: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 179: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp + 306, // 180: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 138, // 181: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 304, // 182: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 182: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 9, // 183: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 286, // 184: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 299, // 185: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp + 288, // 184: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 301, // 185: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp 138, // 186: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 304, // 187: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 187: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 138, // 188: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 304, // 189: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 189: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 18, // 190: openshell.v1.DeleteProviderRefreshResponse.outcome:type_name -> openshell.v1.DeletionOutcome 10, // 191: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory 134, // 192: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 307, // 193: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 308, // 194: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 309, // 193: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 310, // 194: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary 139, // 195: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 287, // 196: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 289, // 196: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry 149, // 197: openshell.v1.ProviderProfile.files:type_name -> openshell.v1.ProviderProfileFile 148, // 198: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile 148, // 199: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 304, // 200: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 200: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 129, // 201: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem 130, // 202: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic 148, // 203: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 304, // 204: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 204: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 129, // 205: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem 130, // 206: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic 148, // 207: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 304, // 208: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 208: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 129, // 209: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem 130, // 210: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic 18, // 211: openshell.v1.DeleteProviderResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 304, // 212: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 212: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 18, // 213: openshell.v1.DeleteProviderProfileResponse.outcome:type_name -> openshell.v1.DeletionOutcome 162, // 214: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 288, // 215: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 289, // 216: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - 290, // 217: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 291, // 218: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 290, // 215: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 291, // 216: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + 292, // 217: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 293, // 218: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry 4, // 219: openshell.v1.GetSandboxProviderEnvironmentResponse.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason - 292, // 220: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry + 294, // 220: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry 11, // 221: openshell.v1.ProviderEnvironmentValue.classification:type_name -> openshell.v1.ProviderEnvironmentValueClassification 163, // 222: openshell.v1.ProviderEnvironmentValue.static_credential_binding:type_name -> openshell.v1.StaticCredentialBinding - 299, // 223: openshell.v1.ProviderEnvironmentValue.expiration_time:type_name -> google.protobuf.Timestamp + 301, // 223: openshell.v1.ProviderEnvironmentValue.expiration_time:type_name -> google.protobuf.Timestamp 165, // 224: openshell.v1.ProviderEnvironmentSnapshot.values:type_name -> openshell.v1.ProviderEnvironmentValue - 293, // 225: openshell.v1.ProviderEnvironmentSnapshot.dynamic_credentials:type_name -> openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry + 295, // 225: openshell.v1.ProviderEnvironmentSnapshot.dynamic_credentials:type_name -> openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry 4, // 226: openshell.v1.ProviderEnvironmentSnapshot.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason - 294, // 227: openshell.v1.ProviderEnvironmentSnapshot.files:type_name -> openshell.v1.ProviderEnvironmentSnapshot.FilesEntry - 303, // 228: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration - 304, // 229: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 301, // 230: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 309, // 231: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 296, // 227: openshell.v1.ProviderEnvironmentSnapshot.files:type_name -> openshell.v1.ProviderEnvironmentSnapshot.FilesEntry + 305, // 228: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration + 306, // 229: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 303, // 230: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 311, // 231: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue 170, // 232: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 295, // 233: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 297, // 233: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry 171, // 234: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule 172, // 235: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint 173, // 236: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule 175, // 237: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules 176, // 238: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules 177, // 239: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 310, // 240: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 308, // 241: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 311, // 242: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 312, // 240: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 310, // 241: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 313, // 242: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule 174, // 243: openshell.v1.AddDenyRules.target:type_name -> openshell.v1.L7RuleTarget - 312, // 244: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 314, // 244: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule 174, // 245: openshell.v1.AddAllowRules.target:type_name -> openshell.v1.L7RuleTarget - 296, // 246: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 304, // 247: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 298, // 246: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 306, // 247: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 188, // 248: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 304, // 249: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 249: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector 188, // 250: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision 13, // 251: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus 12, // 252: openshell.v1.SandboxConfigurationAdmission.state:type_name -> openshell.v1.ConfigurationAdmissionState 185, // 253: openshell.v1.ReportSandboxConfigurationRequest.admission:type_name -> openshell.v1.SandboxConfigurationAdmission 13, // 254: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 299, // 255: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp - 299, // 256: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp - 301, // 257: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 297, // 258: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 304, // 259: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 299, // 260: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp + 301, // 255: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp + 301, // 256: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp + 303, // 257: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 299, // 258: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 306, // 259: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 260: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp 115, // 261: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine 115, // 262: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine 195, // 263: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello @@ -21932,276 +22133,281 @@ var file_openshell_proto_depIdxs = []int32{ 220, // 265: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult 221, // 266: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose 199, // 267: openshell.v1.SupervisorMessage.config_update_result:type_name -> openshell.v1.ConfigUpdateResult - 196, // 268: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 201, // 269: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 204, // 270: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 209, // 271: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 221, // 272: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 202, // 273: openshell.v1.GatewayMessage.session_redirect:type_name -> openshell.v1.SessionRedirect - 197, // 274: openshell.v1.GatewayMessage.config_update:type_name -> openshell.v1.ConfigUpdate - 197, // 275: openshell.v1.SessionAccepted.bootstrap:type_name -> openshell.v1.ConfigUpdate - 303, // 276: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration - 313, // 277: openshell.v1.ConfigUpdate.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot - 166, // 278: openshell.v1.ConfigUpdate.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot - 6, // 279: openshell.v1.ConfigUpdateResult.outcome:type_name -> openshell.v1.ConfigApplyOutcome - 198, // 280: openshell.v1.ConfigUpdateResult.failure:type_name -> openshell.v1.ConfigApplyFailure - 185, // 281: openshell.v1.ConfigUpdateResult.admission:type_name -> openshell.v1.SandboxConfigurationAdmission - 200, // 282: openshell.v1.ConfigUpdateResult.policy_load:type_name -> openshell.v1.PolicyLoadResult - 210, // 283: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 211, // 284: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 212, // 285: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 209, // 286: openshell.v1.PeerRelayInit.relay_open:type_name -> openshell.v1.RelayOpen - 214, // 287: openshell.v1.PeerRelayFrame.init:type_name -> openshell.v1.PeerRelayInit - 216, // 288: openshell.v1.PeerNotifyConfigUpdateRequest.sandbox:type_name -> openshell.v1.PeerConfigSandboxTarget - 217, // 289: openshell.v1.PeerNotifyConfigUpdateRequest.provider:type_name -> openshell.v1.PeerConfigProviderTarget - 299, // 290: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp - 299, // 291: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp - 222, // 292: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 224, // 293: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 310, // 294: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 299, // 295: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp - 299, // 296: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp - 299, // 297: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp - 299, // 298: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp - 301, // 299: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 301, // 300: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 304, // 301: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 223, // 302: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 226, // 303: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 225, // 304: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 304, // 305: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 226, // 306: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 299, // 307: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp - 304, // 308: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 309: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 310: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 236, // 311: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval - 304, // 312: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 310, // 313: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 304, // 314: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 315: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 304, // 316: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 299, // 317: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp - 246, // 318: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 298, // 319: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 314, // 320: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 314, // 321: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 314, // 322: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 18, // 323: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 300, // 324: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 15, // 325: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 304, // 326: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 15, // 327: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 256, // 328: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 304, // 329: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 18, // 330: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 304, // 331: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 256, // 332: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 299, // 333: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp - 19, // 334: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult - 272, // 335: openshell.v1.EndpointObservation.failure:type_name -> openshell.v1.EndpointFailure - 264, // 336: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation - 266, // 337: openshell.v1.ReportEndpointStatusRequest.token_grant_status:type_name -> openshell.v1.TokenGrantStatus - 267, // 338: openshell.v1.TokenGrantStatus.last_failure:type_name -> openshell.v1.TokenGrantFailureObservation - 272, // 339: openshell.v1.TokenGrantFailureObservation.failure:type_name -> openshell.v1.EndpointFailure - 19, // 340: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult - 299, // 341: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp - 272, // 342: openshell.v1.EndpointStatus.last_failure:type_name -> openshell.v1.EndpointFailure - 299, // 343: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp - 299, // 344: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp - 299, // 345: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp - 299, // 346: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp - 299, // 347: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp - 299, // 348: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp - 299, // 349: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp - 299, // 350: openshell.v1.SandboxProvisioning.preparation_deadline:type_name -> google.protobuf.Timestamp - 299, // 351: openshell.v1.SandboxProvisioning.admission_start_time:type_name -> google.protobuf.Timestamp - 20, // 352: openshell.v1.SandboxServiceExposure.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 21, // 353: openshell.v1.EndpointFailure.domain:type_name -> openshell.v1.EndpointFailureDomain - 16, // 354: openshell.v1.EndpointFailure.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 120, // 355: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry.value:type_name -> openshell.v1.ProviderCredentialValue - 299, // 356: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 299, // 357: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 134, // 358: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 163, // 359: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 134, // 360: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 26, // 361: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 28, // 362: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 30, // 363: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 54, // 364: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 62, // 365: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest - 64, // 366: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 65, // 367: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 55, // 368: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest - 56, // 369: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest - 57, // 370: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest - 58, // 371: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest - 66, // 372: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 67, // 373: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 68, // 374: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 86, // 375: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 69, // 376: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 70, // 377: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 71, // 378: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 72, // 379: openshell.v1.OpenShell.SuspendSandbox:input_type -> openshell.v1.SuspendSandboxRequest - 73, // 380: openshell.v1.OpenShell.ResumeSandbox:input_type -> openshell.v1.ResumeSandboxRequest - 91, // 381: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 93, // 382: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 94, // 383: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 95, // 384: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 97, // 385: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 101, // 386: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 103, // 387: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 109, // 388: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 110, // 389: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 117, // 390: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 118, // 391: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 119, // 392: openshell.v1.OpenShell.GetProviderCredentials:input_type -> openshell.v1.GetProviderCredentialsRequest - 122, // 393: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 127, // 394: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 128, // 395: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 152, // 396: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 154, // 397: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 156, // 398: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 123, // 399: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 140, // 400: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 142, // 401: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 144, // 402: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 146, // 403: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 124, // 404: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 159, // 405: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 315, // 406: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 316, // 407: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 169, // 408: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 179, // 409: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 181, // 410: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 183, // 411: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 265, // 412: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 88, // 413: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 186, // 414: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest - 161, // 415: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 167, // 416: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest - 189, // 417: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 190, // 418: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 193, // 419: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 205, // 420: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest - 207, // 421: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest - 213, // 422: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 215, // 423: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame - 88, // 424: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 265, // 425: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 86, // 426: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 218, // 427: openshell.v1.OpenShell.PeerNotifyConfigUpdate:input_type -> openshell.v1.PeerNotifyConfigUpdateRequest - 113, // 428: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 228, // 429: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 230, // 430: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 232, // 431: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 234, // 432: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 237, // 433: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 239, // 434: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 241, // 435: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 243, // 436: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 245, // 437: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 22, // 438: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 24, // 439: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 248, // 440: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 250, // 441: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 252, // 442: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 254, // 443: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 257, // 444: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 259, // 445: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 261, // 446: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 27, // 447: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 29, // 448: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 31, // 449: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 74, // 450: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 63, // 451: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse - 74, // 452: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 75, // 453: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 59, // 454: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 59, // 455: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 60, // 456: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse - 61, // 457: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse - 76, // 458: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 77, // 459: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 78, // 460: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 87, // 461: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 90, // 462: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 74, // 463: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 74, // 464: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 74, // 465: openshell.v1.OpenShell.SuspendSandbox:output_type -> openshell.v1.SandboxResponse - 74, // 466: openshell.v1.OpenShell.ResumeSandbox:output_type -> openshell.v1.SandboxResponse - 92, // 467: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 100, // 468: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 100, // 469: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 96, // 470: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 98, // 471: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 102, // 472: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 107, // 473: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 109, // 474: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 107, // 475: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 125, // 476: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 125, // 477: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 121, // 478: openshell.v1.OpenShell.GetProviderCredentials:output_type -> openshell.v1.GetProviderCredentialsResponse - 126, // 479: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 151, // 480: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 150, // 481: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 153, // 482: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 155, // 483: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 157, // 484: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 125, // 485: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 141, // 486: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 143, // 487: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 145, // 488: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 147, // 489: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 158, // 490: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 160, // 491: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 317, // 492: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 318, // 493: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 178, // 494: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 180, // 495: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 182, // 496: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 184, // 497: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 268, // 498: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 89, // 499: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 187, // 500: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse - 164, // 501: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 168, // 502: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse - 192, // 503: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 191, // 504: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 194, // 505: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 206, // 506: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse - 208, // 507: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse - 213, // 508: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 215, // 509: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame - 89, // 510: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 268, // 511: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 87, // 512: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 219, // 513: openshell.v1.OpenShell.PeerNotifyConfigUpdate:output_type -> openshell.v1.PeerNotifyConfigUpdateResponse - 114, // 514: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 229, // 515: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 231, // 516: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 233, // 517: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 235, // 518: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 238, // 519: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 240, // 520: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 242, // 521: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 244, // 522: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 247, // 523: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 23, // 524: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 25, // 525: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 249, // 526: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 251, // 527: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 253, // 528: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 255, // 529: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 258, // 530: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 260, // 531: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 262, // 532: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 447, // [447:533] is the sub-list for method output_type - 361, // [361:447] is the sub-list for method input_type - 361, // [361:361] is the sub-list for extension type_name - 361, // [361:361] is the sub-list for extension extendee - 0, // [0:361] is the sub-list for field type_name + 274, // 268: openshell.v1.SupervisorMessage.service_advertisement:type_name -> openshell.v1.SupervisorServiceAdvertisement + 196, // 269: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 201, // 270: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 204, // 271: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 209, // 272: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 221, // 273: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 202, // 274: openshell.v1.GatewayMessage.session_redirect:type_name -> openshell.v1.SessionRedirect + 197, // 275: openshell.v1.GatewayMessage.config_update:type_name -> openshell.v1.ConfigUpdate + 273, // 276: openshell.v1.GatewayMessage.service_route_lease:type_name -> openshell.v1.ServiceRouteLease + 197, // 277: openshell.v1.SessionAccepted.bootstrap:type_name -> openshell.v1.ConfigUpdate + 305, // 278: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration + 315, // 279: openshell.v1.ConfigUpdate.sandbox_config:type_name -> openshell.sandbox.v1.SandboxConfigSnapshot + 166, // 280: openshell.v1.ConfigUpdate.provider_environment:type_name -> openshell.v1.ProviderEnvironmentSnapshot + 6, // 281: openshell.v1.ConfigUpdateResult.outcome:type_name -> openshell.v1.ConfigApplyOutcome + 198, // 282: openshell.v1.ConfigUpdateResult.failure:type_name -> openshell.v1.ConfigApplyFailure + 185, // 283: openshell.v1.ConfigUpdateResult.admission:type_name -> openshell.v1.SandboxConfigurationAdmission + 200, // 284: openshell.v1.ConfigUpdateResult.policy_load:type_name -> openshell.v1.PolicyLoadResult + 210, // 285: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 211, // 286: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 212, // 287: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 209, // 288: openshell.v1.PeerRelayInit.relay_open:type_name -> openshell.v1.RelayOpen + 214, // 289: openshell.v1.PeerRelayFrame.init:type_name -> openshell.v1.PeerRelayInit + 216, // 290: openshell.v1.PeerNotifyConfigUpdateRequest.sandbox:type_name -> openshell.v1.PeerConfigSandboxTarget + 217, // 291: openshell.v1.PeerNotifyConfigUpdateRequest.provider:type_name -> openshell.v1.PeerConfigProviderTarget + 301, // 292: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp + 301, // 293: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp + 222, // 294: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 224, // 295: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 312, // 296: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 301, // 297: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp + 301, // 298: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp + 301, // 299: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp + 301, // 300: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp + 303, // 301: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 303, // 302: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 306, // 303: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 223, // 304: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 226, // 305: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 225, // 306: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 306, // 307: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 226, // 308: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 301, // 309: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp + 306, // 310: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 311: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 312: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 236, // 313: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval + 306, // 314: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 312, // 315: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 306, // 316: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 317: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 318: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 301, // 319: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp + 246, // 320: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 300, // 321: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 316, // 322: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 316, // 323: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 316, // 324: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 18, // 325: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 302, // 326: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 15, // 327: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 306, // 328: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 15, // 329: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 256, // 330: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 306, // 331: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 18, // 332: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 306, // 333: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 256, // 334: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 301, // 335: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp + 19, // 336: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult + 272, // 337: openshell.v1.EndpointObservation.failure:type_name -> openshell.v1.EndpointFailure + 264, // 338: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation + 266, // 339: openshell.v1.ReportEndpointStatusRequest.token_grant_status:type_name -> openshell.v1.TokenGrantStatus + 267, // 340: openshell.v1.TokenGrantStatus.last_failure:type_name -> openshell.v1.TokenGrantFailureObservation + 272, // 341: openshell.v1.TokenGrantFailureObservation.failure:type_name -> openshell.v1.EndpointFailure + 19, // 342: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult + 301, // 343: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp + 272, // 344: openshell.v1.EndpointStatus.last_failure:type_name -> openshell.v1.EndpointFailure + 301, // 345: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp + 301, // 346: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp + 301, // 347: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp + 301, // 348: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp + 301, // 349: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp + 301, // 350: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp + 301, // 351: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp + 301, // 352: openshell.v1.SandboxProvisioning.preparation_deadline:type_name -> google.protobuf.Timestamp + 301, // 353: openshell.v1.SandboxProvisioning.admission_start_time:type_name -> google.protobuf.Timestamp + 20, // 354: openshell.v1.SandboxServiceExposure.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 21, // 355: openshell.v1.EndpointFailure.domain:type_name -> openshell.v1.EndpointFailureDomain + 16, // 356: openshell.v1.EndpointFailure.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction + 305, // 357: openshell.v1.ServiceRouteLease.duration:type_name -> google.protobuf.Duration + 301, // 358: openshell.v1.ServiceRouteLease.expires_at:type_name -> google.protobuf.Timestamp + 317, // 359: openshell.v1.SupervisorServiceAdvertisement.routes:type_name -> openshell.sandbox.v1.SandboxServiceRoute + 120, // 360: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry.value:type_name -> openshell.v1.ProviderCredentialValue + 301, // 361: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 301, // 362: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 134, // 363: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 163, // 364: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 134, // 365: openshell.v1.ProviderEnvironmentSnapshot.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 26, // 366: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 28, // 367: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 30, // 368: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 54, // 369: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 62, // 370: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest + 64, // 371: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 65, // 372: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 55, // 373: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest + 56, // 374: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest + 57, // 375: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest + 58, // 376: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest + 66, // 377: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 67, // 378: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 68, // 379: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 86, // 380: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 69, // 381: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 70, // 382: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 71, // 383: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 72, // 384: openshell.v1.OpenShell.SuspendSandbox:input_type -> openshell.v1.SuspendSandboxRequest + 73, // 385: openshell.v1.OpenShell.ResumeSandbox:input_type -> openshell.v1.ResumeSandboxRequest + 91, // 386: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 93, // 387: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 94, // 388: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 95, // 389: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 97, // 390: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 101, // 391: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 103, // 392: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 109, // 393: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 110, // 394: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 117, // 395: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 118, // 396: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 119, // 397: openshell.v1.OpenShell.GetProviderCredentials:input_type -> openshell.v1.GetProviderCredentialsRequest + 122, // 398: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 127, // 399: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 128, // 400: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 152, // 401: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 154, // 402: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 156, // 403: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 123, // 404: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 140, // 405: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 142, // 406: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 144, // 407: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 146, // 408: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 124, // 409: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 159, // 410: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 318, // 411: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 319, // 412: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 169, // 413: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 179, // 414: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 181, // 415: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 183, // 416: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 265, // 417: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 88, // 418: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 186, // 419: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest + 161, // 420: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 167, // 421: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 189, // 422: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 190, // 423: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 193, // 424: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 205, // 425: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest + 207, // 426: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest + 213, // 427: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 215, // 428: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame + 88, // 429: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 265, // 430: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 86, // 431: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 218, // 432: openshell.v1.OpenShell.PeerNotifyConfigUpdate:input_type -> openshell.v1.PeerNotifyConfigUpdateRequest + 113, // 433: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 228, // 434: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 230, // 435: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 232, // 436: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 234, // 437: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 237, // 438: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 239, // 439: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 241, // 440: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 243, // 441: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 245, // 442: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 22, // 443: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 24, // 444: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 248, // 445: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 250, // 446: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 252, // 447: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 254, // 448: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 257, // 449: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 259, // 450: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 261, // 451: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 27, // 452: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 29, // 453: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 31, // 454: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 74, // 455: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 63, // 456: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse + 74, // 457: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 75, // 458: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 59, // 459: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 59, // 460: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 60, // 461: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse + 61, // 462: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse + 76, // 463: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 77, // 464: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 78, // 465: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 87, // 466: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 90, // 467: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 74, // 468: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 74, // 469: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 74, // 470: openshell.v1.OpenShell.SuspendSandbox:output_type -> openshell.v1.SandboxResponse + 74, // 471: openshell.v1.OpenShell.ResumeSandbox:output_type -> openshell.v1.SandboxResponse + 92, // 472: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 100, // 473: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 100, // 474: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 96, // 475: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 98, // 476: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 102, // 477: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 107, // 478: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 109, // 479: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 107, // 480: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 125, // 481: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 125, // 482: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 121, // 483: openshell.v1.OpenShell.GetProviderCredentials:output_type -> openshell.v1.GetProviderCredentialsResponse + 126, // 484: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 151, // 485: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 150, // 486: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 153, // 487: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 155, // 488: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 157, // 489: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 125, // 490: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 141, // 491: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 143, // 492: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 145, // 493: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 147, // 494: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 158, // 495: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 160, // 496: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 320, // 497: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 321, // 498: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 178, // 499: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 180, // 500: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 182, // 501: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 184, // 502: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 268, // 503: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 89, // 504: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 187, // 505: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse + 164, // 506: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 168, // 507: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 192, // 508: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 191, // 509: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 194, // 510: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 206, // 511: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse + 208, // 512: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse + 213, // 513: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 215, // 514: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame + 89, // 515: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 268, // 516: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 87, // 517: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 219, // 518: openshell.v1.OpenShell.PeerNotifyConfigUpdate:output_type -> openshell.v1.PeerNotifyConfigUpdateResponse + 114, // 519: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 229, // 520: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 231, // 521: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 233, // 522: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 235, // 523: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 238, // 524: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 240, // 525: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 242, // 526: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 244, // 527: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 247, // 528: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 23, // 529: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 25, // 530: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 249, // 531: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 251, // 532: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 253, // 533: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 255, // 534: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 258, // 535: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 260, // 536: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 262, // 537: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 452, // [452:538] is the sub-list for method output_type + 366, // [366:452] is the sub-list for method input_type + 366, // [366:366] is the sub-list for extension type_name + 366, // [366:366] is the sub-list for extension extendee + 0, // [0:366] is the sub-list for field type_name } func init() { file_openshell_proto_init() } @@ -22257,6 +22463,7 @@ func file_openshell_proto_init() { (*SupervisorMessage_RelayOpenResult)(nil), (*SupervisorMessage_RelayClose)(nil), (*SupervisorMessage_ConfigUpdateResult)(nil), + (*SupervisorMessage_ServiceAdvertisement)(nil), } file_openshell_proto_msgTypes[172].OneofWrappers = []any{ (*GatewayMessage_SessionAccepted)(nil), @@ -22266,6 +22473,7 @@ func file_openshell_proto_init() { (*GatewayMessage_RelayClose)(nil), (*GatewayMessage_SessionRedirect)(nil), (*GatewayMessage_ConfigUpdate)(nil), + (*GatewayMessage_ServiceRouteLease)(nil), } file_openshell_proto_msgTypes[187].OneofWrappers = []any{ (*RelayOpen_Ssh)(nil), @@ -22292,7 +22500,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: 277, + NumMessages: 279, NumExtensions: 0, NumServices: 1, }, diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index 544c988817..59246aa16d 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -1607,3 +1607,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.